[Thread] add E2E UDP test for app usage
Bug: 323300829 Bug: 325983649 Change-Id: I0e6b9029cb58a2b985e80003d6339e536d706ed3
This commit is contained in:
parent
8b15cc8f8e
commit
e9daad6264
3 changed files with 119 additions and 12 deletions
|
|
@ -29,6 +29,7 @@ import static com.google.common.io.BaseEncoding.base16;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.thread.utils.FullThreadDevice;
|
||||
import android.net.thread.utils.OtDaemonController;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
|
||||
|
|
@ -45,14 +46,25 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
|
||||
@LargeTest
|
||||
@RequiresThreadFeature
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ThreadIntegrationTest {
|
||||
// The byte[] buffer size for UDP tests
|
||||
private static final int UDP_BUFFER_SIZE = 1024;
|
||||
|
||||
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
|
||||
private static final byte[] DEFAULT_DATASET_TLVS =
|
||||
base16().decode(
|
||||
|
|
@ -66,24 +78,32 @@ public class ThreadIntegrationTest {
|
|||
|
||||
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
|
||||
|
||||
private ExecutorService mExecutor;
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private final ThreadNetworkControllerWrapper mController =
|
||||
ThreadNetworkControllerWrapper.newInstance(mContext);
|
||||
private OtDaemonController mOtCtl;
|
||||
private FullThreadDevice mFtd;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mOtCtl = new OtDaemonController();
|
||||
mController.leaveAndWait();
|
||||
|
||||
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
|
||||
mOtCtl.factoryReset();
|
||||
|
||||
mFtd = new FullThreadDevice(10 /* nodeId */);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mController.setTestNetworkAsUpstreamAndWait(null);
|
||||
mController.leaveAndWait();
|
||||
|
||||
mFtd.destroy();
|
||||
mExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -153,10 +173,57 @@ public class ThreadIntegrationTest {
|
|||
assertThat(mOtCtl.getCountryCode()).isEqualTo("CN");
|
||||
}
|
||||
|
||||
private static String runThreadCommand(String cmd) {
|
||||
return runShellCommandOrThrow("cmd thread_network " + cmd);
|
||||
@Test
|
||||
public void udp_appStartEchoServer_endDeviceUdpEchoSuccess() throws Exception {
|
||||
// Topology:
|
||||
// Test App ------ thread-wpan ------ End Device
|
||||
|
||||
mController.joinAndWait(DEFAULT_DATASET);
|
||||
startFtdChild(mFtd, DEFAULT_DATASET);
|
||||
final Inet6Address serverAddress = mOtCtl.getMeshLocalAddresses().get(0);
|
||||
final int serverPort = 9527;
|
||||
|
||||
mExecutor.execute(() -> startUdpEchoServerAndWait(serverAddress, serverPort));
|
||||
mFtd.udpOpen();
|
||||
mFtd.udpSend("Hello,Thread", serverAddress, serverPort);
|
||||
String udpReply = mFtd.udpReceive();
|
||||
|
||||
assertThat(udpReply).isEqualTo("Hello,Thread");
|
||||
}
|
||||
|
||||
// TODO (b/323300829): add more tests for integration with linux platform and
|
||||
// ConnectivityService
|
||||
|
||||
private static String runThreadCommand(String cmd) {
|
||||
return runShellCommandOrThrow("cmd thread_network " + cmd);
|
||||
}
|
||||
|
||||
private void startFtdChild(FullThreadDevice ftd, ActiveOperationalDataset activeDataset)
|
||||
throws Exception {
|
||||
ftd.factoryReset();
|
||||
ftd.joinNetwork(activeDataset);
|
||||
ftd.waitForStateAnyOf(List.of("router", "child"), Duration.ofSeconds(8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a UDP echo server and replies to the first UDP message.
|
||||
*
|
||||
* <p>This method exits when the first UDP message is received and the reply is sent
|
||||
*/
|
||||
private void startUdpEchoServerAndWait(InetAddress serverAddress, int serverPort) {
|
||||
try (var udpServerSocket = new DatagramSocket(serverPort, serverAddress)) {
|
||||
DatagramPacket recvPacket =
|
||||
new DatagramPacket(new byte[UDP_BUFFER_SIZE], UDP_BUFFER_SIZE);
|
||||
udpServerSocket.receive(recvPacket);
|
||||
byte[] sendBuffer = Arrays.copyOf(recvPacket.getData(), recvPacket.getData().length);
|
||||
udpServerSocket.send(
|
||||
new DatagramPacket(
|
||||
sendBuffer,
|
||||
sendBuffer.length,
|
||||
(Inet6Address) recvPacket.getAddress(),
|
||||
recvPacket.getPort()));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import static android.net.thread.utils.IntegrationTestUtils.waitFor;
|
|||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
|
|
@ -218,6 +216,11 @@ public final class FullThreadDevice {
|
|||
return matcher.group(4);
|
||||
}
|
||||
|
||||
/** Sends a UDP message to given IPv6 address and port. */
|
||||
public void udpSend(String message, Inet6Address serverAddr, int serverPort) {
|
||||
executeCommand("udp send %s %d %s", serverAddr.getHostAddress(), serverPort, message);
|
||||
}
|
||||
|
||||
/** Enables the SRP client and run in autostart mode. */
|
||||
public void autoStartSrpClient() {
|
||||
executeCommand("srp client autostart enable");
|
||||
|
|
@ -474,7 +477,7 @@ public final class FullThreadDevice {
|
|||
break;
|
||||
}
|
||||
if (line.startsWith("Error")) {
|
||||
fail("ot-cli-ftd reported an error: " + line);
|
||||
throw new IOException("ot-cli-ftd reported an error: " + line);
|
||||
}
|
||||
if (!line.startsWith("> ")) {
|
||||
result.add(line);
|
||||
|
|
|
|||
|
|
@ -16,13 +16,16 @@
|
|||
|
||||
package android.net.thread.utils;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpPrefix;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.android.compatibility.common.util.SystemUtil;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -53,10 +56,7 @@ public final class OtDaemonController {
|
|||
|
||||
/** Returns the list of IPv6 addresses on ot-daemon. */
|
||||
public List<Inet6Address> getAddresses() {
|
||||
String output = executeCommand("ipaddr");
|
||||
return Arrays.asList(output.split("\n")).stream()
|
||||
.map(String::trim)
|
||||
.filter(str -> !str.equals("Done"))
|
||||
return executeCommandAndParse("ipaddr").stream()
|
||||
.map(addr -> InetAddresses.parseNumericAddress(addr))
|
||||
.map(inetAddr -> (Inet6Address) inetAddr)
|
||||
.toList();
|
||||
|
|
@ -70,17 +70,54 @@ public final class OtDaemonController {
|
|||
|
||||
/** Returns the ML-EID of the device. */
|
||||
public Inet6Address getMlEid() {
|
||||
String addressStr = executeCommand("ipaddr mleid").split("\n")[0].trim();
|
||||
String addressStr = executeCommandAndParse("ipaddr mleid").get(0);
|
||||
return (Inet6Address) InetAddresses.parseNumericAddress(addressStr);
|
||||
}
|
||||
|
||||
/** Returns the country code on ot-daemon. */
|
||||
public String getCountryCode() {
|
||||
String countryCodeStr = executeCommand("region").split("\n")[0].trim();
|
||||
return countryCodeStr;
|
||||
return executeCommandAndParse("region").get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IPv6 Mesh-Local addresses on ot-daemon.
|
||||
*
|
||||
* <p>The return List can be empty if no Mesh-Local prefix exists.
|
||||
*/
|
||||
public List<Inet6Address> getMeshLocalAddresses() {
|
||||
IpPrefix meshLocalPrefix = getMeshLocalPrefix();
|
||||
if (meshLocalPrefix == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return getAddresses().stream().filter(addr -> meshLocalPrefix.contains(addr)).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Mesh-Local prefix or {@code null} if none exists (e.g. the Active Dataset is not
|
||||
* set).
|
||||
*/
|
||||
@Nullable
|
||||
public IpPrefix getMeshLocalPrefix() {
|
||||
List<IpPrefix> prefixes =
|
||||
executeCommandAndParse("prefix meshlocal").stream()
|
||||
.map(prefix -> new IpPrefix(prefix))
|
||||
.toList();
|
||||
return prefixes.isEmpty() ? null : prefixes.get(0);
|
||||
}
|
||||
|
||||
public String executeCommand(String cmd) {
|
||||
return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a ot-ctl command and parse the output to a list of strings.
|
||||
*
|
||||
* <p>The trailing "Done" in the command output will be dropped.
|
||||
*/
|
||||
public List<String> executeCommandAndParse(String cmd) {
|
||||
return Arrays.asList(executeCommand(cmd).split("\n")).stream()
|
||||
.map(String::trim)
|
||||
.filter(str -> !str.equals("Done"))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue