Merge changes from topic "mdns-ttl" into main

* changes:
  [mdns] add service-side impl for NSD service TTL support
  [mdns] add API for setting/getting TTL value
This commit is contained in:
Treehugger Robot 2024-02-29 21:00:27 +00:00 • committed by Gerrit Code Review
commit 0520e26936
17 changed files with 604 additions and 101 deletions

View file

@ -17,11 +17,13 @@ package android.net.nsd;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.util.Objects;
/**
@ -34,7 +36,7 @@ public final class AdvertisingRequest implements Parcelable {
/**
* Only update the registration without sending exit and re-announcement.
*/
public static final int NSD_ADVERTISING_UPDATE_ONLY = 1;
public static final long NSD_ADVERTISING_UPDATE_ONLY = 1;
@NonNull
@ -46,7 +48,9 @@ public final class AdvertisingRequest implements Parcelable {
NsdServiceInfo.class.getClassLoader(), NsdServiceInfo.class);
final int protocolType = in.readInt();
final long advertiseConfig = in.readLong();
return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig);
final long ttlSeconds = in.readLong();
final Duration ttl = ttlSeconds < 0 ? null : Duration.ofSeconds(ttlSeconds);
return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig, ttl);
}
@Override
@ -60,6 +64,9 @@ public final class AdvertisingRequest implements Parcelable {
// Bitmask of @AdvertisingConfig flags. Uses a long to allow 64 possible flags in the future.
private final long mAdvertisingConfig;
@Nullable
private final Duration mTtl;
/**
* @hide
*/
@ -73,10 +80,11 @@ public final class AdvertisingRequest implements Parcelable {
* The constructor for the advertiseRequest
*/
private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType,
long advertisingConfig) {
long advertisingConfig, @NonNull Duration ttl) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
mAdvertisingConfig = advertisingConfig;
mTtl = ttl;
}
/**
@ -101,12 +109,25 @@ public final class AdvertisingRequest implements Parcelable {
return mAdvertisingConfig;
}
/**
* Returns the time interval that the resource records may be cached on a DNS resolver or
* {@code null} if not specified.
*
* @hide
*/
// @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
@Nullable
public Duration getTtl() {
return mTtl;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("serviceInfo: ").append(mServiceInfo)
.append(", protocolType: ").append(mProtocolType)
.append(", advertisingConfig: ").append(mAdvertisingConfig);
.append(", advertisingConfig: ").append(mAdvertisingConfig)
.append(", ttl: ").append(mTtl);
return sb.toString();
}
@ -120,13 +141,14 @@ public final class AdvertisingRequest implements Parcelable {
final AdvertisingRequest otherRequest = (AdvertisingRequest) other;
return mServiceInfo.equals(otherRequest.mServiceInfo)
&& mProtocolType == otherRequest.mProtocolType
&& mAdvertisingConfig == otherRequest.mAdvertisingConfig;
&& mAdvertisingConfig == otherRequest.mAdvertisingConfig
&& Objects.equals(mTtl, otherRequest.mTtl);
}
}
@Override
public int hashCode() {
return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig);
return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
}
@Override
@ -139,6 +161,7 @@ public final class AdvertisingRequest implements Parcelable {
dest.writeParcelable(mServiceInfo, flags);
dest.writeInt(mProtocolType);
dest.writeLong(mAdvertisingConfig);
dest.writeLong(mTtl == null ? -1 : mTtl.getSeconds());
}
// @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
@ -151,6 +174,8 @@ public final class AdvertisingRequest implements Parcelable {
private final NsdServiceInfo mServiceInfo;
private final int mProtocolType;
private long mAdvertisingConfig;
@Nullable
private Duration mTtl;
/**
* Creates a new {@link Builder} object.
*/
@ -170,11 +195,44 @@ public final class AdvertisingRequest implements Parcelable {
return this;
}
/**
* Sets the time interval that the resource records may be cached on a DNS resolver.
*
* If this method is not called or {@code ttl} is {@code null}, default TTL values
* will be used for the service when it's registered. Otherwise, the {@code ttl}
* will be used for all resource records of this service.
*
* When registering a service, {@link NsdManager#FAILURE_BAD_PARAMETERS} will be returned
* if {@code ttl} is smaller than 30 seconds.
*
* Note: only number of seconds of {@code ttl} is used.
*
* @param ttl the maximum duration that the DNS resource records will be cached
*
* @see AdvertisingRequest#getTtl
* @hide
*/
// @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
@NonNull
public Builder setTtl(@Nullable Duration ttl) {
if (ttl == null) {
mTtl = null;
return this;
}
final long ttlSeconds = ttl.getSeconds();
if (ttlSeconds < 0 || ttlSeconds > 0xffffffffL) {
throw new IllegalArgumentException(
"ttlSeconds exceeds the allowed range (value = " + ttlSeconds
+ ", allowedRanged = [0, 0xffffffffL])");
}
mTtl = Duration.ofSeconds(ttlSeconds);
return this;
}
/** Creates a new {@link AdvertisingRequest} object. */
@NonNull
public AdvertisingRequest build() {
return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig);
return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl);
}
}
}

View file

@ -160,6 +160,8 @@ public final class NsdManager {
"com.android.net.flags.advertise_request_api";
static final String NSD_CUSTOM_HOSTNAME_ENABLED =
"com.android.net.flags.nsd_custom_hostname_enabled";
static final String NSD_CUSTOM_TTL_ENABLED =
"com.android.net.flags.nsd_custom_ttl_enabled";
}
/**
@ -327,6 +329,20 @@ public final class NsdManager {
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
/**
* The minimum TTL seconds which is allowed for a service registration.
*
* @hide
*/
public static final long TTL_SECONDS_MIN = 30L;
/**
* The maximum TTL seconds which is allowed for a service registration.
*
* @hide
*/
public static final long TTL_SECONDS_MAX = 10 * 3600L;
private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
static {
EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");

View file

@ -16,8 +16,6 @@
package android.net.nsd;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@ -35,6 +33,7 @@ import com.android.net.module.util.InetAddressUtils;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -71,6 +70,10 @@ public final class NsdServiceInfo implements Parcelable {
private int mInterfaceIndex;
// The timestamp that all resource records associated with this service are considered invalid.
@Nullable
private Instant mExpirationTime;
public NsdServiceInfo() {
mSubtypes = new ArraySet<>();
mTxtRecord = new ArrayMap<>();
@ -99,6 +102,7 @@ public final class NsdServiceInfo implements Parcelable {
mPort = other.getPort();
mNetwork = other.getNetwork();
mInterfaceIndex = other.getInterfaceIndex();
mExpirationTime = other.getExpirationTime();
}
/** Get the service name */
@ -490,6 +494,38 @@ public final class NsdServiceInfo implements Parcelable {
return Collections.unmodifiableSet(mSubtypes);
}
/**
* Sets the timestamp after when this service is expired.
*
* Note: only number of seconds of {@code expirationTime} is used.
*
* @hide
*/
public void setExpirationTime(@Nullable Instant expirationTime) {
if (expirationTime == null) {
mExpirationTime = null;
} else {
mExpirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
}
}
/**
* Returns the timestamp after when this service is expired or {@code null} if it's unknown.
*
* A service is considered expired if any of its DNS record is expired.
*
* Clients that are depending on the refreshness of the service information should not continue
* use this service after the returned timestamp. Instead, clients may re-send queries for the
* service to get updated the service information.
*
* @hide
*/
// @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED)
@Nullable
public Instant getExpirationTime() {
return mExpirationTime;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -499,7 +535,8 @@ public final class NsdServiceInfo implements Parcelable {
.append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
.append(", hostname: ").append(mHostname)
.append(", port: ").append(mPort)
.append(", network: ").append(mNetwork);
.append(", network: ").append(mNetwork)
.append(", expirationTime: ").append(mExpirationTime);
byte[] txtRecord = getTxtRecord();
sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
@ -539,6 +576,7 @@ public final class NsdServiceInfo implements Parcelable {
InetAddressUtils.parcelInetAddress(dest, address, flags);
}
dest.writeString(mHostname);
dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1);
}
/** Implement the Parcelable interface */
@ -569,6 +607,8 @@ public final class NsdServiceInfo implements Parcelable {
info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in));
}
info.mHostname = in.readString();
final long seconds = in.readLong();
info.setExpirationTime(seconds < 0 ? null : Instant.ofEpochSecond(seconds));
return info;
}

View file

@ -28,6 +28,7 @@ import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
import static android.net.nsd.NsdManager.SUBTYPE_LABEL_REGEX;
import static android.net.nsd.NsdManager.TYPE_REGEX;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
@ -116,6 +117,7 @@ import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -739,6 +741,33 @@ public class NsdService extends INsdManager.Stub {
return new ArraySet<>(subtypeMap.values());
}
private boolean checkTtl(
@Nullable Duration ttl, @NonNull ClientInfo clientInfo) {
if (ttl == null) {
return true;
}
final long ttlSeconds = ttl.toSeconds();
final int uid = clientInfo.getUid();
// Allows Thread module in the system_server to register TTL that is smaller than
// 30 seconds
final long minTtlSeconds = uid == SYSTEM_UID ? 0 : NsdManager.TTL_SECONDS_MIN;
// Allows Thread module in the system_server to register TTL that is larger than
// 10 hours
final long maxTtlSeconds =
uid == SYSTEM_UID ? 0xffffffffL : NsdManager.TTL_SECONDS_MAX;
if (ttlSeconds < minTtlSeconds || ttlSeconds > maxTtlSeconds) {
mServiceLogs.e("ttlSeconds exceeds allowed range (value = "
+ ttlSeconds + ", allowedRange = [" + minTtlSeconds
+ ", " + maxTtlSeconds + " ])");
return false;
}
return true;
}
@Override
public boolean processMessage(Message msg) {
final ClientInfo clientInfo;
@ -965,11 +994,19 @@ public class NsdService extends INsdManager.Stub {
break;
}
if (!checkTtl(advertisingRequest.getTtl(), clientInfo)) {
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */);
break;
}
serviceInfo.setSubtypes(subtypes);
maybeStartMonitoringSockets();
final MdnsAdvertisingOptions mdnsAdvertisingOptions =
MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(
isUpdateOnly).build();
MdnsAdvertisingOptions.newBuilder()
.setIsOnlyUpdate(isUpdateOnly)
.setTtl(advertisingRequest.getTtl())
.build();
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
mdnsAdvertisingOptions, clientInfo.mUid);
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
@ -1512,6 +1549,7 @@ public class NsdService extends INsdManager.Stub {
network == null ? INetd.LOCAL_NET_ID : network.netId,
serviceInfo.getInterfaceIndex());
servInfo.setSubtypes(dedupSubtypeLabels(serviceInfo.getSubtypes()));
servInfo.setExpirationTime(serviceInfo.getExpirationTime());
return servInfo;
}
@ -2680,6 +2718,10 @@ public class NsdService extends INsdManager.Stub {
return sb.toString();
}
public int getUid() {
return mUid;
}
private boolean isPreSClient() {
return mIsPreSClient;
}

View file

@ -449,7 +449,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.getAdvertisingOptions());
} catch (NameConflictException e) {
mSharedLog.wtf("Name conflict adding services that should have unique names",
e);
@ -515,7 +516,7 @@ public class MdnsAdvertiser {
final Registration registration = mPendingRegistrations.valueAt(i);
try {
advertiser.addService(mPendingRegistrations.keyAt(i),
registration.getServiceInfo());
registration.getServiceInfo(), registration.getAdvertisingOptions());
} catch (NameConflictException e) {
mSharedLog.wtf("Name conflict adding services that should have unique names",
e);
@ -587,15 +588,17 @@ public class MdnsAdvertiser {
@NonNull
private NsdServiceInfo mServiceInfo;
final int mClientUid;
private final MdnsAdvertisingOptions mAdvertisingOptions;
int mConflictDuringProbingCount;
int mConflictAfterProbingCount;
private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid) {
private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid,
@NonNull MdnsAdvertisingOptions advertisingOptions) {
this.mOriginalServiceName = serviceInfo.getServiceName();
this.mOriginalHostname = serviceInfo.getHostname();
this.mServiceInfo = serviceInfo;
this.mClientUid = clientUid;
this.mAdvertisingOptions = advertisingOptions;
}
/** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */
@ -697,6 +700,11 @@ public class MdnsAdvertiser {
public NsdServiceInfo getServiceInfo() {
return mServiceInfo;
}
@NonNull
public MdnsAdvertisingOptions getAdvertisingOptions() {
return mAdvertisingOptions;
}
}
/**
@ -855,7 +863,7 @@ public class MdnsAdvertiser {
}
mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes "
+ subtypes + " advertisingOptions " + advertisingOptions);
registration = new Registration(service, clientUid);
registration = new Registration(service, clientUid, advertisingOptions);
final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
if (network == null) {
// If registering on all networks, no advertiser must have conflicts

View file

@ -16,6 +16,11 @@
package com.android.server.connectivity.mdns;
import android.annotation.Nullable;
import java.time.Duration;
import java.util.Objects;
/**
* API configuration parameters for advertising the mDNS service.
*
@ -27,13 +32,15 @@ public class MdnsAdvertisingOptions {
private static MdnsAdvertisingOptions sDefaultOptions;
private final boolean mIsOnlyUpdate;
@Nullable
private final Duration mTtl;
/**
* Parcelable constructs for a {@link MdnsAdvertisingOptions}.
*/
MdnsAdvertisingOptions(
boolean isOnlyUpdate) {
MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl) {
this.mIsOnlyUpdate = isOnlyUpdate;
this.mTtl = ttl;
}
/**
@ -60,9 +67,36 @@ public class MdnsAdvertisingOptions {
return mIsOnlyUpdate;
}
/**
* Returns the TTL for all records in a service.
*/
@Nullable
public Duration getTtl() {
return mTtl;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof MdnsAdvertisingOptions)) {
return false;
} else {
final MdnsAdvertisingOptions otherOptions = (MdnsAdvertisingOptions) other;
return mIsOnlyUpdate == otherOptions.mIsOnlyUpdate
&& Objects.equals(mTtl, otherOptions.mTtl);
}
}
@Override
public int hashCode() {
return Objects.hash(mIsOnlyUpdate, mTtl);
}
@Override
public String toString() {
return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + '}';
return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + ", mTtl=" + mTtl
+ '}';
}
/**
@ -70,6 +104,8 @@ public class MdnsAdvertisingOptions {
*/
public static final class Builder {
private boolean mIsOnlyUpdate = false;
@Nullable
private Duration mTtl;
private Builder() {
}
@ -82,11 +118,19 @@ public class MdnsAdvertisingOptions {
return this;
}
/**
* Sets the TTL duration for all records of the service.
*/
public Builder setTtl(@Nullable Duration ttl) {
this.mTtl = ttl;
return this;
}
/**
* Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder.
*/
public MdnsAdvertisingOptions build() {
return new MdnsAdvertisingOptions(mIsOnlyUpdate);
return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl);
}
}
}

View file

@ -258,8 +258,10 @@ 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,
@NonNull MdnsAdvertisingOptions advertisingOptions) throws NameConflictException {
final int replacedExitingService =
mRecordRepository.addService(id, service, advertisingOptions.getTtl());
// Cancel announcements for the existing service. This only happens for exiting services
// (so cancelling exiting announcements), as per RecordRepository.addService.
if (replacedExitingService >= 0) {

View file

@ -45,6 +45,7 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -75,9 +76,9 @@ public class MdnsRecordRepository {
// TTL for records with a host name as the resource record's name (e.g., A, AAAA, HINFO) or a
// host name contained within the resource record's rdata (e.g., SRV, reverse mapping PTR
// record)
private static final long NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
private static final long DEFAULT_NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
// TTL for other records
private static final long NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
private static final long DEFAULT_NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
// Top-level domain for link-local queries, as per RFC6762 3.
private static final String LOCAL_TLD = "local";
@ -193,6 +194,9 @@ public class MdnsRecordRepository {
*/
private boolean isProbing;
@Nullable
private Duration ttl;
/**
* Create a ServiceRegistration with only update the subType.
*/
@ -200,16 +204,32 @@ public class MdnsRecordRepository {
NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
newServiceInfo.setSubtypes(newSubtypes);
return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
repliedServiceCount, sentPacketCount, exiting, isProbing);
repliedServiceCount, sentPacketCount, exiting, isProbing, ttl);
}
/**
* Create a ServiceRegistration for dns-sd service registration (RFC6763).
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing) {
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
@Nullable Duration ttl) {
this.serviceInfo = serviceInfo;
final long nonNameRecordsTtlMillis;
final long nameRecordsTtlMillis;
// When custom TTL is specified, all records of the service will use the custom TTL.
// This is typically useful for SRP (Service Registration Protocol:
// https://datatracker.ietf.org/doc/html/draft-ietf-dnssd-srp-24) Advertising Proxy
// where all records in a single SRP are required the same TTL.
if (ttl != null) {
nonNameRecordsTtlMillis = ttl.toMillis();
nameRecordsTtlMillis = ttl.toMillis();
} else {
nonNameRecordsTtlMillis = DEFAULT_NON_NAME_RECORDS_TTL_MILLIS;
nameRecordsTtlMillis = DEFAULT_NAME_RECORDS_TTL_MILLIS;
}
final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname());
final String[] hostname =
@ -229,7 +249,7 @@ public class MdnsRecordRepository {
serviceType,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
nonNameRecordsTtlMillis,
serviceName),
true /* sharedName */));
for (String subtype : serviceInfo.getSubtypes()) {
@ -239,7 +259,7 @@ public class MdnsRecordRepository {
MdnsUtils.constructFullSubtype(serviceType, subtype),
0L /* receiptTimeMillis */,
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
nonNameRecordsTtlMillis,
serviceName),
true /* sharedName */));
}
@ -249,7 +269,7 @@ public class MdnsRecordRepository {
new MdnsServiceRecord(serviceName,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
nameRecordsTtlMillis,
0 /* servicePriority */, 0 /* serviceWeight */,
serviceInfo.getPort(),
hostname),
@ -261,7 +281,7 @@ public class MdnsRecordRepository {
0L /* receiptTimeMillis */,
// Service name is verified unique after probing
true /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
nonNameRecordsTtlMillis,
attrsToTextEntries(serviceInfo.getAttributes())),
false /* sharedName */);
@ -275,7 +295,7 @@ public class MdnsRecordRepository {
DNS_SD_SERVICE_TYPE,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
nonNameRecordsTtlMillis,
serviceType),
true /* sharedName */));
} else {
@ -292,7 +312,7 @@ public class MdnsRecordRepository {
new MdnsInetAddressRecord(hostname,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
nameRecordsTtlMillis,
address),
false /* sharedName */));
}
@ -315,9 +335,9 @@ public class MdnsRecordRepository {
* @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount) {
int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) {
this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
false /* exiting */, true /* isProbing */);
false /* exiting */, true /* isProbing */, ttl);
}
void setProbing(boolean probing) {
@ -339,7 +359,7 @@ public class MdnsRecordRepository {
revDnsAddr,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
DEFAULT_NAME_RECORDS_TTL_MILLIS,
mDeviceHostname),
false /* sharedName */));
@ -349,7 +369,7 @@ public class MdnsRecordRepository {
mDeviceHostname,
0L /* receiptTimeMillis */,
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
DEFAULT_NAME_RECORDS_TTL_MILLIS,
addr.getAddress()),
false /* sharedName */));
}
@ -378,11 +398,13 @@ public class MdnsRecordRepository {
* This may remove/replace any existing service that used the name added but is exiting.
* @param serviceId A unique service ID.
* @param serviceInfo Service info to add.
* @param ttl the TTL duration for all records of {@code serviceInfo} or {@code null}
* @return If the added service replaced another with a matching name (which was exiting), the
* 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 Duration ttl)
throws NameConflictException {
if (mServices.contains(serviceId)) {
throw new IllegalArgumentException(
"Service ID must not be reused across registrations: " + serviceId);
@ -397,7 +419,7 @@ public class MdnsRecordRepository {
final ServiceRegistration registration = new ServiceRegistration(
mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
NO_PACKET /* sentPacketCount */);
NO_PACKET /* sentPacketCount */, ttl);
mServices.put(serviceId, registration);
// Remove existing exiting service
@ -776,7 +798,7 @@ public class MdnsRecordRepository {
true /* cacheFlush */,
// TODO: RFC6762 6.1: "In general, the TTL given for an NSEC record SHOULD
// be the same as the TTL that the record would have had, had it existed."
NAME_RECORDS_TTL_MILLIS,
DEFAULT_NAME_RECORDS_TTL_MILLIS,
question.getName(),
new int[] { question.getType() });
additionalAnswerInfo.add(
@ -1211,7 +1233,7 @@ public class MdnsRecordRepository {
if (existing == null) return null;
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
existing.repliedServiceCount, existing.sentPacketCount);
existing.repliedServiceCount, existing.sentPacketCount, existing.ttl);
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService);
}

View file

@ -28,6 +28,7 @@ import android.text.TextUtils;
import com.android.net.module.util.ByteUtils;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -62,7 +63,8 @@ public class MdnsServiceInfo implements Parcelable {
source.createStringArrayList(),
source.createTypedArrayList(TextEntry.CREATOR),
source.readInt(),
source.readParcelable(null));
source.readParcelable(Network.class.getClassLoader()),
Instant.ofEpochSecond(source.readLong()));
}
@Override
@ -89,6 +91,9 @@ public class MdnsServiceInfo implements Parcelable {
@Nullable
private final Network network;
@NonNull
private final Instant expirationTime;
/** Constructs a {@link MdnsServiceInfo} object with default values. */
public MdnsServiceInfo(
String serviceInstanceName,
@ -110,7 +115,8 @@ public class MdnsServiceInfo implements Parcelable {
textStrings,
/* textEntries= */ null,
/* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
/* network= */ null);
/* network= */ null,
/* expirationTime= */ Instant.MAX);
}
/** Constructs a {@link MdnsServiceInfo} object with default values. */
@ -135,7 +141,8 @@ public class MdnsServiceInfo implements Parcelable {
textStrings,
textEntries,
/* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
/* network= */ null);
/* network= */ null,
/* expirationTime= */ Instant.MAX);
}
/**
@ -165,7 +172,8 @@ public class MdnsServiceInfo implements Parcelable {
textStrings,
textEntries,
interfaceIndex,
/* network= */ null);
/* network= */ null,
/* expirationTime= */ Instant.MAX);
}
/**
@ -184,7 +192,8 @@ public class MdnsServiceInfo implements Parcelable {
@Nullable List<String> textStrings,
@Nullable List<TextEntry> textEntries,
int interfaceIndex,
@Nullable Network network) {
@Nullable Network network,
@NonNull Instant expirationTime) {
this.serviceInstanceName = serviceInstanceName;
this.serviceType = serviceType;
this.subtypes = new ArrayList<>();
@ -217,6 +226,7 @@ public class MdnsServiceInfo implements Parcelable {
this.attributes = Collections.unmodifiableMap(attributes);
this.interfaceIndex = interfaceIndex;
this.network = network;
this.expirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond());
}
private static List<TextEntry> parseTextStrings(List<String> textStrings) {
@ -313,6 +323,17 @@ public class MdnsServiceInfo implements Parcelable {
return network;
}
/**
* Returns the timestamp after when this service is expired or {@code null} if the expiration
* time is unknown.
*
* A service is considered expired if any of its DNS record is expired.
*/
@NonNull
public Instant getExpirationTime() {
return expirationTime;
}
/**
* Returns attribute value for {@code key} as a UTF-8 string. It's the caller who must make sure
* that the value of {@code key} is indeed a UTF-8 string. {@code null} will be returned if no
@ -364,6 +385,7 @@ public class MdnsServiceInfo implements Parcelable {
out.writeTypedList(textEntries);
out.writeInt(interfaceIndex);
out.writeParcelable(network, 0);
out.writeLong(expirationTime.getEpochSecond());
}
@Override
@ -377,7 +399,8 @@ public class MdnsServiceInfo implements Parcelable {
+ ", interfaceIndex: " + interfaceIndex
+ ", network: " + network
+ ", textStrings: " + textStrings
+ ", textEntries: " + textEntries;
+ ", textEntries: " + textEntries
+ ", expirationTime: " + expirationTime;
}
@ -496,4 +519,4 @@ public class MdnsServiceInfo implements Parcelable {
out.writeByteArray(value);
}
}
}
}

View file

@ -38,6 +38,7 @@ import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -309,6 +310,7 @@ public class MdnsServiceTypeClient {
textStrings = response.getTextRecord().getStrings();
textEntries = response.getTextRecord().getEntries();
}
Instant now = Instant.now();
// TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
return new MdnsServiceInfo(
serviceInstanceName,
@ -321,7 +323,8 @@ public class MdnsServiceTypeClient {
textStrings,
textEntries,
response.getInterfaceIndex(),
response.getNetwork());
response.getNetwork(),
now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
}
/**

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.nsd
import android.net.nsd.AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY
import android.net.nsd.NsdManager.PROTOCOL_DNS_SD
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.parcelingRoundTrip
import java.time.Duration
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import org.junit.Test
import org.junit.runner.RunWith
// TODO: move this class to CTS tests when AdvertisingRequest is made public
/** Unit tests for {@link AdvertisingRequest}. */
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@ConnectivityModuleTest
class AdvertisingRequestTest {
@Test
fun testParcelingIsLossLess() {
val info = NsdServiceInfo().apply {
serviceType = "_ipp._tcp"
}
val beforeParcel = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
.setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(30L))
.build()
val afterParcel = parcelingRoundTrip(beforeParcel)
assertEquals(beforeParcel.serviceInfo.serviceType, afterParcel.serviceInfo.serviceType)
assertEquals(beforeParcel.advertisingConfig, afterParcel.advertisingConfig)
}
@Test
fun testBuilder_setNullTtl_success() {
val info = NsdServiceInfo().apply {
serviceType = "_ipp._tcp"
}
val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
.setTtl(null)
.build()
assertNull(request.ttl)
}
@Test
fun testBuilder_setPropertiesSuccess() {
val info = NsdServiceInfo().apply {
serviceType = "_ipp._tcp"
}
val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
.setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(100L))
.build()
assertEquals("_ipp._tcp", request.serviceInfo.serviceType)
assertEquals(PROTOCOL_DNS_SD, request.protocolType)
assertEquals(NSD_ADVERTISING_UPDATE_ONLY, request.advertisingConfig)
assertEquals(Duration.ofSeconds(100L), request.ttl)
}
@Test
fun testEquality() {
val info = NsdServiceInfo().apply {
serviceType = "_ipp._tcp"
}
val request1 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
val request2 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build()
val request3 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
.setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(120L))
.build()
val request4 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD)
.setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY)
.setTtl(Duration.ofSeconds(120L))
.build()
assertEquals(request1, request2)
assertEquals(request3, request4)
assertNotEquals(request1, request3)
assertNotEquals(request2, request4)
}
}

View file

@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@ -54,6 +55,7 @@ import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.List;
import java.time.Duration;
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@ -224,6 +226,23 @@ public class NsdManagerTest {
verify(listener, timeout(mTimeoutMs).times(1)).onServiceRegistered(request);
}
@Test
public void testRegisterServiceWithCustomTtl() throws Exception {
final NsdManager manager = mManager;
final NsdServiceInfo info = new NsdServiceInfo("another_name2", "another_type2");
info.setPort(2203);
final AdvertisingRequest request = new AdvertisingRequest.Builder(info, PROTOCOL)
.setTtl(Duration.ofSeconds(30)).build();
final NsdManager.RegistrationListener listener = mock(
NsdManager.RegistrationListener.class);
manager.registerService(request, Runnable::run, listener);
AdvertisingRequest capturedRequest = getAdvertisingRequest(
req -> verify(mServiceConn).registerService(anyInt(), req.capture()));
assertEquals(request, capturedRequest);
}
private void doTestRegisterService() throws Exception {
NsdManager manager = mManager;
@ -501,4 +520,12 @@ public class NsdManagerTest {
verifier.accept(captor);
return captor.getValue();
}
AdvertisingRequest getAdvertisingRequest(
ThrowingConsumer<ArgumentCaptor<AdvertisingRequest>> verifier) throws Exception {
final ArgumentCaptor<AdvertisingRequest> captor =
ArgumentCaptor.forClass(AdvertisingRequest.class);
verifier.accept(captor);
return captor.getValue();
}
}

View file

@ -83,6 +83,7 @@ import android.net.mdns.aidl.GetAddressInfo;
import android.net.mdns.aidl.IMDnsEventListener;
import android.net.mdns.aidl.RegistrationInfo;
import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.AdvertisingRequest;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
import android.net.nsd.MDnsManager;
@ -101,6 +102,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Pair;
@ -110,6 +112,7 @@ import androidx.test.filters.SmallTest;
import com.android.metrics.NetworkNsdReportedMetrics;
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
import com.android.server.connectivity.mdns.MdnsAdvertisingOptions;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
@ -137,6 +140,8 @@ import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@ -971,7 +976,8 @@ public class NsdServiceTest {
List.of() /* textStrings */,
List.of() /* textEntries */,
1234,
network);
network,
Instant.MAX /* expirationTime */);
// Callbacks for query sent.
listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
@ -1001,7 +1007,8 @@ public class NsdServiceTest {
List.of() /* textStrings */,
List.of() /* textEntries */,
1234,
network);
network,
Instant.MAX /* expirationTime */);
// Verify onServiceUpdated callback.
listener.onServiceUpdated(updatedServiceInfo);
@ -1133,7 +1140,8 @@ public class NsdServiceTest {
List.of(), /* textStrings */
List.of(), /* textEntries */
1234, /* interfaceIndex */
network);
network,
Instant.MAX /* expirationTime */);
// Verify onServiceNameDiscovered callback
listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
@ -1154,7 +1162,8 @@ public class NsdServiceTest {
null, /* textStrings */
null, /* textEntries */
1234, /* interfaceIndex */
network);
network,
Instant.MAX /* expirationTime */);
// Verify onServiceNameRemoved callback
listener.onServiceNameRemoved(removedInfo);
verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info ->
@ -1276,7 +1285,8 @@ public class NsdServiceTest {
List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
1234,
network);
network,
Instant.ofEpochSecond(1000_000L) /* expirationTime */);
// Verify onServiceFound callback
doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
@ -1301,6 +1311,7 @@ public class NsdServiceTest {
assertTrue(info.getHostAddresses().stream().anyMatch(
address -> address.equals(parseNumericAddress("2001:db8::2"))));
assertEquals(network, info.getNetwork());
assertEquals(Instant.ofEpochSecond(1000_000L), info.getExpirationTime());
// Verify the listener has been unregistered.
verify(mDiscoveryManager, timeout(TIMEOUT_MS))
@ -1517,6 +1528,82 @@ public class NsdServiceTest {
false /* isLegacy */, regId, 10L /* durationMs */);
}
@Test
public void testAdvertiseCustomTtl_validTtl_success() {
runValidTtlAdvertisingTest(30L);
runValidTtlAdvertisingTest(10 * 3600L);
}
@Test
public void testAdvertiseCustomTtl_ttlSmallerThan30SecondsButClientIsSystemServer_success() {
when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
runValidTtlAdvertisingTest(29L);
}
@Test
public void testAdvertiseCustomTtl_ttlLargerThan10HoursButClientIsSystemServer_success() {
when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID);
runValidTtlAdvertisingTest(10 * 3600L + 1);
runValidTtlAdvertisingTest(0xffffffffL);
}
private void runValidTtlAdvertisingTest(long validTtlSeconds) {
setMdnsAdvertiserEnabled();
final NsdManager client = connectClient(mService);
final RegistrationListener regListener = mock(RegistrationListener.class);
final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
regInfo.setPort(1234);
final AdvertisingRequest request =
new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
.setTtl(Duration.ofSeconds(validTtlSeconds)).build();
client.registerService(request, Runnable::run, regListener);
waitForIdle();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
final MdnsAdvertisingOptions expectedAdverstingOptions =
MdnsAdvertisingOptions.newBuilder().setTtl(request.getTtl()).build();
verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), any(),
eq(expectedAdverstingOptions), anyInt());
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
final int regId = idCaptor.getValue();
cb.onRegisterServiceSucceeded(regId, regInfo);
verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
}
@Test
public void testAdvertiseCustomTtl_invalidTtl_FailsWithBadParameters() {
setMdnsAdvertiserEnabled();
final long invalidTtlSeconds = 29L;
final NsdManager client = connectClient(mService);
final RegistrationListener regListener = mock(RegistrationListener.class);
final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any());
final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE);
regInfo.setPort(1234);
final AdvertisingRequest request =
new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD)
.setTtl(Duration.ofSeconds(invalidTtlSeconds)).build();
client.registerService(request, Runnable::run, regListener);
waitForIdle();
verify(regListener, timeout(TIMEOUT_MS))
.onRegistrationFailed(any(), eq(FAILURE_BAD_PARAMETERS));
}
@Test
public void testStopServiceResolutionWithMdnsDiscoveryManager() {
setMdnsDiscoveryManagerEnabled();

View file

@ -38,6 +38,7 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.waitForIdle
import java.net.NetworkInterface
import java.time.Duration
import java.util.Objects
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@ -313,9 +314,9 @@ class MdnsAdvertiserTest {
eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(
anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
verify(mockInterfaceAdvertiser2).addService(
anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE))
anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any())
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
@ -489,15 +490,15 @@ class MdnsAdvertiserTest {
eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any()
)
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
argThat { it.matches(SERVICE_1) })
argThat { it.matches(SERVICE_1) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
argThat { it.matches(expectedRenamed) })
argThat { it.matches(expectedRenamed) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
argThat { it.matches(LONG_SERVICE_1) })
argThat { it.matches(LONG_SERVICE_1) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
argThat { it.matches(expectedLongRenamed) })
argThat { it.matches(expectedLongRenamed) }, any())
verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
argThat { it.matches(expectedCaseInsensitiveRenamed) })
argThat { it.matches(expectedCaseInsensitiveRenamed) }, any())
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
@ -532,7 +533,7 @@ class MdnsAdvertiserTest {
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
argThat { it.matches(ALL_NETWORKS_SERVICE) })
argThat { it.matches(ALL_NETWORKS_SERVICE) }, any())
val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build()
@ -554,7 +555,24 @@ class MdnsAdvertiserTest {
// Newly created MdnsInterfaceAdvertiser will get addService() call.
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) }
verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1),
argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) })
argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }, any())
}
@Test
fun testAddOrUpdateService_customTtl_registeredSuccess() {
val advertiser = MdnsAdvertiser(
thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context)
val updateOptions =
MdnsAdvertisingOptions.newBuilder().setTtl(Duration.ofSeconds(30)).build()
postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
updateOptions, TEST_CLIENT_UID_1) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
val socketCb = socketCbCaptor.value
postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), any(), eq(updateOptions))
}
@Test

View file

@ -132,7 +132,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
@ -403,9 +403,10 @@ class MdnsInterfaceAdvertiserTest {
@Test
fun testReplaceExitingService() {
doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
.addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE)
verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
.addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE,
MdnsAdvertisingOptions.getDefaultOptions())
verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
verify(announcer).stop(TEST_SERVICE_ID_DUPLICATE)
verify(prober).startProbing(any())
}
@ -413,7 +414,7 @@ class MdnsInterfaceAdvertiserTest {
@Test
fun testUpdateExistingService() {
doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
.addService(eq(TEST_SERVICE_ID_DUPLICATE), any())
.addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
val subTypes = setOf("_sub")
advertiser.updateService(TEST_SERVICE_ID_DUPLICATE, subTypes)
verify(repository).updateService(eq(TEST_SERVICE_ID_DUPLICATE), any())
@ -427,8 +428,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, MdnsAdvertisingOptions.getDefaultOptions())
verify(repository).addService(serviceId, serviceInfo, null /* ttl */)
verify(prober).startProbing(testProbingInfo)
// Simulate probing success: continues to announcing

View file

@ -145,7 +145,8 @@ class MdnsRecordRepositoryTest {
fun testAddServiceAndProbe() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@ -180,10 +181,10 @@ class MdnsRecordRepositoryTest {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
assertFailsWith(NameConflictException::class) {
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */)
}
assertFailsWith(NameConflictException::class) {
repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3)
repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3, null /* ttl */)
}
}
@ -224,9 +225,9 @@ class MdnsRecordRepositoryTest {
@Test
fun testInvalidReuseOfServiceId() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
assertFailsWith(IllegalArgumentException::class) {
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* ttl */)
}
}
@ -235,7 +236,7 @@ class MdnsRecordRepositoryTest {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@ -327,7 +328,7 @@ class MdnsRecordRepositoryTest {
repository.exitService(TEST_SERVICE_ID_1)
assertEquals(TEST_SERVICE_ID_1,
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */))
assertEquals(1, repository.servicesCount)
repository.removeService(TEST_SERVICE_ID_2)
@ -824,7 +825,7 @@ class MdnsRecordRepositoryTest {
repository.initWithService(
TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME)
@ -890,7 +891,8 @@ class MdnsRecordRepositoryTest {
repository.initWithService(
TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(),
listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24)))
repository.addService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1)
repository.addService(
TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1, null /* ttl */)
repository.removeService(TEST_CUSTOM_HOST_ID_1)
repository.removeService(TEST_SERVICE_CUSTOM_HOST_ID_1)
@ -989,8 +991,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val packet = MdnsPacket(
0 /* flags */,
@ -1020,8 +1022,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServicesCaseInsensitive() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val packet = MdnsPacket(
0 /* flags */,
@ -1050,8 +1052,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_customHosts_differentAddresses() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@ -1074,8 +1076,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_customHosts_moreAddressesThanUs_conflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@ -1101,8 +1103,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@ -1122,8 +1124,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@ -1147,8 +1149,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
val packet = MdnsPacket(
0, /* flags */
@ -1171,8 +1173,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val otherTtlMillis = 1234L
val packet = MdnsPacket(
@ -1200,8 +1202,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
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 /* ttl */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */)
val otherTtlMillis = 1234L
val packet = MdnsPacket(
@ -1256,7 +1258,8 @@ class MdnsRecordRepositoryTest {
makeFlags(includeInetAddressesInProbing = true))
repository.updateAddresses(TEST_ADDRESSES)
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 /* ttl */))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@ -1689,7 +1692,7 @@ private fun MdnsRecordRepository.addServiceAndFinishProbing(
serviceId: Int,
serviceInfo: NsdServiceInfo
): AnnouncementInfo {
addService(serviceId, serviceInfo)
addService(serviceId, serviceInfo, null /* ttl */)
val probingInfo = setServiceProbing(serviceId)
assertNotNull(probingInfo)
return onProbingSucceeded(probingInfo)

View file

@ -35,6 +35,7 @@ import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@ -202,7 +203,8 @@ public class MdnsServiceInfoTest {
List.of(),
/* textEntries= */ null,
/* interfaceIndex= */ 20,
network);
network,
Instant.MAX /* expirationTime */);
assertEquals(network, info2.getNetwork());
}
@ -225,7 +227,8 @@ public class MdnsServiceInfoTest {
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
MdnsServiceInfo.TextEntry.fromString("test=")),
20 /* interfaceIndex */,
new Network(123));
new Network(123),
Instant.MAX /* expirationTime */);
beforeParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);