From 29918c1d4535f025af45a11911fecfeffbf8bc4a Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Mon, 24 Jun 2024 17:23:53 +0800 Subject: [PATCH] [Thread] add config_thread_mdns_vendor_specific_txts per offline discussion, it's better to provide support of customizing any vendor-specific TXTs to make the solution more generic. The preivous defined "vgh" TXT entry is deprected. Bug: 343671003 Change-Id: I6e2804e346d04078b7d28bfdc18ed6c23c5c10e6 --- .../res/values/config_thread.xml | 20 ++++- .../res/values/overlayable.xml | 1 + .../ThreadNetworkControllerService.java | 42 ++++++++-- .../cts/ThreadNetworkControllerTest.java | 2 - .../ThreadNetworkControllerServiceTest.java | 82 ++++++++++++++----- 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml index 02a9ce6ed8..40270388ad 100644 --- a/service/ServiceConnectivityResources/res/values/config_thread.xml +++ b/service/ServiceConnectivityResources/res/values/config_thread.xml @@ -54,9 +54,21 @@ --> Thread Border Router - - false + + diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index 158b0c8db5..fbaae05361 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -51,6 +51,7 @@ + diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java index 2f60d9acee..f4b80ac275 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java @@ -139,6 +139,7 @@ import java.security.SecureRandom; import java.time.Clock; import java.time.DateTimeException; import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -344,8 +345,8 @@ 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); + final String[] vendorSpecificTxts = + resources.getStringArray(R.array.config_thread_mdns_vendor_specific_txts); if (!modelName.isEmpty()) { if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) { @@ -375,19 +376,44 @@ 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)); + meshcopTxts.nonStandardTxtEntries = makeVendorSpecificTxtAttrs(vendorSpecificTxts); return meshcopTxts; } /** - * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google. + * Parses vendor-specific TXT entries from "=" separated strings into list of {@link + * DnsTxtAttribute}. * - * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0" + * @throws IllegalArgumentsException if invalid TXT entries are found in {@code vendorTxts} */ - private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) { - final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8); - return new DnsTxtAttribute("vgh", value); + @VisibleForTesting + static List makeVendorSpecificTxtAttrs(String[] vendorTxts) { + List txts = new ArrayList<>(); + for (String txt : vendorTxts) { + String[] kv = txt.split("=", 2 /* limit */); // Split with only the first '=' + if (kv.length < 1) { + throw new IllegalArgumentException( + "Invalid vendor-specific TXT is found in resources: " + txt); + } + + if (kv[0].length() < 2) { + throw new IllegalArgumentException( + "Invalid vendor-specific TXT key \"" + + kv[0] + + "\": it must contain at least 2 characters"); + } + + if (!kv[0].startsWith("v")) { + throw new IllegalArgumentException( + "Invalid vendor-specific TXT key \"" + + kv[0] + + "\": it doesn't start with \"v\""); + } + + txts.add(new DnsTxtAttribute(kv[0], (kv.length >= 2 ? kv[1] : "").getBytes(UTF_8))); + } + return txts; } private void onOtDaemonDied() { 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 41f34ff4e1..11c4819b7e 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -859,7 +859,6 @@ 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 @@ -886,7 +885,6 @@ 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/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java index 6e2369f4ed..b67a9aff16 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java @@ -49,6 +49,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static java.nio.charset.StandardCharsets.UTF_8; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -96,11 +98,11 @@ 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.time.Instant; import java.time.ZoneId; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; @@ -150,7 +152,6 @@ 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; @@ -203,8 +204,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); + when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts))) + .thenReturn(new String[] {}); final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml")); mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources); @@ -247,8 +248,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(true); + when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts))) + .thenReturn(new String[] {"vt=test"}); mService.initialize(); mTestLooper.dispatchAll(); @@ -258,19 +259,7 @@ public final class ThreadNetworkControllerServiceTest { 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))); + .containsExactly(new DnsTxtAttribute("vt", "test".getBytes(UTF_8))); } @Test @@ -342,6 +331,61 @@ public final class ThreadNetworkControllerServiceTest { return ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources).vendorOui; } + @Test + public void makeVendorSpecificTxtAttrs_validTxts_returnsParsedTxtAttrs() { + String[] txts = new String[] {"va=123", "vb=", "vc"}; + + List attrs = mService.makeVendorSpecificTxtAttrs(txts); + + assertThat(attrs) + .containsExactly( + new DnsTxtAttribute("va", "123".getBytes(UTF_8)), + new DnsTxtAttribute("vb", new byte[] {}), + new DnsTxtAttribute("vc", new byte[] {})); + } + + @Test + public void makeVendorSpecificTxtAttrs_txtKeyNotStartWithV_throwsIllegalArgument() { + String[] txts = new String[] {"abc=123"}; + + assertThrows( + IllegalArgumentException.class, () -> mService.makeVendorSpecificTxtAttrs(txts)); + } + + @Test + public void makeVendorSpecificTxtAttrs_txtIsTooShort_throwsIllegalArgument() { + String[] txtEmptyKey = new String[] {"=123"}; + String[] txtSingleCharKey = new String[] {"v=456"}; + + assertThrows( + IllegalArgumentException.class, + () -> mService.makeVendorSpecificTxtAttrs(txtEmptyKey)); + assertThrows( + IllegalArgumentException.class, + () -> mService.makeVendorSpecificTxtAttrs(txtSingleCharKey)); + } + + @Test + public void makeVendorSpecificTxtAttrs_txtValueIsEmpty_parseSuccess() { + String[] txts = new String[] {"va=", "vb"}; + + List attrs = mService.makeVendorSpecificTxtAttrs(txts); + + assertThat(attrs) + .containsExactly( + new DnsTxtAttribute("va", new byte[] {}), + new DnsTxtAttribute("vb", new byte[] {})); + } + + @Test + public void makeVendorSpecificTxtAttrs_multipleEquals_splittedByTheFirstEqual() { + String[] txts = new String[] {"va=abc=def=123"}; + + List attrs = mService.makeVendorSpecificTxtAttrs(txts); + + assertThat(attrs).containsExactly(new DnsTxtAttribute("va", "abc=def=123".getBytes(UTF_8))); + } + @Test public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception { mService.initialize();