Merge "Add mcts tag to MainlineCTS dnsresolver test modules." into main
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index a0b2434..7b52694 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -62,6 +62,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts-tethering"
+        "mts-tethering",
+        "mcts-tethering",
     ],
 }
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 5e9bbcb..50d6c4b 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -18,7 +18,6 @@
 
 import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.SOCK_NONBLOCK;
 import static android.system.OsConstants.SOCK_RAW;
 import static android.system.OsConstants.SOL_SOCKET;
 import static android.system.OsConstants.SO_SNDTIMEO;
@@ -39,21 +38,12 @@
 import android.net.MacAddress;
 import android.net.TrafficStats;
 import android.net.util.SocketUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructTimeval;
 import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.net.module.util.FdEventsReader;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.structs.Icmpv6Header;
 import com.android.net.module.util.structs.LlaOption;
@@ -113,11 +103,6 @@
 
     private static final int DAY_IN_SECONDS = 86_400;
 
-    // Commands for IpServer to control RouterAdvertisementDaemon
-    private static final int CMD_START        = 1;
-    private static final int CMD_STOP         = 2;
-    private static final int CMD_BUILD_NEW_RA = 3;
-
     private final InterfaceParams mInterface;
     private final InetSocketAddress mAllNodes;
 
@@ -135,13 +120,9 @@
     @GuardedBy("mLock")
     private RaParams mRaParams;
 
-    // To be accessed only from RaMessageHandler
-    private RsPacketListener mRsPacketListener;
-
     private volatile FileDescriptor mSocket;
     private volatile MulticastTransmitter mMulticastTransmitter;
