Merge "Add mdns files and unit tests"
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkRequest;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Tests for {@link ConnectivityMonitor}. */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class ConnectivityMonitorWithConnectivityManagerTests {
|
||||
@Mock private Context mContext;
|
||||
@Mock private ConnectivityMonitor.Listener mockListener;
|
||||
@Mock private ConnectivityManager mConnectivityManager;
|
||||
|
||||
private ConnectivityMonitorWithConnectivityManager monitor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mConnectivityManager).when(mContext)
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
monitor = new ConnectivityMonitorWithConnectivityManager(mContext, mockListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialState_shouldNotRegisterNetworkCallback() {
|
||||
verifyNetworkCallbackRegistered(0 /* time */);
|
||||
verifyNetworkCallbackUnregistered(0 /* time */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDiscovery_shouldRegisterNetworkCallback() {
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
|
||||
verifyNetworkCallbackRegistered(1 /* time */);
|
||||
verifyNetworkCallbackUnregistered(0 /* time */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDiscoveryTwice_shouldRegisterOneNetworkCallback() {
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
|
||||
verifyNetworkCallbackRegistered(1 /* time */);
|
||||
verifyNetworkCallbackUnregistered(0 /* time */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDiscovery_shouldUnregisterNetworkCallback() {
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
monitor.stopWatchingConnectivityChanges();
|
||||
|
||||
verifyNetworkCallbackRegistered(1 /* time */);
|
||||
verifyNetworkCallbackUnregistered(1 /* time */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDiscoveryTwice_shouldUnregisterNetworkCallback() {
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
monitor.stopWatchingConnectivityChanges();
|
||||
|
||||
verifyNetworkCallbackRegistered(1 /* time */);
|
||||
verifyNetworkCallbackUnregistered(1 /* time */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntentFired_shouldNotifyListener() {
|
||||
InOrder inOrder = inOrder(mockListener);
|
||||
monitor.startWatchingConnectivityChanges();
|
||||
|
||||
final ArgumentCaptor<NetworkCallback> callbackCaptor =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
verify(mConnectivityManager, times(1)).registerNetworkCallback(
|
||||
any(NetworkRequest.class), callbackCaptor.capture());
|
||||
|
||||
final NetworkCallback callback = callbackCaptor.getValue();
|
||||
final Network testNetwork = new Network(1 /* netId */);
|
||||
|
||||
// Simulate network available.
|
||||
callback.onAvailable(testNetwork);
|
||||
inOrder.verify(mockListener).onConnectivityChanged();
|
||||
|
||||
// Simulate network lost.
|
||||
callback.onLost(testNetwork);
|
||||
inOrder.verify(mockListener).onConnectivityChanged();
|
||||
|
||||
// Simulate network unavailable.
|
||||
callback.onUnavailable();
|
||||
inOrder.verify(mockListener).onConnectivityChanged();
|
||||
}
|
||||
|
||||
private void verifyNetworkCallbackRegistered(int time) {
|
||||
verify(mConnectivityManager, times(time)).registerNetworkCallback(
|
||||
any(NetworkRequest.class), any(NetworkCallback.class));
|
||||
}
|
||||
|
||||
private void verifyNetworkCallbackUnregistered(int time) {
|
||||
verify(mConnectivityManager, times(time))
|
||||
.unregisterNetworkCallback(any(NetworkCallback.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
/** Tests for {@link MdnsDiscoveryManager}. */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsDiscoveryManagerTests {
|
||||
|
||||
private static final String SERVICE_TYPE_1 = "_googlecast._tcp.local";
|
||||
private static final String SERVICE_TYPE_2 = "_test._tcp.local";
|
||||
|
||||
@Mock private ExecutorProvider executorProvider;
|
||||
@Mock private MdnsSocketClient socketClient;
|
||||
@Mock private MdnsServiceTypeClient mockServiceTypeClientOne;
|
||||
@Mock private MdnsServiceTypeClient mockServiceTypeClientTwo;
|
||||
|
||||
@Mock MdnsServiceBrowserListener mockListenerOne;
|
||||
@Mock MdnsServiceBrowserListener mockListenerTwo;
|
||||
private MdnsDiscoveryManager discoveryManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mockServiceTypeClientOne.getServiceTypeLabels())
|
||||
.thenReturn(TextUtils.split(SERVICE_TYPE_1, "\\."));
|
||||
when(mockServiceTypeClientTwo.getServiceTypeLabels())
|
||||
.thenReturn(TextUtils.split(SERVICE_TYPE_2, "\\."));
|
||||
|
||||
discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient) {
|
||||
@Override
|
||||
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType) {
|
||||
if (serviceType.equals(SERVICE_TYPE_1)) {
|
||||
return mockServiceTypeClientOne;
|
||||
} else if (serviceType.equals(SERVICE_TYPE_2)) {
|
||||
return mockServiceTypeClientTwo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerListener_unregisterListener() throws IOException {
|
||||
discoveryManager.registerListener(
|
||||
SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
verify(socketClient).startDiscovery();
|
||||
verify(mockServiceTypeClientOne)
|
||||
.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
when(mockServiceTypeClientOne.stopSendAndReceive(mockListenerOne)).thenReturn(true);
|
||||
discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne);
|
||||
verify(mockServiceTypeClientOne).stopSendAndReceive(mockListenerOne);
|
||||
verify(socketClient).stopDiscovery();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerMultipleListeners() throws IOException {
|
||||
discoveryManager.registerListener(
|
||||
SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
verify(socketClient).startDiscovery();
|
||||
verify(mockServiceTypeClientOne)
|
||||
.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
discoveryManager.registerListener(
|
||||
SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
verify(mockServiceTypeClientTwo)
|
||||
.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onResponseReceived() {
|
||||
discoveryManager.registerListener(
|
||||
SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
discoveryManager.registerListener(
|
||||
SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
MdnsResponse responseForServiceTypeOne = createMockResponse(SERVICE_TYPE_1);
|
||||
discoveryManager.onResponseReceived(responseForServiceTypeOne);
|
||||
verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne);
|
||||
|
||||
MdnsResponse responseForServiceTypeTwo = createMockResponse(SERVICE_TYPE_2);
|
||||
discoveryManager.onResponseReceived(responseForServiceTypeTwo);
|
||||
verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo);
|
||||
|
||||
MdnsResponse responseForSubtype = createMockResponse("subtype._sub._googlecast._tcp.local");
|
||||
discoveryManager.onResponseReceived(responseForSubtype);
|
||||
verify(mockServiceTypeClientOne).processResponse(responseForSubtype);
|
||||
}
|
||||
|
||||
private MdnsResponse createMockResponse(String serviceType) {
|
||||
MdnsPointerRecord mockPointerRecord = mock(MdnsPointerRecord.class);
|
||||
MdnsResponse mockResponse = mock(MdnsResponse.class);
|
||||
when(mockResponse.getPointerRecords())
|
||||
.thenReturn(Collections.singletonList(mockPointerRecord));
|
||||
when(mockPointerRecord.getName()).thenReturn(TextUtils.split(serviceType, "\\."));
|
||||
return mockResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.util.Locale;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsPacketReaderTests {
|
||||
|
||||
@Test
|
||||
public void testLimits() throws IOException {
|
||||
byte[] data = new byte[25];
|
||||
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
|
||||
|
||||
// After creating a new reader, confirm that the remaining is equal to the packet length
|
||||
// (or that there is no temporary limit).
|
||||
MdnsPacketReader packetReader = new MdnsPacketReader(datagramPacket);
|
||||
assertEquals(data.length, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we can set the temporary limit to 0.
|
||||
packetReader.setLimit(0);
|
||||
assertEquals(0, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we can clear the temporary limit, and restore to the length of the packet.
|
||||
packetReader.clearLimit();
|
||||
assertEquals(data.length, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we can set the temporary limit to the actual length of the packet.
|
||||
// While parsing packets, it is common to set the limit to the length of the packet.
|
||||
packetReader.setLimit(data.length);
|
||||
assertEquals(data.length, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we ignore negative limits.
|
||||
packetReader.setLimit(-10);
|
||||
assertEquals(data.length, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we can set the temporary limit to something less than the packet length.
|
||||
packetReader.setLimit(data.length / 2);
|
||||
assertEquals(data.length / 2, packetReader.getRemaining());
|
||||
|
||||
// Confirm that we throw an exception if trying to set the temporary limit beyond the
|
||||
// packet length.
|
||||
packetReader.clearLimit();
|
||||
try {
|
||||
packetReader.setLimit(data.length * 2 + 1);
|
||||
fail("Should have thrown an IOException when trying to set the temporary limit beyond "
|
||||
+ "the packet length");
|
||||
} catch (IOException e) {
|
||||
// Expected
|
||||
} catch (Exception e) {
|
||||
fail(String.format(
|
||||
Locale.ROOT,
|
||||
"Should not have thrown any other exception except " + "for IOException: %s",
|
||||
e.getMessage()));
|
||||
}
|
||||
assertEquals(data.length, packetReader.getRemaining());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.net.module.util.HexDump;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
// The record test data does not use compressed names (label pointers), since that would require
|
||||
// additional data to populate the label dictionary accordingly.
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsRecordTests {
|
||||
private static final String TAG = "MdnsRecordTests";
|
||||
private static final int MAX_PACKET_SIZE = 4096;
|
||||
private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
|
||||
private static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
|
||||
|
||||
@Test
|
||||
public void testInet4AddressRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0474657374000001" + "0001000011940004" + "0A010203");
|
||||
assertNotNull(dataIn);
|
||||
String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
assertEquals("test", name[0]);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_A, type);
|
||||
|
||||
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
|
||||
Inet4Address addr = record.getInet4Address();
|
||||
assertEquals("/10.1.2.3", addr.toString());
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeAAAInet6AddressRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"047465737400001C"
|
||||
+ "0001000011940010"
|
||||
+ "AABBCCDD11223344"
|
||||
+ "A0B0C0D010203040");
|
||||
assertNotNull(dataIn);
|
||||
String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
packet.setSocketAddress(
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_AAAA, type);
|
||||
|
||||
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA,
|
||||
reader);
|
||||
assertNull(record.getInet4Address());
|
||||
Inet6Address addr = record.getInet6Address();
|
||||
assertEquals("/aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040", addr.toString());
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV6_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeAAAInet4AddressRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"047465737400001C"
|
||||
+ "0001000011940010"
|
||||
+ "0000000000000000"
|
||||
+ "0000FFFF10203040");
|
||||
assertNotNull(dataIn);
|
||||
HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
packet.setSocketAddress(
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_AAAA, type);
|
||||
|
||||
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA,
|
||||
reader);
|
||||
assertNull(record.getInet6Address());
|
||||
Inet4Address addr = record.getInet4Address();
|
||||
assertEquals("/16.32.48.64", addr.toString());
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
final byte[] expectedDataIn =
|
||||
HexDump.hexStringToByteArray("047465737400001C000100001194000410203040");
|
||||
assertNotNull(expectedDataIn);
|
||||
String expectedDataInText = HexDump.dumpHexString(expectedDataIn, 0, expectedDataIn.length);
|
||||
|
||||
assertEquals(expectedDataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointerRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"047465737400000C"
|
||||
+ "000100001194000E"
|
||||
+ "03666F6F03626172"
|
||||
+ "047175787800");
|
||||
assertNotNull(dataIn);
|
||||
String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_PTR, type);
|
||||
|
||||
MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
|
||||
String[] pointer = record.getPointer();
|
||||
assertEquals("foo.bar.quxx", MdnsRecord.labelsToString(pointer));
|
||||
|
||||
assertFalse(record.hasSubtype());
|
||||
assertNull(record.getSubtype());
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0474657374000021"
|
||||
+ "0001000011940014"
|
||||
+ "000100FF1F480366"
|
||||
+ "6F6F036261720471"
|
||||
+ "75787800");
|
||||
assertNotNull(dataIn);
|
||||
String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_SRV, type);
|
||||
|
||||
MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
|
||||
|
||||
int servicePort = record.getServicePort();
|
||||
assertEquals(8008, servicePort);
|
||||
|
||||
String serviceHost = MdnsRecord.labelsToString(record.getServiceHost());
|
||||
assertEquals("foo.bar.quxx", serviceHost);
|
||||
|
||||
assertEquals(1, record.getServicePriority());
|
||||
assertEquals(255, record.getServiceWeight());
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextRecord() throws IOException {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0474657374000010"
|
||||
+ "0001000011940024"
|
||||
+ "0D613D68656C6C6F"
|
||||
+ "2074686572650C62"
|
||||
+ "3D31323334353637"
|
||||
+ "3839300878797A3D"
|
||||
+ "21402324");
|
||||
assertNotNull(dataIn);
|
||||
String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
|
||||
|
||||
// Decode
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
|
||||
String[] name = reader.readLabels();
|
||||
assertNotNull(name);
|
||||
assertEquals(1, name.length);
|
||||
String fqdn = MdnsRecord.labelsToString(name);
|
||||
assertEquals("test", fqdn);
|
||||
|
||||
int type = reader.readUInt16();
|
||||
assertEquals(MdnsRecord.TYPE_TXT, type);
|
||||
|
||||
MdnsTextRecord record = new MdnsTextRecord(name, reader);
|
||||
|
||||
List<String> strings = record.getStrings();
|
||||
assertNotNull(strings);
|
||||
assertEquals(3, strings.size());
|
||||
|
||||
assertEquals("a=hello there", strings.get(0));
|
||||
assertEquals("b=1234567890", strings.get(1));
|
||||
assertEquals("xyz=!@#$", strings.get(2));
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
|
||||
packet = writer.getPacket(MULTICAST_IPV4_ADDRESS);
|
||||
byte[] dataOut = packet.getData();
|
||||
|
||||
String dataOutText = HexDump.dumpHexString(dataOut, 0, packet.getLength());
|
||||
Log.d(TAG, dataOutText);
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.server.connectivity.mdns.MdnsResponseDecoder.Clock;
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.android.net.module.util.HexDump;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsResponseDecoderTests {
|
||||
private static final byte[] data = HexDump.hexStringToByteArray(
|
||||
"0000840000000004"
|
||||
+ "00000003134A6F68"
|
||||
+ "6E6E792773204368"
|
||||
+ "726F6D6563617374"
|
||||
+ "0B5F676F6F676C65"
|
||||
+ "63617374045F7463"
|
||||
+ "70056C6F63616C00"
|
||||
+ "0010800100001194"
|
||||
+ "006C2369643D3937"
|
||||
+ "3062663534376237"
|
||||
+ "3533666336336332"
|
||||
+ "6432613336626238"
|
||||
+ "3936616261380576"
|
||||
+ "653D30320D6D643D"
|
||||
+ "4368726F6D656361"
|
||||
+ "73741269633D2F73"
|
||||
+ "657475702F69636F"
|
||||
+ "6E2E706E6716666E"
|
||||
+ "3D4A6F686E6E7927"
|
||||
+ "73204368726F6D65"
|
||||
+ "636173740463613D"
|
||||
+ "350473743D30095F"
|
||||
+ "7365727669636573"
|
||||
+ "075F646E732D7364"
|
||||
+ "045F756470C03100"
|
||||
+ "0C00010000119400"
|
||||
+ "02C020C020000C00"
|
||||
+ "01000011940002C0"
|
||||
+ "0CC00C0021800100"
|
||||
+ "000078001C000000"
|
||||
+ "001F49134A6F686E"
|
||||
+ "6E79277320436872"
|
||||
+ "6F6D6563617374C0"
|
||||
+ "31C0F30001800100"
|
||||
+ "0000780004C0A864"
|
||||
+ "68C0F3002F800100"
|
||||
+ "0000780005C0F300"
|
||||
+ "0140C00C002F8001"
|
||||
+ "000011940009C00C"
|
||||
+ "00050000800040");
|
||||
|
||||
private static final byte[] data6 = HexDump.hexStringToByteArray(
|
||||
"0000840000000001000000030B5F676F6F676C656361737404"
|
||||
+ "5F746370056C6F63616C00000C000100000078003330476F6F676C"
|
||||
+ "652D486F6D652D4D61782D61363836666331323961366638636265"
|
||||
+ "31643636353139343065336164353766C00CC02E00108001000011"
|
||||
+ "9400C02369643D6136383666633132396136663863626531643636"
|
||||
+ "3531393430653361643537662363643D4133304233303032363546"
|
||||
+ "36384341313233353532434639344141353742314613726D3D4335"
|
||||
+ "35393134383530383841313638330576653D3035126D643D476F6F"
|
||||
+ "676C6520486F6D65204D61781269633D2F73657475702F69636F6E"
|
||||
+ "2E706E6710666E3D417474696320737065616B65720863613D3130"
|
||||
+ "3234340473743D320F62733D464138464341363734453537046E66"
|
||||
+ "3D320372733DC02E0021800100000078002D000000001F49246136"
|
||||
+ "3836666331322D396136662D386362652D316436362D3531393430"
|
||||
+ "65336164353766C01DC13F001C8001000000780010200033330000"
|
||||
+ "0000DA6C63FFFE7C74830109018001000000780004C0A801026C6F"
|
||||
+ "63616C0000018001000000780004C0A8010A000001800100000078"
|
||||
+ "0004C0A8010A00000000000000");
|
||||
|
||||
private static final String DUMMY_CAST_SERVICE_NAME = "_googlecast";
|
||||
private static final String[] DUMMY_CAST_SERVICE_TYPE =
|
||||
new String[] {DUMMY_CAST_SERVICE_NAME, "_tcp", "local"};
|
||||
|
||||
private final List<MdnsResponse> responses = new LinkedList<>();
|
||||
|
||||
private final Clock mClock = mock(Clock.class);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, DUMMY_CAST_SERVICE_TYPE);
|
||||
assertNotNull(data);
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||
packet.setSocketAddress(
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
|
||||
responses.clear();
|
||||
int errorCode = decoder.decode(packet, responses);
|
||||
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
|
||||
assertEquals(1, responses.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeWithNullServiceType() {
|
||||
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
|
||||
assertNotNull(data);
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||
packet.setSocketAddress(
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
|
||||
responses.clear();
|
||||
int errorCode = decoder.decode(packet, responses);
|
||||
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
|
||||
assertEquals(2, responses.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeMultipleAnswerPacket() throws IOException {
|
||||
MdnsResponse response = responses.get(0);
|
||||
assertTrue(response.isComplete());
|
||||
|
||||
MdnsInetAddressRecord inet4AddressRecord = response.getInet4AddressRecord();
|
||||
Inet4Address inet4Addr = inet4AddressRecord.getInet4Address();
|
||||
|
||||
assertNotNull(inet4Addr);
|
||||
assertEquals("/192.168.100.104", inet4Addr.toString());
|
||||
|
||||
MdnsServiceRecord serviceRecord = response.getServiceRecord();
|
||||
String serviceName = serviceRecord.getServiceName();
|
||||
assertEquals(DUMMY_CAST_SERVICE_NAME, serviceName);
|
||||
|
||||
String serviceInstanceName = serviceRecord.getServiceInstanceName();
|
||||
assertEquals("Johnny's Chromecast", serviceInstanceName);
|
||||
|
||||
String serviceHost = MdnsRecord.labelsToString(serviceRecord.getServiceHost());
|
||||
assertEquals("Johnny's Chromecast.local", serviceHost);
|
||||
|
||||
int serviceProto = serviceRecord.getServiceProtocol();
|
||||
assertEquals(MdnsServiceRecord.PROTO_TCP, serviceProto);
|
||||
|
||||
int servicePort = serviceRecord.getServicePort();
|
||||
assertEquals(8009, servicePort);
|
||||
|
||||
int servicePriority = serviceRecord.getServicePriority();
|
||||
assertEquals(0, servicePriority);
|
||||
|
||||
int serviceWeight = serviceRecord.getServiceWeight();
|
||||
assertEquals(0, serviceWeight);
|
||||
|
||||
MdnsTextRecord textRecord = response.getTextRecord();
|
||||
List<String> textStrings = textRecord.getStrings();
|
||||
assertEquals(7, textStrings.size());
|
||||
assertEquals("id=970bf547b753fc63c2d2a36bb896aba8", textStrings.get(0));
|
||||
assertEquals("ve=02", textStrings.get(1));
|
||||
assertEquals("md=Chromecast", textStrings.get(2));
|
||||
assertEquals("ic=/setup/icon.png", textStrings.get(3));
|
||||
assertEquals("fn=Johnny's Chromecast", textStrings.get(4));
|
||||
assertEquals("ca=5", textStrings.get(5));
|
||||
assertEquals("st=0", textStrings.get(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeIPv6AnswerPacket() throws IOException {
|
||||
MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, DUMMY_CAST_SERVICE_TYPE);
|
||||
assertNotNull(data6);
|
||||
DatagramPacket packet = new DatagramPacket(data6, data6.length);
|
||||
packet.setSocketAddress(
|
||||
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
|
||||
|
||||
responses.clear();
|
||||
int errorCode = decoder.decode(packet, responses);
|
||||
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
|
||||
|
||||
MdnsResponse response = responses.get(0);
|
||||
assertTrue(response.isComplete());
|
||||
|
||||
MdnsInetAddressRecord inet6AddressRecord = response.getInet6AddressRecord();
|
||||
assertNotNull(inet6AddressRecord);
|
||||
Inet4Address inet4Addr = inet6AddressRecord.getInet4Address();
|
||||
assertNull(inet4Addr);
|
||||
|
||||
Inet6Address inet6Addr = inet6AddressRecord.getInet6Address();
|
||||
assertNotNull(inet6Addr);
|
||||
assertEquals(inet6Addr.getHostAddress(), "2000:3333::da6c:63ff:fe7c:7483");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsComplete() {
|
||||
MdnsResponse response = responses.get(0);
|
||||
assertTrue(response.isComplete());
|
||||
|
||||
response.clearPointerRecords();
|
||||
assertFalse(response.isComplete());
|
||||
|
||||
response = responses.get(0);
|
||||
response.setInet4AddressRecord(null);
|
||||
assertFalse(response.isComplete());
|
||||
|
||||
response = responses.get(0);
|
||||
response.setInet6AddressRecord(null);
|
||||
assertFalse(response.isComplete());
|
||||
|
||||
response = responses.get(0);
|
||||
response.setServiceRecord(null);
|
||||
assertFalse(response.isComplete());
|
||||
|
||||
response = responses.get(0);
|
||||
response.setTextRecord(null);
|
||||
assertFalse(response.isComplete());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.android.net.module.util.HexDump;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// The record test data does not use compressed names (label pointers), since that would require
|
||||
// additional data to populate the label dictionary accordingly.
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsResponseTests {
|
||||
private static final String TAG = "MdnsResponseTests";
|
||||
// MDNS response packet for name "test" with an IPv4 address of 10.1.2.3
|
||||
private static final byte[] dataIn_ipv4_1 = HexDump.hexStringToByteArray(
|
||||
"0474657374000001" + "0001000011940004" + "0A010203");
|
||||
// MDNS response packet for name "tess" with an IPv4 address of 10.1.2.4
|
||||
private static final byte[] dataIn_ipv4_2 = HexDump.hexStringToByteArray(
|
||||
"0474657373000001" + "0001000011940004" + "0A010204");
|
||||
// MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040
|
||||
private static final byte[] dataIn_ipv6_1 = HexDump.hexStringToByteArray(
|
||||
"047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203040");
|
||||
// MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030
|
||||
private static final byte[] dataIn_ipv6_2 = HexDump.hexStringToByteArray(
|
||||
"047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203030");
|
||||
// MDNS response w/name "test" & PTR to foo.bar.quxx
|
||||
private static final byte[] dataIn_ptr_1 = HexDump.hexStringToByteArray(
|
||||
"047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787800");
|
||||
// MDNS response w/name "test" & PTR to foo.bar.quxy
|
||||
private static final byte[] dataIn_ptr_2 = HexDump.hexStringToByteArray(
|
||||
"047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787900");
|
||||
// MDNS response w/name "test" & Service for host foo.bar.quxx
|
||||
private static final byte[] dataIn_service_1 = HexDump.hexStringToByteArray(
|
||||
"0474657374000021"
|
||||
+ "0001000011940014"
|
||||
+ "000100FF1F480366"
|
||||
+ "6F6F036261720471"
|
||||
+ "75787800");
|
||||
// MDNS response w/name "test" & Service for host test
|
||||
private static final byte[] dataIn_service_2 = HexDump.hexStringToByteArray(
|
||||
"0474657374000021" + "000100001194000B" + "000100FF1F480474" + "657374");
|
||||
// MDNS response w/name "test" & the following text strings:
|
||||
// "a=hello there", "b=1234567890", and "xyz=!$$$"
|
||||
private static final byte[] dataIn_text_1 = HexDump.hexStringToByteArray(
|
||||
"0474657374000010"
|
||||
+ "0001000011940024"
|
||||
+ "0D613D68656C6C6F"
|
||||
+ "2074686572650C62"
|
||||
+ "3D31323334353637"
|
||||
+ "3839300878797A3D"
|
||||
+ "21242424");
|
||||
// MDNS response w/name "test" & the following text strings:
|
||||
// "a=hello there", "b=1234567890", and "xyz=!@#$"
|
||||
private static final byte[] dataIn_text_2 = HexDump.hexStringToByteArray(
|
||||
"0474657374000010"
|
||||
+ "0001000011940024"
|
||||
+ "0D613D68656C6C6F"
|
||||
+ "2074686572650C62"
|
||||
+ "3D31323334353637"
|
||||
+ "3839300878797A3D"
|
||||
+ "21402324");
|
||||
|
||||
// The following helper classes act as wrappers so that IPv4 and IPv6 address records can
|
||||
// be explicitly created by type using same constructor signature as all other records.
|
||||
static class MdnsInet4AddressRecord extends MdnsInetAddressRecord {
|
||||
public MdnsInet4AddressRecord(String[] name, MdnsPacketReader reader) throws IOException {
|
||||
super(name, MdnsRecord.TYPE_A, reader);
|
||||
}
|
||||
}
|
||||
|
||||
static class MdnsInet6AddressRecord extends MdnsInetAddressRecord {
|
||||
public MdnsInet6AddressRecord(String[] name, MdnsPacketReader reader) throws IOException {
|
||||
super(name, MdnsRecord.TYPE_AAAA, reader);
|
||||
}
|
||||
}
|
||||
|
||||
// This helper class just wraps the data bytes of a response packet with the contained record
|
||||
// type.
|
||||
// Its only purpose is to make the test code a bit more readable.
|
||||
static class PacketAndRecordClass {
|
||||
public final byte[] packetData;
|
||||
public final Class<?> recordClass;
|
||||
|
||||
public PacketAndRecordClass() {
|
||||
packetData = null;
|
||||
recordClass = null;
|
||||
}
|
||||
|
||||
public PacketAndRecordClass(byte[] data, Class<?> c) {
|
||||
packetData = data;
|
||||
recordClass = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct an MdnsResponse with the specified data packets applied.
|
||||
private MdnsResponse makeMdnsResponse(long time, List<PacketAndRecordClass> responseList)
|
||||
throws IOException {
|
||||
MdnsResponse response = new MdnsResponse(time);
|
||||
for (PacketAndRecordClass responseData : responseList) {
|
||||
DatagramPacket packet =
|
||||
new DatagramPacket(responseData.packetData, responseData.packetData.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
// Apply the right kind of record to the response.
|
||||
if (responseData.recordClass == MdnsInet4AddressRecord.class) {
|
||||
response.setInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
|
||||
} else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
|
||||
response.setInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
|
||||
} else if (responseData.recordClass == MdnsPointerRecord.class) {
|
||||
response.addPointerRecord(new MdnsPointerRecord(name, reader));
|
||||
} else if (responseData.recordClass == MdnsServiceRecord.class) {
|
||||
response.setServiceRecord(new MdnsServiceRecord(name, reader));
|
||||
} else if (responseData.recordClass == MdnsTextRecord.class) {
|
||||
response.setTextRecord(new MdnsTextRecord(name, reader));
|
||||
} else {
|
||||
fail("Unsupported/unexpected MdnsRecord subtype used in test - invalid test!");
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInet4AddressRecord_returnsAddedRecord() throws IOException {
|
||||
DatagramPacket packet = new DatagramPacket(dataIn_ipv4_1, dataIn_ipv4_1.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
|
||||
MdnsResponse response = new MdnsResponse(0);
|
||||
assertFalse(response.hasInet4AddressRecord());
|
||||
assertTrue(response.setInet4AddressRecord(record));
|
||||
assertEquals(response.getInet4AddressRecord(), record);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInet6AddressRecord_returnsAddedRecord() throws IOException {
|
||||
DatagramPacket packet = new DatagramPacket(dataIn_ipv6_1, dataIn_ipv6_1.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
MdnsInetAddressRecord record =
|
||||
new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
|
||||
MdnsResponse response = new MdnsResponse(0);
|
||||
assertFalse(response.hasInet6AddressRecord());
|
||||
assertTrue(response.setInet6AddressRecord(record));
|
||||
assertEquals(response.getInet6AddressRecord(), record);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPointerRecords_returnsAddedRecord() throws IOException {
|
||||
DatagramPacket packet = new DatagramPacket(dataIn_ptr_1, dataIn_ptr_1.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
|
||||
MdnsResponse response = new MdnsResponse(0);
|
||||
assertFalse(response.hasPointerRecords());
|
||||
assertTrue(response.addPointerRecord(record));
|
||||
List<MdnsPointerRecord> recordList = response.getPointerRecords();
|
||||
assertNotNull(recordList);
|
||||
assertEquals(1, recordList.size());
|
||||
assertEquals(record, recordList.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getServiceRecord_returnsAddedRecord() throws IOException {
|
||||
DatagramPacket packet = new DatagramPacket(dataIn_service_1, dataIn_service_1.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
|
||||
MdnsResponse response = new MdnsResponse(0);
|
||||
assertFalse(response.hasServiceRecord());
|
||||
assertTrue(response.setServiceRecord(record));
|
||||
assertEquals(response.getServiceRecord(), record);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTextRecord_returnsAddedRecord() throws IOException {
|
||||
DatagramPacket packet = new DatagramPacket(dataIn_text_1, dataIn_text_1.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
reader.skip(2); // skip record type indication.
|
||||
MdnsTextRecord record = new MdnsTextRecord(name, reader);
|
||||
MdnsResponse response = new MdnsResponse(0);
|
||||
assertFalse(response.hasTextRecord());
|
||||
assertTrue(response.setTextRecord(record));
|
||||
assertEquals(response.getTextRecord(), record);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeRecordsFrom_indicates_change_on_ipv4_address() throws IOException {
|
||||
MdnsResponse response = makeMdnsResponse(
|
||||
0,
|
||||
Arrays.asList(
|
||||
new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class)));
|
||||
// Now create a new response that updates the address.
|
||||
MdnsResponse response2 = makeMdnsResponse(
|
||||
100,
|
||||
Arrays.asList(
|
||||
new PacketAndRecordClass(dataIn_ipv4_2, MdnsInet4AddressRecord.class)));
|
||||
assertTrue(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeRecordsFrom_indicates_change_on_ipv6_address() throws IOException {
|
||||
MdnsResponse response = makeMdnsResponse(
|
||||
0,
|
||||
Arrays.asList(
|
||||
new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class)));
|
||||
// Now create a new response that updates the address.
|
||||
MdnsResponse response2 = makeMdnsResponse(
|
||||
100,
|
||||
Arrays.asList(
|
||||
new PacketAndRecordClass(dataIn_ipv6_2, MdnsInet6AddressRecord.class)));
|
||||
assertTrue(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeRecordsFrom_indicates_change_on_text() throws IOException {
|
||||
MdnsResponse response = makeMdnsResponse(
|
||||
0,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class)));
|
||||
// Now create a new response that updates the address.
|
||||
MdnsResponse response2 = makeMdnsResponse(
|
||||
100,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_text_2, MdnsTextRecord.class)));
|
||||
assertTrue(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeRecordsFrom_indicates_change_on_service() throws IOException {
|
||||
MdnsResponse response = makeMdnsResponse(
|
||||
0,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_service_1, MdnsServiceRecord.class)));
|
||||
// Now create a new response that updates the address.
|
||||
MdnsResponse response2 = makeMdnsResponse(
|
||||
100,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class)));
|
||||
assertTrue(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeRecordsFrom_indicates_change_on_pointer() throws IOException {
|
||||
MdnsResponse response = makeMdnsResponse(
|
||||
0,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class)));
|
||||
// Now create a new response that updates the address.
|
||||
MdnsResponse response2 = makeMdnsResponse(
|
||||
100,
|
||||
Arrays.asList(new PacketAndRecordClass(dataIn_ptr_2, MdnsPointerRecord.class)));
|
||||
assertTrue(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void mergeRecordsFrom_indicates_noChange() throws IOException {
|
||||
//MdnsConfigsFlagsImpl.useReducedMergeRecordUpdateEvents.override(true);
|
||||
List<PacketAndRecordClass> recordList =
|
||||
Arrays.asList(
|
||||
new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class),
|
||||
new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class),
|
||||
new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class),
|
||||
new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class),
|
||||
new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class));
|
||||
// Create a two identical responses.
|
||||
MdnsResponse response = makeMdnsResponse(0, recordList);
|
||||
MdnsResponse response2 = makeMdnsResponse(100, recordList);
|
||||
// Merging should not indicate any change.
|
||||
assertFalse(response.mergeRecordsFrom(response2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
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 android.annotation.NonNull;
|
||||
|
||||
import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Tests for {@link MdnsServiceTypeClient}. */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsServiceTypeClientTests {
|
||||
|
||||
private static final String SERVICE_TYPE = "_googlecast._tcp.local";
|
||||
|
||||
@Mock
|
||||
private MdnsServiceBrowserListener mockListenerOne;
|
||||
@Mock
|
||||
private MdnsServiceBrowserListener mockListenerTwo;
|
||||
@Mock
|
||||
private MdnsPacketWriter mockPacketWriter;
|
||||
@Mock
|
||||
private MdnsSocketClient mockSocketClient;
|
||||
@Captor
|
||||
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
|
||||
|
||||
private final byte[] buf = new byte[10];
|
||||
|
||||
private DatagramPacket[] expectedPackets;
|
||||
private ScheduledFuture<?>[] expectedSendFutures;
|
||||
private FakeExecutor currentThreadExecutor = new FakeExecutor();
|
||||
|
||||
private MdnsServiceTypeClient client;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("DoNotMock")
|
||||
public void setUp() throws IOException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
expectedPackets = new DatagramPacket[16];
|
||||
expectedSendFutures = new ScheduledFuture<?>[16];
|
||||
|
||||
for (int i = 0; i < expectedSendFutures.length; ++i) {
|
||||
expectedPackets[i] = new DatagramPacket(buf, 0, 5);
|
||||
expectedSendFutures[i] = Mockito.mock(ScheduledFuture.class);
|
||||
}
|
||||
when(mockPacketWriter.getPacket(any(SocketAddress.class)))
|
||||
.thenReturn(expectedPackets[0])
|
||||
.thenReturn(expectedPackets[1])
|
||||
.thenReturn(expectedPackets[2])
|
||||
.thenReturn(expectedPackets[3])
|
||||
.thenReturn(expectedPackets[4])
|
||||
.thenReturn(expectedPackets[5])
|
||||
.thenReturn(expectedPackets[6])
|
||||
.thenReturn(expectedPackets[7])
|
||||
.thenReturn(expectedPackets[8])
|
||||
.thenReturn(expectedPackets[9])
|
||||
.thenReturn(expectedPackets[10])
|
||||
.thenReturn(expectedPackets[11])
|
||||
.thenReturn(expectedPackets[12])
|
||||
.thenReturn(expectedPackets[13])
|
||||
.thenReturn(expectedPackets[14])
|
||||
.thenReturn(expectedPackets[15]);
|
||||
|
||||
client =
|
||||
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
|
||||
@Override
|
||||
MdnsPacketWriter createMdnsPacketWriter() {
|
||||
return mockPacketWriter;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_activeScanMode() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// First burst, 3 queries.
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
1, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Second burst will be sent after initialTimeBetweenBurstsMs, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
3, MdnsConfigs.initialTimeBetweenBurstsMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
4, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
5, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Third burst will be sent after initialTimeBetweenBurstsMs * 2, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
6, MdnsConfigs.initialTimeBetweenBurstsMs() * 2, /* expectsUnicastResponse= */
|
||||
false);
|
||||
verifyAndSendQuery(
|
||||
7, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
8, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Forth burst will be sent after initialTimeBetweenBurstsMs * 4, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
9, MdnsConfigs.initialTimeBetweenBurstsMs() * 4, /* expectsUnicastResponse= */
|
||||
false);
|
||||
verifyAndSendQuery(
|
||||
10, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
11, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Fifth burst will be sent after timeBetweenBurstsMs, 3 queries.
|
||||
verifyAndSendQuery(12, MdnsConfigs.timeBetweenBurstsMs(), /* expectsUnicastResponse= */
|
||||
false);
|
||||
verifyAndSendQuery(
|
||||
13, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
14, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
|
||||
// Stop sending packets.
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
verify(expectedSendFutures[15]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_reentry_activeScanMode() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// First burst, first query is sent.
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
|
||||
// After the first query is sent, change the subtypes, and restart.
|
||||
searchOptions =
|
||||
MdnsSearchOptions.newBuilder()
|
||||
.addSubtype("12345")
|
||||
.addSubtype("abcde")
|
||||
.setIsPassiveMode(false)
|
||||
.build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
// The previous scheduled task should be canceled.
|
||||
verify(expectedSendFutures[1]).cancel(true);
|
||||
|
||||
// Queries should continue to be sent.
|
||||
verifyAndSendQuery(1, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
3, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
|
||||
// Stop sending packets.
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
verify(expectedSendFutures[5]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_passiveScanMode() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// First burst, 3 query.
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
1, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Second burst will be sent after timeBetweenBurstsMs, 1 query.
|
||||
verifyAndSendQuery(3, MdnsConfigs.timeBetweenBurstsMs(), /* expectsUnicastResponse= */
|
||||
false);
|
||||
// Third burst will be sent after timeBetweenBurstsMs, 1 query.
|
||||
verifyAndSendQuery(4, MdnsConfigs.timeBetweenBurstsMs(), /* expectsUnicastResponse= */
|
||||
false);
|
||||
|
||||
// Stop sending packets.
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
verify(expectedSendFutures[5]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_reentry_passiveScanMode() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// First burst, first query is sent.
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
|
||||
// After the first query is sent, change the subtypes, and restart.
|
||||
searchOptions =
|
||||
MdnsSearchOptions.newBuilder()
|
||||
.addSubtype("12345")
|
||||
.addSubtype("abcde")
|
||||
.setIsPassiveMode(true)
|
||||
.build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
// The previous scheduled task should be canceled.
|
||||
verify(expectedSendFutures[1]).cancel(true);
|
||||
|
||||
// Queries should continue to be sent.
|
||||
verifyAndSendQuery(1, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
3, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
|
||||
// Stop sending packets.
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
verify(expectedSendFutures[5]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testQueryTaskConfig_alwaysAskForUnicastResponse() {
|
||||
//MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
QueryTaskConfig config =
|
||||
new QueryTaskConfig(searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1);
|
||||
|
||||
// This is the first query. We will ask for unicast response.
|
||||
assertTrue(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, 1);
|
||||
|
||||
// For the rest of queries in this burst, we will NOT ask for unicast response.
|
||||
for (int i = 1; i < MdnsConfigs.queriesPerBurst(); i++) {
|
||||
int oldTransactionId = config.transactionId;
|
||||
config = config.getConfigForNextRun();
|
||||
assertFalse(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, oldTransactionId + 1);
|
||||
}
|
||||
|
||||
// This is the first query of a new burst. We will ask for unicast response.
|
||||
int oldTransactionId = config.transactionId;
|
||||
config = config.getConfigForNextRun();
|
||||
assertTrue(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, oldTransactionId + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryTaskConfig_askForUnicastInFirstQuery() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
QueryTaskConfig config =
|
||||
new QueryTaskConfig(searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1);
|
||||
|
||||
// This is the first query. We will ask for unicast response.
|
||||
assertTrue(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, 1);
|
||||
|
||||
// For the rest of queries in this burst, we will NOT ask for unicast response.
|
||||
for (int i = 1; i < MdnsConfigs.queriesPerBurst(); i++) {
|
||||
int oldTransactionId = config.transactionId;
|
||||
config = config.getConfigForNextRun();
|
||||
assertFalse(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, oldTransactionId + 1);
|
||||
}
|
||||
|
||||
// This is the first query of a new burst. We will NOT ask for unicast response.
|
||||
int oldTransactionId = config.transactionId;
|
||||
config = config.getConfigForNextRun();
|
||||
assertFalse(config.expectUnicastResponse);
|
||||
assertEquals(config.subtypes, searchOptions.getSubtypes());
|
||||
assertEquals(config.transactionId, oldTransactionId + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testIfPreviousTaskIsCanceledWhenNewSessionStarts() {
|
||||
//MdnsConfigsFlagsImpl.useSessionIdToScheduleMdnsTask.override(true);
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
// Change the sutypes and start a new session.
|
||||
searchOptions =
|
||||
MdnsSearchOptions.newBuilder()
|
||||
.addSubtype("12345")
|
||||
.addSubtype("abcde")
|
||||
.setIsPassiveMode(true)
|
||||
.build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the first mdns task is not successful canceled and it gets
|
||||
// executed anyway.
|
||||
firstMdnsTask.run();
|
||||
|
||||
// Although it gets executes, no more task gets scheduled.
|
||||
assertNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testIfPreviousTaskIsCanceledWhenSessionStops() {
|
||||
//MdnsConfigsFlagsImpl.shouldCancelScanTaskWhenFutureIsNull.override(true);
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(true).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
// Change the sutypes and start a new session.
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the first mdns task is not successful canceled and it gets
|
||||
// executed anyway.
|
||||
currentThreadExecutor.getAndClearSubmittedRunnable().run();
|
||||
|
||||
// Although it gets executes, no more task gets scheduled.
|
||||
assertNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processResponse_incompleteResponse() {
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
MdnsResponse response = mock(MdnsResponse.class);
|
||||
when(response.getServiceInstanceName()).thenReturn("service-instance-1");
|
||||
when(response.isComplete()).thenReturn(false);
|
||||
|
||||
client.processResponse(response);
|
||||
|
||||
verify(mockListenerOne, never()).onServiceFound(any(MdnsServiceInfo.class));
|
||||
verify(mockListenerOne, never()).onServiceUpdated(any(MdnsServiceInfo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processIPv4Response_completeResponseForNewServiceInstance() throws Exception {
|
||||
final String ipV4Address = "192.168.1.1";
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
"service-instance-1",
|
||||
ipV4Address,
|
||||
5353,
|
||||
Collections.singletonList("ABCDE"),
|
||||
Collections.emptyMap());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Process a second response with a different port and updated text attributes.
|
||||
MdnsResponse secondResponse =
|
||||
createResponse(
|
||||
"service-instance-1",
|
||||
ipV4Address,
|
||||
5354,
|
||||
Collections.singletonList("ABCDE"),
|
||||
Collections.singletonMap("key", "value"));
|
||||
client.processResponse(secondResponse);
|
||||
|
||||
// Verify onServiceFound was called once for the initial response.
|
||||
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
|
||||
MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
|
||||
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
|
||||
assertEquals(initialServiceInfo.getIpv4Address(), ipV4Address);
|
||||
assertEquals(initialServiceInfo.getPort(), 5353);
|
||||
assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
|
||||
assertNull(initialServiceInfo.getAttributeByKey("key"));
|
||||
|
||||
// Verify onServiceUpdated was called once for the second response.
|
||||
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
|
||||
MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
|
||||
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
|
||||
assertEquals(updatedServiceInfo.getIpv4Address(), ipV4Address);
|
||||
assertEquals(updatedServiceInfo.getPort(), 5354);
|
||||
assertTrue(updatedServiceInfo.hasSubtypes());
|
||||
assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
|
||||
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processIPv6Response_getCorrectServiceInfo() throws Exception {
|
||||
final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
"service-instance-1",
|
||||
ipV6Address,
|
||||
5353,
|
||||
Collections.singletonList("ABCDE"),
|
||||
Collections.emptyMap());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Process a second response with a different port and updated text attributes.
|
||||
MdnsResponse secondResponse =
|
||||
createResponse(
|
||||
"service-instance-1",
|
||||
ipV6Address,
|
||||
5354,
|
||||
Collections.singletonList("ABCDE"),
|
||||
Collections.singletonMap("key", "value"));
|
||||
client.processResponse(secondResponse);
|
||||
|
||||
System.out.println("secondResponses ip"
|
||||
+ secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
|
||||
|
||||
// Verify onServiceFound was called once for the initial response.
|
||||
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
|
||||
MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
|
||||
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
|
||||
assertEquals(initialServiceInfo.getIpv6Address(), ipV6Address);
|
||||
assertEquals(initialServiceInfo.getPort(), 5353);
|
||||
assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
|
||||
assertNull(initialServiceInfo.getAttributeByKey("key"));
|
||||
|
||||
// Verify onServiceUpdated was called once for the second response.
|
||||
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
|
||||
MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
|
||||
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
|
||||
assertEquals(updatedServiceInfo.getIpv6Address(), ipV6Address);
|
||||
assertEquals(updatedServiceInfo.getPort(), 5354);
|
||||
assertTrue(updatedServiceInfo.hasSubtypes());
|
||||
assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
|
||||
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processResponse_goodBye() {
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
MdnsResponse response = mock(MdnsResponse.class);
|
||||
when(response.getServiceInstanceName()).thenReturn("goodbye-service-instance-name");
|
||||
when(response.isGoodbye()).thenReturn(true);
|
||||
client.processResponse(response);
|
||||
|
||||
verify(mockListenerOne).onServiceRemoved("goodbye-service-instance-name");
|
||||
verify(mockListenerTwo).onServiceRemoved("goodbye-service-instance-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reportExistingServiceToNewlyRegisteredListeners() throws UnknownHostException {
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
"service-instance-1",
|
||||
"192.168.1.1",
|
||||
5353,
|
||||
Collections.singletonList("ABCDE"),
|
||||
Collections.emptyMap());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
// Verify onServiceFound was called once for the existing response.
|
||||
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
|
||||
MdnsServiceInfo existingServiceInfo = serviceInfoCaptor.getAllValues().get(0);
|
||||
assertEquals(existingServiceInfo.getServiceInstanceName(), "service-instance-1");
|
||||
assertEquals(existingServiceInfo.getIpv4Address(), "192.168.1.1");
|
||||
assertEquals(existingServiceInfo.getPort(), 5353);
|
||||
assertEquals(existingServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
|
||||
assertNull(existingServiceInfo.getAttributeByKey("key"));
|
||||
|
||||
// Process a goodbye message for the existing response.
|
||||
MdnsResponse goodByeResponse = mock(MdnsResponse.class);
|
||||
when(goodByeResponse.getServiceInstanceName()).thenReturn("service-instance-1");
|
||||
when(goodByeResponse.isGoodbye()).thenReturn(true);
|
||||
client.processResponse(goodByeResponse);
|
||||
|
||||
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
|
||||
// Verify onServiceFound was not called on the newly registered listener after the existing
|
||||
// response is gone.
|
||||
verify(mockListenerTwo, never()).onServiceFound(any(MdnsServiceInfo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processResponse_notAllowRemoveSearch_shouldNotRemove() throws Exception {
|
||||
final String serviceInstanceName = "service-instance-1";
|
||||
client.startSendAndReceive(
|
||||
mockListenerOne,
|
||||
MdnsSearchOptions.newBuilder().build());
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
|
||||
Map.of());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the response is after TTL.
|
||||
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
|
||||
firstMdnsTask.run();
|
||||
|
||||
// Verify onServiceRemoved was not called.
|
||||
verify(mockListenerOne, never()).onServiceRemoved(serviceInstanceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void processResponse_allowSearchOptionsToRemoveExpiredService_shouldRemove()
|
||||
throws Exception {
|
||||
//MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
|
||||
final String serviceInstanceName = "service-instance-1";
|
||||
client =
|
||||
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
|
||||
@Override
|
||||
MdnsPacketWriter createMdnsPacketWriter() {
|
||||
return mockPacketWriter;
|
||||
}
|
||||
};
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
|
||||
Map.of());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the response is under TTL.
|
||||
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
|
||||
firstMdnsTask.run();
|
||||
|
||||
// Verify onServiceRemoved was not called.
|
||||
verify(mockListenerOne, never()).onServiceRemoved(serviceInstanceName);
|
||||
|
||||
// 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(serviceInstanceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processResponse_searchOptionsNotEnableServiceRemoval_shouldNotRemove()
|
||||
throws Exception {
|
||||
final String serviceInstanceName = "service-instance-1";
|
||||
client =
|
||||
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
|
||||
@Override
|
||||
MdnsPacketWriter createMdnsPacketWriter() {
|
||||
return mockPacketWriter;
|
||||
}
|
||||
};
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
|
||||
Map.of());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the response is after TTL.
|
||||
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
|
||||
firstMdnsTask.run();
|
||||
|
||||
// Verify onServiceRemoved was not called.
|
||||
verify(mockListenerOne, never()).onServiceRemoved(serviceInstanceName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void processResponse_removeServiceAfterTtlExpiresEnabled_shouldRemove()
|
||||
throws Exception {
|
||||
//MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
|
||||
final String serviceInstanceName = "service-instance-1";
|
||||
client =
|
||||
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
|
||||
@Override
|
||||
MdnsPacketWriter createMdnsPacketWriter() {
|
||||
return mockPacketWriter;
|
||||
}
|
||||
};
|
||||
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
|
||||
Map.of());
|
||||
client.processResponse(initialResponse);
|
||||
|
||||
// Clear the scheduled runnable.
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable();
|
||||
|
||||
// Simulate the case where the response is after TTL.
|
||||
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
|
||||
firstMdnsTask.run();
|
||||
|
||||
// Verify onServiceRemoved was not called.
|
||||
verify(mockListenerOne, times(1)).onServiceRemoved(serviceInstanceName);
|
||||
}
|
||||
|
||||
// verifies that the right query was enqueued with the right delay, and send query by executing
|
||||
// the runnable.
|
||||
private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse) {
|
||||
assertEquals(currentThreadExecutor.getAndClearLastScheduledDelayInMs(), timeInMs);
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
|
||||
if (expectsUnicastResponse) {
|
||||
verify(mockSocketClient).sendUnicastPacket(expectedPackets[index]);
|
||||
} else {
|
||||
verify(mockSocketClient).sendMulticastPacket(expectedPackets[index]);
|
||||
}
|
||||
}
|
||||
|
||||
// A fake ScheduledExecutorService that keeps tracking the last scheduled Runnable and its delay
|
||||
// time.
|
||||
private class FakeExecutor extends ScheduledThreadPoolExecutor {
|
||||
private long lastScheduledDelayInMs;
|
||||
private Runnable lastScheduledRunnable;
|
||||
private Runnable lastSubmittedRunnable;
|
||||
private int futureIndex;
|
||||
|
||||
FakeExecutor() {
|
||||
super(1);
|
||||
lastScheduledDelayInMs = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable command) {
|
||||
Future<?> future = super.submit(command);
|
||||
lastSubmittedRunnable = command;
|
||||
return future;
|
||||
}
|
||||
|
||||
// Don't call through the real implementation, just track the scheduled Runnable, and
|
||||
// returns a ScheduledFuture.
|
||||
@Override
|
||||
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
||||
lastScheduledDelayInMs = delay;
|
||||
lastScheduledRunnable = command;
|
||||
return expectedSendFutures[futureIndex++];
|
||||
}
|
||||
|
||||
// Returns the delay of the last scheduled task, and clear it.
|
||||
long getAndClearLastScheduledDelayInMs() {
|
||||
long val = lastScheduledDelayInMs;
|
||||
lastScheduledDelayInMs = -1;
|
||||
return val;
|
||||
}
|
||||
|
||||
// Returns the last scheduled task, and clear it.
|
||||
Runnable getAndClearLastScheduledRunnable() {
|
||||
Runnable val = lastScheduledRunnable;
|
||||
lastScheduledRunnable = null;
|
||||
return val;
|
||||
}
|
||||
|
||||
Runnable getAndClearSubmittedRunnable() {
|
||||
Runnable val = lastSubmittedRunnable;
|
||||
lastSubmittedRunnable = null;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a complete mDNS response.
|
||||
private MdnsResponse createResponse(
|
||||
@NonNull String serviceInstanceName,
|
||||
@NonNull String host,
|
||||
int port,
|
||||
@NonNull List<String> subtypes,
|
||||
@NonNull Map<String, String> textAttributes)
|
||||
throws UnknownHostException {
|
||||
String[] hostName = new String[]{"hostname"};
|
||||
MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
|
||||
when(serviceRecord.getServiceHost()).thenReturn(hostName);
|
||||
when(serviceRecord.getServicePort()).thenReturn(port);
|
||||
|
||||
MdnsResponse response = spy(new MdnsResponse(0));
|
||||
|
||||
MdnsInetAddressRecord inetAddressRecord = mock(MdnsInetAddressRecord.class);
|
||||
if (host.contains(":")) {
|
||||
when(inetAddressRecord.getInet6Address())
|
||||
.thenReturn((Inet6Address) Inet6Address.getByName(host));
|
||||
response.setInet6AddressRecord(inetAddressRecord);
|
||||
} else {
|
||||
when(inetAddressRecord.getInet4Address())
|
||||
.thenReturn((Inet4Address) Inet4Address.getByName(host));
|
||||
response.setInet4AddressRecord(inetAddressRecord);
|
||||
}
|
||||
|
||||
MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
|
||||
List<String> textStrings = new ArrayList<>();
|
||||
for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
|
||||
textStrings.add(kv.getKey() + "=" + kv.getValue());
|
||||
}
|
||||
when(textRecord.getStrings()).thenReturn(textStrings);
|
||||
|
||||
response.setServiceRecord(serviceRecord);
|
||||
response.setTextRecord(textRecord);
|
||||
|
||||
doReturn(false).when(response).isGoodbye();
|
||||
doReturn(true).when(response).isComplete();
|
||||
doReturn(serviceInstanceName).when(response).getServiceInstanceName();
|
||||
doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiManager.MulticastLock;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import com.android.net.module.util.HexDump;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Tests for {@link MdnsSocketClient} */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsSocketClientTests {
|
||||
private static final long TIMEOUT = 500;
|
||||
private final byte[] buf = new byte[10];
|
||||
final AtomicBoolean enableMulticastResponse = new AtomicBoolean(true);
|
||||
final AtomicBoolean enableUnicastResponse = new AtomicBoolean(true);
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private WifiManager mockWifiManager;
|
||||
@Mock private MdnsSocket mockMulticastSocket;
|
||||
@Mock private MdnsSocket mockUnicastSocket;
|
||||
@Mock private MulticastLock mockMulticastLock;
|
||||
@Mock private MdnsSocketClient.Callback mockCallback;
|
||||
|
||||
private MdnsSocketClient mdnsClient;
|
||||
|
||||
@Before
|
||||
public void setup() throws RuntimeException, IOException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
|
||||
.thenReturn(mockMulticastLock);
|
||||
|
||||
mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
|
||||
@Override
|
||||
MdnsSocket createMdnsSocket(int port) throws IOException {
|
||||
if (port == MdnsConstants.MDNS_PORT) {
|
||||
return mockMulticastSocket;
|
||||
}
|
||||
return mockUnicastSocket;
|
||||
}
|
||||
};
|
||||
mdnsClient.setCallback(mockCallback);
|
||||
|
||||
doAnswer(
|
||||
(InvocationOnMock invocationOnMock) -> {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0000840000000004"
|
||||
+ "00000003134A6F68"
|
||||
+ "6E6E792773204368"
|
||||
+ "726F6D6563617374"
|
||||
+ "0B5F676F6F676C65"
|
||||
+ "63617374045F7463"
|
||||
+ "70056C6F63616C00"
|
||||
+ "0010800100001194"
|
||||
+ "006C2369643D3937"
|
||||
+ "3062663534376237"
|
||||
+ "3533666336336332"
|
||||
+ "6432613336626238"
|
||||
+ "3936616261380576"
|
||||
+ "653D30320D6D643D"
|
||||
+ "4368726F6D656361"
|
||||
+ "73741269633D2F73"
|
||||
+ "657475702F69636F"
|
||||
+ "6E2E706E6716666E"
|
||||
+ "3D4A6F686E6E7927"
|
||||
+ "73204368726F6D65"
|
||||
+ "636173740463613D"
|
||||
+ "350473743D30095F"
|
||||
+ "7365727669636573"
|
||||
+ "075F646E732D7364"
|
||||
+ "045F756470C03100"
|
||||
+ "0C00010000119400"
|
||||
+ "02C020C020000C00"
|
||||
+ "01000011940002C0"
|
||||
+ "0CC00C0021800100"
|
||||
+ "000078001C000000"
|
||||
+ "001F49134A6F686E"
|
||||
+ "6E79277320436872"
|
||||
+ "6F6D6563617374C0"
|
||||
+ "31C0F30001800100"
|
||||
+ "0000780004C0A864"
|
||||
+ "68C0F3002F800100"
|
||||
+ "0000780005C0F300"
|
||||
+ "0140C00C002F8001"
|
||||
+ "000011940009C00C"
|
||||
+ "00050000800040");
|
||||
if (enableMulticastResponse.get()) {
|
||||
DatagramPacket packet = invocationOnMock.getArgument(0);
|
||||
packet.setData(dataIn);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.when(mockMulticastSocket)
|
||||
.receive(any(DatagramPacket.class));
|
||||
doAnswer(
|
||||
(InvocationOnMock invocationOnMock) -> {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0000840000000004"
|
||||
+ "00000003134A6F68"
|
||||
+ "6E6E792773204368"
|
||||
+ "726F6D6563617374"
|
||||
+ "0B5F676F6F676C65"
|
||||
+ "63617374045F7463"
|
||||
+ "70056C6F63616C00"
|
||||
+ "0010800100001194"
|
||||
+ "006C2369643D3937"
|
||||
+ "3062663534376237"
|
||||
+ "3533666336336332"
|
||||
+ "6432613336626238"
|
||||
+ "3936616261380576"
|
||||
+ "653D30320D6D643D"
|
||||
+ "4368726F6D656361"
|
||||
+ "73741269633D2F73"
|
||||
+ "657475702F69636F"
|
||||
+ "6E2E706E6716666E"
|
||||
+ "3D4A6F686E6E7927"
|
||||
+ "73204368726F6D65"
|
||||
+ "636173740463613D"
|
||||
+ "350473743D30095F"
|
||||
+ "7365727669636573"
|
||||
+ "075F646E732D7364"
|
||||
+ "045F756470C03100"
|
||||
+ "0C00010000119400"
|
||||
+ "02C020C020000C00"
|
||||
+ "01000011940002C0"
|
||||
+ "0CC00C0021800100"
|
||||
+ "000078001C000000"
|
||||
+ "001F49134A6F686E"
|
||||
+ "6E79277320436872"
|
||||
+ "6F6D6563617374C0"
|
||||
+ "31C0F30001800100"
|
||||
+ "0000780004C0A864"
|
||||
+ "68C0F3002F800100"
|
||||
+ "0000780005C0F300"
|
||||
+ "0140C00C002F8001"
|
||||
+ "000011940009C00C"
|
||||
+ "00050000800040");
|
||||
if (enableUnicastResponse.get()) {
|
||||
DatagramPacket packet = invocationOnMock.getArgument(0);
|
||||
packet.setData(dataIn);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.when(mockUnicastSocket)
|
||||
.receive(any(DatagramPacket.class));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mdnsClient.stopDiscovery();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testSendPackets_useSeparateSocketForUnicast()
|
||||
throws InterruptedException, IOException {
|
||||
//MdnsConfigsFlagsImpl.useSeparateSocketToSendUnicastQuery.override(true);
|
||||
//MdnsConfigsFlagsImpl.checkMulticastResponse.override(true);
|
||||
//MdnsConfigsFlagsImpl.checkMulticastResponseIntervalMs
|
||||
// .override(DateUtils.SECOND_IN_MILLIS);
|
||||
mdnsClient.startDiscovery();
|
||||
Thread multicastReceiverThread = mdnsClient.multicastReceiveThread;
|
||||
Thread unicastReceiverThread = mdnsClient.unicastReceiveThread;
|
||||
Thread sendThread = mdnsClient.sendThread;
|
||||
|
||||
assertTrue(multicastReceiverThread.isAlive());
|
||||
assertTrue(sendThread.isAlive());
|
||||
assertTrue(unicastReceiverThread.isAlive());
|
||||
|
||||
// Sends a packet.
|
||||
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
|
||||
mdnsClient.sendMulticastPacket(packet);
|
||||
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
|
||||
// it may not be called yet. So timeout is added.
|
||||
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
|
||||
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
|
||||
|
||||
// Verify the packet is sent by the unicast socket.
|
||||
mdnsClient.sendUnicastPacket(packet);
|
||||
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
|
||||
verify(mockUnicastSocket, timeout(TIMEOUT).times(1)).send(packet);
|
||||
|
||||
// Stop the MdnsClient, and ensure that it stops in a reasonable amount of time.
|
||||
// Run part of the test logic in a background thread, in case stopDiscovery() blocks
|
||||
// for a long time (the foreground thread can fail the test early).
|
||||
final CountDownLatch stopDiscoveryLatch = new CountDownLatch(1);
|
||||
Thread testThread =
|
||||
new Thread(
|
||||
new Runnable() {
|
||||
@RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
|
||||
@Override
|
||||
public void run() {
|
||||
mdnsClient.stopDiscovery();
|
||||
stopDiscoveryLatch.countDown();
|
||||
}
|
||||
});
|
||||
testThread.start();
|
||||
assertTrue(stopDiscoveryLatch.await(DateUtils.SECOND_IN_MILLIS, TimeUnit.MILLISECONDS));
|
||||
|
||||
// We should be able to join in a reasonable amount of time, to prove that the
|
||||
// the MdnsClient exited without sending the large queue of packets.
|
||||
testThread.join(DateUtils.SECOND_IN_MILLIS);
|
||||
|
||||
assertFalse(multicastReceiverThread.isAlive());
|
||||
assertFalse(sendThread.isAlive());
|
||||
assertFalse(unicastReceiverThread.isAlive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendPackets_useSameSocketForMulticastAndUnicast()
|
||||
throws InterruptedException, IOException {
|
||||
mdnsClient.startDiscovery();
|
||||
Thread multicastReceiverThread = mdnsClient.multicastReceiveThread;
|
||||
Thread unicastReceiverThread = mdnsClient.unicastReceiveThread;
|
||||
Thread sendThread = mdnsClient.sendThread;
|
||||
|
||||
assertTrue(multicastReceiverThread.isAlive());
|
||||
assertTrue(sendThread.isAlive());
|
||||
assertNull(unicastReceiverThread);
|
||||
|
||||
// Sends a packet.
|
||||
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
|
||||
mdnsClient.sendMulticastPacket(packet);
|
||||
// mockMulticastSocket.send() will be called on another thread. If we verify it immediately,
|
||||
// it may not be called yet. So timeout is added.
|
||||
verify(mockMulticastSocket, timeout(TIMEOUT).times(1)).send(packet);
|
||||
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
|
||||
|
||||
// Verify the packet is sent by the multicast socket as well.
|
||||
mdnsClient.sendUnicastPacket(packet);
|
||||
verify(mockMulticastSocket, timeout(TIMEOUT).times(2)).send(packet);
|
||||
verify(mockUnicastSocket, timeout(TIMEOUT).times(0)).send(packet);
|
||||
|
||||
// Stop the MdnsClient, and ensure that it stops in a reasonable amount of time.
|
||||
// Run part of the test logic in a background thread, in case stopDiscovery() blocks
|
||||
// for a long time (the foreground thread can fail the test early).
|
||||
final CountDownLatch stopDiscoveryLatch = new CountDownLatch(1);
|
||||
Thread testThread =
|
||||
new Thread(
|
||||
new Runnable() {
|
||||
@RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
|
||||
@Override
|
||||
public void run() {
|
||||
mdnsClient.stopDiscovery();
|
||||
stopDiscoveryLatch.countDown();
|
||||
}
|
||||
});
|
||||
testThread.start();
|
||||
assertTrue(stopDiscoveryLatch.await(DateUtils.SECOND_IN_MILLIS, TimeUnit.MILLISECONDS));
|
||||
|
||||
// We should be able to join in a reasonable amount of time, to prove that the
|
||||
// the MdnsClient exited without sending the large queue of packets.
|
||||
testThread.join(DateUtils.SECOND_IN_MILLIS);
|
||||
|
||||
assertFalse(multicastReceiverThread.isAlive());
|
||||
assertFalse(sendThread.isAlive());
|
||||
assertNull(unicastReceiverThread);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStop() throws IOException {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
Thread multicastReceiverThread = mdnsClient.multicastReceiveThread;
|
||||
Thread socketThread = mdnsClient.sendThread;
|
||||
|
||||
assertTrue(multicastReceiverThread.isAlive());
|
||||
assertTrue(socketThread.isAlive());
|
||||
|
||||
mdnsClient.stopDiscovery();
|
||||
|
||||
assertFalse(multicastReceiverThread.isAlive());
|
||||
assertFalse(socketThread.isAlive());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopDiscovery_queueIsCleared() throws IOException {
|
||||
mdnsClient.startDiscovery();
|
||||
mdnsClient.stopDiscovery();
|
||||
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
|
||||
|
||||
synchronized (mdnsClient.multicastPacketQueue) {
|
||||
assertTrue(mdnsClient.multicastPacketQueue.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendPacket_afterDiscoveryStops() throws IOException {
|
||||
mdnsClient.startDiscovery();
|
||||
mdnsClient.stopDiscovery();
|
||||
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
|
||||
|
||||
synchronized (mdnsClient.multicastPacketQueue) {
|
||||
assertTrue(mdnsClient.multicastPacketQueue.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testSendPacket_queueReachesSizeLimit() throws IOException {
|
||||
//MdnsConfigsFlagsImpl.mdnsPacketQueueMaxSize.override(2L);
|
||||
mdnsClient.startDiscovery();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
mdnsClient.sendMulticastPacket(new DatagramPacket(buf, 0, 5));
|
||||
}
|
||||
|
||||
synchronized (mdnsClient.multicastPacketQueue) {
|
||||
assertTrue(mdnsClient.multicastPacketQueue.size() <= 2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMulticastResponseReceived_useSeparateSocketForUnicast() throws IOException {
|
||||
mdnsClient.setCallback(mockCallback);
|
||||
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
verify(mockCallback, timeout(TIMEOUT).atLeast(1))
|
||||
.onResponseReceived(any(MdnsResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMulticastResponseReceived_useSameSocketForMulticastAndUnicast()
|
||||
throws Exception {
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
|
||||
.onResponseReceived(any(MdnsResponse.class));
|
||||
|
||||
mdnsClient.stopDiscovery();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedToParseMdnsResponse_useSeparateSocketForUnicast() throws IOException {
|
||||
mdnsClient.setCallback(mockCallback);
|
||||
|
||||
// Both multicast socket and unicast socket receive malformed responses.
|
||||
byte[] dataIn = HexDump.hexStringToByteArray("0000840000000004");
|
||||
doAnswer(
|
||||
(InvocationOnMock invocationOnMock) -> {
|
||||
// Malformed data.
|
||||
DatagramPacket packet = invocationOnMock.getArgument(0);
|
||||
packet.setData(dataIn);
|
||||
return null;
|
||||
})
|
||||
.when(mockMulticastSocket)
|
||||
.receive(any(DatagramPacket.class));
|
||||
doAnswer(
|
||||
(InvocationOnMock invocationOnMock) -> {
|
||||
// Malformed data.
|
||||
DatagramPacket packet = invocationOnMock.getArgument(0);
|
||||
packet.setData(dataIn);
|
||||
return null;
|
||||
})
|
||||
.when(mockUnicastSocket)
|
||||
.receive(any(DatagramPacket.class));
|
||||
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
verify(mockCallback, timeout(TIMEOUT).atLeast(1))
|
||||
.onFailedToParseMdnsResponse(anyInt(), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE));
|
||||
|
||||
mdnsClient.stopDiscovery();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedToParseMdnsResponse_useSameSocketForMulticastAndUnicast()
|
||||
throws IOException {
|
||||
doAnswer(
|
||||
(InvocationOnMock invocationOnMock) -> {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray("0000840000000004");
|
||||
DatagramPacket packet = invocationOnMock.getArgument(0);
|
||||
packet.setData(dataIn);
|
||||
return null;
|
||||
})
|
||||
.when(mockMulticastSocket)
|
||||
.receive(any(DatagramPacket.class));
|
||||
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
verify(mockCallback, timeout(TIMEOUT).atLeast(1))
|
||||
.onFailedToParseMdnsResponse(1, MdnsResponseErrorCode.ERROR_END_OF_FILE);
|
||||
|
||||
mdnsClient.stopDiscovery();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("MdnsConfigs is not configurable currently.")
|
||||
public void testMulticastResponseIsNotReceived() throws IOException, InterruptedException {
|
||||
//MdnsConfigsFlagsImpl.checkMulticastResponse.override(true);
|
||||
//MdnsConfigsFlagsImpl.checkMulticastResponseIntervalMs
|
||||
// .override(DateUtils.SECOND_IN_MILLIS);
|
||||
//MdnsConfigsFlagsImpl.useSeparateSocketToSendUnicastQuery.override(true);
|
||||
enableMulticastResponse.set(false);
|
||||
enableUnicastResponse.set(true);
|
||||
|
||||
mdnsClient.startDiscovery();
|
||||
DatagramPacket packet = new DatagramPacket(buf, 0, 5);
|
||||
mdnsClient.sendUnicastPacket(packet);
|
||||
mdnsClient.sendMulticastPacket(packet);
|
||||
|
||||
// Wait for the timer to be triggered.
|
||||
Thread.sleep(MdnsConfigs.checkMulticastResponseIntervalMs() * 2);
|
||||
|
||||
assertFalse(mdnsClient.receivedMulticastResponse);
|
||||
assertTrue(mdnsClient.receivedUnicastResponse);
|
||||
assertTrue(mdnsClient.cannotReceiveMulticastResponse.get());
|
||||
|
||||
// Allow multicast response and verify the states again.
|
||||
enableMulticastResponse.set(true);
|
||||
Thread.sleep(DateUtils.SECOND_IN_MILLIS);
|
||||
|
||||
// Verify cannotReceiveMulticastResponse is reset to false.
|
||||
assertTrue(mdnsClient.receivedMulticastResponse);
|
||||
assertTrue(mdnsClient.receivedUnicastResponse);
|
||||
assertFalse(mdnsClient.cannotReceiveMulticastResponse.get());
|
||||
|
||||
// Stop the discovery and start a new session. Don't respond the unicsat query either in
|
||||
// this session.
|
||||
enableMulticastResponse.set(false);
|
||||
enableUnicastResponse.set(false);
|
||||
mdnsClient.stopDiscovery();
|
||||
mdnsClient.startDiscovery();
|
||||
|
||||
// Verify the states are reset.
|
||||
assertFalse(mdnsClient.receivedMulticastResponse);
|
||||
assertFalse(mdnsClient.receivedUnicastResponse);
|
||||
assertFalse(mdnsClient.cannotReceiveMulticastResponse.get());
|
||||
|
||||
mdnsClient.sendUnicastPacket(packet);
|
||||
mdnsClient.sendMulticastPacket(packet);
|
||||
Thread.sleep(MdnsConfigs.checkMulticastResponseIntervalMs() * 2);
|
||||
|
||||
// Verify cannotReceiveMulticastResponse is not set the true because we didn't receive the
|
||||
// unicast response either. This is expected for users who don't have any cast device.
|
||||
assertFalse(mdnsClient.receivedMulticastResponse);
|
||||
assertFalse(mdnsClient.receivedUnicastResponse);
|
||||
assertFalse(mdnsClient.cannotReceiveMulticastResponse.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
|
||||
/** Tests for {@link MdnsSocket}. */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsSocketTests {
|
||||
|
||||
@Mock private NetworkInterfaceWrapper mockNetworkInterfaceWrapper;
|
||||
@Mock private MulticastSocket mockMulticastSocket;
|
||||
@Mock private MulticastNetworkInterfaceProvider mockMulticastNetworkInterfaceProvider;
|
||||
private SocketAddress socketIPv4Address;
|
||||
private SocketAddress socketIPv6Address;
|
||||
|
||||
private byte[] data = new byte[25];
|
||||
private final DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
|
||||
private NetworkInterface networkInterface;
|
||||
|
||||
private MdnsSocket mdnsSocket;
|
||||
|
||||
@Before
|
||||
public void setUp() throws SocketException, UnknownHostException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
networkInterface = createEmptyNetworkInterface();
|
||||
when(mockNetworkInterfaceWrapper.getNetworkInterface()).thenReturn(networkInterface);
|
||||
when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
|
||||
.thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
|
||||
socketIPv4Address = new InetSocketAddress(
|
||||
InetAddress.getByName("224.0.0.251"), MdnsConstants.MDNS_PORT);
|
||||
socketIPv6Address = new InetSocketAddress(
|
||||
InetAddress.getByName("FF02::FB"), MdnsConstants.MDNS_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMdnsSocket() throws IOException {
|
||||
mdnsSocket =
|
||||
new MdnsSocket(mockMulticastNetworkInterfaceProvider, MdnsConstants.MDNS_PORT) {
|
||||
@Override
|
||||
MulticastSocket createMulticastSocket(int port) throws IOException {
|
||||
return mockMulticastSocket;
|
||||
}
|
||||
};
|
||||
mdnsSocket.send(datagramPacket);
|
||||
verify(mockMulticastSocket).setNetworkInterface(networkInterface);
|
||||
verify(mockMulticastSocket).send(datagramPacket);
|
||||
|
||||
mdnsSocket.receive(datagramPacket);
|
||||
verify(mockMulticastSocket).receive(datagramPacket);
|
||||
|
||||
mdnsSocket.joinGroup();
|
||||
verify(mockMulticastSocket).joinGroup(socketIPv4Address, networkInterface);
|
||||
|
||||
mdnsSocket.leaveGroup();
|
||||
verify(mockMulticastSocket).leaveGroup(socketIPv4Address, networkInterface);
|
||||
|
||||
mdnsSocket.close();
|
||||
verify(mockMulticastSocket).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6OnlyNetwork_IPv6Enabled() throws IOException {
|
||||
// Have mockMulticastNetworkInterfaceProvider send back an IPv6Only networkInterfaceWrapper
|
||||
networkInterface = createEmptyNetworkInterface();
|
||||
when(mockNetworkInterfaceWrapper.getNetworkInterface()).thenReturn(networkInterface);
|
||||
when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
|
||||
.thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
|
||||
|
||||
mdnsSocket =
|
||||
new MdnsSocket(mockMulticastNetworkInterfaceProvider, MdnsConstants.MDNS_PORT) {
|
||||
@Override
|
||||
MulticastSocket createMulticastSocket(int port) throws IOException {
|
||||
return mockMulticastSocket;
|
||||
}
|
||||
};
|
||||
|
||||
when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
|
||||
Collections.singletonList(mockNetworkInterfaceWrapper)))
|
||||
.thenReturn(true);
|
||||
|
||||
mdnsSocket.joinGroup();
|
||||
verify(mockMulticastSocket).joinGroup(socketIPv6Address, networkInterface);
|
||||
|
||||
mdnsSocket.leaveGroup();
|
||||
verify(mockMulticastSocket).leaveGroup(socketIPv6Address, networkInterface);
|
||||
|
||||
mdnsSocket.close();
|
||||
verify(mockMulticastSocket).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIPv6OnlyNetwork_IPv6Toggle() throws IOException {
|
||||
// Have mockMulticastNetworkInterfaceProvider send back a networkInterfaceWrapper
|
||||
networkInterface = createEmptyNetworkInterface();
|
||||
when(mockNetworkInterfaceWrapper.getNetworkInterface()).thenReturn(networkInterface);
|
||||
when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
|
||||
.thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
|
||||
|
||||
mdnsSocket =
|
||||
new MdnsSocket(mockMulticastNetworkInterfaceProvider, MdnsConstants.MDNS_PORT) {
|
||||
@Override
|
||||
MulticastSocket createMulticastSocket(int port) throws IOException {
|
||||
return mockMulticastSocket;
|
||||
}
|
||||
};
|
||||
|
||||
when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
|
||||
Collections.singletonList(mockNetworkInterfaceWrapper)))
|
||||
.thenReturn(true);
|
||||
|
||||
mdnsSocket.joinGroup();
|
||||
verify(mockMulticastSocket).joinGroup(socketIPv6Address, networkInterface);
|
||||
|
||||
mdnsSocket.leaveGroup();
|
||||
verify(mockMulticastSocket).leaveGroup(socketIPv6Address, networkInterface);
|
||||
|
||||
mdnsSocket.close();
|
||||
verify(mockMulticastSocket).close();
|
||||
}
|
||||
|
||||
private NetworkInterface createEmptyNetworkInterface() {
|
||||
try {
|
||||
Constructor<NetworkInterface> constructor =
|
||||
NetworkInterface.class.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Tests for {@link MulticastNetworkInterfaceProvider}. */
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MulticastNetworkInterfaceProviderTests {
|
||||
|
||||
@Mock private NetworkInterfaceWrapper loopbackInterface;
|
||||
@Mock private NetworkInterfaceWrapper pointToPointInterface;
|
||||
@Mock private NetworkInterfaceWrapper virtualInterface;
|
||||
@Mock private NetworkInterfaceWrapper inactiveMulticastInterface;
|
||||
@Mock private NetworkInterfaceWrapper activeIpv6MulticastInterface;
|
||||
@Mock private NetworkInterfaceWrapper activeIpv6MulticastInterfaceTwo;
|
||||
@Mock private NetworkInterfaceWrapper nonMulticastInterface;
|
||||
@Mock private NetworkInterfaceWrapper multicastInterfaceOne;
|
||||
@Mock private NetworkInterfaceWrapper multicastInterfaceTwo;
|
||||
|
||||
private final List<NetworkInterfaceWrapper> networkInterfaces = new ArrayList<>();
|
||||
private MulticastNetworkInterfaceProvider provider;
|
||||
private Context context;
|
||||
|
||||
@Before
|
||||
public void setUp() throws SocketException, UnknownHostException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
context = InstrumentationRegistry.getContext();
|
||||
|
||||
setupNetworkInterface(
|
||||
loopbackInterface,
|
||||
true /* isUp */,
|
||||
true /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
pointToPointInterface,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
true /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
virtualInterface,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
true /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
inactiveMulticastInterface,
|
||||
false /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
nonMulticastInterface,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
false /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
activeIpv6MulticastInterface,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
true /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
activeIpv6MulticastInterfaceTwo,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
true /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
multicastInterfaceOne,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
setupNetworkInterface(
|
||||
multicastInterfaceTwo,
|
||||
true /* isUp */,
|
||||
false /* isLoopBack */,
|
||||
false /* isPointToPoint */,
|
||||
false /* isVirtual */,
|
||||
true /* supportsMulticast */,
|
||||
false /* isIpv6 */);
|
||||
|
||||
provider =
|
||||
new MulticastNetworkInterfaceProvider(context) {
|
||||
@Override
|
||||
List<NetworkInterfaceWrapper> getNetworkInterfaces() {
|
||||
return networkInterfaces;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMulticastNetworkInterfaces() {
|
||||
// getNetworkInterfaces returns 1 multicast interface and 5 interfaces that can not be used
|
||||
// to send and receive multicast packets.
|
||||
networkInterfaces.add(loopbackInterface);
|
||||
networkInterfaces.add(pointToPointInterface);
|
||||
networkInterfaces.add(virtualInterface);
|
||||
networkInterfaces.add(inactiveMulticastInterface);
|
||||
networkInterfaces.add(nonMulticastInterface);
|
||||
networkInterfaces.add(multicastInterfaceOne);
|
||||
|
||||
assertEquals(Collections.singletonList(multicastInterfaceOne),
|
||||
provider.getMulticastNetworkInterfaces());
|
||||
|
||||
// getNetworkInterfaces returns 2 multicast interfaces after a connectivity change.
|
||||
networkInterfaces.clear();
|
||||
networkInterfaces.add(multicastInterfaceOne);
|
||||
networkInterfaces.add(multicastInterfaceTwo);
|
||||
|
||||
provider.connectivityMonitor.notifyConnectivityChange();
|
||||
|
||||
assertEquals(networkInterfaces, provider.getMulticastNetworkInterfaces());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartWatchingConnectivityChanges() {
|
||||
ConnectivityMonitor mockMonitor = mock(ConnectivityMonitor.class);
|
||||
provider.connectivityMonitor = mockMonitor;
|
||||
|
||||
InOrder inOrder = inOrder(mockMonitor);
|
||||
|
||||
provider.startWatchingConnectivityChanges();
|
||||
inOrder.verify(mockMonitor).startWatchingConnectivityChanges();
|
||||
|
||||
provider.stopWatchingConnectivityChanges();
|
||||
inOrder.verify(mockMonitor).stopWatchingConnectivityChanges();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpV6OnlyNetwork_EmptyNetwork() {
|
||||
// getNetworkInterfaces returns no network interfaces.
|
||||
assertFalse(provider.isOnIpV6OnlyNetwork(networkInterfaces));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpV6OnlyNetwork_IPv4Only() {
|
||||
// getNetworkInterfaces returns two IPv4 network interface.
|
||||
networkInterfaces.add(multicastInterfaceOne);
|
||||
networkInterfaces.add(multicastInterfaceTwo);
|
||||
assertFalse(provider.isOnIpV6OnlyNetwork(networkInterfaces));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpV6OnlyNetwork_MixedNetwork() {
|
||||
// getNetworkInterfaces returns one IPv6 network interface.
|
||||
networkInterfaces.add(activeIpv6MulticastInterface);
|
||||
networkInterfaces.add(multicastInterfaceOne);
|
||||
networkInterfaces.add(activeIpv6MulticastInterfaceTwo);
|
||||
networkInterfaces.add(multicastInterfaceTwo);
|
||||
assertFalse(provider.isOnIpV6OnlyNetwork(networkInterfaces));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpV6OnlyNetwork_IPv6Only() {
|
||||
// getNetworkInterfaces returns one IPv6 network interface.
|
||||
networkInterfaces.add(activeIpv6MulticastInterface);
|
||||
networkInterfaces.add(activeIpv6MulticastInterfaceTwo);
|
||||
assertTrue(provider.isOnIpV6OnlyNetwork(networkInterfaces));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpV6OnlyNetwork_IPv6Enabled() {
|
||||
// getNetworkInterfaces returns one IPv6 network interface.
|
||||
networkInterfaces.add(activeIpv6MulticastInterface);
|
||||
assertTrue(provider.isOnIpV6OnlyNetwork(networkInterfaces));
|
||||
|
||||
final List<NetworkInterfaceWrapper> interfaces = provider.getMulticastNetworkInterfaces();
|
||||
assertEquals(Collections.singletonList(activeIpv6MulticastInterface), interfaces);
|
||||
}
|
||||
|
||||
private void setupNetworkInterface(
|
||||
@NonNull NetworkInterfaceWrapper networkInterfaceWrapper,
|
||||
boolean isUp,
|
||||
boolean isLoopback,
|
||||
boolean isPointToPoint,
|
||||
boolean isVirtual,
|
||||
boolean supportsMulticast,
|
||||
boolean isIpv6)
|
||||
throws SocketException, UnknownHostException {
|
||||
when(networkInterfaceWrapper.isUp()).thenReturn(isUp);
|
||||
when(networkInterfaceWrapper.isLoopback()).thenReturn(isLoopback);
|
||||
when(networkInterfaceWrapper.isPointToPoint()).thenReturn(isPointToPoint);
|
||||
when(networkInterfaceWrapper.isVirtual()).thenReturn(isVirtual);
|
||||
when(networkInterfaceWrapper.supportsMulticast()).thenReturn(supportsMulticast);
|
||||
if (isIpv6) {
|
||||
InterfaceAddress interfaceAddress = mock(InterfaceAddress.class);
|
||||
InetAddress ip6Address = Inet6Address.getByName("2001:4860:0:1001::68");
|
||||
when(interfaceAddress.getAddress()).thenReturn(ip6Address);
|
||||
when(networkInterfaceWrapper.getInterfaceAddresses())
|
||||
.thenReturn(Collections.singletonList(interfaceAddress));
|
||||
} else {
|
||||
Inet4Address ip = (Inet4Address) Inet4Address.getByName("192.168.0.1");
|
||||
InterfaceAddress interfaceAddress = mock(InterfaceAddress.class);
|
||||
when(interfaceAddress.getAddress()).thenReturn(ip);
|
||||
when(networkInterfaceWrapper.getInterfaceAddresses())
|
||||
.thenReturn(Collections.singletonList(interfaceAddress));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user