Merge changes I09e780af,I21367c66 am: 94a4149a8f
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2584341 Change-Id: Ieed20f6d237dde9bee48f932236c5fb0a587d6c9 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -55,6 +55,7 @@ import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -599,7 +600,10 @@ public class NsdService extends INsdManager.Stub {
|
||||
|
||||
final NsdServiceInfo info = args.serviceInfo;
|
||||
id = getUniqueId();
|
||||
final String serviceType = constructServiceType(info.getServiceType());
|
||||
final Pair<String, String> typeAndSubtype =
|
||||
parseTypeAndSubtype(info.getServiceType());
|
||||
final String serviceType = typeAndSubtype == null
|
||||
? null : typeAndSubtype.first;
|
||||
if (clientInfo.mUseJavaBackend
|
||||
|| mDeps.isMdnsDiscoveryManagerEnabled(mContext)
|
||||
|| useDiscoveryManagerForType(serviceType)) {
|
||||
@@ -613,12 +617,17 @@ public class NsdService extends INsdManager.Stub {
|
||||
maybeStartMonitoringSockets();
|
||||
final MdnsListener listener =
|
||||
new DiscoveryListener(clientId, id, info, listenServiceType);
|
||||
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
|
||||
.setNetwork(info.getNetwork())
|
||||
.setIsPassiveMode(true)
|
||||
.build();
|
||||
final MdnsSearchOptions.Builder optionsBuilder =
|
||||
MdnsSearchOptions.newBuilder()
|
||||
.setNetwork(info.getNetwork())
|
||||
.setIsPassiveMode(true);
|
||||
if (typeAndSubtype.second != null) {
|
||||
// The parsing ensures subtype starts with an underscore.
|
||||
// MdnsSearchOptions expects the underscore to not be present.
|
||||
optionsBuilder.addSubtype(typeAndSubtype.second.substring(1));
|
||||
}
|
||||
mMdnsDiscoveryManager.registerListener(
|
||||
listenServiceType, listener, options);
|
||||
listenServiceType, listener, optionsBuilder.build());
|
||||
storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
|
||||
clientInfo.onDiscoverServicesStarted(clientId, info);
|
||||
clientInfo.log("Register a DiscoveryListener " + id
|
||||
@@ -697,7 +706,9 @@ public class NsdService extends INsdManager.Stub {
|
||||
id = getUniqueId();
|
||||
final NsdServiceInfo serviceInfo = args.serviceInfo;
|
||||
final String serviceType = serviceInfo.getServiceType();
|
||||
final String registerServiceType = constructServiceType(serviceType);
|
||||
final Pair<String, String> typeSubtype = parseTypeAndSubtype(serviceType);
|
||||
final String registerServiceType = typeSubtype == null
|
||||
? null : typeSubtype.first;
|
||||
if (clientInfo.mUseJavaBackend
|
||||
|| mDeps.isMdnsAdvertiserEnabled(mContext)
|
||||
|| useAdvertiserForType(registerServiceType)) {
|
||||
@@ -712,7 +723,11 @@ public class NsdService extends INsdManager.Stub {
|
||||
serviceInfo.getServiceName()));
|
||||
|
||||
maybeStartMonitoringSockets();
|
||||
mAdvertiser.addService(id, serviceInfo);
|
||||
// TODO: pass in the subtype as well. Including the subtype in the
|
||||
// service type would generate service instance names like
|
||||
// Name._subtype._sub._type._tcp, which is incorrect
|
||||
// (it should be Name._type._tcp).
|
||||
mAdvertiser.addService(id, serviceInfo, typeSubtype.second);
|
||||
storeAdvertiserRequestMap(clientId, id, clientInfo);
|
||||
} else {
|
||||
maybeStartDaemon();
|
||||
@@ -778,7 +793,10 @@ public class NsdService extends INsdManager.Stub {
|
||||
|
||||
final NsdServiceInfo info = args.serviceInfo;
|
||||
id = getUniqueId();
|
||||
final String serviceType = constructServiceType(info.getServiceType());
|
||||
final Pair<String, String> typeSubtype =
|
||||
parseTypeAndSubtype(info.getServiceType());
|
||||
final String serviceType = typeSubtype == null
|
||||
? null : typeSubtype.first;
|
||||
if (clientInfo.mUseJavaBackend
|
||||
|| mDeps.isMdnsDiscoveryManagerEnabled(mContext)
|
||||
|| useDiscoveryManagerForType(serviceType)) {
|
||||
@@ -871,7 +889,10 @@ public class NsdService extends INsdManager.Stub {
|
||||
|
||||
final NsdServiceInfo info = args.serviceInfo;
|
||||
id = getUniqueId();
|
||||
final String serviceType = constructServiceType(info.getServiceType());
|
||||
final Pair<String, String> typeAndSubtype =
|
||||
parseTypeAndSubtype(info.getServiceType());
|
||||
final String serviceType = typeAndSubtype == null
|
||||
? null : typeAndSubtype.first;
|
||||
if (serviceType == null) {
|
||||
clientInfo.onServiceInfoCallbackRegistrationFailed(clientId,
|
||||
NsdManager.FAILURE_BAD_PARAMETERS);
|
||||
@@ -1315,28 +1336,39 @@ public class NsdService extends INsdManager.Stub {
|
||||
* Check the given service type is valid and construct it to a service type
|
||||
* which can use for discovery / resolution service.
|
||||
*
|
||||
* <p> The valid service type should be 2 labels, or 3 labels if the query is for a
|
||||
* <p>The valid service type should be 2 labels, or 3 labels if the query is for a
|
||||
* subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
|
||||
* 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.
|
||||
*
|
||||
* @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 String constructServiceType(String serviceType) {
|
||||
public static Pair<String, String> parseTypeAndSubtype(String serviceType) {
|
||||
if (TextUtils.isEmpty(serviceType)) return null;
|
||||
|
||||
final String typeOrSubtypePattern = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
|
||||
final Pattern serviceTypePattern = Pattern.compile(
|
||||
"^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
|
||||
+ "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))"
|
||||
// Optional leading subtype (_subtype._type._tcp)
|
||||
// (?: xxx) is a non-capturing parenthesis, don't capture the dot
|
||||
"^(?:(" + typeOrSubtypePattern + ")\\.)?"
|
||||
// Actual type (_type._tcp.local)
|
||||
+ "(" + typeOrSubtypePattern + "\\._(?:tcp|udp))"
|
||||
// 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
|
||||
+ "(?:,(" + typeOrSubtypePattern + "))?"
|
||||
+ "$");
|
||||
final Matcher matcher = serviceTypePattern.matcher(serviceType);
|
||||
if (!matcher.matches()) return null;
|
||||
return matcher.group(1) == null
|
||||
? matcher.group(2)
|
||||
: matcher.group(1) + "_sub." + matcher.group(2);
|
||||
// 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);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -270,7 +270,8 @@ public class MdnsAdvertiser {
|
||||
mPendingRegistrations.put(id, registration);
|
||||
for (int i = 0; i < mAdvertisers.size(); i++) {
|
||||
try {
|
||||
mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo());
|
||||
mAdvertisers.valueAt(i).addService(
|
||||
id, registration.getServiceInfo(), registration.getSubtype());
|
||||
} catch (NameConflictException e) {
|
||||
Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
|
||||
}
|
||||
@@ -298,9 +299,10 @@ public class MdnsAdvertiser {
|
||||
}
|
||||
mAdvertisers.put(socket, advertiser);
|
||||
for (int i = 0; i < mPendingRegistrations.size(); i++) {
|
||||
final Registration registration = mPendingRegistrations.valueAt(i);
|
||||
try {
|
||||
advertiser.addService(mPendingRegistrations.keyAt(i),
|
||||
mPendingRegistrations.valueAt(i).getServiceInfo());
|
||||
registration.getServiceInfo(), registration.getSubtype());
|
||||
} catch (NameConflictException e) {
|
||||
Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
|
||||
}
|
||||
@@ -329,10 +331,13 @@ public class MdnsAdvertiser {
|
||||
private int mConflictCount;
|
||||
@NonNull
|
||||
private NsdServiceInfo mServiceInfo;
|
||||
@Nullable
|
||||
private final String mSubtype;
|
||||
|
||||
private Registration(@NonNull NsdServiceInfo serviceInfo) {
|
||||
private Registration(@NonNull NsdServiceInfo serviceInfo, @Nullable String subtype) {
|
||||
this.mOriginalName = serviceInfo.getServiceName();
|
||||
this.mServiceInfo = serviceInfo;
|
||||
this.mSubtype = subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,6 +392,11 @@ public class MdnsAdvertiser {
|
||||
public NsdServiceInfo getServiceInfo() {
|
||||
return mServiceInfo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSubtype() {
|
||||
return mSubtype;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,8 +453,9 @@ public class MdnsAdvertiser {
|
||||
* Add a service to advertise.
|
||||
* @param id A unique ID for the service.
|
||||
* @param service The service info to advertise.
|
||||
* @param subtype An optional subtype to advertise the service with.
|
||||
*/
|
||||
public void addService(int id, NsdServiceInfo service) {
|
||||
public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
|
||||
checkThread();
|
||||
if (mRegistrations.get(id) != null) {
|
||||
Log.e(TAG, "Adding duplicate registration for " + service);
|
||||
@@ -453,10 +464,10 @@ public class MdnsAdvertiser {
|
||||
return;
|
||||
}
|
||||
|
||||
mSharedLog.i("Adding service " + service + " with ID " + id);
|
||||
mSharedLog.i("Adding service " + service + " with ID " + id + " and subtype " + subtype);
|
||||
|
||||
final Network network = service.getNetwork();
|
||||
final Registration registration = new Registration(service);
|
||||
final Registration registration = new Registration(service, subtype);
|
||||
final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
|
||||
if (network == null) {
|
||||
// If registering on all networks, no advertiser must have conflicts
|
||||
|
||||
@@ -212,8 +212,9 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand
|
||||
*
|
||||
* @throws NameConflictException There is already a service being advertised with that name.
|
||||
*/
|
||||
public void addService(int id, NsdServiceInfo service) throws NameConflictException {
|
||||
final int replacedExitingService = mRecordRepository.addService(id, service);
|
||||
public void addService(int id, NsdServiceInfo service, @Nullable String subtype)
|
||||
throws NameConflictException {
|
||||
final int replacedExitingService = mRecordRepository.addService(id, service, subtype);
|
||||
// Cancel announcements for the existing service. This only happens for exiting services
|
||||
// (so cancelling exiting announcements), as per RecordRepository.addService.
|
||||
if (replacedExitingService >= 0) {
|
||||
|
||||
@@ -69,6 +69,8 @@ public class MdnsRecordRepository {
|
||||
|
||||
// Top-level domain for link-local queries, as per RFC6762 3.
|
||||
private static final String LOCAL_TLD = "local";
|
||||
// Subtype separator as per RFC6763 7.1 (_printer._sub._http._tcp.local)
|
||||
private static final String SUBTYPE_SEPARATOR = "_sub";
|
||||
|
||||
// Service type for service enumeration (RFC6763 9.)
|
||||
private static final String[] DNS_SD_SERVICE_TYPE =
|
||||
@@ -156,13 +158,15 @@ public class MdnsRecordRepository {
|
||||
@NonNull
|
||||
public final List<RecordInfo<?>> allRecords;
|
||||
@NonNull
|
||||
public final RecordInfo<MdnsPointerRecord> ptrRecord;
|
||||
public final List<RecordInfo<MdnsPointerRecord>> ptrRecords;
|
||||
@NonNull
|
||||
public final RecordInfo<MdnsServiceRecord> srvRecord;
|
||||
@NonNull
|
||||
public final RecordInfo<MdnsTextRecord> txtRecord;
|
||||
@NonNull
|
||||
public final NsdServiceInfo serviceInfo;
|
||||
@Nullable
|
||||
public final String subtype;
|
||||
|
||||
/**
|
||||
* Whether the service is sending exit announcements and will be destroyed soon.
|
||||
@@ -175,14 +179,16 @@ public class MdnsRecordRepository {
|
||||
* @param deviceHostname Hostname of the device (for the interface used)
|
||||
* @param serviceInfo Service to advertise
|
||||
*/
|
||||
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo) {
|
||||
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
|
||||
@Nullable String subtype) {
|
||||
this.serviceInfo = serviceInfo;
|
||||
this.subtype = subtype;
|
||||
|
||||
final String[] serviceType = splitServiceType(serviceInfo);
|
||||
final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
|
||||
|
||||
// Service PTR record
|
||||
ptrRecord = new RecordInfo<>(
|
||||
final RecordInfo<MdnsPointerRecord> ptrRecord = new RecordInfo<>(
|
||||
serviceInfo,
|
||||
new MdnsPointerRecord(
|
||||
serviceType,
|
||||
@@ -192,6 +198,26 @@ public class MdnsRecordRepository {
|
||||
serviceName),
|
||||
true /* sharedName */, true /* probing */);
|
||||
|
||||
if (subtype == null) {
|
||||
this.ptrRecords = Collections.singletonList(ptrRecord);
|
||||
} else {
|
||||
final String[] subtypeName = new String[serviceType.length + 2];
|
||||
System.arraycopy(serviceType, 0, subtypeName, 2, serviceType.length);
|
||||
subtypeName[0] = subtype;
|
||||
subtypeName[1] = SUBTYPE_SEPARATOR;
|
||||
final RecordInfo<MdnsPointerRecord> subtypeRecord = new RecordInfo<>(
|
||||
serviceInfo,
|
||||
new MdnsPointerRecord(
|
||||
subtypeName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
NON_NAME_RECORDS_TTL_MILLIS,
|
||||
serviceName),
|
||||
true /* sharedName */, true /* probing */);
|
||||
|
||||
this.ptrRecords = List.of(ptrRecord, subtypeRecord);
|
||||
}
|
||||
|
||||
srvRecord = new RecordInfo<>(
|
||||
serviceInfo,
|
||||
new MdnsServiceRecord(serviceName,
|
||||
@@ -211,8 +237,8 @@ public class MdnsRecordRepository {
|
||||
attrsToTextEntries(serviceInfo.getAttributes())),
|
||||
false /* sharedName */, true /* probing */);
|
||||
|
||||
final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(4);
|
||||
allRecords.add(ptrRecord);
|
||||
final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
|
||||
allRecords.addAll(ptrRecords);
|
||||
allRecords.add(srvRecord);
|
||||
allRecords.add(txtRecord);
|
||||
// Service type enumeration record (RFC6763 9.)
|
||||
@@ -275,7 +301,8 @@ public class MdnsRecordRepository {
|
||||
* ID of the replaced service.
|
||||
* @throws NameConflictException There is already a (non-exiting) service using the name.
|
||||
*/
|
||||
public int addService(int serviceId, NsdServiceInfo serviceInfo) throws NameConflictException {
|
||||
public int addService(int serviceId, NsdServiceInfo serviceInfo, @Nullable String subtype)
|
||||
throws NameConflictException {
|
||||
if (mServices.contains(serviceId)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Service ID must not be reused across registrations: " + serviceId);
|
||||
@@ -288,7 +315,7 @@ public class MdnsRecordRepository {
|
||||
}
|
||||
|
||||
final ServiceRegistration registration = new ServiceRegistration(
|
||||
mDeviceHostname, serviceInfo);
|
||||
mDeviceHostname, serviceInfo, subtype);
|
||||
mServices.put(serviceId, registration);
|
||||
|
||||
// Remove existing exiting service
|
||||
@@ -344,24 +371,25 @@ public class MdnsRecordRepository {
|
||||
if (registration == null) return null;
|
||||
if (registration.exiting) return null;
|
||||
|
||||
// Send exit (TTL 0) for the PTR record, if the record was sent (in particular don't send
|
||||
// Send exit (TTL 0) for the PTR records, if at least one was sent (in particular don't send
|
||||
// if still probing)
|
||||
if (registration.ptrRecord.lastSentTimeMs == 0L) {
|
||||
if (CollectionUtils.all(registration.ptrRecords, r -> r.lastSentTimeMs == 0L)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
registration.exiting = true;
|
||||
final MdnsPointerRecord expiredRecord = new MdnsPointerRecord(
|
||||
registration.ptrRecord.record.getName(),
|
||||
0L /* receiptTimeMillis */,
|
||||
true /* cacheFlush */,
|
||||
0L /* ttlMillis */,
|
||||
registration.ptrRecord.record.getPointer());
|
||||
final List<MdnsRecord> expiredRecords = CollectionUtils.map(registration.ptrRecords,
|
||||
r -> new MdnsPointerRecord(
|
||||
r.record.getName(),
|
||||
0L /* receiptTimeMillis */,
|
||||
true /* cacheFlush */,
|
||||
0L /* ttlMillis */,
|
||||
r.record.getPointer()));
|
||||
|
||||
// Exit should be skipped if the record is still advertised by another service, but that
|
||||
// would be a conflict (2 service registrations with the same service name), so it would
|
||||
// not have been allowed by the repository.
|
||||
return new MdnsAnnouncer.ExitAnnouncementInfo(id, Collections.singletonList(expiredRecord));
|
||||
return new MdnsAnnouncer.ExitAnnouncementInfo(id, expiredRecords);
|
||||
}
|
||||
|
||||
public void removeService(int id) {
|
||||
@@ -442,7 +470,7 @@ public class MdnsRecordRepository {
|
||||
for (int i = 0; i < mServices.size(); i++) {
|
||||
final ServiceRegistration registration = mServices.valueAt(i);
|
||||
if (registration.exiting) continue;
|
||||
addReplyFromService(question, registration.allRecords, registration.ptrRecord,
|
||||
addReplyFromService(question, registration.allRecords, registration.ptrRecords,
|
||||
registration.srvRecord, registration.txtRecord, replyUnicast, now,
|
||||
answerInfo, additionalAnswerRecords);
|
||||
}
|
||||
@@ -499,7 +527,7 @@ public class MdnsRecordRepository {
|
||||
*/
|
||||
private void addReplyFromService(@NonNull MdnsRecord question,
|
||||
@NonNull List<RecordInfo<?>> serviceRecords,
|
||||
@Nullable RecordInfo<MdnsPointerRecord> servicePtrRecord,
|
||||
@Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
|
||||
@Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
|
||||
@Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
|
||||
boolean replyUnicast, long now, @NonNull List<RecordInfo<?>> answerInfo,
|
||||
@@ -531,7 +559,8 @@ public class MdnsRecordRepository {
|
||||
}
|
||||
|
||||
hasKnownAnswer = true;
|
||||
hasDnsSdPtrRecordAnswer |= (info == servicePtrRecord);
|
||||
hasDnsSdPtrRecordAnswer |= (servicePtrRecords != null
|
||||
&& CollectionUtils.any(servicePtrRecords, r -> info == r));
|
||||
hasDnsSdSrvRecordAnswer |= (info == serviceSrvRecord);
|
||||
|
||||
// TODO: responses to probe queries should bypass this check and only ensure the
|
||||
@@ -791,10 +820,11 @@ public class MdnsRecordRepository {
|
||||
*/
|
||||
@Nullable
|
||||
public MdnsProber.ProbingInfo renameServiceForConflict(int serviceId, NsdServiceInfo newInfo) {
|
||||
if (!mServices.contains(serviceId)) return null;
|
||||
final ServiceRegistration existing = mServices.get(serviceId);
|
||||
if (existing == null) return null;
|
||||
|
||||
final ServiceRegistration newService = new ServiceRegistration(
|
||||
mDeviceHostname, newInfo);
|
||||
mDeviceHostname, newInfo, existing.subtype);
|
||||
mServices.put(serviceId, newService);
|
||||
return makeProbingInfo(serviceId, newService.srvRecord.record);
|
||||
}
|
||||
|
||||
@@ -28,13 +28,16 @@ import android.util.Pair;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.server.connectivity.mdns.util.MdnsUtils;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -205,9 +208,22 @@ public class MdnsServiceTypeClient {
|
||||
|
||||
private boolean responseMatchesOptions(@NonNull MdnsResponse response,
|
||||
@NonNull MdnsSearchOptions options) {
|
||||
if (options.getResolveInstanceName() == null) return true;
|
||||
// DNS is case-insensitive, so ignore case in the comparison
|
||||
return options.getResolveInstanceName().equalsIgnoreCase(response.getServiceInstanceName());
|
||||
final boolean matchesInstanceName = options.getResolveInstanceName() == null
|
||||
// DNS is case-insensitive, so ignore case in the comparison
|
||||
|| MdnsUtils.equalsIgnoreDnsCase(options.getResolveInstanceName(),
|
||||
response.getServiceInstanceName());
|
||||
|
||||
// If discovery is requiring some subtypes, the response must have one that matches a
|
||||
// requested one.
|
||||
final List<String> responseSubtypes = response.getSubtypes() == null
|
||||
? Collections.emptyList() : response.getSubtypes();
|
||||
final boolean matchesSubtype = options.getSubtypes().size() == 0
|
||||
|| CollectionUtils.any(options.getSubtypes(), requiredSub ->
|
||||
CollectionUtils.any(responseSubtypes, actualSub ->
|
||||
MdnsUtils.equalsIgnoreDnsCase(
|
||||
MdnsConstants.SUBTYPE_PREFIX + requiredSub, actualSub)));
|
||||
|
||||
return matchesInstanceName && matchesSubtype;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
|
||||
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
|
||||
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
|
||||
|
||||
import static com.android.server.NsdService.constructServiceType;
|
||||
import static com.android.server.NsdService.parseTypeAndSubtype;
|
||||
import static com.android.testutils.ContextUtils.mockService;
|
||||
|
||||
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
|
||||
@@ -77,6 +77,7 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -84,6 +85,7 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.server.NsdService.Dependencies;
|
||||
import com.android.server.connectivity.mdns.MdnsAdvertiser;
|
||||
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
|
||||
import com.android.server.connectivity.mdns.MdnsSearchOptions;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo;
|
||||
import com.android.server.connectivity.mdns.MdnsSocketProvider;
|
||||
@@ -104,6 +106,7 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -968,6 +971,34 @@ public class NsdServiceTest {
|
||||
.onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
|
||||
public void testDiscoveryWithMdnsDiscoveryManager_UsesSubtypes() {
|
||||
final String typeWithSubtype = SERVICE_TYPE + ",_subtype";
|
||||
final NsdManager client = connectClient(mService);
|
||||
final NsdServiceInfo regInfo = new NsdServiceInfo("Instance", typeWithSubtype);
|
||||
final Network network = new Network(999);
|
||||
regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
|
||||
regInfo.setPort(12345);
|
||||
regInfo.setNetwork(network);
|
||||
|
||||
final RegistrationListener regListener = mock(RegistrationListener.class);
|
||||
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
|
||||
waitForIdle();
|
||||
verify(mAdvertiser).addService(anyInt(), argThat(s ->
|
||||
"Instance".equals(s.getServiceName())
|
||||
&& SERVICE_TYPE.equals(s.getServiceType())), eq("_subtype"));
|
||||
|
||||
final DiscoveryListener discListener = mock(DiscoveryListener.class);
|
||||
client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
|
||||
waitForIdle();
|
||||
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
|
||||
ArgumentCaptor.forClass(MdnsSearchOptions.class);
|
||||
verify(mDiscoveryManager).registerListener(eq(SERVICE_TYPE + ".local"), any(),
|
||||
optionsCaptor.capture());
|
||||
assertEquals(Collections.singletonList("subtype"), optionsCaptor.getValue().getSubtypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolutionWithMdnsDiscoveryManager() throws UnknownHostException {
|
||||
setMdnsDiscoveryManagerEnabled();
|
||||
@@ -976,7 +1007,7 @@ public class NsdServiceTest {
|
||||
final ResolveListener resolveListener = mock(ResolveListener.class);
|
||||
final Network network = new Network(999);
|
||||
final String serviceType = "_nsd._service._tcp";
|
||||
final String constructedServiceType = "_nsd._sub._service._tcp.local";
|
||||
final String constructedServiceType = "_service._tcp.local";
|
||||
final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
|
||||
ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
|
||||
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
|
||||
@@ -984,12 +1015,14 @@ public class NsdServiceTest {
|
||||
client.resolveService(request, resolveListener);
|
||||
waitForIdle();
|
||||
verify(mSocketProvider).startMonitoringSockets();
|
||||
// TODO(b/266167702): this is a bug, as registerListener should be done _service._tcp, and
|
||||
// _sub should be in the list of subtypes in the options.
|
||||
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
|
||||
ArgumentCaptor.forClass(MdnsSearchOptions.class);
|
||||
verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
|
||||
listenerCaptor.capture(), argThat(options ->
|
||||
network.equals(options.getNetwork())
|
||||
&& SERVICE_NAME.equals(options.getResolveInstanceName())));
|
||||
listenerCaptor.capture(),
|
||||
optionsCaptor.capture());
|
||||
assertEquals(network, optionsCaptor.getValue().getNetwork());
|
||||
// Subtypes are not used for resolution, only for discovery
|
||||
assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
|
||||
|
||||
final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
|
||||
final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
|
||||
@@ -1013,8 +1046,7 @@ public class NsdServiceTest {
|
||||
verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
|
||||
final NsdServiceInfo info = infoCaptor.getValue();
|
||||
assertEquals(SERVICE_NAME, info.getServiceName());
|
||||
// TODO(b/266167702): this should be ._service._tcp (as per legacy behavior)
|
||||
assertEquals("._nsd._sub._service._tcp", info.getServiceType());
|
||||
assertEquals("._service._tcp", info.getServiceType());
|
||||
assertEquals(PORT, info.getPort());
|
||||
assertTrue(info.getAttributes().containsKey("key"));
|
||||
assertEquals(1, info.getAttributes().size());
|
||||
@@ -1057,7 +1089,7 @@ public class NsdServiceTest {
|
||||
|
||||
final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(mAdvertiser).addService(serviceIdCaptor.capture(),
|
||||
argThat(info -> matches(info, regInfo)));
|
||||
argThat(info -> matches(info, regInfo)), eq(null) /* subtype */);
|
||||
|
||||
client.unregisterService(regListenerWithoutFeature);
|
||||
waitForIdle();
|
||||
@@ -1114,8 +1146,10 @@ public class NsdServiceTest {
|
||||
waitForIdle();
|
||||
|
||||
// The advertiser is enabled for _type2 but not _type1
|
||||
verify(mAdvertiser, never()).addService(anyInt(), argThat(info -> matches(info, service1)));
|
||||
verify(mAdvertiser).addService(anyInt(), argThat(info -> matches(info, service2)));
|
||||
verify(mAdvertiser, never()).addService(
|
||||
anyInt(), argThat(info -> matches(info, service1)), eq(null) /* subtype */);
|
||||
verify(mAdvertiser).addService(
|
||||
anyInt(), argThat(info -> matches(info, service2)), eq(null) /* subtype */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1140,7 +1174,7 @@ public class NsdServiceTest {
|
||||
verify(mSocketProvider).startMonitoringSockets();
|
||||
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(mAdvertiser).addService(idCaptor.capture(), argThat(info ->
|
||||
matches(info, regInfo)));
|
||||
matches(info, regInfo)), eq(null) /* subtype */);
|
||||
|
||||
// Verify onServiceRegistered callback
|
||||
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
|
||||
@@ -1176,7 +1210,7 @@ public class NsdServiceTest {
|
||||
|
||||
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
|
||||
waitForIdle();
|
||||
verify(mAdvertiser, never()).addService(anyInt(), any());
|
||||
verify(mAdvertiser, never()).addService(anyInt(), any(), any());
|
||||
|
||||
verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
|
||||
argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
|
||||
@@ -1204,7 +1238,8 @@ public class NsdServiceTest {
|
||||
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
// Service name is truncated to 63 characters
|
||||
verify(mAdvertiser).addService(idCaptor.capture(),
|
||||
argThat(info -> info.getServiceName().equals("a".repeat(63))));
|
||||
argThat(info -> info.getServiceName().equals("a".repeat(63))),
|
||||
eq(null) /* subtype */);
|
||||
|
||||
// Verify onServiceRegistered callback
|
||||
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
|
||||
@@ -1222,7 +1257,7 @@ public class NsdServiceTest {
|
||||
final ResolveListener resolveListener = mock(ResolveListener.class);
|
||||
final Network network = new Network(999);
|
||||
final String serviceType = "_nsd._service._tcp";
|
||||
final String constructedServiceType = "_nsd._sub._service._tcp.local";
|
||||
final String constructedServiceType = "_service._tcp.local";
|
||||
final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
|
||||
ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
|
||||
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
|
||||
@@ -1230,8 +1265,14 @@ public class NsdServiceTest {
|
||||
client.resolveService(request, resolveListener);
|
||||
waitForIdle();
|
||||
verify(mSocketProvider).startMonitoringSockets();
|
||||
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
|
||||
ArgumentCaptor.forClass(MdnsSearchOptions.class);
|
||||
verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
|
||||
listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
|
||||
listenerCaptor.capture(),
|
||||
optionsCaptor.capture());
|
||||
assertEquals(network, optionsCaptor.getValue().getNetwork());
|
||||
// Subtypes are not used for resolution, only for discovery
|
||||
assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
|
||||
|
||||
client.stopServiceResolution(resolveListener);
|
||||
waitForIdle();
|
||||
@@ -1246,16 +1287,22 @@ public class NsdServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructServiceType() {
|
||||
public void testParseTypeAndSubtype() {
|
||||
final String serviceType1 = "test._tcp";
|
||||
final String serviceType2 = "_test._quic";
|
||||
final String serviceType3 = "_123._udp.";
|
||||
final String serviceType4 = "_TEST._999._tcp.";
|
||||
final String serviceType3 = "_test._quic,_test1,_test2";
|
||||
final String serviceType4 = "_123._udp.";
|
||||
final String serviceType5 = "_TEST._999._tcp.";
|
||||
final String serviceType6 = "_998._tcp.,_TEST";
|
||||
final String serviceType7 = "_997._tcp,_TEST";
|
||||
|
||||
assertEquals(null, constructServiceType(serviceType1));
|
||||
assertEquals(null, constructServiceType(serviceType2));
|
||||
assertEquals("_123._udp", constructServiceType(serviceType3));
|
||||
assertEquals("_TEST._sub._999._tcp", constructServiceType(serviceType4));
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1274,7 +1321,7 @@ public class NsdServiceTest {
|
||||
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
|
||||
waitForIdle();
|
||||
verify(mSocketProvider).startMonitoringSockets();
|
||||
verify(mAdvertiser).addService(anyInt(), any());
|
||||
verify(mAdvertiser).addService(anyInt(), any(), any());
|
||||
|
||||
// Verify the discovery uses MdnsDiscoveryManager
|
||||
final DiscoveryListener discListener = mock(DiscoveryListener.class);
|
||||
|
||||
@@ -57,6 +57,7 @@ private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
|
||||
private val TEST_NETWORK_1 = mock(Network::class.java)
|
||||
private val TEST_NETWORK_2 = mock(Network::class.java)
|
||||
private val TEST_HOSTNAME = arrayOf("Android_test", "local")
|
||||
private const val TEST_SUBTYPE = "_subtype"
|
||||
|
||||
private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
|
||||
port = 12345
|
||||
@@ -130,7 +131,7 @@ class MdnsAdvertiserTest {
|
||||
@Test
|
||||
fun testAddService_OneNetwork() {
|
||||
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
|
||||
|
||||
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
|
||||
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
|
||||
@@ -161,7 +162,7 @@ class MdnsAdvertiserTest {
|
||||
@Test
|
||||
fun testAddService_AllNetworks() {
|
||||
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
|
||||
postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE) }
|
||||
postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE) }
|
||||
|
||||
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
|
||||
verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE.network),
|
||||
@@ -179,6 +180,10 @@ class MdnsAdvertiserTest {
|
||||
verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
|
||||
eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any()
|
||||
)
|
||||
verify(mockInterfaceAdvertiser1).addService(
|
||||
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
|
||||
verify(mockInterfaceAdvertiser2).addService(
|
||||
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
|
||||
|
||||
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
|
||||
postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
|
||||
@@ -207,20 +212,21 @@ class MdnsAdvertiserTest {
|
||||
@Test
|
||||
fun testAddService_Conflicts() {
|
||||
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
|
||||
|
||||
val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
|
||||
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
|
||||
val oneNetSocketCb = oneNetSocketCbCaptor.value
|
||||
|
||||
// Register a service with the same name on all networks (name conflict)
|
||||
postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE) }
|
||||
postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE, null /* subtype */) }
|
||||
val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
|
||||
verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
|
||||
val allNetSocketCb = allNetSocketCbCaptor.value
|
||||
|
||||
postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1) }
|
||||
postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE) }
|
||||
postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1, null /* subtype */) }
|
||||
postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
|
||||
null /* subtype */) }
|
||||
|
||||
// Callbacks for matching network and all networks both get the socket
|
||||
postSync {
|
||||
@@ -248,13 +254,13 @@ class MdnsAdvertiserTest {
|
||||
eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any()
|
||||
)
|
||||
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
|
||||
argThat { it.matches(SERVICE_1) })
|
||||
argThat { it.matches(SERVICE_1) }, eq(null))
|
||||
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
|
||||
argThat { it.matches(expectedRenamed) })
|
||||
argThat { it.matches(expectedRenamed) }, eq(null))
|
||||
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
|
||||
argThat { it.matches(LONG_SERVICE_1) })
|
||||
argThat { it.matches(LONG_SERVICE_1) }, eq(null))
|
||||
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
|
||||
argThat { it.matches(expectedLongRenamed) })
|
||||
argThat { it.matches(expectedLongRenamed) }, eq(null))
|
||||
|
||||
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
|
||||
postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
|
||||
@@ -278,7 +284,7 @@ class MdnsAdvertiserTest {
|
||||
fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
|
||||
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
|
||||
verify(mockDeps, times(1)).generateHostname()
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
|
||||
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
|
||||
postSync { advertiser.removeService(SERVICE_ID_1) }
|
||||
verify(mockDeps, times(2)).generateHostname()
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class MdnsInterfaceAdvertiserTest {
|
||||
knownServices.add(inv.getArgument(0))
|
||||
|
||||
-1
|
||||
}.`when`(repository).addService(anyInt(), any())
|
||||
}.`when`(repository).addService(anyInt(), any(), any())
|
||||
doAnswer { inv ->
|
||||
knownServices.remove(inv.getArgument(0))
|
||||
null
|
||||
@@ -278,8 +278,8 @@ class MdnsInterfaceAdvertiserTest {
|
||||
doReturn(serviceId).`when`(testProbingInfo).serviceId
|
||||
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
|
||||
|
||||
advertiser.addService(serviceId, serviceInfo)
|
||||
verify(repository).addService(serviceId, serviceInfo)
|
||||
advertiser.addService(serviceId, serviceInfo, null /* subtype */)
|
||||
verify(repository).addService(serviceId, serviceInfo, null /* subtype */)
|
||||
verify(prober).startProbing(testProbingInfo)
|
||||
|
||||
// Simulate probing success: continues to announcing
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.junit.runner.RunWith
|
||||
private const val TEST_SERVICE_ID_1 = 42
|
||||
private const val TEST_SERVICE_ID_2 = 43
|
||||
private const val TEST_PORT = 12345
|
||||
private const val TEST_SUBTYPE = "_subtype"
|
||||
private val TEST_HOSTNAME = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
|
||||
private val TEST_ADDRESSES = listOf(
|
||||
LinkAddress(parseNumericAddress("192.0.2.111"), 24),
|
||||
@@ -86,7 +87,8 @@ class MdnsRecordRepositoryTest {
|
||||
fun testAddServiceAndProbe() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
assertEquals(0, repository.servicesCount)
|
||||
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
|
||||
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
|
||||
null /* subtype */))
|
||||
assertEquals(1, repository.servicesCount)
|
||||
|
||||
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
|
||||
@@ -118,18 +120,18 @@ class MdnsRecordRepositoryTest {
|
||||
@Test
|
||||
fun testAddAndConflicts() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
|
||||
assertFailsWith(NameConflictException::class) {
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInvalidReuseOfServiceId() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* subtype */)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ class MdnsRecordRepositoryTest {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
|
||||
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
|
||||
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
|
||||
|
||||
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
|
||||
@@ -179,6 +181,41 @@ class MdnsRecordRepositoryTest {
|
||||
assertEquals(0, repository.servicesCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExitAnnouncements_WithSubtype() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
|
||||
|
||||
val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
|
||||
assertNotNull(exitAnnouncement)
|
||||
assertEquals(1, repository.servicesCount)
|
||||
val packet = exitAnnouncement.getPacket(0)
|
||||
|
||||
assertEquals(0x8400 /* response, authoritative */, packet.flags)
|
||||
assertEquals(0, packet.questions.size)
|
||||
assertEquals(0, packet.authorityRecords.size)
|
||||
assertEquals(0, packet.additionalRecords.size)
|
||||
|
||||
assertContentEquals(listOf(
|
||||
MdnsPointerRecord(
|
||||
arrayOf("_testservice", "_tcp", "local"),
|
||||
0L /* receiptTimeMillis */,
|
||||
true /* cacheFlush */,
|
||||
0L /* ttlMillis */,
|
||||
arrayOf("MyTestService", "_testservice", "_tcp", "local")),
|
||||
MdnsPointerRecord(
|
||||
arrayOf("_subtype", "_sub", "_testservice", "_tcp", "local"),
|
||||
0L /* receiptTimeMillis */,
|
||||
true /* cacheFlush */,
|
||||
0L /* ttlMillis */,
|
||||
arrayOf("MyTestService", "_testservice", "_tcp", "local")),
|
||||
), packet.answers)
|
||||
|
||||
repository.removeService(TEST_SERVICE_ID_1)
|
||||
assertEquals(0, repository.servicesCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExitingServiceReAdded() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
@@ -186,7 +223,8 @@ class MdnsRecordRepositoryTest {
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
|
||||
repository.exitService(TEST_SERVICE_ID_1)
|
||||
|
||||
assertEquals(TEST_SERVICE_ID_1, repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
|
||||
assertEquals(TEST_SERVICE_ID_1,
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */))
|
||||
assertEquals(1, repository.servicesCount)
|
||||
|
||||
repository.removeService(TEST_SERVICE_ID_2)
|
||||
@@ -196,7 +234,8 @@ class MdnsRecordRepositoryTest {
|
||||
@Test
|
||||
fun testOnProbingSucceeded() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
|
||||
TEST_SUBTYPE)
|
||||
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
|
||||
val packet = announcementInfo.getPacket(0)
|
||||
|
||||
@@ -205,6 +244,7 @@ class MdnsRecordRepositoryTest {
|
||||
assertEquals(0, packet.authorityRecords.size)
|
||||
|
||||
val serviceType = arrayOf("_testservice", "_tcp", "local")
|
||||
val serviceSubtype = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
|
||||
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
|
||||
val v4AddrRev = getReverseDnsAddress(TEST_ADDRESSES[0].address)
|
||||
val v6Addr1Rev = getReverseDnsAddress(TEST_ADDRESSES[1].address)
|
||||
@@ -250,6 +290,13 @@ class MdnsRecordRepositoryTest {
|
||||
false /* cacheFlush */,
|
||||
4500000L /* ttlMillis */,
|
||||
serviceName),
|
||||
MdnsPointerRecord(
|
||||
serviceSubtype,
|
||||
0L /* receiptTimeMillis */,
|
||||
// Not a unique name owned by the announcer, so cacheFlush=false
|
||||
false /* cacheFlush */,
|
||||
4500000L /* ttlMillis */,
|
||||
serviceName),
|
||||
MdnsServiceRecord(
|
||||
serviceName,
|
||||
0L /* receiptTimeMillis */,
|
||||
@@ -319,9 +366,21 @@ class MdnsRecordRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun testGetReply() {
|
||||
doGetReplyTest(subtype = null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetReply_WithSubtype() {
|
||||
doGetReplyTest(TEST_SUBTYPE)
|
||||
}
|
||||
|
||||
private fun doGetReplyTest(subtype: String?) {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
val questions = listOf(MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"),
|
||||
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, subtype)
|
||||
val queriedName = if (subtype == null) arrayOf("_testservice", "_tcp", "local")
|
||||
else arrayOf(subtype, "_sub", "_testservice", "_tcp", "local")
|
||||
|
||||
val questions = listOf(MdnsPointerRecord(queriedName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
// TTL and data is empty for a question
|
||||
@@ -344,7 +403,7 @@ class MdnsRecordRepositoryTest {
|
||||
|
||||
assertEquals(listOf(
|
||||
MdnsPointerRecord(
|
||||
arrayOf("_testservice", "_tcp", "local"),
|
||||
queriedName,
|
||||
0L /* receiptTimeMillis */,
|
||||
false /* cacheFlush */,
|
||||
longTtl,
|
||||
@@ -405,8 +464,8 @@ class MdnsRecordRepositoryTest {
|
||||
@Test
|
||||
fun testGetConflictingServices() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
|
||||
|
||||
val packet = MdnsPacket(
|
||||
0 /* flags */,
|
||||
@@ -433,8 +492,8 @@ class MdnsRecordRepositoryTest {
|
||||
@Test
|
||||
fun testGetConflictingServices_IdenticalService() {
|
||||
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
|
||||
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
|
||||
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
|
||||
|
||||
val otherTtlMillis = 1234L
|
||||
val packet = MdnsPacket(
|
||||
@@ -460,10 +519,13 @@ class MdnsRecordRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun MdnsRecordRepository.initWithService(serviceId: Int, serviceInfo: NsdServiceInfo):
|
||||
AnnouncementInfo {
|
||||
private fun MdnsRecordRepository.initWithService(
|
||||
serviceId: Int,
|
||||
serviceInfo: NsdServiceInfo,
|
||||
subtype: String? = null
|
||||
): AnnouncementInfo {
|
||||
updateAddresses(TEST_ADDRESSES)
|
||||
addService(serviceId, serviceInfo)
|
||||
addService(serviceId, serviceInfo, subtype)
|
||||
val probingInfo = setServiceProbing(serviceId)
|
||||
assertNotNull(probingInfo)
|
||||
return onProbingSucceeded(probingInfo)
|
||||
|
||||
@@ -41,6 +41,7 @@ import android.net.InetAddresses;
|
||||
import android.net.Network;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
|
||||
@@ -52,6 +53,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
@@ -1056,6 +1058,87 @@ public class MdnsServiceTypeClientTests {
|
||||
inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
|
||||
client = new MdnsServiceTypeClient(
|
||||
SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
|
||||
|
||||
final String matchingInstance = "instance1";
|
||||
final String subtype = "_subtype";
|
||||
final String otherInstance = "instance2";
|
||||
final String ipV4Address = "192.0.2.0";
|
||||
final String ipV6Address = "2001:db8::";
|
||||
|
||||
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
|
||||
// Search with different case. Note MdnsSearchOptions subtype doesn't start with "_"
|
||||
.addSubtype("Subtype").build();
|
||||
|
||||
client.startSendAndReceive(mockListenerOne, options);
|
||||
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
// Complete response from instanceName
|
||||
final MdnsPacket packetWithoutSubtype = createResponse(
|
||||
matchingInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap() /* textAttributes */, TEST_TTL);
|
||||
final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
|
||||
packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
|
||||
|
||||
// Add a subtype PTR record
|
||||
final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
|
||||
newAnswers.add(new MdnsPointerRecord(
|
||||
// PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
|
||||
Stream.concat(Stream.of(subtype, "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
|
||||
.toArray(String[]::new),
|
||||
originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
|
||||
originalPtr.getPointer()));
|
||||
final MdnsPacket packetWithSubtype = new MdnsPacket(
|
||||
packetWithoutSubtype.flags,
|
||||
packetWithoutSubtype.questions,
|
||||
newAnswers,
|
||||
packetWithoutSubtype.authorityRecords,
|
||||
packetWithoutSubtype.additionalRecords);
|
||||
client.processResponse(packetWithSubtype, INTERFACE_INDEX, mockNetwork);
|
||||
|
||||
// Complete response from otherInstanceName, without subtype
|
||||
client.processResponse(createResponse(
|
||||
otherInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap() /* textAttributes */, TEST_TTL),
|
||||
INTERFACE_INDEX, mockNetwork);
|
||||
|
||||
// Address update from otherInstanceName
|
||||
client.processResponse(createResponse(
|
||||
otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
|
||||
|
||||
// Goodbye from otherInstanceName
|
||||
client.processResponse(createResponse(
|
||||
otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), 0L /* ttl */), INTERFACE_INDEX, mockNetwork);
|
||||
|
||||
// mockListenerOne gets notified for the requested instance
|
||||
final ArgumentMatcher<MdnsServiceInfo> subtypeInstanceMatcher = info ->
|
||||
info.getServiceInstanceName().equals(matchingInstance)
|
||||
&& info.getSubtypes().equals(Collections.singletonList(subtype));
|
||||
verify(mockListenerOne).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
|
||||
verify(mockListenerOne).onServiceFound(argThat(subtypeInstanceMatcher));
|
||||
|
||||
// ...but does not get any callback for the other instance
|
||||
verify(mockListenerOne, never()).onServiceFound(matchServiceName(otherInstance));
|
||||
verify(mockListenerOne, never()).onServiceNameDiscovered(matchServiceName(otherInstance));
|
||||
verify(mockListenerOne, never()).onServiceUpdated(matchServiceName(otherInstance));
|
||||
verify(mockListenerOne, never()).onServiceRemoved(matchServiceName(otherInstance));
|
||||
|
||||
// mockListenerTwo gets notified for both though
|
||||
final InOrder inOrder = inOrder(mockListenerTwo);
|
||||
inOrder.verify(mockListenerTwo).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
|
||||
inOrder.verify(mockListenerTwo).onServiceFound(argThat(subtypeInstanceMatcher));
|
||||
|
||||
inOrder.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
|
||||
inOrder.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
|
||||
inOrder.verify(mockListenerTwo).onServiceUpdated(matchServiceName(otherInstance));
|
||||
inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotifyAllServicesRemoved() {
|
||||
client = new MdnsServiceTypeClient(
|
||||
|
||||
Reference in New Issue
Block a user