-    private volatile RaMessageHandler mRaMessageHandler;
-    private volatile HandlerThread mRaHandlerThread;
+    private volatile UnicastResponder mUnicastResponder;
 
     /** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/
     public static class RaParams {
@@ -263,94 +244,6 @@
         }
     }
 
-    private class RaMessageHandler extends Handler {
-        RaMessageHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_START:
-                    mRsPacketListener = new RsPacketListener(this);
-                    mRsPacketListener.start();
-                    break;
-                case CMD_STOP:
-                    if (mRsPacketListener != null) {
-                        mRsPacketListener.stop();
-                        mRsPacketListener = null;
-                    }
-                    break;
-                case CMD_BUILD_NEW_RA:
-                    synchronized (mLock) {
-                        // raInfo.first is deprecatedParams and raInfo.second is newParams.
-                        final Pair<RaParams, RaParams> raInfo = (Pair<RaParams, RaParams>) msg.obj;
-                        if (raInfo.first != null) {
-                            mDeprecatedInfoTracker.putPrefixes(raInfo.first.prefixes);
-                            mDeprecatedInfoTracker.putDnses(raInfo.first.dnses);
-                        }
-
-                        if (raInfo.second != null) {
-                            // Process information that is no longer deprecated.
-                            mDeprecatedInfoTracker.removePrefixes(raInfo.second.prefixes);
-                            mDeprecatedInfoTracker.removeDnses(raInfo.second.dnses);
-                        }
-                        mRaParams = raInfo.second;
-                        assembleRaLocked();
-                    }
-
-                    maybeNotifyMulticastTransmitter();
-                    break;
-                default:
-                    Log.e(TAG, "Unknown message, cmd = " + String.valueOf(msg.what));
-                    break;
-            }
-        }
-    }
-
-    private class RsPacketListener extends FdEventsReader<RsPacketListener.RecvBuffer> {
-        private static final class RecvBuffer {
-            // The recycled buffer for receiving Router Solicitations from clients.
-            // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
-            // This is fine since currently only byte 0 is examined anyway.
-            final byte[] mBytes = new byte[IPV6_MIN_MTU];
-            final InetSocketAddress mSrcAddr = new InetSocketAddress(0);
-        }
-
-        RsPacketListener(@NonNull Handler handler) {
-            super(handler, new RecvBuffer());
-        }
-
-        @Override
-        protected int recvBufSize(@NonNull RecvBuffer buffer) {
-            return buffer.mBytes.length;
-        }
-
-        @Override
-        protected FileDescriptor createFd() {
-            return mSocket;
-        }
-
-        @Override
-        protected int readPacket(@NonNull FileDescriptor fd, @NonNull RecvBuffer buffer)
-                throws Exception {
-            return Os.recvfrom(
-                    fd, buffer.mBytes, 0, buffer.mBytes.length, 0 /* flags */, buffer.mSrcAddr);
-        }
-
-        @Override
-        protected final void handlePacket(@NonNull RecvBuffer buffer, int length) {
-            // Do the least possible amount of validations.
-            if (buffer.mSrcAddr == null
-                    || length <= 0
-                    || buffer.mBytes[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
-                return;
-            }
-
-            maybeSendRA(buffer.mSrcAddr);
-        }
-    }
-
     public RouterAdvertisementDaemon(InterfaceParams ifParams) {
         mInterface = ifParams;
         mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
@@ -359,43 +252,48 @@
 
     /** Build new RA.*/
     public void buildNewRa(RaParams deprecatedParams, RaParams newParams) {
-        final Pair<RaParams, RaParams> raInfo = new Pair<>(deprecatedParams, newParams);
-        sendMessage(CMD_BUILD_NEW_RA, raInfo);
+        synchronized (mLock) {
+            if (deprecatedParams != null) {
+                mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes);
+                mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses);
+            }
+
+            if (newParams != null) {
+                // Process information that is no longer deprecated.
+                mDeprecatedInfoTracker.removePrefixes(newParams.prefixes);
+                mDeprecatedInfoTracker.removeDnses(newParams.dnses);
+            }
+
+            mRaParams = newParams;
+            assembleRaLocked();
+        }
+
+        maybeNotifyMulticastTransmitter();
     }
 
     /** Start router advertisement daemon. */
     public boolean start() {
         if (!createSocket()) {
-            Log.e(TAG, "Failed to start RouterAdvertisementDaemon.");
             return false;
         }
 
         mMulticastTransmitter = new MulticastTransmitter();
         mMulticastTransmitter.start();
 
-        mRaHandlerThread = new HandlerThread(TAG);
-        mRaHandlerThread.start();
-        mRaMessageHandler = new RaMessageHandler(mRaHandlerThread.getLooper());
+        mUnicastResponder = new UnicastResponder();
+        mUnicastResponder.start();
 
-        return sendMessage(CMD_START);
+        return true;
     }
 
     /** Stop router advertisement daemon. */
     public void stop() {
-        if (!sendMessage(CMD_STOP)) {
-            Log.e(TAG, "RouterAdvertisementDaemon has been stopped or was never started.");
-            return;
-        }
-
-        mRaHandlerThread.quitSafely();
-        mRaHandlerThread = null;
-        mRaMessageHandler = null;
-
         closeSocket();
         // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before
         // the thread's termination.
         maybeNotifyMulticastTransmitter();
         mMulticastTransmitter = null;
+        mUnicastResponder = null;
     }
 
     @GuardedBy("mLock")
@@ -605,7 +503,7 @@
 
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_NEIGHBOR);
         try {
-            mSocket = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
+            mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
             // Setting SNDTIMEO is purely for defensive purposes.
             Os.setsockoptTimeval(
                     mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
@@ -667,17 +565,34 @@
         }
     }
 
-    private boolean sendMessage(int cmd) {
-        return sendMessage(cmd, null);
-    }
+    private final class UnicastResponder extends Thread {
+        private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
+        // The recycled buffer for receiving Router Solicitations from clients.
+        // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+        // This is fine since currently only byte 0 is examined anyway.
+        private final byte[] mSolicitation = new byte[IPV6_MIN_MTU];
 
-    private boolean sendMessage(int cmd, @Nullable Object obj) {
-        if (mRaMessageHandler == null) {
-            return false;
+        @Override
+        public void run() {
+            while (isSocketValid()) {
+                try {
+                    // Blocking receive.
+                    final int rval = Os.recvfrom(
+                            mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor);
+                    // Do the least possible amount of validation.
+                    if (rval < 1 || mSolicitation[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
+                        continue;
+                    }
+                } catch (ErrnoException | SocketException e) {
+                    if (isSocketValid()) {
+                        Log.e(TAG, "recvfrom error: " + e);
+                    }
+                    continue;
+                }
+
+                maybeSendRA(mSolicitor);
+            }
         }
-
-        return mRaMessageHandler.sendMessage(
-                Message.obtain(mRaMessageHandler, cmd, obj));
     }
 
     // TODO: Consider moving this to run on a provided Looper as a Handler,
diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java
index 4ab6d3e..ee422ab 100644
--- a/framework/src/android/net/BpfNetMapsReader.java
+++ b/framework/src/android/net/BpfNetMapsReader.java
@@ -36,7 +36,6 @@
 import android.os.ServiceSpecificException;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
@@ -278,13 +277,6 @@
     public boolean getDataSaverEnabled() {
         throwIfPreT("getDataSaverEnabled is not available on pre-T devices");
 
-        // Note that this is not expected to be called until V given that it relies on the
-        // counterpart platform solution to set data saver status to bpf.
-        // See {@code NetworkManagementService#setDataSaverModeEnabled}.
-        if (!SdkLevel.isAtLeastV()) {
-            Log.wtf(TAG, "getDataSaverEnabled is not expected to be called on pre-V devices");
-        }
-
         try {
             return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED;
         } catch (ErrnoException e) {
diff --git a/nearby/README.md b/nearby/README.md
index 0d26563..81d2199 100644
--- a/nearby/README.md
+++ b/nearby/README.md
@@ -47,8 +47,16 @@
 ## Build and Install
 
 ```sh
-Build unbundled module using banchan
+For master on AOSP (Android) host
+$ source build/envsetup.sh
+$ lunch aosp_oriole-trunk_staging-userdebug
+$ m com.android.tethering
+$ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/com.android.tethering.capex --output /tmp/tethering.apex
+$ adb install /tmp/tethering.apex
+$ adb reboot
 
+For udc-mainline-prod on Google internal host
+Build unbundled module using banchan
 $ source build/envsetup.sh
 $ banchan com.google.android.tethering mainline_modules_arm64
 $ m apps_only dist
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 66a1ffe..4309d7e 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -39,6 +39,7 @@
         "cts",
         "general-tests",
         "mts-tethering",
+        "mcts-tethering",
     ],
     certificate: "platform",
     sdk_version: "module_current",
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 9c01dda..6086207 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -112,6 +112,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -750,14 +751,14 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         transactionId = getUniqueId();
-                        final Pair<String, String> typeAndSubtype =
+                        final Pair<String, List<String>> typeAndSubtype =
                                 parseTypeAndSubtype(info.getServiceType());
                         final String serviceType = typeAndSubtype == null
                                 ? null : typeAndSubtype.first;
                         if (clientInfo.mUseJavaBackend
                                 || mDeps.isMdnsDiscoveryManagerEnabled(mContext)
                                 || useDiscoveryManagerForType(serviceType)) {
-                            if (serviceType == null) {
+                            if (serviceType == null || typeAndSubtype.second.size() > 1) {
                                 clientInfo.onDiscoverServicesFailedImmediately(clientRequestId,
                                         NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
                                 break;
@@ -772,10 +773,11 @@
                                             .setNetwork(info.getNetwork())
                                             .setRemoveExpiredService(true)
                                             .setIsPassiveMode(true);
-                            if (typeAndSubtype.second != null) {
+                            if (!typeAndSubtype.second.isEmpty()) {
                                 // The parsing ensures subtype starts with an underscore.
                                 // MdnsSearchOptions expects the underscore to not be present.
-                                optionsBuilder.addSubtype(typeAndSubtype.second.substring(1));
+                                optionsBuilder.addSubtype(
+                                        typeAndSubtype.second.get(0).substring(1));
                             }
                             mMdnsDiscoveryManager.registerListener(
                                     listenServiceType, listener, optionsBuilder.build());
@@ -864,7 +866,8 @@
                         transactionId = getUniqueId();
                         final NsdServiceInfo serviceInfo = args.serviceInfo;
                         final String serviceType = serviceInfo.getServiceType();
-                        final Pair<String, String> typeSubtype = parseTypeAndSubtype(serviceType);
+                        final Pair<String, List<String>> typeSubtype = parseTypeAndSubtype(
+                                serviceType);
                         final String registerServiceType = typeSubtype == null
                                 ? null : typeSubtype.first;
                         if (clientInfo.mUseJavaBackend
@@ -881,10 +884,11 @@
                                     serviceInfo.getServiceName()));
 
                             Set<String> subtypes = new ArraySet<>(serviceInfo.getSubtypes());
-                            if (!TextUtils.isEmpty(typeSubtype.second)) {
-                                subtypes.add(typeSubtype.second);
+                            for (String subType: typeSubtype.second) {
+                                if (!TextUtils.isEmpty(subType)) {
+                                    subtypes.add(subType);
+                                }
                             }
-
                             subtypes = dedupSubtypeLabels(subtypes);
 
                             if (!checkSubtypeLabels(subtypes)) {
@@ -894,7 +898,6 @@
                             }
 
                             serviceInfo.setSubtypes(subtypes);
-
                             maybeStartMonitoringSockets();
                             mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
                                     MdnsAdvertisingOptions.newBuilder().build());
@@ -976,7 +979,7 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         transactionId = getUniqueId();
-                        final Pair<String, String> typeSubtype =
+                        final Pair<String, List<String>> typeSubtype =
                                 parseTypeAndSubtype(info.getServiceType());
                         final String serviceType = typeSubtype == null
                                 ? null : typeSubtype.first;
@@ -1077,7 +1080,7 @@
 
                         final NsdServiceInfo info = args.serviceInfo;
                         transactionId = getUniqueId();
-                        final Pair<String, String> typeAndSubtype =
+                        final Pair<String, List<String>> typeAndSubtype =
                                 parseTypeAndSubtype(info.getServiceType());
                         final String serviceType = typeAndSubtype == null
                                 ? null : typeAndSubtype.first;
@@ -1626,17 +1629,17 @@
      * underscore; they are alphanumerical characters or dashes or underscore, except the
      * last one that is just alphanumerical. The last label must be _tcp or _udp.
      *
-     * <p>The subtype may also be specified with a comma after the service type, for example
-     * _type._tcp,_subtype.
+     * <p>The subtypes may also be specified with a comma after the service type, for example
+     * _type._tcp,_subtype1,_subtype2
      *
      * @param serviceType the request service type for discovery / resolution service
      * @return constructed service type or null if the given service type is invalid.
      */
     @Nullable
-    public static Pair<String, String> parseTypeAndSubtype(String serviceType) {
+    public static Pair<String, List<String>> parseTypeAndSubtype(String serviceType) {
         if (TextUtils.isEmpty(serviceType)) return null;
 
-        final Pattern serviceTypePattern = Pattern.compile(
+        final String regexString =
                 // Optional leading subtype (_subtype._type._tcp)
                 // (?: xxx) is a non-capturing parenthesis, don't capture the dot
                 "^(?:(" + TYPE_SUBTYPE_LABEL_REGEX + ")\\.)?"
@@ -1645,14 +1648,25 @@
                         // Drop '.' at the end of service type that is compatible with old backend.
                         // e.g. allow "_type._tcp.local."
                         + "\\.?"
-                        // Optional subtype after comma, for "_type._tcp,_subtype" format
-                        + "(?:,(" + TYPE_SUBTYPE_LABEL_REGEX + "))?"
-                        + "$");
+                        // Optional subtype after comma, for "_type._tcp,_subtype1,_subtype2" format
+                        + "((?:," + TYPE_SUBTYPE_LABEL_REGEX + ")*)"
+                        + "$";
+        final Pattern serviceTypePattern = Pattern.compile(regexString);
         final Matcher matcher = serviceTypePattern.matcher(serviceType);
         if (!matcher.matches()) return null;
-        // Use the subtype either at the beginning or after the comma
-        final String subtype = matcher.group(1) != null ? matcher.group(1) : matcher.group(3);
-        return new Pair<>(matcher.group(2), subtype);
+        final String queryType = matcher.group(2);
+        // Use the subtype at the beginning
+        if (matcher.group(1) != null) {
+            return new Pair<>(queryType, List.of(matcher.group(1)));
+        }
+        // Use the subtypes at the end
+        final String subTypesStr = matcher.group(3);
+        if (subTypesStr != null && !subTypesStr.isEmpty()) {
+            final String[] subTypes = subTypesStr.substring(1).split(",");
+            return new Pair<>(queryType, List.of(subTypes));
+        }
+
+        return new Pair<>(queryType, Collections.emptyList());
     }
 
     /** Returns {@code true} if {@code subtype} is a valid DNS-SD subtype label. */
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index a5e5afb..a5c4fea 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -84,6 +84,13 @@
         "host/**/*.kt",
     ],
     libs: ["tradefed"],
-    test_suites: ["ats", "device-tests", "general-tests", "cts", "mts-networking"],
+    test_suites: [
+        "ats",
+        "device-tests",
+        "general-tests",
+        "cts",
+        "mts-networking",
+        "mcts-networking",
+    ],
     data: [":ConnectivityTestPreparer"],
 }
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 9310888..3d53d6c 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -131,6 +131,7 @@
         "cts",
         "general-tests",
         "mts-tethering",
+        "mcts-tethering",
     ],
 }
 
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index fe2f813..84b6745 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -626,6 +626,7 @@
             }
         }
         agent.unregister()
+        callback.eventuallyExpect<Lost> { it.network == agent.network }
         // callback will be unregistered in tearDown()
     }
 
diff --git a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
index 9de7f4d..8919666 100644
--- a/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
+++ b/tests/unit/java/android/net/BpfNetMapsReaderTest.kt
@@ -213,7 +213,6 @@
         assertFalse(isUidNetworkingBlocked(TEST_UID3))
     }
 
-    @IgnoreUpTo(VERSION_CODES.UPSIDE_DOWN_CAKE)
     @Test
     fun testGetDataSaverEnabled() {
         testDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, U8(DATA_SAVER_DISABLED))
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index e4683be..4dc96f1 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -1454,14 +1454,22 @@
         final String serviceType5 = "_TEST._999._tcp.";
         final String serviceType6 = "_998._tcp.,_TEST";
         final String serviceType7 = "_997._tcp,_TEST";
+        final String serviceType8 = "_997._tcp,_test1,_test2,_test3";
+        final String serviceType9 = "_test4._997._tcp,_test1,_test2,_test3";
 
         assertNull(parseTypeAndSubtype(serviceType1));
         assertNull(parseTypeAndSubtype(serviceType2));
         assertNull(parseTypeAndSubtype(serviceType3));
-        assertEquals(new Pair<>("_123._udp", null), parseTypeAndSubtype(serviceType4));
-        assertEquals(new Pair<>("_999._tcp", "_TEST"), parseTypeAndSubtype(serviceType5));
-        assertEquals(new Pair<>("_998._tcp", "_TEST"), parseTypeAndSubtype(serviceType6));
-        assertEquals(new Pair<>("_997._tcp", "_TEST"), parseTypeAndSubtype(serviceType7));
+        assertEquals(new Pair<>("_123._udp", Collections.emptyList()),
+                parseTypeAndSubtype(serviceType4));
+        assertEquals(new Pair<>("_999._tcp", List.of("_TEST")), parseTypeAndSubtype(serviceType5));
+        assertEquals(new Pair<>("_998._tcp", List.of("_TEST")), parseTypeAndSubtype(serviceType6));
+        assertEquals(new Pair<>("_997._tcp", List.of("_TEST")), parseTypeAndSubtype(serviceType7));
+
+        assertEquals(new Pair<>("_997._tcp", List.of("_test1", "_test2", "_test3")),
+                parseTypeAndSubtype(serviceType8));
+        assertEquals(new Pair<>("_997._tcp", List.of("_test4")),
+                parseTypeAndSubtype(serviceType9));
     }
 
     @Test