[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
This commit is contained in:
parent
f2315d018b
commit
29918c1d45
5 changed files with 114 additions and 33 deletions
|
|
@ -54,9 +54,21 @@
|
|||
-->
|
||||
<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).
|
||||
<!-- Specifies vendor-specific mDNS TXT entries which will be included in the "_meshcop._udp"
|
||||
service. The TXT entries list MUST conform to the format requirement in RFC 6763 section 6. For
|
||||
example, the key and value of each TXT entry MUST be separated with "=". If the value length is
|
||||
0, the trailing "=" may be omitted. Additionally, the TXT keys MUST start with "v" and be at
|
||||
least 2 characters.
|
||||
|
||||
Note, do not include credentials in any of the TXT entries - they will be advertised on Wi-Fi
|
||||
or Ethernet link.
|
||||
|
||||
An example config can be:
|
||||
<string-array name="config_thread_mdns_vendor_specific_txts">
|
||||
<item>vab=123</item>
|
||||
<item>vcd</item>
|
||||
</string-array>
|
||||
-->
|
||||
<bool name="config_thread_managed_by_google_home">false</bool>
|
||||
<string-array name="config_thread_mdns_vendor_specific_txts">
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
<item type="string" name="config_thread_vendor_name" />
|
||||
<item type="string" name="config_thread_vendor_oui" />
|
||||
<item type="string" name="config_thread_model_name" />
|
||||
<item type="array" name="config_thread_mdns_vendor_specific_txts" />
|
||||
</policy>
|
||||
</overlayable>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -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<DnsTxtAttribute> makeVendorSpecificTxtAttrs(String[] vendorTxts) {
|
||||
List<DnsTxtAttribute> 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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<DnsTxtAttribute> 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<DnsTxtAttribute> 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<DnsTxtAttribute> 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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue