Merge "Add aggressive query mode" into main

This commit is contained in:
Paul Hu 2024-01-30 07:37:41 +00:00 • committed by Gerrit Code Review
commit b4fba147cd
5 changed files with 255 additions and 41 deletions

View file

@ -35,6 +35,7 @@ import static com.android.networkstack.apishim.ConstantsShim.REGISTER_NSD_OFFLOA
import static com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserMetrics;
import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
@ -252,6 +253,8 @@ public class NsdService extends INsdManager.Stub {
private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
new RemoteCallbackList<>();
@NonNull
private final MdnsFeatureFlags mMdnsFeatureFlags;
private static class OffloadEngineInfo {
@NonNull final String mInterfaceName;
@ -794,7 +797,10 @@ public class NsdService extends INsdManager.Stub {
MdnsSearchOptions.newBuilder()
.setNetwork(discoveryRequest.getNetwork())
.setRemoveExpiredService(true)
.setQueryMode(PASSIVE_QUERY_MODE);
.setQueryMode(
mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
? AGGRESSIVE_QUERY_MODE
: PASSIVE_QUERY_MODE);
if (subtype != null) {
// checkSubtypeLabels() ensures that subtypes start with '_' but
// MdnsSearchOptions expects the underscore to not be present.
@ -1063,7 +1069,9 @@ public class NsdService extends INsdManager.Stub {
transactionId, resolveServiceType);
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
.setQueryMode(PASSIVE_QUERY_MODE)
.setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
? AGGRESSIVE_QUERY_MODE
: PASSIVE_QUERY_MODE)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
@ -1161,7 +1169,9 @@ public class NsdService extends INsdManager.Stub {
transactionId, resolveServiceType);
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
.setQueryMode(PASSIVE_QUERY_MODE)
.setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
? AGGRESSIVE_QUERY_MODE
: PASSIVE_QUERY_MODE)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
@ -1787,7 +1797,7 @@ public class NsdService extends INsdManager.Stub {
am.addOnUidImportanceListener(new UidImportanceListener(handler),
mRunningAppActiveImportanceCutoff);
final MdnsFeatureFlags flags = new MdnsFeatureFlags.Builder()
mMdnsFeatureFlags = new MdnsFeatureFlags.Builder()
.setIsMdnsOffloadFeatureEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
@ -1800,18 +1810,21 @@ public class NsdService extends INsdManager.Stub {
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
.setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
.setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
.build();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), flags);
LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), mMdnsFeatureFlags);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"),
mMdnsFeatureFlags);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"), flags,
mContext);
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"),
mMdnsFeatureFlags, mContext);
mClock = deps.makeClock();
}

View file

@ -57,6 +57,11 @@ public class MdnsFeatureFlags {
*/
public static final String NSD_UNICAST_REPLY_ENABLED = "nsd_unicast_reply_enabled";
/**
* A feature flag to control whether the aggressive query mode should be enabled.
*/
public static final String NSD_AGGRESSIVE_QUERY_MODE = "nsd_aggressive_query_mode";
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@ -75,6 +80,9 @@ public class MdnsFeatureFlags {
// Flag to enable replying unicast to queries requesting unicast replies
public final boolean mIsUnicastReplyEnabled;
// Flag for aggressive query mode
public final boolean mIsAggressiveQueryModeEnabled;
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@ -102,6 +110,13 @@ public class MdnsFeatureFlags {
return mIsUnicastReplyEnabled || isForceEnabledForTest(NSD_UNICAST_REPLY_ENABLED);
}
/**
* Indicates whether {@link #NSD_AGGRESSIVE_QUERY_MODE} is enabled, including for testing.
*/
public boolean isAggressiveQueryModeEnabled() {
return mIsAggressiveQueryModeEnabled || isForceEnabledForTest(NSD_AGGRESSIVE_QUERY_MODE);
}
/**
* The constructor for {@link MdnsFeatureFlags}.
*/
@ -111,6 +126,7 @@ public class MdnsFeatureFlags {
boolean isLabelCountLimitEnabled,
boolean isKnownAnswerSuppressionEnabled,
boolean isUnicastReplyEnabled,
boolean isAggressiveQueryModeEnabled,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@ -118,6 +134,7 @@ public class MdnsFeatureFlags {
mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
mIsKnownAnswerSuppressionEnabled = isKnownAnswerSuppressionEnabled;
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
mOverrideProvider = overrideProvider;
}
@ -136,6 +153,7 @@ public class MdnsFeatureFlags {
private boolean mIsLabelCountLimitEnabled;
private boolean mIsKnownAnswerSuppressionEnabled;
private boolean mIsUnicastReplyEnabled;
private boolean mIsAggressiveQueryModeEnabled;
private FlagOverrideProvider mOverrideProvider;
/**
@ -148,6 +166,7 @@ public class MdnsFeatureFlags {
mIsLabelCountLimitEnabled = true; // Default enabled.
mIsKnownAnswerSuppressionEnabled = false;
mIsUnicastReplyEnabled = true;
mIsAggressiveQueryModeEnabled = false;
mOverrideProvider = null;
}
@ -223,6 +242,16 @@ public class MdnsFeatureFlags {
return this;
}
/**
* Set whether the aggressive query mode is enabled.
*
* @see #NSD_AGGRESSIVE_QUERY_MODE
*/
public Builder setIsAggressiveQueryModeEnabled(boolean isAggressiveQueryModeEnabled) {
mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
return this;
}
/**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
@ -233,6 +262,7 @@ public class MdnsFeatureFlags {
mIsLabelCountLimitEnabled,
mIsKnownAnswerSuppressionEnabled,
mIsUnicastReplyEnabled,
mIsAggressiveQueryModeEnabled,
mOverrideProvider);
}
}

View file

@ -44,6 +44,10 @@ public class MdnsSearchOptions implements Parcelable {
public static final int PASSIVE_QUERY_MODE = 0;
// Active query mode scans frequently.
public static final int ACTIVE_QUERY_MODE = 1;
// Aggressive query mode scans more frequently than the active mode at first, and sends both
// unicast and multicast queries simultaneously, but in long sessions it eventually sends as
// many queries as the PASSIVE mode.
public static final int AGGRESSIVE_QUERY_MODE = 2;
/** @hide */
public static final Parcelable.Creator<MdnsSearchOptions> CREATOR =

View file

@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import android.annotation.NonNull;
@ -35,13 +36,23 @@ public class QueryTaskConfig {
private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
(int) MdnsConfigs.initialTimeBetweenBurstsMs();
private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
private static final int MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS =
(int) MdnsConfigs.timeBetweenBurstsMs();
private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
(int) MdnsConfigs.timeBetweenQueriesInBurstMs();
private static final int QUERIES_PER_BURST_PASSIVE_MODE =
(int) MdnsConfigs.queriesPerBurstPassive();
private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
@VisibleForTesting
// RFC 6762 5.2: The interval between the first two queries MUST be at least one second.
static final int INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS = 1000;
@VisibleForTesting
// Basically this tries to send one query per typical DTIM interval 100ms, to maximize the
// chances that a query will be received if devices are using a DTIM multiplier (in which case
// they only listen once every [multiplier] DTIM intervals).
static final int TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS = 100;
static final int MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS = 60000;
// The following fields are used by QueryTask so we need to test them.
@VisibleForTesting
final List<String> subtypes;
@ -81,6 +92,7 @@ public class QueryTaskConfig {
this.queryCount = queryCount;
this.socketKey = other.socketKey;
}
QueryTaskConfig(@NonNull Collection<String> subtypes,
int queryMode,
boolean onlyUseIpv6OnIpv6OnlyNetworks,
@ -96,21 +108,59 @@ public class QueryTaskConfig {
this.expectUnicastResponse = true;
this.isFirstBurst = true;
// Config the scan frequency based on the scan mode.
if (this.queryMode == PASSIVE_QUERY_MODE) {
if (this.queryMode == AGGRESSIVE_QUERY_MODE) {
this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
this.delayUntilNextTaskWithoutBackoffMs =
TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
} else if (this.queryMode == PASSIVE_QUERY_MODE) {
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
// in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
this.timeBetweenBurstsInMs = MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
} else {
// In active scan mode, sends a burst of QUERIES_PER_BURST queries,
// TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
// then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
// doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
this.socketKey = socketKey;
this.queryCount = 0;
this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
boolean isLastQueryInBurst) {
if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) {
return 0;
}
if (isLastQueryInBurst) {
return timeBetweenBurstsInMs;
}
return queryMode == AGGRESSIVE_QUERY_MODE
? TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS
: TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst) {
if (!isLastQueryInBurst) {
return false;
}
if (queryMode == AGGRESSIVE_QUERY_MODE) {
return true;
}
return alwaysAskForUnicastResponse;
}
int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst) {
if (!isLastQueryInBurst) {
return timeBetweenBurstsInMs;
}
final int maxTimeBetweenBursts = queryMode == AGGRESSIVE_QUERY_MODE
? MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS : MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
return timeBetweenBurstsInMs < maxTimeBetweenBursts
? Math.min(timeBetweenBurstsInMs * 2, maxTimeBetweenBursts) : timeBetweenBurstsInMs;
}
/**
@ -122,43 +172,28 @@ public class QueryTaskConfig {
if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
newTransactionId = 1;
}
boolean newExpectUnicastResponse = false;
boolean newIsFirstBurst = isFirstBurst;
int newQueriesPerBurst = queriesPerBurst;
int newBurstCounter = burstCounter + 1;
long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
// Only the first query expects uni-cast response.
if (newBurstCounter == queriesPerBurst) {
final boolean isFirstQueryInBurst = newBurstCounter == 1;
final boolean isLastQueryInBurst = newBurstCounter == queriesPerBurst;
if (isLastQueryInBurst) {
newBurstCounter = 0;
if (alwaysAskForUnicastResponse) {
newExpectUnicastResponse = true;
}
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
// then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
if (isFirstBurst) {
newIsFirstBurst = false;
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
// then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
if (queryMode == PASSIVE_QUERY_MODE) {
newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
}
}
// In active scan mode, sends a burst of QUERIES_PER_BURST queries,
// TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
// then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
// doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
TIME_BETWEEN_BURSTS_MS);
}
} else {
newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
return new QueryTaskConfig(this, newQueryCount, newTransactionId,
newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
getNextExpectUnicastResponse(isLastQueryInBurst), newIsFirstBurst, newBurstCounter,
newQueriesPerBurst, getNextTimeBetweenBurstsMs(isLastQueryInBurst),
getDelayUntilNextTaskWithoutBackoff(isFirstQueryInBurst, isLastQueryInBurst));
}
/**

View file

@ -17,8 +17,12 @@
package com.android.server.connectivity.mdns;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.ACTIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.EVENT_START_QUERYTASK;
import static com.android.server.connectivity.mdns.QueryTaskConfig.INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
import static com.android.server.connectivity.mdns.QueryTaskConfig.MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS;
import static com.android.server.connectivity.mdns.QueryTaskConfig.TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
@ -147,8 +151,8 @@ public class MdnsServiceTypeClientTests {
MockitoAnnotations.initMocks(this);
doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
expectedIPv4Packets = new DatagramPacket[16];
expectedIPv6Packets = new DatagramPacket[16];
expectedIPv4Packets = new DatagramPacket[24];
expectedIPv6Packets = new DatagramPacket[24];
socketKey = new SocketKey(mockNetwork, INTERFACE_INDEX);
for (int i = 0; i < expectedIPv4Packets.length; ++i) {
@ -173,7 +177,15 @@ public class MdnsServiceTypeClientTests {
.thenReturn(expectedIPv4Packets[12])
.thenReturn(expectedIPv4Packets[13])
.thenReturn(expectedIPv4Packets[14])
.thenReturn(expectedIPv4Packets[15]);
.thenReturn(expectedIPv4Packets[15])
.thenReturn(expectedIPv4Packets[16])
.thenReturn(expectedIPv4Packets[17])
.thenReturn(expectedIPv4Packets[18])
.thenReturn(expectedIPv4Packets[19])
.thenReturn(expectedIPv4Packets[20])
.thenReturn(expectedIPv4Packets[21])
.thenReturn(expectedIPv4Packets[22])
.thenReturn(expectedIPv4Packets[23]);
when(mockPacketWriter.getPacket(IPV6_ADDRESS))
.thenReturn(expectedIPv6Packets[0])
@ -191,7 +203,15 @@ public class MdnsServiceTypeClientTests {
.thenReturn(expectedIPv6Packets[12])
.thenReturn(expectedIPv6Packets[13])
.thenReturn(expectedIPv6Packets[14])
.thenReturn(expectedIPv6Packets[15]);
.thenReturn(expectedIPv6Packets[15])
.thenReturn(expectedIPv6Packets[16])
.thenReturn(expectedIPv6Packets[17])
.thenReturn(expectedIPv6Packets[18])
.thenReturn(expectedIPv6Packets[19])
.thenReturn(expectedIPv6Packets[20])
.thenReturn(expectedIPv6Packets[21])
.thenReturn(expectedIPv6Packets[22])
.thenReturn(expectedIPv6Packets[23]);
thread = new HandlerThread("MdnsServiceTypeClientTests");
thread.start();
@ -1672,6 +1692,118 @@ public class MdnsServiceTypeClientTests {
socketKey);
}
private int getBetweenBurstTime(int burstCounter, int currentBetweenTime, int maxBetweenTime,
int initialBetweenTime) {
return currentBetweenTime < maxBetweenTime
? Math.min(initialBetweenTime * (int) Math.pow(2, burstCounter), maxBetweenTime)
: currentBetweenTime;
}
@Test
public void sendQueries_aggressiveScanMode() {
final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
int burstCounter = 0;
int betweenBurstTime = 0;
for (int i = 0; i < expectedIPv4Packets.length; i += 3) {
verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
/* expectsUnicastResponse= */ false);
betweenBurstTime = getBetweenBurstTime(burstCounter, betweenBurstTime,
MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS,
INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS);
burstCounter++;
}
// Verify that Task is not removed before stopSendAndReceive was called.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
// Stop sending packets.
stopSendAndReceive(mockListenerOne);
verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
}
@Test
public void sendQueries_reentry_aggressiveScanMode() {
final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE).setQueryMode(AGGRESSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
// First burst, first query is sent.
verifyAndSendQuery(0, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
// After the first query is sent, change the subtypes, and restart.
final MdnsSearchOptions searchOptions2 = MdnsSearchOptions.newBuilder().addSubtype(SUBTYPE)
.addSubtype("_subtype2").setQueryMode(AGGRESSIVE_QUERY_MODE).build();
startSendAndReceive(mockListenerOne, searchOptions2);
// The previous scheduled task should be canceled.
verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
// Queries should continue to be sent.
verifyAndSendQuery(1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ true);
verifyAndSendQuery(2, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
verifyAndSendQuery(3, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
/* expectsUnicastResponse= */ false);
// Stop sending packets.
stopSendAndReceive(mockListenerOne);
verify(mockDeps, times(3)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
}
@Test
public void sendQueries_blendScanWithQueryBackoff() {
final int numOfQueriesBeforeBackoff = 11;
final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
.addSubtype(SUBTYPE)
.setQueryMode(AGGRESSIVE_QUERY_MODE)
.setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
.build();
startSendAndReceive(mockListenerOne, searchOptions);
// Always try to remove the task.
verify(mockDeps, times(1)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
int burstCounter = 0;
int betweenBurstTime = 0;
for (int i = 0; i < numOfQueriesBeforeBackoff; i += 3) {
verifyAndSendQuery(i, betweenBurstTime, /* expectsUnicastResponse= */ true);
verifyAndSendQuery(i + 1, /* timeInMs= */ 0, /* expectsUnicastResponse= */ false);
verifyAndSendQuery(i + 2, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
/* expectsUnicastResponse= */ false);
betweenBurstTime = getBetweenBurstTime(burstCounter, betweenBurstTime,
MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS,
INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS);
burstCounter++;
}
// In backoff mode, the current scheduled task will be canceled and reschedule if the
// 0.8 * smallestRemainingTtl is larger than time to next run.
long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
doReturn(true).when(mockDeps).hasMessages(any(), eq(EVENT_START_QUERYTASK));
processResponse(createResponse(
"service-instance-1", "192.0.2.123", 5353,
SERVICE_TYPE_LABELS,
Collections.emptyMap(), TEST_TTL), socketKey);
verify(mockDeps, times(2)).removeMessages(any(), eq(EVENT_START_QUERYTASK));
assertNotNull(delayMessage);
verifyAndSendQuery(12 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
14 /* scheduledCount */);
currentTime += (long) (TEST_TTL / 2 * 0.8);
doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
verifyAndSendQuery(13 /* index */, 0 /* timeInMs */,
false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
15 /* scheduledCount */);
verifyAndSendQuery(14 /* index */, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
16 /* scheduledCount */);
}
private static MdnsServiceInfo matchServiceName(String name) {
return argThat(info -> info.getServiceInstanceName().equals(name));
}