Merge "Enable java bpf map always in U devices"
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index fbb342d..846abcb 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -295,8 +295,7 @@
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
- (short) (NLM_F_REQUEST | NLM_F_DUMP));
+ requestSocketDump(h1);
final NativeHandle h2 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
@@ -325,7 +324,7 @@
}
@VisibleForTesting
- public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
+ void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
final byte[] msg = new byte[length];
final ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
@@ -350,6 +349,12 @@
}
}
+ @VisibleForTesting
+ void requestSocketDump(NativeHandle handle) {
+ sendIpv4NfGenMsg(handle, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ (short) (NLM_F_REQUEST | NLM_F_DUMP));
+ }
+
private void closeFdInNativeHandle(final NativeHandle h) {
try {
h.close();
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index d38a7c3..23fb60c 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -16,28 +16,32 @@
package com.android.networkstack.tethering;
+import static android.system.OsConstants.EAGAIN;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.NETLINK_NETFILTER;
+
import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET;
import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW;
import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK;
import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY;
import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.NativeHandle;
-import android.system.Os;
+import android.system.ErrnoException;
+import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.ConntrackMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkSocket;
import com.android.net.module.util.netlink.StructNlMsgHdr;
import org.junit.Before;
@@ -45,18 +49,18 @@
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketAddress;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConntrackSocketTest {
private static final long TIMEOUT = 500;
+ private static final String TAG = ConntrackSocketTest.class.getSimpleName();
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -80,51 +84,72 @@
mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
}
+ void findConnectionOrThrow(FileDescriptor fd, InetSocketAddress local, InetSocketAddress remote)
+ throws Exception {
+ Log.d(TAG, "Looking for socket " + local + " -> " + remote);
+
+ // Loop until the socket is found (and return) or recvMessage throws an exception.
+ while (true) {
+ final ByteBuffer buffer = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
+
+ // Parse all the netlink messages in the dump.
+ // NetlinkMessage#parse returns null if the message is truncated or invalid.
+ while (buffer.remaining() > 0) {
+ NetlinkMessage nlmsg = NetlinkMessage.parse(buffer, NETLINK_NETFILTER);
+ Log.d(TAG, "Got netlink message: " + nlmsg);
+ if (!(nlmsg instanceof ConntrackMessage)) {
+ continue;
+ }
+
+ StructNlMsgHdr nlmsghdr = nlmsg.getHeader();
+ ConntrackMessage ctmsg = (ConntrackMessage) nlmsg;
+ ConntrackMessage.Tuple tuple = ctmsg.tupleOrig;
+
+ if (nlmsghdr.nlmsg_type == (NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW)
+ && tuple.protoNum == IPPROTO_TCP
+ && tuple.srcIp.equals(local.getAddress())
+ && tuple.dstIp.equals(remote.getAddress())
+ && tuple.srcPort == (short) local.getPort()
+ && tuple.dstPort == (short) remote.getPort()) {
+ return;
+ }
+ }
+ }
+ }
+
@Test
public void testIpv4ConntrackSocket() throws Exception {
// Set up server and connect.
- final InetSocketAddress anyAddress = new InetSocketAddress(
- InetAddress.getByName("127.0.0.1"), 0);
+ final InetAddress localhost = InetAddress.getByName("127.0.0.1");
+ final InetSocketAddress anyAddress = new InetSocketAddress(localhost, 0);
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(anyAddress);
- final SocketAddress theAddress = serverSocket.getLocalSocketAddress();
+ final InetSocketAddress theAddress =
+ (InetSocketAddress) serverSocket.getLocalSocketAddress();
// Make a connection to the server.
final Socket socket = new Socket();
socket.connect(theAddress);
+ final InetSocketAddress localAddress = (InetSocketAddress) socket.getLocalSocketAddress();
final Socket acceptedSocket = serverSocket.accept();
final NativeHandle handle = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
- mOffloadHw.sendIpv4NfGenMsg(handle,
- (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
- (short) (NLM_F_REQUEST | NLM_F_DUMP));
-
- boolean foundConntrackEntry = false;
- ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE);
- buffer.order(ByteOrder.nativeOrder());
+ mOffloadHw.requestSocketDump(handle);
try {
- while (Os.read(handle.getFileDescriptor(), buffer) > 0) {
- buffer.flip();
-
- // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr
- // so we can confirm that the conntrack added is for the TCP connection above.
- final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer);
- assertNotNull(nlmsghdr);
-
- // As long as 1 conntrack entry is found test case will pass, even if it's not
- // the from the TCP connection above.
- if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) {
- foundConntrackEntry = true;
- break;
- }
+ findConnectionOrThrow(handle.getFileDescriptor(), localAddress, theAddress);
+ // No exceptions? Socket was found, test passes.
+ } catch (ErrnoException e) {
+ if (e.errno == EAGAIN) {
+ fail("Did not find socket " + localAddress + "->" + theAddress + " in dump");
+ } else {
+ throw e;
}
} finally {
socket.close();
serverSocket.close();
+ acceptedSocket.close();
}
- assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message",
- foundConntrackEntry);
}
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
index 7a8fcc0..7c19359 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
@@ -28,21 +28,24 @@
public interface MdnsServiceBrowserListener {
/**
- * Called when an mDNS service instance is found.
+ * Called when an mDNS service instance is found. This method would be called only if all
+ * service records (PTR, SRV, TXT, A or AAAA) are received .
*
* @param serviceInfo The found mDNS service instance.
*/
void onServiceFound(@NonNull MdnsServiceInfo serviceInfo);
/**
- * Called when an mDNS service instance is updated.
+ * Called when an mDNS service instance is updated. This method would be called only if all
+ * service records (PTR, SRV, TXT, A or AAAA) are received before.
*
* @param serviceInfo The updated mDNS service instance.
*/
void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo);
/**
- * Called when an mDNS service instance is no longer valid and removed.
+ * Called when a mDNS service instance is no longer valid and removed. This method would be
+ * called only if all service records (PTR, SRV, TXT, A or AAAA) are received before.
*
* @param serviceInfo The service instance of the removed mDNS service.
*/
@@ -75,4 +78,19 @@
* @param errorCode The error code, defined in {@link MdnsResponseErrorCode}.
*/
void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode);
+
+ /**
+ * Called when a mDNS service instance is discovered. This method would be called if the PTR
+ * record has been received.
+ *
+ * @param serviceInfo The discovered mDNS service instance.
+ */
+ void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo);
+
+ /**
+ * Called when a discovered mDNS service instance is no longer valid and removed.
+ *
+ * @param serviceInfo The service instance of the removed mDNS service.
+ */
+ void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo);
}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index f1b2def..9683bc9 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -270,7 +270,8 @@
public Map<String, String> getAttributes() {
Map<String, String> map = new HashMap<>(attributes.size());
for (Map.Entry<String, byte[]> kv : attributes.entrySet()) {
- map.put(kv.getKey(), new String(kv.getValue(), UTF_8));
+ final byte[] value = kv.getValue();
+ map.put(kv.getKey(), value == null ? null : new String(value, UTF_8));
}
return Collections.unmodifiableMap(map);
}
@@ -342,7 +343,7 @@
// 2. If there is no '=' in a DNS-SD TXT record string, then it is a
// boolean attribute, simply identified as being present, with no value.
if (delimitPos < 0) {
- return new TextEntry(new String(textBytes, US_ASCII), "");
+ return new TextEntry(new String(textBytes, US_ASCII), (byte[]) null);
} else if (delimitPos == 0) {
return null;
}
@@ -353,13 +354,13 @@
/** Creates a new {@link TextEntry} with given key and value of a UTF-8 string. */
public TextEntry(String key, String value) {
- this(key, value.getBytes(UTF_8));
+ this(key, value == null ? null : value.getBytes(UTF_8));
}
/** Creates a new {@link TextEntry} with given key and value of a byte array. */
public TextEntry(String key, byte[] value) {
this.key = key;
- this.value = value.clone();
+ this.value = value == null ? null : value.clone();
}
private TextEntry(Parcel in) {
@@ -372,17 +373,24 @@
}
public byte[] getValue() {
- return value.clone();
+ return value == null ? null : value.clone();
}
/** Converts this {@link TextEntry} instance to '=' separated byte array. */
public byte[] toBytes() {
- return ByteUtils.concat(key.getBytes(US_ASCII), new byte[]{'='}, value);
+ final byte[] keyBytes = key.getBytes(US_ASCII);
+ if (value == null) {
+ return keyBytes;
+ }
+ return ByteUtils.concat(keyBytes, new byte[]{'='}, value);
}
/** Converts this {@link TextEntry} instance to '=' separated string. */
@Override
public String toString() {
+ if (value == null) {
+ return key;
+ }
return key + "=" + new String(value, UTF_8);
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 0fd6025..dd4ff9b 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -148,10 +148,11 @@
this.searchOptions = searchOptions;
if (listeners.add(listener)) {
for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
+ final MdnsServiceInfo info =
+ buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ listener.onServiceNameDiscovered(info);
if (existingResponse.isComplete()) {
- listener.onServiceFound(
- buildMdnsServiceInfoFromResponse(existingResponse,
- serviceTypeLabels));
+ listener.onServiceFound(info);
}
}
}
@@ -226,6 +227,7 @@
boolean newServiceFound = false;
boolean existingServiceChanged = false;
+ boolean serviceBecomesComplete = false;
if (currentResponse == null) {
newServiceFound = true;
currentResponse = response;
@@ -233,10 +235,13 @@
if (serviceInstanceName != null) {
instanceNameToResponse.put(serviceInstanceName, currentResponse);
}
- } else if (currentResponse.mergeRecordsFrom(response)) {
- existingServiceChanged = true;
+ } else {
+ boolean before = currentResponse.isComplete();
+ existingServiceChanged = currentResponse.mergeRecordsFrom(response);
+ boolean after = currentResponse.isComplete();
+ serviceBecomesComplete = !before && after;
}
- if (!currentResponse.isComplete() || (!newServiceFound && !existingServiceChanged)) {
+ if (!newServiceFound && !existingServiceChanged) {
return;
}
MdnsServiceInfo serviceInfo =
@@ -244,9 +249,15 @@
for (MdnsServiceBrowserListener listener : listeners) {
if (newServiceFound) {
- listener.onServiceFound(serviceInfo);
- } else {
- listener.onServiceUpdated(serviceInfo);
+ listener.onServiceNameDiscovered(serviceInfo);
+ }
+
+ if (currentResponse.isComplete()) {
+ if (newServiceFound || serviceBecomesComplete) {
+ listener.onServiceFound(serviceInfo);
+ } else {
+ listener.onServiceUpdated(serviceInfo);
+ }
}
}
}
@@ -259,7 +270,10 @@
for (MdnsServiceBrowserListener listener : listeners) {
final MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
- listener.onServiceRemoved(serviceInfo);
+ if (response.isComplete()) {
+ listener.onServiceRemoved(serviceInfo);
+ }
+ listener.onServiceNameRemoved(serviceInfo);
}
}
@@ -423,7 +437,7 @@
Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
while (iter.hasNext()) {
MdnsResponse existingResponse = iter.next();
- if (existingResponse.isComplete()
+ if (existingResponse.hasServiceRecord()
&& existingResponse
.getServiceRecord()
.getRemainingTTL(SystemClock.elapsedRealtime())
@@ -436,7 +450,10 @@
final MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(
existingResponse, serviceTypeLabels);
- listener.onServiceRemoved(serviceInfo);
+ if (existingResponse.isComplete()) {
+ listener.onServiceRemoved(serviceInfo);
+ }
+ listener.onServiceNameRemoved(serviceInfo);
}
}
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 437622b..8ed735a 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -66,7 +66,6 @@
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
- "java/com/android/server/NetworkManagementServiceTest.java",
"java/com/android/server/VpnManagerServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index c327868..45a9dbc 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -498,7 +498,7 @@
public void testConnectivityManagerDoesNotLeakContext() throws Exception {
final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
- final int attempts = 100;
+ final int attempts = 600;
final long waitIntervalMs = 50;
for (int i = 0; i < attempts; i++) {
forceGC();
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
deleted file mode 100644
index 7688a6b..0000000
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2012 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 com.android.server;
-
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
-import static android.util.DebugUtils.valueToString;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.INetd;
-import android.net.INetdUnsolicitedEventListener;
-import android.net.LinkAddress;
-import android.net.NetworkPolicyManager;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.ArrayMap;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.server.NetworkManagementService.Dependencies;
-import com.android.server.net.BaseNetworkObserver;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.function.BiFunction;
-
-/**
- * Tests for {@link NetworkManagementService}.
- */
-@RunWith(DevSdkIgnoreRunner.class)
-@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class NetworkManagementServiceTest {
- private NetworkManagementService mNMService;
- @Mock private Context mContext;
- @Mock private ConnectivityManager mCm;
- @Mock private IBatteryStats.Stub mBatteryStatsService;
- @Mock private INetd.Stub mNetdService;
-
- private static final int TEST_UID = 111;
-
- @NonNull
- @Captor
- private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
-
- private final MockDependencies mDeps = new MockDependencies();
-
- private final class MockDependencies extends Dependencies {
- @Override
- public IBinder getService(String name) {
- switch (name) {
- case BatteryStats.SERVICE_NAME:
- return mBatteryStatsService;
- default:
- throw new UnsupportedOperationException("Unknown service " + name);
- }
- }
-
- @Override
- public void registerLocalService(NetworkManagementInternal nmi) {
- }
-
- @Override
- public INetd getNetd() {
- return mNetdService;
- }
-
- @Override
- public int getCallingUid() {
- return Process.SYSTEM_UID;
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- doNothing().when(mNetdService)
- .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
- doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
- eq(ConnectivityManager.class));
- doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
- // Start the service and wait until it connects to our socket.
- mNMService = NetworkManagementService.create(mContext, mDeps);
- }
-
- @After
- public void tearDown() throws Exception {
- mNMService.shutdown();
- }
-
- private static <T> T expectSoon(T mock) {
- return verify(mock, timeout(200));
- }
-
- /**
- * Tests that network observers work properly.
- */
- @Test
- public void testNetworkObservers() throws Exception {
- BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
- doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
- mNMService.registerObserver(observer);
-
- // Forget everything that happened to the mock so far, so we can explicitly verify
- // everything that happens and does not happen to it from now on.
-
- INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
- reset(observer);
- // Now call unsolListener methods and ensure that the observer methods are
- // called. After every method we expect a callback soon after; to ensure that
- // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
-
- /**
- * Interface changes.
- */
- unsolListener.onInterfaceAdded("rmnet12");
- expectSoon(observer).interfaceAdded("rmnet12");
-
- unsolListener.onInterfaceRemoved("eth1");
- expectSoon(observer).interfaceRemoved("eth1");
-
- unsolListener.onInterfaceChanged("clat4", true);
- expectSoon(observer).interfaceStatusChanged("clat4", true);
-
- unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
- expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
-
- /**
- * Bandwidth control events.
- */
- unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
- expectSoon(observer).limitReached("data", "rmnet_usb0");
-
- /**
- * Interface class activity.
- */
- unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
-
- unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
-
- unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
-
- /**
- * IP address changes.
- */
- unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
- expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
-
- unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
- expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
-
- unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
- expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
-
- /**
- * DNS information broadcasts.
- */
- unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
- expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
- new String[]{"2001:db8::1"});
-
- unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
- new String[]{"2001:db8::1", "2001:db8::2"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
- new String[]{"2001:db8::1", "2001:db8::2"});
-
- // We don't check for negative lifetimes, only for parse errors.
- unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
- new String[]{"::1"});
-
- // No syntax checking on the addresses.
- unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
- new String[]{"", "::", "", "foo", "::1"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
- new String[]{"", "::", "", "foo", "::1"});
-
- // Make sure nothing else was called.
- verifyNoMoreInteractions(observer);
- }
-
- @Test
- public void testFirewallEnabled() {
- mNMService.setFirewallEnabled(true);
- assertTrue(mNMService.isFirewallEnabled());
-
- mNMService.setFirewallEnabled(false);
- assertFalse(mNMService.isFirewallEnabled());
- }
-
- @Test
- public void testNetworkRestrictedDefault() {
- assertFalse(mNMService.isNetworkRestricted(TEST_UID));
- }
-
- @Test
- public void testMeteredNetworkRestrictions() throws RemoteException {
- // Make sure the mocked netd method returns true.
- doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
-
- // Restrict usage of mobile data in background
- mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
- assertTrue("Should be true since mobile data usage is restricted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
-
- mNMService.setDataSaverModeEnabled(true);
- verify(mNetdService).bandwidthEnableDataSaver(true);
-
- mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
- assertTrue("Should be true since data saver is on and the uid is not allowlisted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
-
- mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
- assertFalse("Should be false since data saver is on and the uid is allowlisted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
-
- // remove uid from allowlist and turn datasaver off again
- mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
- verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
- mNMService.setDataSaverModeEnabled(false);
- verify(mNetdService).bandwidthEnableDataSaver(false);
- assertFalse("Network should not be restricted when data saver is off",
- mNMService.isNetworkRestricted(TEST_UID));
- }
-
- @Test
- public void testFirewallChains() {
- final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
- // Dozable chain
- final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
- isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
- // Powersaver chain
- final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
- isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
- // Standby chain
- final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
- isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
- isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
- // Restricted mode chain
- final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
- isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
- // Low Power Standby chain
- final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
- isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
-
- final int[] chains = {
- FIREWALL_CHAIN_STANDBY,
- FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_DOZABLE,
- FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
- };
- final int[] states = {
- INetd.FIREWALL_RULE_ALLOW,
- INetd.FIREWALL_RULE_DENY,
- NetworkPolicyManager.FIREWALL_RULE_DEFAULT
- };
- BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
- return String.format("Unexpected value for chain: %s and state: %s",
- valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
- valueToString(INetd.class, "FIREWALL_RULE_", state));
- };
- for (int chain : chains) {
- final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
- mNMService.setFirewallChainEnabled(chain, true);
- verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
- for (int state : states) {
- mNMService.setFirewallUidRule(chain, TEST_UID, state);
- assertEquals(errorMsg.apply(chain, state),
- expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
- }
- mNMService.setFirewallChainEnabled(chain, false);
- verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
- }
- }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index d3934c2..ebdb73f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -164,7 +164,8 @@
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
List.of(
MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
- MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
+ MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
+ MdnsServiceInfo.TextEntry.fromString("test=")));
beforeParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -208,11 +209,11 @@
}
@Test
- public void textEntry_fromStringWithoutAssignPunc_valueisEmpty() {
+ public void textEntry_fromStringWithoutAssignPunc_noValue() {
TextEntry entry = TextEntry.fromString("AA");
assertEquals("AA", entry.getKey());
- assertArrayEquals(new byte[] {}, entry.getValue());
+ assertNull(entry.getValue());
}
@Test
@@ -241,11 +242,11 @@
}
@Test
- public void textEntry_fromBytesWithoutAssignPunc_valueisEmpty() {
+ public void textEntry_fromBytesWithoutAssignPunc_noValue() {
TextEntry entry = TextEntry.fromBytes(new byte[] {'A', 'A'});
assertEquals("AA", entry.getKey());
- assertArrayEquals(new byte[] {}, entry.getValue());
+ assertNull(entry.getValue());
}
@Test
@@ -270,4 +271,12 @@
assertEquals(new TextEntry("BB", "xxyyzz"), new TextEntry("BB", "xxyyzz"));
assertEquals(new TextEntry("AA", "XXYYZZ"), new TextEntry("AA", "XXYYZZ"));
}
+
+ @Test
+ public void textEntry_fromString_valueIsEmpty() {
+ TextEntry entry = TextEntry.fromString("AA=");
+
+ assertEquals("AA", entry.getKey());
+ assertArrayEquals(new byte[] {}, entry.getValue());
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 6f8b85a..462685a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -18,6 +18,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -26,16 +27,18 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
import android.text.TextUtils;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
@@ -49,6 +52,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -72,7 +76,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class MdnsServiceTypeClientTests {
-
+ private static final int INTERFACE_INDEX = 999;
private static final String SERVICE_TYPE = "_googlecast._tcp.local";
private static final String[] SERVICE_TYPE_LABELS = TextUtils.split(SERVICE_TYPE, "\\.");
@@ -379,15 +383,41 @@
assertNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
}
+ private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
+ String[] serviceType, String ipv4Address, String ipv6Address, int port,
+ List<String> subTypes, Map<String, String> attributes, int interfaceIndex) {
+ assertEquals(serviceName, serviceInfo.getServiceInstanceName());
+ assertArrayEquals(serviceType, serviceInfo.getServiceType());
+ assertEquals(ipv4Address, serviceInfo.getIpv4Address());
+ assertEquals(ipv6Address, serviceInfo.getIpv6Address());
+ assertEquals(port, serviceInfo.getPort());
+ assertEquals(subTypes, serviceInfo.getSubtypes());
+ for (String key : attributes.keySet()) {
+ assertEquals(attributes.get(key), serviceInfo.getAttributeByKey(key));
+ }
+ assertEquals(interfaceIndex, serviceInfo.getInterfaceIndex());
+ }
+
@Test
public void processResponse_incompleteResponse() {
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
MdnsResponse response = mock(MdnsResponse.class);
when(response.getServiceInstanceName()).thenReturn("service-instance-1");
+ doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
when(response.isComplete()).thenReturn(false);
client.processResponse(response);
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ null /* ipv6Address */,
+ 0 /* port */,
+ List.of() /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX);
verify(mockListenerOne, never()).onServiceFound(any(MdnsServiceInfo.class));
verify(mockListenerOne, never()).onServiceUpdated(any(MdnsServiceInfo.class));
@@ -404,7 +434,7 @@
"service-instance-1",
ipV4Address,
5353,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
/* interfaceIndex= */ 20);
client.processResponse(initialResponse);
@@ -415,14 +445,26 @@
"service-instance-1",
ipV4Address,
5354,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.singletonMap("key", "value"),
/* interfaceIndex= */ 20);
client.processResponse(secondResponse);
+ // Verify onServiceNameDiscovered was called once for the initial response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ 20 /* interfaceIndex */);
+
// Verify onServiceFound was called once for the initial response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(initialServiceInfo.getPort(), 5353);
@@ -432,7 +474,7 @@
// Verify onServiceUpdated was called once for the second response.
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
- MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
+ MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(2);
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(updatedServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
@@ -453,7 +495,7 @@
"service-instance-1",
ipV6Address,
5353,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
/* interfaceIndex= */ 20);
client.processResponse(initialResponse);
@@ -464,7 +506,7 @@
"service-instance-1",
ipV6Address,
5354,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.singletonMap("key", "value"),
/* interfaceIndex= */ 20);
client.processResponse(secondResponse);
@@ -472,9 +514,21 @@
System.out.println("secondResponses ip"
+ secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
+ // Verify onServiceNameDiscovered was called once for the initial response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ 20 /* interfaceIndex */);
+
// Verify onServiceFound was called once for the initial response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(initialServiceInfo.getPort(), 5353);
@@ -484,7 +538,7 @@
// Verify onServiceUpdated was called once for the second response.
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
- MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
+ MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(2);
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(updatedServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
@@ -494,6 +548,23 @@
assertEquals(updatedServiceInfo.getInterfaceIndex(), 20);
}
+ private void verifyServiceRemovedNoCallback(MdnsServiceBrowserListener listener) {
+ verify(listener, never()).onServiceRemoved(any());
+ verify(listener, never()).onServiceNameRemoved(any());
+ }
+
+ private void verifyServiceRemovedCallback(MdnsServiceBrowserListener listener,
+ String serviceName, String[] serviceType, int interfaceIndex) {
+ verify(listener).onServiceRemoved(argThat(
+ info -> serviceName.equals(info.getServiceInstanceName())
+ && Arrays.equals(serviceType, info.getServiceType())
+ && info.getInterfaceIndex() == interfaceIndex));
+ verify(listener).onServiceNameRemoved(argThat(
+ info -> serviceName.equals(info.getServiceInstanceName())
+ && Arrays.equals(serviceType, info.getServiceType())
+ && info.getInterfaceIndex() == interfaceIndex));
+ }
+
@Test
public void processResponse_goodBye() throws Exception {
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
@@ -501,37 +572,32 @@
final String serviceName = "service-instance-1";
final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
- final int interfaceIndex = 999;
// Process the initial response.
final MdnsResponse initialResponse =
createResponse(
serviceName,
ipV6Address,
5353 /* port */,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
- interfaceIndex);
+ INTERFACE_INDEX);
client.processResponse(initialResponse);
MdnsResponse response = mock(MdnsResponse.class);
doReturn("goodbye-service").when(response).getServiceInstanceName();
- doReturn(interfaceIndex).when(response).getInterfaceIndex();
+ doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
doReturn(true).when(response).isGoodbye();
client.processResponse(response);
- // Verify onServiceRemoved won't be called if the service is not existed.
- verify(mockListenerOne, never()).onServiceRemoved(any());
- verify(mockListenerTwo, never()).onServiceRemoved(any());
+ // Verify removed callback won't be called if the service is not existed.
+ verifyServiceRemovedNoCallback(mockListenerOne);
+ verifyServiceRemovedNoCallback(mockListenerTwo);
- // Verify onServiceRemoved would be called.
+ // Verify removed callback would be called.
doReturn(serviceName).when(response).getServiceInstanceName();
client.processResponse(response);
- verify(mockListenerOne).onServiceRemoved(argThat(
- info -> serviceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == interfaceIndex));
- verify(mockListenerTwo).onServiceRemoved(argThat(
- info -> serviceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == interfaceIndex));
+ verifyServiceRemovedCallback(
+ mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX);
+ verifyServiceRemovedCallback(
+ mockListenerTwo, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX);
}
@Test
@@ -542,15 +608,28 @@
"service-instance-1",
"192.168.1.1",
5353,
- Collections.singletonList("ABCDE"),
- Collections.emptyMap());
+ /* subtype= */ "ABCDE",
+ Collections.emptyMap(),
+ INTERFACE_INDEX);
client.processResponse(initialResponse);
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ // Verify onServiceNameDiscovered was called once for the existing response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ "192.168.1.1" /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX);
+
// Verify onServiceFound was called once for the existing response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo existingServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo existingServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(existingServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(existingServiceInfo.getIpv4Address(), "192.168.1.1");
assertEquals(existingServiceInfo.getPort(), 5353);
@@ -567,6 +646,7 @@
// Verify onServiceFound was not called on the newly registered listener after the existing
// response is gone.
+ verify(mockListenerTwo, never()).onServiceNameDiscovered(any(MdnsServiceInfo.class));
verify(mockListenerTwo, never()).onServiceFound(any(MdnsServiceInfo.class));
}
@@ -580,9 +660,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of());
+ Map.of(), INTERFACE_INDEX);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -592,8 +672,8 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
}
@Test
@@ -614,9 +694,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), 999 /* interfaceIndex */);
+ Map.of(), INTERFACE_INDEX);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -626,18 +706,16 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
// Simulate the case where the response is after TTL.
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was called.
- verify(mockListenerOne, times(1)).onServiceRemoved(argThat(
- info -> serviceInstanceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == 999));
+ // Verify removed callback was called.
+ verifyServiceRemovedCallback(
+ mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX);
}
@Test
@@ -656,9 +734,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of());
+ Map.of(), INTERFACE_INDEX);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -668,8 +746,8 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
}
@Test
@@ -690,9 +768,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), 999 /* interfaceIndex */);
+ Map.of(), INTERFACE_INDEX);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -702,11 +780,117 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was called.
- verify(mockListenerOne, times(1)).onServiceRemoved(argThat(
- info -> serviceInstanceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == 999));
+ // Verify removed callback was called.
+ verifyServiceRemovedCallback(
+ mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX);
+ }
+
+ @Test
+ public void testProcessResponse_InOrder() throws Exception {
+ final String serviceName = "service-instance";
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+ client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ InOrder inOrder = inOrder(mockListenerOne);
+
+ // Process the initial response which is incomplete.
+ final MdnsResponse initialResponse =
+ createResponse(
+ serviceName,
+ null,
+ 5353,
+ "ABCDE" /* subtype */,
+ Collections.emptyMap(),
+ INTERFACE_INDEX);
+ client.processResponse(initialResponse);
+
+ // Process a second response which has ip address to make response become complete.
+ final MdnsResponse secondResponse =
+ createResponse(
+ serviceName,
+ ipV4Address,
+ 5353,
+ "ABCDE" /* subtype */,
+ Collections.emptyMap(),
+ INTERFACE_INDEX);
+ client.processResponse(secondResponse);
+
+ // Process a third response with a different ip address, port and updated text attributes.
+ final MdnsResponse thirdResponse =
+ createResponse(
+ serviceName,
+ ipV6Address,
+ 5354,
+ "ABCDE" /* subtype */,
+ Collections.singletonMap("key", "value"),
+ INTERFACE_INDEX);
+ client.processResponse(thirdResponse);
+
+ // Process the last response which is goodbye message.
+ final MdnsResponse lastResponse = mock(MdnsResponse.class);
+ doReturn(serviceName).when(lastResponse).getServiceInstanceName();
+ doReturn(true).when(lastResponse).isGoodbye();
+ client.processResponse(lastResponse);
+
+ // Verify onServiceNameDiscovered was first called for the initial response.
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX);
+
+ // Verify onServiceFound was second called for the second response.
+ inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX);
+
+ // Verify onServiceUpdated was third called for the third response.
+ inOrder.verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX);
+
+ // Verify onServiceRemoved was called for the last response.
+ inOrder.verify(mockListenerOne).onServiceRemoved(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX);
+
+ // Verify onServiceNameRemoved was called for the last response.
+ inOrder.verify(mockListenerOne).onServiceNameRemoved(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX);
}
// verifies that the right query was enqueued with the right delay, and send query by executing
@@ -771,19 +955,8 @@
}
}
- private MdnsResponse createResponse(
- @NonNull String serviceInstanceName,
- @NonNull String host,
- int port,
- @NonNull List<String> subtypes,
- @NonNull Map<String, String> textAttributes)
- throws Exception {
- return createResponse(serviceInstanceName, host, port, subtypes, textAttributes,
- /* interfaceIndex= */ -1);
- }
-
- // Creates a complete mDNS response.
- private MdnsResponse createResponse(
+ // Creates a mock mDNS response.
+ private MdnsResponse createMockResponse(
@NonNull String serviceInstanceName,
@NonNull String host,
int port,
@@ -830,4 +1003,73 @@
doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
return response;
}
+
+ // Creates a mDNS response.
+ private MdnsResponse createResponse(
+ @NonNull String serviceInstanceName,
+ @Nullable String host,
+ int port,
+ @NonNull String subtype,
+ @NonNull Map<String, String> textAttributes,
+ int interfaceIndex)
+ throws Exception {
+ MdnsResponse response = new MdnsResponse(0);
+ response.setInterfaceIndex(interfaceIndex);
+
+ // Set PTR record
+ final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
+ new String[]{subtype, MdnsConstants.SUBTYPE_LABEL, "test"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ new String[]{serviceInstanceName});
+ response.addPointerRecord(pointerRecord);
+
+ // Set SRV record.
+ final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
+ new String[] {"service"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ port,
+ new String[]{"hostname"});
+ response.setServiceRecord(serviceRecord);
+
+ // Set A/AAAA record.
+ if (host != null) {
+ if (InetAddresses.parseNumericAddress(host) instanceof Inet6Address) {
+ final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+ new String[] {"address"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ Inet6Address.getByName(host));
+ response.setInet6AddressRecord(inetAddressRecord);
+ } else {
+ final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+ new String[] {"address"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ Inet4Address.getByName(host));
+ response.setInet4AddressRecord(inetAddressRecord);
+ }
+ }
+
+ // Set TXT record.
+ final List<TextEntry> textEntries = new ArrayList<>();
+ for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
+ textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
+ }
+ final MdnsTextRecord textRecord = new MdnsTextRecord(
+ new String[] {"text"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ textEntries);
+ response.setTextRecord(textRecord);
+ return response;
+ }
}
\ No newline at end of file