From 151275359f776b005a37e43f376148ccda54fd4c Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Sun, 2 Jun 2024 16:22:24 +0800 Subject: [PATCH] [Thread] add the "vgh" TXT entry Allows vendors to specify whether the Thread network is managed by Google or not. Bug: 343671003 Change-Id: Ie4d4050add0485643ea3eff0c576868b086f848c --- .../res/values/config_thread.xml | 6 + .../android/server/thread/NsdPublisher.java | 6 +- .../ThreadNetworkControllerService.java | 22 +++- .../cts/ThreadNetworkControllerTest.java | 3 +- .../net/thread/utils/FullThreadDevice.java | 4 + .../server/thread/NsdPublisherTest.java | 111 ++++-------------- .../ThreadNetworkControllerServiceTest.java | 23 +++- 7 files changed, 80 insertions(+), 95 deletions(-) diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml index 4783f2bc51..02a9ce6ed8 100644 --- a/service/ServiceConnectivityResources/res/values/config_thread.xml +++ b/service/ServiceConnectivityResources/res/values/config_thread.xml @@ -53,4 +53,10 @@ UTF-8 bytes. --> Thread Border Router + + + false diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java index d0cb9b8119..1447ff8457 100644 --- a/thread/service/java/com/android/server/thread/NsdPublisher.java +++ b/thread/service/java/com/android/server/thread/NsdPublisher.java @@ -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 txtList = new ArrayList<>(); for (Map.Entry 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. diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 737ec41e8b..0c77deef08 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -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..."); diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java index 11c4819b7e..0e95703943 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -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 diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java index d24059a10b..c0a8eea834 100644 --- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java +++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java @@ -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; diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java index 3cae84f2ab..ae0bc80ae7 100644 --- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java +++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java @@ -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 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 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) + 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 actualServiceInfoCaptor = ArgumentCaptor.forClass(NsdServiceInfo.class); ArgumentCaptor 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 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 makeAddresses(String... addressStrings) { List addresses = new ArrayList<>(); @@ -836,30 +799,6 @@ public final class NsdPublisherTest { return addresses; } - private static class TxtMatcher implements ArgumentMatcher> { - private final List mAttributes; - - TxtMatcher(List attributes) { - mAttributes = attributes; - } - - @Override - public boolean matches(List 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. diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java index 8f60783322..2f58943907 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java @@ -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