Skip to content
Snippets Groups Projects
Commit 6d618d77 authored by Kangping Dong's avatar Kangping Dong
Browse files

[mdns] add support for advertising subtypes

This commit adds support of advertising and discovering services
with subtypes.

With this change, client can publish a new service with subtypes with
code:
```
NsdServiceInfo info = new NsdServiceInfo();
info.setServiceName("My Service");
info.setServiceType("_meshcop._udp");
info.setSubtypes(Set.of("_abc"));

nsdManager.registerService(info, PROTOCOL_DNS_SD, listener);
```

Bug: 265095929
Test: atest CtsNetTestCases FrameworksNetTests
Change-Id: Ia83182e2d43002243d1ef4357d14a8056d7da14b
parent b94bd28e
No related branches found
No related tags found
No related merge requests found
......@@ -251,6 +251,7 @@ package android.net.nsd {
method public int getPort();
method public String getServiceName();
method public String getServiceType();
method @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") @NonNull public java.util.Set<java.lang.String> getSubtypes();
method public void removeAttribute(String);
method public void setAttribute(String, String);
method @Deprecated public void setHost(java.net.InetAddress);
......@@ -259,6 +260,7 @@ package android.net.nsd {
method public void setPort(int);
method public void setServiceName(String);
method public void setServiceType(String);
method @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public void setSubtypes(@NonNull java.util.Set<java.lang.String>);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
}
......
......@@ -150,6 +150,8 @@ public final class NsdManager {
public static class Flags {
static final String REGISTER_NSD_OFFLOAD_ENGINE_API =
"com.android.net.flags.register_nsd_offload_engine_api";
static final String NSD_SUBTYPES_SUPPORT_ENABLED =
"com.android.net.flags.nsd_subtypes_support_enabled";
}
/**
......
......@@ -16,6 +16,9 @@
package android.net.nsd;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
......@@ -24,6 +27,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.net.module.util.InetAddressUtils;
......@@ -35,6 +39,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A class representing service information for network service discovery
......@@ -48,6 +53,8 @@ public final class NsdServiceInfo implements Parcelable {
private String mServiceType;
private final Set<String> mSubtypes = new ArraySet<>();
private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
private final List<InetAddress> mHostAddresses = new ArrayList<>();
......@@ -391,11 +398,41 @@ public final class NsdServiceInfo implements Parcelable {
mInterfaceIndex = interfaceIndex;
}
/**
* Sets the subtypes to be advertised for this service instance.
*
* The elements in {@code subtypes} should be the subtype identifiers which have the trailing
* "._sub" removed. For example, the subtype should be "_printer" for
* "_printer._sub._http._tcp.local".
*
* Only one subtype will be registered if multiple elements of {@code subtypes} have the same
* case-insensitive value.
*/
@FlaggedApi(NsdManager.Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
public void setSubtypes(@NonNull Set<String> subtypes) {
mSubtypes.clear();
mSubtypes.addAll(subtypes);
}
/**
* Returns subtypes of this service instance.
*
* When this object is returned by the service discovery/browse APIs (etc. {@link
* NsdManager.DiscoveryListener}), the return value may or may not include the subtypes of this
* service.
*/
@FlaggedApi(NsdManager.Flags.NSD_SUBTYPES_SUPPORT_ENABLED)
@NonNull
public Set<String> getSubtypes() {
return Collections.unmodifiableSet(mSubtypes);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("name: ").append(mServiceName)
.append(", type: ").append(mServiceType)
.append(", subtypes: ").append(TextUtils.join(", ", mSubtypes))
.append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
.append(", port: ").append(mPort)
.append(", network: ").append(mNetwork);
......@@ -414,6 +451,7 @@ public final class NsdServiceInfo implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mServiceName);
dest.writeString(mServiceType);
dest.writeStringList(new ArrayList<>(mSubtypes));
dest.writeInt(mPort);
// TXT record key/value pairs.
......@@ -445,6 +483,7 @@ public final class NsdServiceInfo implements Parcelable {
NsdServiceInfo info = new NsdServiceInfo();
info.mServiceName = in.readString();
info.mServiceType = in.readString();
info.setSubtypes(new ArraySet<>(in.createStringArrayList()));
info.mPort = in.readInt();
// TXT record key/value pairs.
......
......@@ -18,6 +18,7 @@ package android.net.nsd;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
......@@ -40,6 +41,7 @@ import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
......@@ -114,6 +116,7 @@ public class NsdServiceInfoTest {
NsdServiceInfo fullInfo = new NsdServiceInfo();
fullInfo.setServiceName("kitten");
fullInfo.setServiceType("_kitten._tcp");
fullInfo.setSubtypes(Set.of("_thread", "_matter"));
fullInfo.setPort(4242);
fullInfo.setHostAddresses(List.of(IPV4_ADDRESS));
fullInfo.setNetwork(new Network(123));
......@@ -149,7 +152,7 @@ public class NsdServiceInfoTest {
assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
}
public void checkParcelable(NsdServiceInfo original) {
private static void checkParcelable(NsdServiceInfo original) {
// Write to parcel.
Parcel p = Parcel.obtain();
Bundle writer = new Bundle();
......@@ -179,11 +182,20 @@ public class NsdServiceInfoTest {
}
}
public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
private static void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
byte[] txtRecord = shouldBeEmpty.getTxtRecord();
if (txtRecord == null || txtRecord.length == 0) {
return;
}
fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord));
}
@Test
public void testSubtypesValidSubtypesSuccess() {
NsdServiceInfo info = new NsdServiceInfo();
info.setSubtypes(Set.of("_thread", "_matter"));
assertEquals(Set.of("_thread", "_matter"), info.getSubtypes());
}
}
......@@ -43,6 +43,7 @@ import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
import android.net.cts.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
import android.net.cts.NsdResolveRecord.ResolveEvent.ResolutionStopped
......@@ -991,6 +992,104 @@ class NsdManagerTest {
}
}
@Test
fun testSubtypeAdvertisingAndDiscovery_withSetSubtypesApi() {
runSubtypeAdvertisingAndDiscoveryTest(useLegacySpecifier = false)
}
@Test
fun testSubtypeAdvertisingAndDiscovery_withSetSubtypesApiAndLegacySpecifier() {
runSubtypeAdvertisingAndDiscoveryTest(useLegacySpecifier = true)
}
private fun runSubtypeAdvertisingAndDiscoveryTest(useLegacySpecifier: Boolean) {
val si = makeTestServiceInfo(network = testNetwork1.network)
if (useLegacySpecifier) {
si.subtypes = setOf("_subtype1")
// Test "_type._tcp.local,_subtype" syntax with the registration
si.serviceType = si.serviceType + ",_subtype2"
} else {
si.subtypes = setOf("_subtype1", "_subtype2")
}
val registrationRecord = NsdRegistrationRecord()
val baseTypeDiscoveryRecord = NsdDiscoveryRecord()
val subtype1DiscoveryRecord = NsdDiscoveryRecord()
val subtype2DiscoveryRecord = NsdDiscoveryRecord()
val otherSubtypeDiscoveryRecord = NsdDiscoveryRecord()
tryTest {
registerService(registrationRecord, si)
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, baseTypeDiscoveryRecord)
// Test "<subtype>._type._tcp.local" syntax with discovery. Note this is not
// "<subtype>._sub._type._tcp.local".
nsdManager.discoverServices("_othersubtype.$serviceType",
NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, otherSubtypeDiscoveryRecord)
nsdManager.discoverServices("_subtype1.$serviceType",
NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, subtype1DiscoveryRecord)
nsdManager.discoverServices("_subtype2.$serviceType",
NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, subtype2DiscoveryRecord)
val info1 = subtype1DiscoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
assertTrue(info1.subtypes.contains("_subtype1"))
val info2 = subtype2DiscoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
assertTrue(info2.subtypes.contains("_subtype2"))
baseTypeDiscoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStarted>()
// The subtype callback was registered later but called, no need for an extra delay
otherSubtypeDiscoveryRecord.assertNoCallback(timeoutMs = 0)
} cleanupStep {
nsdManager.stopServiceDiscovery(baseTypeDiscoveryRecord)
nsdManager.stopServiceDiscovery(subtype1DiscoveryRecord)
nsdManager.stopServiceDiscovery(subtype2DiscoveryRecord)
nsdManager.stopServiceDiscovery(otherSubtypeDiscoveryRecord)
baseTypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
subtype1DiscoveryRecord.expectCallback<DiscoveryStopped>()
subtype2DiscoveryRecord.expectCallback<DiscoveryStopped>()
otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
} cleanup {
nsdManager.unregisterService(registrationRecord)
}
}
@Test
fun testSubtypeAdvertising_tooManySubtypes_returnsFailureBadParameters() {
val si = makeTestServiceInfo(network = testNetwork1.network)
// Sets 101 subtypes in total
val seq = generateSequence(1) { it + 1}
si.subtypes = seq.take(100).toList().map {it -> "_subtype" + it}.toSet()
si.serviceType = si.serviceType + ",_subtype"
val record = NsdRegistrationRecord()
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, Executor { it.run() }, record)
val failedCb = record.expectCallback<RegistrationFailed>(REGISTRATION_TIMEOUT_MS)
assertEquals(NsdManager.FAILURE_BAD_PARAMETERS, failedCb.errorCode)
}
@Test
fun testSubtypeAdvertising_emptySubtypeLabel_returnsFailureBadParameters() {
val si = makeTestServiceInfo(network = testNetwork1.network)
si.subtypes = setOf("")
val record = NsdRegistrationRecord()
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, Executor { it.run() }, record)
val failedCb = record.expectCallback<RegistrationFailed>(REGISTRATION_TIMEOUT_MS)
assertEquals(NsdManager.FAILURE_BAD_PARAMETERS, failedCb.errorCode)
}
@Test
fun testRegisterWithConflictDuringProbing() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment