Merge "[Thread] add the "vgh" TXT entry" into main am: 7ff092e3d1

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3109107

Change-Id: I08858370a5fe7fc00519efc79180bbdc8d3fdc3d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot 2024-06-12 18:27:23 +00:00 • committed by Automerger Merge Worker
commit a00548da3d
7 changed files with 80 additions and 95 deletions

View file

@ -53,4 +53,10 @@
UTF-8 bytes.
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
<!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
respectively (The TXT value is a string).
-->
<bool name="config_thread_managed_by_google_home">false</bool>
</resources>

View file

@ -45,7 +45,6 @@ import com.android.server.thread.openthread.INsdStatusReceiver;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -615,9 +614,8 @@ public final class NsdPublisher extends INsdPublisher.Stub {
}
List<DnsTxtAttribute> txtList = new ArrayList<>();
for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
DnsTxtAttribute attribute = new DnsTxtAttribute();
attribute.name = entry.getKey();
attribute.value = Arrays.copyOf(entry.getValue(), entry.getValue().length);
DnsTxtAttribute attribute =
new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
txtList.add(attribute);
}
// TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.

View file

@ -61,6 +61,8 @@ import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLING;
import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@ -117,6 +119,7 @@ import com.android.server.ServiceManagerWrapper;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
@ -130,7 +133,6 @@ import libcore.util.HexEncoding;
import java.io.IOException;
import java.net.Inet6Address;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Clock;
import java.time.DateTimeException;
@ -338,9 +340,11 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
final boolean managedByGoogle =
resources.getBoolean(R.bool.config_thread_managed_by_google_home);
if (!modelName.isEmpty()) {
if (modelName.getBytes(StandardCharsets.UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Model name is longer than "
+ MAX_MODEL_NAME_UTF8_BYTES
@ -350,7 +354,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
}
if (!vendorName.isEmpty()) {
if (vendorName.getBytes(StandardCharsets.UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
if (vendorName.getBytes(UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Vendor name is longer than "
+ MAX_VENDOR_NAME_UTF8_BYTES
@ -367,9 +371,21 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
return meshcopTxts;
}
/**
* Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
*
* @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
*/
private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
return new DnsTxtAttribute("vgh", value);
}
private void onOtDaemonDied() {
checkOnHandlerThread();
Log.w(TAG, "OT daemon is dead, clean up...");

View file

@ -45,7 +45,6 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@ -859,6 +858,7 @@ public class ThreadNetworkControllerTest {
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@ -885,6 +885,7 @@ public class ThreadNetworkControllerTest {
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test

View file

@ -17,7 +17,9 @@ package android.net.thread.utils;
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.google.common.io.BaseEncoding.base16;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.net.InetAddresses;
@ -26,7 +28,9 @@ import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
import android.os.Handler;
import android.os.HandlerThread;
import com.google.errorprone.annotations.FormatMethod;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;

View file

@ -22,14 +22,15 @@ import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.net.DnsResolver;
import android.net.InetAddresses;
@ -50,21 +51,23 @@ import com.android.server.thread.openthread.INsdStatusReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
/** Unit tests for {@link NsdPublisher}. */
public final class NsdPublisherTest {
private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
private static final DnsTxtAttribute TEST_TXT_ENTRY_2 =
new DnsTxtAttribute("key2", new byte[] {0x03});
@Mock private NsdManager mMockNsdManager;
@Mock private DnsResolver mMockDnsResolver;
@ -87,19 +90,15 @@ public final class NsdPublisherTest {
public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@ -126,11 +125,10 @@ public final class NsdPublisherTest {
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
assertThat(actualServiceInfo.getAttributes().get("key1"))
.isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
assertThat(actualServiceInfo.getAttributes().get("key2"))
.isEqualTo(new byte[] {(byte) 0x03});
assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
.isEqualTo(TEST_TXT_ENTRY_1.value);
assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
.isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onSuccess();
}
@ -138,19 +136,15 @@ public final class NsdPublisherTest {
public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@ -177,21 +171,16 @@ public final class NsdPublisherTest {
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
assertThat(actualServiceInfo.getAttributes().get("key1"))
.isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
assertThat(actualServiceInfo.getAttributes().get("key2"))
.isEqualTo(new byte[] {(byte) 0x03});
assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
.isEqualTo(TEST_TXT_ENTRY_1.value);
assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
.isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
}
@Test
public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
doThrow(new IllegalArgumentException("NsdManager fails"))
.when(mMockNsdManager)
.registerService(any(), anyInt(), any(Executor.class), any());
@ -202,7 +191,7 @@ public final class NsdPublisherTest {
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@ -215,16 +204,13 @@ public final class NsdPublisherTest {
throws Exception {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@ -260,16 +246,13 @@ public final class NsdPublisherTest {
public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@ -587,8 +570,8 @@ public final class NsdPublisherTest {
List.of(
InetAddress.parseNumericAddress("2001::1"),
InetAddress.parseNumericAddress("2001::2")));
serviceInfo.setAttribute("key1", new byte[] {(byte) 0x01, (byte) 0x02});
serviceInfo.setAttribute("key2", new byte[] {(byte) 0x03});
serviceInfo.setAttribute(TEST_TXT_ENTRY_1.name, TEST_TXT_ENTRY_1.value);
serviceInfo.setAttribute(TEST_TXT_ENTRY_2.name, TEST_TXT_ENTRY_2.value);
serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
mTestLooper.dispatchAll();
@ -599,11 +582,8 @@ public final class NsdPublisherTest {
eq("_test._tcp"),
eq(12345),
eq(List.of("2001::1", "2001::2")),
argThat(
new TxtMatcher(
List.of(
makeTxtAttribute("key1", List.of(0x01, 0x02)),
makeTxtAttribute("key2", List.of(0x03))))),
(List<DnsTxtAttribute>)
argThat(containsInAnyOrder(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2)),
anyInt());
}
@ -725,10 +705,6 @@ public final class NsdPublisherTest {
@Test
public void reset_unregisterAll() {
prepareTest();
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
@ -740,7 +716,7 @@ public final class NsdPublisherTest {
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
List.of(txt1, txt2),
List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@ -814,19 +790,6 @@ public final class NsdPublisherTest {
verify(spyNsdPublisher, times(1)).reset();
}
private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
txtAttribute.name = name;
txtAttribute.value = new byte[value.size()];
for (int i = 0; i < value.size(); ++i) {
txtAttribute.value[i] = value.get(i).byteValue();
}
return txtAttribute;
}
private static List<InetAddress> makeAddresses(String... addressStrings) {
List<InetAddress> addresses = new ArrayList<>();
@ -836,30 +799,6 @@ public final class NsdPublisherTest {
return addresses;
}
private static class TxtMatcher implements ArgumentMatcher<List<DnsTxtAttribute>> {
private final List<DnsTxtAttribute> mAttributes;
TxtMatcher(List<DnsTxtAttribute> attributes) {
mAttributes = attributes;
}
@Override
public boolean matches(List<DnsTxtAttribute> argument) {
if (argument.size() != mAttributes.size()) {
return false;
}
for (int i = 0; i < argument.size(); ++i) {
if (!Objects.equals(argument.get(i).name, mAttributes.get(i).name)) {
return false;
}
if (!Arrays.equals(argument.get(i).value, mAttributes.get(i).value)) {
return false;
}
}
return true;
}
}
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
// thread looper, so TestLooper needs to be created inside each test case to install the
// correct looper.

View file

@ -78,6 +78,7 @@ import androidx.test.filters.SmallTest;
import com.android.connectivity.resources.R;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
@ -94,6 +95,7 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
import java.util.concurrent.CompletableFuture;
@ -145,6 +147,7 @@ public final class ThreadNetworkControllerServiceTest {
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@ -197,6 +200,8 @@ public final class ThreadNetworkControllerServiceTest {
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
.thenReturn(TEST_VGH_VALUE);
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@ -232,13 +237,15 @@ public final class ThreadNetworkControllerServiceTest {
}
@Test
public void initialize_vendorAndModelNameInResourcesAreSetToOtDaemon() throws Exception {
public void initialize_resourceOverlayValuesAreSetToOtDaemon() throws Exception {
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
.thenReturn(true);
mService.initialize();
mTestLooper.dispatchAll();
@ -247,6 +254,20 @@ public final class ThreadNetworkControllerServiceTest {
assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
assertThat(meshcopTxts.nonStandardTxtEntries)
.containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
}
@Test
public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
.thenReturn(false);
MeshcopTxtAttributes meshcopTxts =
ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
assertThat(meshcopTxts.nonStandardTxtEntries)
.containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
}
@Test