aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-10 18:50:48 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-01-10 18:50:48 +0000
commitd1f85e7a82a6a98fa8021fa87d4e76612530629e (patch)
tree924ee99440d2870dad726ca994704d4cc2f76194
parent6131f7f0eca36793a80c7cb78cba903a7ecf710d (diff)
parent44f9321fb512b447aff1b623463e182bc5c35455 (diff)
downloadot-br-posix-aml_tz5_341510010.tar.gz
Snap for 11296156 from 44f9321fb512b447aff1b623463e182bc5c35455 to mainline-tzdata5-releaseaml_tz5_341510070aml_tz5_341510050aml_tz5_341510010aml_tz5_341510010
Change-Id: I0965b0b3aaabe0bb008c24653312dc728ad134cd
-rw-r--r--Android.bp44
-rw-r--r--TEST_MAPPING5
-rwxr-xr-xscript/make-java-pretty8
-rw-r--r--src/Android.bp44
-rw-r--r--src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl8
-rw-r--r--src/android/java/Android.bp50
-rw-r--r--src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java213
-rw-r--r--src/android/otdaemon_server.cpp38
-rw-r--r--src/android/otdaemon_server.hpp5
-rw-r--r--src/android/otdaemon_telemetry.cpp671
-rw-r--r--src/android/otdaemon_telemetry.hpp39
-rw-r--r--src/proto/threadnetwork_atoms.proto470
-rw-r--r--tests/android/Android.bp54
-rw-r--r--tests/android/AndroidManifest.xml42
-rw-r--r--tests/android/AndroidTest.xml48
-rw-r--r--tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java179
16 files changed, 1918 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
index 56429eb2..357b58f0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -145,10 +145,12 @@ cc_defaults {
srcs: [
"src/agent/application.cpp",
"src/android/otdaemon_server.cpp",
+ "src/android/otdaemon_telemetry.cpp",
"src/border_agent/border_agent.cpp",
"src/ncp/ncp_openthread.cpp",
"src/sdp_proxy/advertising_proxy.cpp",
"src/sdp_proxy/discovery_proxy.cpp",
+ "src/common/code_utils.cpp",
"src/common/dns_utils.cpp",
"src/common/logging.cpp",
"src/common/mainloop.cpp",
@@ -175,12 +177,17 @@ cc_defaults {
"libcutils",
"libbinder_ndk",
"android.hardware.threadnetwork-V1-ndk",
+ "liblog",
+ "libstatssocket",
],
static_libs: [
"libopenthread-cli",
"ot-core",
"ot-daemon-aidl-ndk",
+ "libstatslog_threadnetwork",
+ "threadnetwork-atom-cc-proto-lite",
+ "libprotobuf-cpp-lite",
],
host_ldlibs: ["-lutil"],
@@ -217,3 +224,40 @@ cc_fuzz {
],
},
}
+
+cc_library_static {
+ name: "libstatslog_threadnetwork",
+ generated_sources: ["statslog_threadnetwork.cpp"],
+ generated_headers: ["statslog_threadnetwork.h"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ export_generated_headers: ["statslog_threadnetwork.h"],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ "libstatssocket",
+ "libutils",
+ ],
+ min_sdk_version: "30",
+ apex_available: [ "com.android.tethering" ],
+}
+
+genrule {
+ name: "statslog_threadnetwork.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_threadnetwork.h --module threadnetwork --namespace threadnetwork",
+ out: [
+ "statslog_threadnetwork.h",
+ ],
+}
+
+genrule {
+ name: "statslog_threadnetwork.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_threadnetwork.cpp --module threadnetwork --namespace threadnetwork --importHeader statslog_threadnetwork.h",
+ out: [
+ "statslog_threadnetwork.cpp",
+ ],
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3eaebfa0..776c5302 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,5 +3,10 @@
{
"name": "CtsThreadNetworkTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "OtDaemonUnitTests"
+ }
]
}
diff --git a/script/make-java-pretty b/script/make-java-pretty
new file mode 100755
index 00000000..27fa3f48
--- /dev/null
+++ b/script/make-java-pretty
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../prebuilts/tools/common/google-java-format/google-java-format
+
+$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../src -name "*.java")
+$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../tests -name "*.java")
diff --git a/src/Android.bp b/src/Android.bp
new file mode 100644
index 00000000..d6477299
--- /dev/null
+++ b/src/Android.bp
@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2023, The OpenThread Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holder nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+package {
+ default_applicable_licenses: ["external_ot-br-posix_license"],
+}
+
+cc_library_static {
+ name: "threadnetwork-atom-cc-proto-lite",
+ proto: {
+ type: "lite",
+ // Need to be able to see the .pb.h files that are generated
+ export_proto_headers: true,
+ },
+ srcs: [
+ "proto/threadnetwork_atoms.proto",
+ ],
+ min_sdk_version: "30",
+ apex_available: [ "com.android.tethering" ],
+}
diff --git a/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl b/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
index fbd99a43..61cae13c 100644
--- a/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
+++ b/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
@@ -111,6 +111,14 @@ oneway interface IOtDaemon {
in byte[] pendingOpDatasetTlvs, in IOtStatusReceiver receiver);
/**
+ * Sets the country code.
+ *
+ * @param countryCode 2 byte country code (as defined in ISO 3166) to set.
+ * @param receiver the receiver to receive result of this operation
+ */
+ oneway void setCountryCode(in String countryCode, in IOtStatusReceiver receiver);
+
+ /**
* Configures the Border Router features.
*
* @param brConfig the border router's configuration
diff --git a/src/android/java/Android.bp b/src/android/java/Android.bp
new file mode 100644
index 00000000..89f7910f
--- /dev/null
+++ b/src/android/java/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (c) 2023, The OpenThread Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holder nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+package {
+ default_applicable_licenses: ["external_ot-br-posix_license"],
+}
+
+filegroup {
+ name: "ot-daemon-testing-sources",
+ srcs: ["com/android/server/thread/openthread/testing/*.java"]
+}
+
+java_library {
+ name: "ot-daemon-testing",
+ srcs: [":ot-daemon-testing-sources"],
+
+ static_libs: [
+ "ot-daemon-aidl-java"
+ ],
+
+ visibility: [
+ "//packages/modules/Connectivity/thread/tests:__subpackages__",
+ "//external/ot-br-posix/tests:__subpackages__",
+ ]
+}
diff --git a/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java b/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
new file mode 100644
index 00000000..0a84418d
--- /dev/null
+++ b/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2023, The OpenThread Authors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.thread.openthread.testing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.IOtDaemon;
+import com.android.server.thread.openthread.IOtDaemonCallback;
+import com.android.server.thread.openthread.IOtStatusReceiver;
+import com.android.server.thread.openthread.OtDaemonState;
+
+import java.time.Duration;
+import java.util.NoSuchElementException;
+
+/** A fake implementation of the {@link IOtDaemon} AIDL API for testing. */
+public final class FakeOtDaemon extends IOtDaemon.Stub {
+ /** The typical Thread network join / attach delay is around 8 seconds. */
+ public static final Duration JOIN_DELAY = Duration.ofSeconds(8);
+
+ static final int OT_DEVICE_ROLE_DISABLED = 0;
+ static final int OT_DEVICE_ROLE_DETACHED = 1;
+ static final int OT_DEVICE_ROLE_CHILD = 2;
+ static final int OT_DEVICE_ROLE_ROUTER = 3;
+ static final int OT_DEVICE_ROLE_LEADER = 4;
+
+ private static final long PROACTIVE_LISTENER_ID = -1;
+
+ private final Handler mHandler;
+ private final OtDaemonState mState;
+
+ @Nullable private DeathRecipient mDeathRecipient;
+
+ @Nullable private ParcelFileDescriptor mTunFd;
+
+ @Nullable private IOtDaemonCallback mCallback;
+
+ @Nullable private Long mCallbackListenerId;
+
+ @Nullable private RemoteException mJoinException;
+
+ public FakeOtDaemon(Handler handler) {
+ mHandler = handler;
+ mState = new OtDaemonState();
+ mState.isInterfaceUp = false;
+ mState.deviceRole = OT_DEVICE_ROLE_DISABLED;
+ mState.activeDatasetTlvs = new byte[0];
+ mState.pendingDatasetTlvs = new byte[0];
+ mState.multicastForwardingEnabled = false;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public void linkToDeath(DeathRecipient recipient, int flags) {
+ if (mDeathRecipient != null && recipient != null) {
+ throw new IllegalStateException("IOtDaemon death recipient is already linked!");
+ }
+
+ mDeathRecipient = recipient;
+ }
+
+ @Override
+ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+ if (mDeathRecipient == null || recipient != mDeathRecipient) {
+ throw new NoSuchElementException("recipient is not linked! " + recipient);
+ }
+
+ mDeathRecipient = null;
+ return true;
+ }
+
+ @Override
+ public void initialize(ParcelFileDescriptor tunFd) throws RemoteException {
+ mTunFd = tunFd;
+ }
+
+ /**
+ * Returns the Thread TUN interface FD sent to OT daemon or {@code null} if {@link initialize}
+ * is never called.
+ */
+ @Nullable
+ public ParcelFileDescriptor getTunFd() {
+ return mTunFd;
+ }
+
+ @Override
+ public void registerStateCallback(IOtDaemonCallback callback, long listenerId)
+ throws RemoteException {
+ mCallback = callback;
+ mCallbackListenerId = listenerId;
+
+ mHandler.post(() -> onStateChanged(mState, mCallbackListenerId));
+ }
+
+ @Nullable
+ public IOtDaemonCallback getStateCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public void join(byte[] activeDataset, IOtStatusReceiver receiver) throws RemoteException {
+ if (mJoinException != null) {
+ throw mJoinException;
+ }
+
+ mHandler.post(
+ () -> {
+ mState.isInterfaceUp = true;
+ mState.deviceRole = OT_DEVICE_ROLE_DETACHED;
+ onStateChanged(mState, PROACTIVE_LISTENER_ID);
+ });
+
+ mHandler.postDelayed(
+ () -> {
+ mState.deviceRole = OT_DEVICE_ROLE_LEADER;
+ mState.activeDatasetTlvs = activeDataset.clone();
+ mState.multicastForwardingEnabled = true;
+
+ onStateChanged(mState, PROACTIVE_LISTENER_ID);
+ try {
+ receiver.onSuccess();
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ },
+ JOIN_DELAY.toMillis());
+ }
+
+ private void onStateChanged(OtDaemonState state, long listenerId) {
+ try {
+ // Make a copy of mState so that clients won't keep a direct reference to it
+ OtDaemonState copyState = new OtDaemonState();
+ copyState.isInterfaceUp = state.isInterfaceUp;
+ copyState.deviceRole = state.deviceRole;
+ copyState.partitionId = state.partitionId;
+ copyState.activeDatasetTlvs = state.activeDatasetTlvs.clone();
+ copyState.pendingDatasetTlvs = state.pendingDatasetTlvs.clone();
+ copyState.multicastForwardingEnabled = state.multicastForwardingEnabled;
+
+ mCallback.onStateChanged(copyState, listenerId);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /** Sets the {@link RemoteException} which will be thrown from {@link #join}. */
+ public void setJoinException(RemoteException exception) {
+ mJoinException = exception;
+ }
+
+ @Override
+ public void leave(IOtStatusReceiver receiver) throws RemoteException {
+ throw new UnsupportedOperationException("FakeOtDaemon#leave is not implemented!");
+ }
+
+ @Override
+ public void configureBorderRouter(
+ BorderRouterConfigurationParcel config, IOtStatusReceiver receiver)
+ throws RemoteException {
+ throw new UnsupportedOperationException(
+ "FakeOtDaemon#configureBorderRouter is not implemented!");
+ }
+
+ @Override
+ public void scheduleMigration(byte[] pendingDataset, IOtStatusReceiver receiver)
+ throws RemoteException {
+ throw new UnsupportedOperationException(
+ "FakeOtDaemon#scheduleMigration is not implemented!");
+ }
+
+ @Override
+ public void setCountryCode(String countryCode, IOtStatusReceiver receiver)
+ throws RemoteException {
+ throw new UnsupportedOperationException(
+ "FakeOtDaemon#scheduleMigration is not implemented!");
+ }
+}
diff --git a/src/android/otdaemon_server.cpp b/src/android/otdaemon_server.cpp
index cf67016d..8eb23846 100644
--- a/src/android/otdaemon_server.cpp
+++ b/src/android/otdaemon_server.cpp
@@ -38,10 +38,12 @@
#include <android/binder_process.h>
#include <openthread/border_router.h>
#include <openthread/ip6.h>
+#include <openthread/link.h>
#include <openthread/openthread-system.h>
#include <openthread/platform/infra_if.h>
#include "agent/vendor.hpp"
+#include "android/otdaemon_telemetry.hpp"
#include "common/code_utils.hpp"
#define BYTE_ARR_END(arr) ((arr) + sizeof(arr))
@@ -117,6 +119,8 @@ void OtDaemonServer::Init(void)
otIp6SetReceiveCallback(GetOtInstance(), OtDaemonServer::ReceiveCallback, this);
otBackboneRouterSetMulticastListenerCallback(GetOtInstance(), OtDaemonServer::HandleBackboneMulticastListenerEvent,
this);
+
+ mTaskRunner.Post(kTelemetryCheckInterval, [this]() { PushTelemetryIfConditionMatch(); });
}
void OtDaemonServer::BinderDeathCallback(void *aBinderServer)
@@ -575,6 +579,27 @@ void OtDaemonServer::SendMgmtPendingSetCallback(otError aResult, void *aBinderSe
}
}
+Status OtDaemonServer::setCountryCode(const std::string &aCountryCode,
+ const std::shared_ptr<IOtStatusReceiver> &aReceiver)
+{
+ static constexpr int kCountryCodeLength = 2;
+ otError error = OT_ERROR_NONE;
+ std::string message;
+ uint16_t countryCode;
+
+ VerifyOrExit((aCountryCode.length() == kCountryCodeLength) && isalpha(aCountryCode[0]) && isalpha(aCountryCode[1]),
+ error = OT_ERROR_INVALID_ARGS, message = "The country code is invalid");
+
+ otbrLogInfo("Set country code: %c%c", aCountryCode[0], aCountryCode[1]);
+ VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized");
+ countryCode = (static_cast<uint16_t>(aCountryCode[0]) << 8) | aCountryCode[1];
+ SuccessOrExit(error = otLinkSetRegion(GetOtInstance(), countryCode), message = "Failed to set the country code");
+
+exit:
+ PropagateResult(error, message, aReceiver);
+ return Status::ok();
+}
+
Status OtDaemonServer::configureBorderRouter(const BorderRouterConfigurationParcel &aBorderRouterConfiguration,
const std::shared_ptr<IOtStatusReceiver> &aReceiver)
{
@@ -630,5 +655,18 @@ binder_status_t OtDaemonServer::dump(int aFd, const char **aArgs, uint32_t aNumA
return STATUS_OK;
}
+
+void OtDaemonServer::PushTelemetryIfConditionMatch()
+{
+ VerifyOrExit(GetOtInstance() != nullptr);
+
+ // TODO: Push telemetry per kTelemetryUploadIntervalThreshold instead of on startup.
+ // TODO: Save unpushed telemetries in local cache to avoid data loss.
+ RetrieveAndPushAtoms(GetOtInstance());
+ mTaskRunner.Post(kTelemetryUploadIntervalThreshold, [this]() { PushTelemetryIfConditionMatch(); });
+
+exit:
+ return;
+}
} // namespace Android
} // namespace otbr
diff --git a/src/android/otdaemon_server.hpp b/src/android/otdaemon_server.hpp
index 387a37b1..c195baac 100644
--- a/src/android/otdaemon_server.hpp
+++ b/src/android/otdaemon_server.hpp
@@ -38,6 +38,7 @@
#include <openthread/ip6.h>
#include "agent/vendor.hpp"
+#include "common/time.hpp"
#include "common/mainloop.hpp"
#include "ncp/ncp_openthread.hpp"
@@ -91,6 +92,7 @@ private:
Status leave(const std::shared_ptr<IOtStatusReceiver> &aReceiver) override;
Status scheduleMigration(const std::vector<uint8_t> &aPendingOpDatasetTlvs,
const std::shared_ptr<IOtStatusReceiver> &aReceiver) override;
+ Status setCountryCode(const std::string &aCountryCode, const std::shared_ptr<IOtStatusReceiver> &aReceiver);
Status configureBorderRouter(const BorderRouterConfigurationParcel &aBorderRouterConfiguration,
const std::shared_ptr<IOtStatusReceiver> &aReceiver) override;
@@ -109,6 +111,7 @@ private:
static void HandleBackboneMulticastListenerEvent(void *aBinderServer,
otBackboneRouterMulticastListenerEvent aEvent,
const otIp6Address *aAddress);
+ void PushTelemetryIfConditionMatch();
otbr::Ncp::ControllerOpenThread &mNcp;
TaskRunner mTaskRunner;
@@ -120,6 +123,8 @@ private:
std::shared_ptr<IOtStatusReceiver> mMigrationReceiver;
std::vector<LeaveCallback> mLeaveCallbacks;
BorderRouterConfigurationParcel mBorderRouterConfiguration;
+ static constexpr Seconds kTelemetryCheckInterval = Seconds(30); // 30 seconds
+ static constexpr Seconds kTelemetryUploadIntervalThreshold = Seconds(60 * 60 * 12); // 12 hours
};
} // namespace Android
diff --git a/src/android/otdaemon_telemetry.cpp b/src/android/otdaemon_telemetry.cpp
new file mode 100644
index 00000000..6026a1e3
--- /dev/null
+++ b/src/android/otdaemon_telemetry.cpp
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) 2023, The OpenThread Authors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "android/otdaemon_telemetry.hpp"
+
+#include <openthread/openthread-system.h>
+#include <openthread/thread.h>
+#include <openthread/thread_ftd.h>
+#include <openthread/platform/radio.h>
+
+#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
+#include <openthread/dnssd_server.h>
+#endif
+#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
+#include <openthread/srp_server.h>
+#endif
+
+#include "statslog_threadnetwork.h"
+#include "common/code_utils.hpp"
+#include "mdns/mdns.hpp"
+#include "proto/threadnetwork_atoms.pb.h"
+
+namespace otbr {
+namespace Android {
+using android::os::statsd::threadnetwork::ThreadnetworkDeviceInfoReported;
+using android::os::statsd::threadnetwork::ThreadnetworkTelemetryDataReported;
+using android::os::statsd::threadnetwork::ThreadnetworkTopoEntryRepeated;
+
+static uint32_t TelemetryNodeTypeFromRoleAndLinkMode(const otDeviceRole &aRole, const otLinkModeConfig &aLinkModeCfg)
+{
+ uint32_t nodeType;
+
+ switch (aRole)
+ {
+ case OT_DEVICE_ROLE_DISABLED:
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_DISABLED;
+ break;
+ case OT_DEVICE_ROLE_DETACHED:
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_DETACHED;
+ break;
+ case OT_DEVICE_ROLE_ROUTER:
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_ROUTER;
+ break;
+ case OT_DEVICE_ROLE_LEADER:
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_LEADER;
+ break;
+ case OT_DEVICE_ROLE_CHILD:
+ if (!aLinkModeCfg.mRxOnWhenIdle)
+ {
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_SLEEPY_END;
+ }
+ else if (!aLinkModeCfg.mDeviceType)
+ {
+ // If it's not an FTD, return as minimal end device.
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_MINIMAL_END;
+ }
+ else
+ {
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_END;
+ }
+ break;
+ default:
+ nodeType = ThreadnetworkTelemetryDataReported::NODE_TYPE_UNSPECIFIED;
+ }
+
+ return nodeType;
+}
+
+#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
+ThreadnetworkTelemetryDataReported::SrpServerState SrpServerStateFromOtSrpServerState(otSrpServerState aSrpServerState)
+{
+ ThreadnetworkTelemetryDataReported::SrpServerState srpServerState;
+
+ switch (aSrpServerState)
+ {
+ case OT_SRP_SERVER_STATE_DISABLED:
+ srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_DISABLED;
+ break;
+ case OT_SRP_SERVER_STATE_RUNNING:
+ srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_RUNNING;
+ break;
+ case OT_SRP_SERVER_STATE_STOPPED:
+ srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_STOPPED;
+ break;
+ default:
+ srpServerState = ThreadnetworkTelemetryDataReported::SRP_SERVER_STATE_UNSPECIFIED;
+ }
+ return srpServerState;
+}
+
+ThreadnetworkTelemetryDataReported::SrpServerAddressMode SrpServerAddressModeFromOtSrpServerAddressMode(
+ otSrpServerAddressMode aSrpServerAddressMode)
+{
+ ThreadnetworkTelemetryDataReported::SrpServerAddressMode srpServerAddressMode;
+
+ switch (aSrpServerAddressMode)
+ {
+ case OT_SRP_SERVER_ADDRESS_MODE_ANYCAST:
+ srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_STATE_ANYCAST;
+ break;
+ case OT_SRP_SERVER_ADDRESS_MODE_UNICAST:
+ srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_UNICAST;
+ break;
+ default:
+ srpServerAddressMode = ThreadnetworkTelemetryDataReported::SRP_SERVER_ADDRESS_MODE_UNSPECIFIED;
+ }
+ return srpServerAddressMode;
+}
+#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY
+
+void CopyMdnsResponseCounters(const MdnsResponseCounters &from,
+ ThreadnetworkTelemetryDataReported::MdnsResponseCounters *to)
+{
+ to->set_success_count(from.mSuccess);
+ to->set_not_found_count(from.mNotFound);
+ to->set_invalid_args_count(from.mInvalidArgs);
+ to->set_duplicated_count(from.mDuplicated);
+ to->set_not_implemented_count(from.mNotImplemented);
+ to->set_unknown_error_count(from.mUnknownError);
+ to->set_aborted_count(from.mAborted);
+ to->set_invalid_state_count(from.mInvalidState);
+}
+
+otError RetrieveTelemetryAtom(otInstance *otInstance,
+ Mdns::Publisher *aPublisher,
+ ThreadnetworkTelemetryDataReported &telemetryDataReported,
+ ThreadnetworkTopoEntryRepeated &topoEntryRepeated,
+ ThreadnetworkDeviceInfoReported &deviceInfoReported)
+{
+ otError error = OT_ERROR_NONE;
+ std::vector<otNeighborInfo> neighborTable;
+
+ // Begin of WpanStats section.
+ auto wpanStats = telemetryDataReported.mutable_wpan_stats();
+
+ {
+ otDeviceRole role = otThreadGetDeviceRole(otInstance);
+ otLinkModeConfig otCfg = otThreadGetLinkMode(otInstance);
+
+ wpanStats->set_node_type(TelemetryNodeTypeFromRoleAndLinkMode(role, otCfg));
+ }
+
+ wpanStats->set_channel(otLinkGetChannel(otInstance));
+
+ {
+ uint16_t ccaFailureRate = otLinkGetCcaFailureRate(otInstance);
+
+ wpanStats->set_mac_cca_fail_rate(static_cast<float>(ccaFailureRate) / 0xffff);
+ }
+
+ {
+ int8_t radioTxPower;
+
+ if (otPlatRadioGetTransmitPower(otInstance, &radioTxPower) == OT_ERROR_NONE)
+ {
+ wpanStats->set_radio_tx_power(radioTxPower);
+ }
+ else
+ {
+ error = OT_ERROR_FAILED;
+ }
+ }
+
+ {
+ const otMacCounters *linkCounters = otLinkGetCounters(otInstance);
+
+ wpanStats->set_phy_rx(linkCounters->mRxTotal);
+ wpanStats->set_phy_tx(linkCounters->mTxTotal);
+ wpanStats->set_mac_unicast_rx(linkCounters->mRxUnicast);
+ wpanStats->set_mac_unicast_tx(linkCounters->mTxUnicast);
+ wpanStats->set_mac_broadcast_rx(linkCounters->mRxBroadcast);
+ wpanStats->set_mac_broadcast_tx(linkCounters->mTxBroadcast);
+ wpanStats->set_mac_tx_ack_req(linkCounters->mTxAckRequested);
+ wpanStats->set_mac_tx_no_ack_req(linkCounters->mTxNoAckRequested);
+ wpanStats->set_mac_tx_acked(linkCounters->mTxAcked);
+ wpanStats->set_mac_tx_data(linkCounters->mTxData);
+ wpanStats->set_mac_tx_data_poll(linkCounters->mTxDataPoll);
+ wpanStats->set_mac_tx_beacon(linkCounters->mTxBeacon);
+ wpanStats->set_mac_tx_beacon_req(linkCounters->mTxBeaconRequest);
+ wpanStats->set_mac_tx_other_pkt(linkCounters->mTxOther);
+ wpanStats->set_mac_tx_retry(linkCounters->mTxRetry);
+ wpanStats->set_mac_rx_data(linkCounters->mRxData);
+ wpanStats->set_mac_rx_data_poll(linkCounters->mRxDataPoll);
+ wpanStats->set_mac_rx_beacon(linkCounters->mRxBeacon);
+ wpanStats->set_mac_rx_beacon_req(linkCounters->mRxBeaconRequest);
+ wpanStats->set_mac_rx_other_pkt(linkCounters->mRxOther);
+ wpanStats->set_mac_rx_filter_whitelist(linkCounters->mRxAddressFiltered);
+ wpanStats->set_mac_rx_filter_dest_addr(linkCounters->mRxDestAddrFiltered);
+ wpanStats->set_mac_tx_fail_cca(linkCounters->mTxErrCca);
+ wpanStats->set_mac_rx_fail_decrypt(linkCounters->mRxErrSec);
+ wpanStats->set_mac_rx_fail_no_frame(linkCounters->mRxErrNoFrame);
+ wpanStats->set_mac_rx_fail_unknown_neighbor(linkCounters->mRxErrUnknownNeighbor);
+ wpanStats->set_mac_rx_fail_invalid_src_addr(linkCounters->mRxErrInvalidSrcAddr);
+ wpanStats->set_mac_rx_fail_fcs(linkCounters->mRxErrFcs);
+ wpanStats->set_mac_rx_fail_other(linkCounters->mRxErrOther);
+ }
+
+ {
+ const otIpCounters *ipCounters = otThreadGetIp6Counters(otInstance);
+
+ wpanStats->set_ip_tx_success(ipCounters->mTxSuccess);
+ wpanStats->set_ip_rx_success(ipCounters->mRxSuccess);
+ wpanStats->set_ip_tx_failure(ipCounters->mTxFailure);
+ wpanStats->set_ip_rx_failure(ipCounters->mRxFailure);
+ }
+ // End of WpanStats section.
+
+ {
+ // Begin of WpanTopoFull section.
+ auto wpanTopoFull = telemetryDataReported.mutable_wpan_topo_full();
+ uint16_t rloc16 = otThreadGetRloc16(otInstance);
+
+ wpanTopoFull->set_rloc16(rloc16);
+
+ {
+ otRouterInfo info;
+
+ if (otThreadGetRouterInfo(otInstance, rloc16, &info) == OT_ERROR_NONE)
+ {
+ wpanTopoFull->set_router_id(info.mRouterId);
+ }
+ else
+ {
+ error = OT_ERROR_FAILED;
+ }
+ }
+
+ {
+ otNeighborInfoIterator iter = OT_NEIGHBOR_INFO_ITERATOR_INIT;
+ otNeighborInfo neighborInfo;
+
+ while (otThreadGetNextNeighborInfo(otInstance, &iter, &neighborInfo) == OT_ERROR_NONE)
+ {
+ neighborTable.push_back(neighborInfo);
+ }
+ }
+ wpanTopoFull->set_neighbor_table_size(neighborTable.size());
+
+ uint16_t childIndex = 0;
+ otChildInfo childInfo;
+ std::vector<otChildInfo> childTable;
+
+ while (otThreadGetChildInfoByIndex(otInstance, childIndex, &childInfo) == OT_ERROR_NONE)
+ {
+ childTable.push_back(childInfo);
+ childIndex++;
+ }
+ wpanTopoFull->set_child_table_size(childTable.size());
+
+ {
+ struct otLeaderData leaderData;
+
+ if (otThreadGetLeaderData(otInstance, &leaderData) == OT_ERROR_NONE)
+ {
+ wpanTopoFull->set_leader_router_id(leaderData.mLeaderRouterId);
+ wpanTopoFull->set_leader_weight(leaderData.mWeighting);
+ // Do not log network_data_version.
+ }
+ else
+ {
+ error = OT_ERROR_FAILED;
+ }
+ }
+
+ uint8_t weight = otThreadGetLocalLeaderWeight(otInstance);
+
+ wpanTopoFull->set_leader_local_weight(weight);
+
+ int8_t rssi = otPlatRadioGetRssi(otInstance);
+
+ wpanTopoFull->set_instant_rssi(rssi);
+
+ const otExtendedPanId *extPanId = otThreadGetExtendedPanId(otInstance);
+ uint64_t extPanIdVal;
+
+ extPanIdVal = ConvertOpenThreadUint64(extPanId->m8);
+ wpanTopoFull->set_has_extended_pan_id(extPanIdVal != 0);
+ // Note: Used leader_router_id instead of leader_rloc16.
+ // Note: Network level info (e.g., extended_pan_id, partition_id, is_active_br) is not logged.
+ // TODO: populate is_active_srp_server, sum_on_link_prefix_changes, preferred_router_id
+ // if needed.
+ // End of WpanTopoFull section.
+
+ // Begin of TopoEntry section.
+ std::map<uint16_t, const otChildInfo *> childMap;
+
+ for (const otChildInfo &childInfo : childTable)
+ {
+ auto pair = childMap.insert({childInfo.mRloc16, &childInfo});
+ if (!pair.second)
+ {
+ // This shouldn't happen, so log an error. It doesn't matter which
+ // duplicate is kept.
+ otbrLogErr("Children with duplicate RLOC16 found: 0x%04x", static_cast<int>(childInfo.mRloc16));
+ }
+ }
+
+ for (const otNeighborInfo &neighborInfo : neighborTable)
+ {
+ auto topoEntry = topoEntryRepeated.mutable_topo_entry_repeated()->add_topo_entries();
+
+ // 0~15: uint16_t rloc_16
+ // 16~31: uint16_t version Thread version of the neighbor
+ uint32_t comboTelemetry1 = 0;
+ comboTelemetry1 |= (((uint32_t)neighborInfo.mRloc16) & 0x0000FFFF);
+ comboTelemetry1 |= ((((uint32_t)neighborInfo.mVersion) & 0x0000FFFF) << 16);
+ topoEntry->set_combo_telemetry1(comboTelemetry1);
+
+ topoEntry->set_age_sec(neighborInfo.mAge);
+
+ // 0~7: uint8_t link_quality_in
+ // 8~15: int8_t average_rssi
+ // 16~23: int8_t last_rssi
+ // 24~31: uint8_t network_data_version
+ uint32_t comboTelemetry2 = 0;
+ comboTelemetry2 |= (((uint32_t)neighborInfo.mLinkQualityIn) & 0x000000FF);
+ comboTelemetry2 |= ((((uint32_t)neighborInfo.mAverageRssi) & 0x000000FF) << 8);
+ comboTelemetry2 |= ((((uint32_t)neighborInfo.mLastRssi) & 0x000000FF) << 16);
+ // network_data_version is populated in the next section.
+ topoEntry->set_combo_telemetry2(comboTelemetry2);
+
+ // Each bit on the flag represents a bool flag
+ // 0: rx_on_when_idle
+ // 1: full_function
+ // 2: secure_data_request
+ // 3: full_network_data
+ // 4: is_child
+ uint32_t topoEntryFlags = 0;
+ topoEntryFlags |= (neighborInfo.mRxOnWhenIdle ? 1 : 0);
+ topoEntryFlags |= ((neighborInfo.mFullThreadDevice ? 1 : 0) << 1);
+ topoEntryFlags |= ((/* secure_data_request */ true ? 1 : 0) << 2);
+ topoEntryFlags |= ((neighborInfo.mFullNetworkData ? 1 : 0) << 3);
+ topoEntry->set_topo_entry_flags(topoEntryFlags);
+
+ topoEntry->set_link_frame_counter(neighborInfo.mLinkFrameCounter);
+ topoEntry->set_mle_frame_counter(neighborInfo.mMleFrameCounter);
+
+ // 0~15: uint16_t mac_frame_error_rate. Frame error rate (0xffff->100%). Requires error tracking feature.
+ // 16~31: uint16_t ip_message_error_rate. (IPv6) msg error rate (0xffff->100%). Requires error tracking
+ // feature.
+ uint32_t comboTelemetry3 = 0;
+ comboTelemetry3 |= ((uint32_t)(neighborInfo.mFrameErrorRate) & 0x0000FFFF);
+ comboTelemetry3 |= ((((uint32_t)neighborInfo.mMessageErrorRate) & 0x0000FFFF) << 16);
+ topoEntry->set_combo_telemetry3(comboTelemetry3);
+
+ if (!neighborInfo.mIsChild)
+ {
+ continue;
+ }
+
+ auto it = childMap.find(neighborInfo.mRloc16);
+ if (it == childMap.end())
+ {
+ otbrLogErr("Neighbor 0x%04x not found in child table", static_cast<int>(neighborInfo.mRloc16));
+ continue;
+ }
+ const otChildInfo *childInfo = it->second;
+
+ comboTelemetry2 |= ((((uint32_t)childInfo->mNetworkDataVersion) & 0x000000FF) << 24);
+ topoEntry->set_combo_telemetry2(comboTelemetry2);
+
+ topoEntryFlags |= ((/* is_child */ true ? 1 : 0) << 4);
+ topoEntry->set_topo_entry_flags(topoEntryFlags);
+
+ topoEntry->set_timeout_sec(childInfo->mTimeout);
+ }
+ // End of TopoEntry section.
+ }
+
+ {
+ // Begin of WpanBorderRouter section.
+ auto wpanBorderRouter = telemetryDataReported.mutable_wpan_border_router();
+ // Begin of BorderRoutingCounters section.
+ auto borderRoutingCouters = wpanBorderRouter->mutable_border_routing_counters();
+ const otBorderRoutingCounters *otBorderRoutingCounters = otIp6GetBorderRoutingCounters(otInstance);
+
+ borderRoutingCouters->mutable_inbound_unicast()->set_packet_count(
+ otBorderRoutingCounters->mInboundUnicast.mPackets);
+ borderRoutingCouters->mutable_inbound_unicast()->set_byte_count(
+ otBorderRoutingCounters->mInboundUnicast.mBytes);
+ borderRoutingCouters->mutable_inbound_multicast()->set_packet_count(
+ otBorderRoutingCounters->mInboundMulticast.mPackets);
+ borderRoutingCouters->mutable_inbound_multicast()->set_byte_count(
+ otBorderRoutingCounters->mInboundMulticast.mBytes);
+ borderRoutingCouters->mutable_outbound_unicast()->set_packet_count(
+ otBorderRoutingCounters->mOutboundUnicast.mPackets);
+ borderRoutingCouters->mutable_outbound_unicast()->set_byte_count(
+ otBorderRoutingCounters->mOutboundUnicast.mBytes);
+ borderRoutingCouters->mutable_outbound_multicast()->set_packet_count(
+ otBorderRoutingCounters->mOutboundMulticast.mPackets);
+ borderRoutingCouters->mutable_outbound_multicast()->set_byte_count(
+ otBorderRoutingCounters->mOutboundMulticast.mBytes);
+ borderRoutingCouters->set_ra_rx(otBorderRoutingCounters->mRaRx);
+ borderRoutingCouters->set_ra_tx_success(otBorderRoutingCounters->mRaTxSuccess);
+ borderRoutingCouters->set_ra_tx_failure(otBorderRoutingCounters->mRaTxFailure);
+ borderRoutingCouters->set_rs_rx(otBorderRoutingCounters->mRsRx);
+ borderRoutingCouters->set_rs_tx_success(otBorderRoutingCounters->mRsTxSuccess);
+ borderRoutingCouters->set_rs_tx_failure(otBorderRoutingCounters->mRsTxFailure);
+
+ // End of BorderRoutingCounters section.
+
+#if OTBR_ENABLE_SRP_ADVERTISING_PROXY
+ // Begin of SrpServerInfo section.
+ {
+ auto srpServer = wpanBorderRouter->mutable_srp_server();
+ otSrpServerLeaseInfo leaseInfo;
+ const otSrpServerHost *host = nullptr;
+ const otSrpServerResponseCounters *responseCounters = otSrpServerGetResponseCounters(otInstance);
+
+ srpServer->set_state(SrpServerStateFromOtSrpServerState(otSrpServerGetState(otInstance)));
+ srpServer->set_port(otSrpServerGetPort(otInstance));
+ srpServer->set_address_mode(
+ SrpServerAddressModeFromOtSrpServerAddressMode(otSrpServerGetAddressMode(otInstance)));
+
+ auto srpServerHosts = srpServer->mutable_hosts();
+ auto srpServerServices = srpServer->mutable_services();
+ auto srpServerResponseCounters = srpServer->mutable_response_counters();
+
+ while ((host = otSrpServerGetNextHost(otInstance, host)))
+ {
+ const otSrpServerService *service = nullptr;
+
+ if (otSrpServerHostIsDeleted(host))
+ {
+ srpServerHosts->set_deleted_count(srpServerHosts->deleted_count() + 1);
+ }
+ else
+ {
+ srpServerHosts->set_fresh_count(srpServerHosts->fresh_count() + 1);
+ otSrpServerHostGetLeaseInfo(host, &leaseInfo);
+ srpServerHosts->set_lease_time_total_ms(srpServerHosts->lease_time_total_ms() + leaseInfo.mLease);
+ srpServerHosts->set_key_lease_time_total_ms(srpServerHosts->key_lease_time_total_ms() +
+ leaseInfo.mKeyLease);
+ srpServerHosts->set_remaining_lease_time_total_ms(srpServerHosts->remaining_lease_time_total_ms() +
+ leaseInfo.mRemainingLease);
+ srpServerHosts->set_remaining_key_lease_time_total_ms(
+ srpServerHosts->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
+ }
+
+ while ((service = otSrpServerHostGetNextService(host, service)))
+ {
+ if (otSrpServerServiceIsDeleted(service))
+ {
+ srpServerServices->set_deleted_count(srpServerServices->deleted_count() + 1);
+ }
+ else
+ {
+ srpServerServices->set_fresh_count(srpServerServices->fresh_count() + 1);
+ otSrpServerServiceGetLeaseInfo(service, &leaseInfo);
+ srpServerServices->set_lease_time_total_ms(srpServerServices->lease_time_total_ms() +
+ leaseInfo.mLease);
+ srpServerServices->set_key_lease_time_total_ms(srpServerServices->key_lease_time_total_ms() +
+ leaseInfo.mKeyLease);
+ srpServerServices->set_remaining_lease_time_total_ms(
+ srpServerServices->remaining_lease_time_total_ms() + leaseInfo.mRemainingLease);
+ srpServerServices->set_remaining_key_lease_time_total_ms(
+ srpServerServices->remaining_key_lease_time_total_ms() + leaseInfo.mRemainingKeyLease);
+ }
+ }
+ }
+
+ srpServerResponseCounters->set_success_count(responseCounters->mSuccess);
+ srpServerResponseCounters->set_server_failure_count(responseCounters->mServerFailure);
+ srpServerResponseCounters->set_format_error_count(responseCounters->mFormatError);
+ srpServerResponseCounters->set_name_exists_count(responseCounters->mNameExists);
+ srpServerResponseCounters->set_refused_count(responseCounters->mRefused);
+ srpServerResponseCounters->set_other_count(responseCounters->mOther);
+ }
+ // End of SrpServerInfo section.
+#endif // OTBR_ENABLE_SRP_ADVERTISING_PROXY
+
+#if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
+ // Begin of DnsServerInfo section.
+ {
+ auto dnsServer = wpanBorderRouter->mutable_dns_server();
+ auto dnsServerResponseCounters = dnsServer->mutable_response_counters();
+ otDnssdCounters otDnssdCounters = *otDnssdGetCounters(otInstance);
+
+ dnsServerResponseCounters->set_success_count(otDnssdCounters.mSuccessResponse);
+ dnsServerResponseCounters->set_server_failure_count(otDnssdCounters.mServerFailureResponse);
+ dnsServerResponseCounters->set_format_error_count(otDnssdCounters.mFormatErrorResponse);
+ dnsServerResponseCounters->set_name_error_count(otDnssdCounters.mNameErrorResponse);
+ dnsServerResponseCounters->set_not_implemented_count(otDnssdCounters.mNotImplementedResponse);
+ dnsServerResponseCounters->set_other_count(otDnssdCounters.mOtherResponse);
+
+ dnsServer->set_resolved_by_local_srp_count(otDnssdCounters.mResolvedBySrp);
+ }
+ // End of DnsServerInfo section.
+#endif // OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
+
+ // Start of MdnsInfo section.
+ if (aPublisher != nullptr)
+ {
+ auto mdns = wpanBorderRouter->mutable_mdns();
+ const MdnsTelemetryInfo &mdnsInfo = aPublisher->GetMdnsTelemetryInfo();
+
+ CopyMdnsResponseCounters(mdnsInfo.mHostRegistrations, mdns->mutable_host_registration_responses());
+ CopyMdnsResponseCounters(mdnsInfo.mServiceRegistrations, mdns->mutable_service_registration_responses());
+ CopyMdnsResponseCounters(mdnsInfo.mHostResolutions, mdns->mutable_host_resolution_responses());
+ CopyMdnsResponseCounters(mdnsInfo.mServiceResolutions, mdns->mutable_service_resolution_responses());
+
+ mdns->set_host_registration_ema_latency_ms(mdnsInfo.mHostRegistrationEmaLatency);
+ mdns->set_service_registration_ema_latency_ms(mdnsInfo.mServiceRegistrationEmaLatency);
+ mdns->set_host_resolution_ema_latency_ms(mdnsInfo.mHostResolutionEmaLatency);
+ mdns->set_service_resolution_ema_latency_ms(mdnsInfo.mServiceResolutionEmaLatency);
+ }
+ // End of MdnsInfo section.
+
+ // End of WpanBorderRouter section.
+
+ // Start of WpanRcp section.
+ {
+ auto wpanRcp = telemetryDataReported.mutable_wpan_rcp();
+ const otRadioSpinelMetrics *otRadioSpinelMetrics = otSysGetRadioSpinelMetrics();
+ auto rcpStabilityStatistics = wpanRcp->mutable_rcp_stability_statistics();
+
+ if (otRadioSpinelMetrics != nullptr)
+ {
+ rcpStabilityStatistics->set_rcp_timeout_count(otRadioSpinelMetrics->mRcpTimeoutCount);
+ rcpStabilityStatistics->set_rcp_reset_count(otRadioSpinelMetrics->mRcpUnexpectedResetCount);
+ rcpStabilityStatistics->set_rcp_restoration_count(otRadioSpinelMetrics->mRcpRestorationCount);
+ rcpStabilityStatistics->set_spinel_parse_error_count(otRadioSpinelMetrics->mSpinelParseErrorCount);
+ }
+
+ // TODO: provide rcp_firmware_update_count info.
+ rcpStabilityStatistics->set_thread_stack_uptime(otInstanceGetUptime(otInstance));
+
+ const otRcpInterfaceMetrics *otRcpInterfaceMetrics = otSysGetRcpInterfaceMetrics();
+
+ if (otRcpInterfaceMetrics != nullptr)
+ {
+ auto rcpInterfaceStatistics = wpanRcp->mutable_rcp_interface_statistics();
+
+ rcpInterfaceStatistics->set_rcp_interface_type(otRcpInterfaceMetrics->mRcpInterfaceType);
+ rcpInterfaceStatistics->set_transferred_frames_count(otRcpInterfaceMetrics->mTransferredFrameCount);
+ rcpInterfaceStatistics->set_transferred_valid_frames_count(
+ otRcpInterfaceMetrics->mTransferredValidFrameCount);
+ rcpInterfaceStatistics->set_transferred_garbage_frames_count(
+ otRcpInterfaceMetrics->mTransferredGarbageFrameCount);
+ rcpInterfaceStatistics->set_rx_frames_count(otRcpInterfaceMetrics->mRxFrameCount);
+ rcpInterfaceStatistics->set_rx_bytes_count(otRcpInterfaceMetrics->mRxFrameByteCount);
+ rcpInterfaceStatistics->set_tx_frames_count(otRcpInterfaceMetrics->mTxFrameCount);
+ rcpInterfaceStatistics->set_tx_bytes_count(otRcpInterfaceMetrics->mTxFrameByteCount);
+ }
+ }
+ // End of WpanRcp section.
+
+ // Start of CoexMetrics section.
+ {
+ auto coexMetrics = telemetryDataReported.mutable_coex_metrics();
+ otRadioCoexMetrics otRadioCoexMetrics;
+
+ if (otPlatRadioGetCoexMetrics(otInstance, &otRadioCoexMetrics) == OT_ERROR_NONE)
+ {
+ coexMetrics->set_count_tx_request(otRadioCoexMetrics.mNumTxRequest);
+ coexMetrics->set_count_tx_grant_immediate(otRadioCoexMetrics.mNumTxGrantImmediate);
+ coexMetrics->set_count_tx_grant_wait(otRadioCoexMetrics.mNumTxGrantWait);
+ coexMetrics->set_count_tx_grant_wait_activated(otRadioCoexMetrics.mNumTxGrantWaitActivated);
+ coexMetrics->set_count_tx_grant_wait_timeout(otRadioCoexMetrics.mNumTxGrantWaitTimeout);
+ coexMetrics->set_count_tx_grant_deactivated_during_request(
+ otRadioCoexMetrics.mNumTxGrantDeactivatedDuringRequest);
+ coexMetrics->set_tx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgTxRequestToGrantTime);
+ coexMetrics->set_count_rx_request(otRadioCoexMetrics.mNumRxRequest);
+ coexMetrics->set_count_rx_grant_immediate(otRadioCoexMetrics.mNumRxGrantImmediate);
+ coexMetrics->set_count_rx_grant_wait(otRadioCoexMetrics.mNumRxGrantWait);
+ coexMetrics->set_count_rx_grant_wait_activated(otRadioCoexMetrics.mNumRxGrantWaitActivated);
+ coexMetrics->set_count_rx_grant_wait_timeout(otRadioCoexMetrics.mNumRxGrantWaitTimeout);
+ coexMetrics->set_count_rx_grant_deactivated_during_request(
+ otRadioCoexMetrics.mNumRxGrantDeactivatedDuringRequest);
+ coexMetrics->set_count_rx_grant_none(otRadioCoexMetrics.mNumRxGrantNone);
+ coexMetrics->set_rx_average_request_to_grant_time_us(otRadioCoexMetrics.mAvgRxRequestToGrantTime);
+ }
+ else
+ {
+ error = OT_ERROR_FAILED;
+ }
+ }
+ // End of CoexMetrics section.
+ }
+
+ deviceInfoReported.set_thread_version(otThreadGetVersion());
+ deviceInfoReported.set_ot_rcp_version(otGetRadioVersionString(otInstance));
+ // TODO: populate ot_host_version, thread_daemon_version.
+
+ return error;
+}
+
+int PushAtom(const ThreadnetworkTelemetryDataReported &telemetryDataReported)
+{
+ const std::string &wpanStats = telemetryDataReported.wpan_stats().SerializeAsString();
+ const std::string &wpanTopoFull = telemetryDataReported.wpan_topo_full().SerializeAsString();
+ const std::string &wpanBorderRouter = telemetryDataReported.wpan_border_router().SerializeAsString();
+ const std::string &wpanRcp = telemetryDataReported.wpan_rcp().SerializeAsString();
+ const std::string &coexMetrics = telemetryDataReported.coex_metrics().SerializeAsString();
+ threadnetwork::BytesField wpanStatsBytesField{wpanStats.c_str(), wpanStats.size()};
+ threadnetwork::BytesField wpanTopoFullBytesField{wpanTopoFull.c_str(), wpanTopoFull.size()};
+ threadnetwork::BytesField wpanBorderRouterBytesField{wpanBorderRouter.c_str(), wpanBorderRouter.size()};
+ threadnetwork::BytesField wpanRcpBytesField{wpanRcp.c_str(), wpanRcp.size()};
+ threadnetwork::BytesField coexMetricsBytesField{coexMetrics.c_str(), coexMetrics.size()};
+ return threadnetwork::stats_write(threadnetwork::THREADNETWORK_TELEMETRY_DATA_REPORTED, wpanStatsBytesField,
+ wpanTopoFullBytesField, wpanBorderRouterBytesField, wpanRcpBytesField,
+ coexMetricsBytesField);
+}
+
+int PushAtom(const ThreadnetworkTopoEntryRepeated &topoEntryRepeated)
+{
+ const std::string &topoEntryField = topoEntryRepeated.topo_entry_repeated().SerializeAsString();
+ threadnetwork::BytesField topoEntryFieldBytesField{topoEntryField.c_str(), topoEntryField.size()};
+ return threadnetwork::stats_write(threadnetwork::THREADNETWORK_TOPO_ENTRY_REPEATED, topoEntryFieldBytesField);
+}
+
+int PushAtom(const ThreadnetworkDeviceInfoReported &deviceInfoReported)
+{
+ const std::string &otHostVersion = deviceInfoReported.ot_host_version();
+ const std::string &otRcpVersion = deviceInfoReported.ot_rcp_version();
+ const int32_t &threadVersion = deviceInfoReported.thread_version();
+ const std::string &threadDaemonVersion = deviceInfoReported.thread_daemon_version();
+ return threadnetwork::stats_write(threadnetwork::THREADNETWORK_DEVICE_INFO_REPORTED, otHostVersion.c_str(),
+ otRcpVersion.c_str(), threadVersion, threadDaemonVersion.c_str());
+}
+
+void RetrieveAndPushAtoms(otInstance *otInstance)
+{
+ ThreadnetworkTelemetryDataReported telemetryDataReported;
+ ThreadnetworkTopoEntryRepeated topoEntryRepeated;
+ ThreadnetworkDeviceInfoReported deviceInfoReported;
+
+ if (RetrieveTelemetryAtom(otInstance, nullptr, telemetryDataReported, topoEntryRepeated, deviceInfoReported) !=
+ OTBR_ERROR_NONE)
+ {
+ otbrLogWarning("Some telemetries are not populated");
+ }
+ if (PushAtom(telemetryDataReported) <= 0)
+ {
+ otbrLogWarning("Failed to push ThreadnetworkTelemetryDataReported");
+ }
+ if (PushAtom(topoEntryRepeated) <= 0)
+ {
+ otbrLogWarning("Failed to push ThreadnetworkTopoEntryRepeated");
+ }
+ if (PushAtom(deviceInfoReported) <= 0)
+ {
+ otbrLogWarning("Failed to push ThreadnetworkDeviceInfoReported");
+ }
+}
+} // namespace Android
+} // namespace otbr
diff --git a/src/android/otdaemon_telemetry.hpp b/src/android/otdaemon_telemetry.hpp
new file mode 100644
index 00000000..669d1c80
--- /dev/null
+++ b/src/android/otdaemon_telemetry.hpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023, The OpenThread Authors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef OTDAEMON_TELEMETRY_HPP_
+#define OTDAEMON_TELEMETRY_HPP_
+
+#include <openthread/instance.h>
+
+namespace otbr {
+namespace Android {
+void RetrieveAndPushAtoms(otInstance *otInstance);
+} // namespace Android
+} // namespace otbr
+#endif // OTDAEMON_TELEMETRY_HPP_
diff --git a/src/proto/threadnetwork_atoms.proto b/src/proto/threadnetwork_atoms.proto
new file mode 100644
index 00000000..e8ca7ea7
--- /dev/null
+++ b/src/proto/threadnetwork_atoms.proto
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) 2023, The OpenThread Authors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd.threadnetwork;
+
+option java_package = "com.android.os.threadnetwork";
+option java_multiple_files = true;
+
+// Thread Telemetry data definition.
+message ThreadnetworkTelemetryDataReported {
+ message WpanStats {
+ optional int32 phy_rx = 1;
+ optional int32 phy_tx = 2;
+ optional int32 mac_unicast_rx = 3;
+ optional int32 mac_unicast_tx = 4;
+ optional int32 mac_broadcast_rx = 5;
+ optional int32 mac_broadcast_tx = 6;
+ optional int32 mac_tx_ack_req = 7;
+ optional int32 mac_tx_no_ack_req = 8;
+ optional int32 mac_tx_acked = 9;
+ optional int32 mac_tx_data = 10;
+ optional int32 mac_tx_data_poll = 11;
+ optional int32 mac_tx_beacon = 12;
+ optional int32 mac_tx_beacon_req = 13;
+ optional int32 mac_tx_other_pkt = 14;
+ optional int32 mac_tx_retry = 15;
+ optional int32 mac_rx_data = 16;
+ optional int32 mac_rx_data_poll = 17;
+ optional int32 mac_rx_beacon = 18;
+ optional int32 mac_rx_beacon_req = 19;
+ optional int32 mac_rx_other_pkt = 20;
+ optional int32 mac_rx_filter_whitelist = 21;
+ optional int32 mac_rx_filter_dest_addr = 22;
+ optional int32 mac_tx_fail_cca = 23;
+ optional int32 mac_rx_fail_decrypt = 24;
+ optional int32 mac_rx_fail_no_frame = 25;
+ optional int32 mac_rx_fail_unknown_neighbor = 26;
+ optional int32 mac_rx_fail_invalid_src_addr = 27;
+ optional int32 mac_rx_fail_fcs = 28;
+ optional int32 mac_rx_fail_other = 29;
+ optional int32 ip_tx_success = 30;
+ optional int32 ip_rx_success = 31;
+ optional int32 ip_tx_failure = 32;
+ optional int32 ip_rx_failure = 33;
+ optional uint32 node_type = 34;
+ optional uint32 channel = 35;
+ optional int32 radio_tx_power = 36;
+ optional float mac_cca_fail_rate = 37;
+ }
+
+ message WpanTopoFull {
+ optional uint32 rloc16 = 1;
+ optional uint32 router_id = 2;
+ optional uint32 leader_router_id = 3;
+ optional uint32 leader_rloc16 = 4; // replaced optional bytes leader_address = 5;
+ optional uint32 leader_weight = 5;
+ optional uint32 leader_local_weight = 6;
+ optional uint32 preferred_router_id = 7;
+ optional uint32 partition_id = 8;
+ optional uint32 child_table_size = 9;
+ optional uint32 neighbor_table_size = 10;
+ optional int32 instant_rssi = 11;
+ optional bool has_extended_pan_id = 12;
+ optional bool is_active_br = 13;
+ optional bool is_active_srp_server = 14;
+ optional uint32 sum_on_link_prefix_changes = 15;
+ }
+
+ enum NodeType {
+ NODE_TYPE_UNSPECIFIED = 0;
+ NODE_TYPE_ROUTER = 1;
+ NODE_TYPE_END = 2;
+ NODE_TYPE_SLEEPY_END = 3;
+ NODE_TYPE_MINIMAL_END = 4;
+
+ NODE_TYPE_OFFLINE = 5;
+ NODE_TYPE_DISABLED = 6;
+ NODE_TYPE_DETACHED = 7;
+
+ NODE_TYPE_NL_LURKER = 0x10;
+ NODE_TYPE_COMMISSIONER = 0x20;
+ NODE_TYPE_LEADER = 0x40;
+ }
+
+ message PacketsAndBytes {
+ optional int64 packet_count = 1;
+ optional int64 byte_count = 2;
+ }
+
+ message Nat64TrafficCounters {
+ optional int64 ipv4_to_ipv6_packets = 1;
+ optional int64 ipv4_to_ipv6_bytes = 2;
+ optional int64 ipv6_to_ipv4_packets = 3;
+ optional int64 ipv6_to_ipv4_bytes = 4;
+ }
+
+ message Nat64ProtocolCounters {
+ optional Nat64TrafficCounters tcp = 1;
+ optional Nat64TrafficCounters udp = 2;
+ optional Nat64TrafficCounters icmp = 3;
+ }
+
+ message Nat64PacketCounters {
+ optional int64 ipv4_to_ipv6_packets = 1;
+ optional int64 ipv6_to_ipv4_packets = 2;
+ }
+
+ message Nat64ErrorCounters {
+ optional Nat64PacketCounters unknown = 1;
+ optional Nat64PacketCounters illegal_packet = 2;
+ optional Nat64PacketCounters unsupported_protocol = 3;
+ optional Nat64PacketCounters no_mapping = 4;
+ }
+
+ message BorderRoutingCounters {
+ // The number of Router Advertisement packets received by otbr-agent on the
+ // infra link
+ optional int64 ra_rx = 1;
+
+ // The number of Router Advertisement packets successfully transmitted by
+ // otbr-agent on the infra link.
+ optional int64 ra_tx_success = 2;
+
+ // The number of Router Advertisement packets failed to transmit by
+ // otbr-agent on the infra link.
+ optional int64 ra_tx_failure = 3;
+
+ // The number of Router Solicitation packets received by otbr-agent on the
+ // infra link
+ optional int64 rs_rx = 4;
+
+ // The number of Router Solicitation packets successfully transmitted by
+ // otbr-agent on the infra link.
+ optional int64 rs_tx_success = 5;
+
+ // The number of Router Solicitation packets failed to transmit by
+ // otbr-agent on the infra link.
+ optional int64 rs_tx_failure = 6;
+
+ // The counters for inbound unicast packets
+ optional PacketsAndBytes inbound_unicast = 7;
+
+ // The counters for inbound multicast packets
+ optional PacketsAndBytes inbound_multicast = 8;
+
+ // The counters for outbound unicast packets
+ optional PacketsAndBytes outbound_unicast = 9;
+
+ // The counters for outbound multicast packets
+ optional PacketsAndBytes outbound_multicast = 10;
+
+ // The inbound and outbound NAT64 traffic through the border router
+ optional Nat64ProtocolCounters nat64_protocol_counters = 11;
+
+ // Error counters for NAT64 translator on the border router
+ optional Nat64ErrorCounters nat64_error_counters = 12;
+ }
+
+ message SrpServerRegistrationInfo {
+ // The number of active hosts/services registered on the SRP server.
+ optional uint32 fresh_count = 1;
+
+ // The number of hosts/services in 'Deleted' state on the SRP server.
+ optional uint32 deleted_count = 2;
+
+ // The sum of lease time in milliseconds of all active hosts/services on the
+ // SRP server.
+ optional uint64 lease_time_total_ms = 3;
+
+ // The sum of key lease time in milliseconds of all active hosts/services on
+ // the SRP server.
+ optional uint64 key_lease_time_total_ms = 4;
+
+ // The sum of remaining lease time in milliseconds of all active
+ // hosts/services on the SRP server.
+ optional uint64 remaining_lease_time_total_ms = 5;
+
+ // The sum of remaining key lease time in milliseconds of all active
+ // hosts/services on the SRP server.
+ optional uint64 remaining_key_lease_time_total_ms = 6;
+ }
+
+ message SrpServerResponseCounters {
+ // The number of successful responses
+ optional uint32 success_count = 1;
+
+ // The number of server failure responses
+ optional uint32 server_failure_count = 2;
+
+ // The number of format error responses
+ optional uint32 format_error_count = 3;
+
+ // The number of 'name exists' responses
+ optional uint32 name_exists_count = 4;
+
+ // The number of refused responses
+ optional uint32 refused_count = 5;
+
+ // The number of other responses
+ optional uint32 other_count = 6;
+ }
+
+ enum SrpServerState {
+ SRP_SERVER_STATE_UNSPECIFIED = 0;
+ SRP_SERVER_STATE_DISABLED = 1;
+ SRP_SERVER_STATE_RUNNING = 2;
+ SRP_SERVER_STATE_STOPPED = 3;
+ }
+
+ // The address mode used by the SRP server
+ enum SrpServerAddressMode {
+ SRP_SERVER_ADDRESS_MODE_UNSPECIFIED = 0;
+ SRP_SERVER_ADDRESS_MODE_UNICAST = 1;
+ SRP_SERVER_ADDRESS_MODE_STATE_ANYCAST = 2;
+ }
+
+ message SrpServerInfo {
+ // The state of the SRP server
+ optional SrpServerState state = 1;
+
+ // Listening port number
+ optional uint32 port = 2;
+ // The address mode {unicast, anycast} of the SRP server
+ optional SrpServerAddressMode address_mode = 3;
+
+ // The registration information of hosts on the SRP server
+ optional SrpServerRegistrationInfo hosts = 4;
+
+ // The registration information of services on the SRP server
+ optional SrpServerRegistrationInfo services = 5;
+
+ // The counters of response codes sent by the SRP server
+ optional SrpServerResponseCounters response_counters = 6;
+ }
+
+ message DnsServerResponseCounters {
+ // The number of successful responses
+ optional uint32 success_count = 1;
+
+ // The number of server failure responses
+ optional uint32 server_failure_count = 2;
+
+ // The number of format error responses
+ optional uint32 format_error_count = 3;
+
+ // The number of name error responses
+ optional uint32 name_error_count = 4;
+
+ // The number of 'not implemented' responses
+ optional uint32 not_implemented_count = 5;
+
+ // The number of other responses
+ optional uint32 other_count = 6;
+ }
+
+ message DnsServerInfo {
+ // The counters of response codes sent by the DNS server
+ optional DnsServerResponseCounters response_counters = 1;
+
+ // The number of DNS queries resolved at the local SRP server
+ optional uint32 resolved_by_local_srp_count = 2;
+ }
+
+ message MdnsResponseCounters {
+ // The number of successful responses
+ optional uint32 success_count = 1;
+
+ // The number of 'not found' responses
+ optional uint32 not_found_count = 2;
+
+ // The number of 'invalid arg' responses
+ optional uint32 invalid_args_count = 3;
+
+ // The number of 'duplicated' responses
+ optional uint32 duplicated_count = 4;
+
+ // The number of 'not implemented' responses
+ optional uint32 not_implemented_count = 5;
+
+ // The number of unknown error responses
+ optional uint32 unknown_error_count = 6;
+
+ // The number of aborted responses
+ optional uint32 aborted_count = 7;
+
+ // The number of invalid state responses
+ optional uint32 invalid_state_count = 8;
+ }
+
+ message MdnsInfo {
+ // The response counters of host registrations
+ optional MdnsResponseCounters host_registration_responses = 1;
+
+ // The response counters of service registrations
+ optional MdnsResponseCounters service_registration_responses = 2;
+
+ // The response counters of host resolutions
+ optional MdnsResponseCounters host_resolution_responses = 3;
+
+ // The response counters of service resolutions
+ optional MdnsResponseCounters service_resolution_responses = 4;
+
+ // The EMA (Exponential Moving Average) latencies of mDNS operations
+
+ // The EMA latency of host registrations in milliseconds
+ optional uint32 host_registration_ema_latency_ms = 5;
+
+ // The EMA latency of service registrations in milliseconds
+ optional uint32 service_registration_ema_latency_ms = 6;
+
+ // The EMA latency of host resolutions in milliseconds
+ optional uint32 host_resolution_ema_latency_ms = 7;
+
+ // The EMA latency of service resolutions in milliseconds
+ optional uint32 service_resolution_ema_latency_ms = 8;
+ }
+
+ enum Nat64State {
+ NAT64_STATE_UNSPECIFIED = 0;
+ NAT64_STATE_DISABLED = 1;
+ NAT64_STATE_NOT_RUNNING = 2;
+ NAT64_STATE_IDLE = 3;
+ NAT64_STATE_ACTIVE = 4;
+ }
+
+ message BorderRoutingNat64State {
+ optional Nat64State prefix_manager_state = 1;
+ optional Nat64State translator_state = 2;
+ }
+
+ message WpanBorderRouter {
+ // Border routing counters
+ optional BorderRoutingCounters border_routing_counters = 1;
+
+ // Information about the SRP server
+ optional SrpServerInfo srp_server = 2;
+
+ // Information about the DNS server
+ optional DnsServerInfo dns_server = 3;
+
+ // Information about the mDNS publisher
+ optional MdnsInfo mdns = 4;
+
+ // Information about the state of components of NAT64
+ optional BorderRoutingNat64State nat64_state = 5;
+ }
+
+ message RcpStabilityStatistics {
+ optional uint32 rcp_timeout_count = 1;
+ optional uint32 rcp_reset_count = 2;
+ optional uint32 rcp_restoration_count = 3;
+ optional uint32 spinel_parse_error_count = 4;
+ optional int32 rcp_firmware_update_count = 5;
+ optional uint32 thread_stack_uptime = 6;
+ }
+
+ message RcpInterfaceStatistics {
+ optional uint32 rcp_interface_type = 1;
+ optional uint64 transferred_frames_count = 2;
+ optional uint64 transferred_valid_frames_count = 3;
+ optional uint64 transferred_garbage_frames_count = 4;
+ optional uint64 rx_frames_count = 5;
+ optional uint64 rx_bytes_count = 6;
+ optional uint64 tx_frames_count = 7;
+ optional uint64 tx_bytes_count = 8;
+ }
+
+ message WpanRcp {
+ optional RcpStabilityStatistics rcp_stability_statistics = 1;
+ optional RcpInterfaceStatistics rcp_interface_statistics = 2;
+ }
+
+ message CoexMetrics {
+ optional uint32 count_tx_request = 1;
+ optional uint32 count_tx_grant_immediate = 2;
+ optional uint32 count_tx_grant_wait = 3;
+ optional uint32 count_tx_grant_wait_activated = 4;
+ optional uint32 count_tx_grant_wait_timeout = 5;
+ optional uint32 count_tx_grant_deactivated_during_request = 6;
+ optional uint32 tx_average_request_to_grant_time_us = 7;
+ optional uint32 count_rx_request = 8;
+ optional uint32 count_rx_grant_immediate = 9;
+ optional uint32 count_rx_grant_wait = 10;
+ optional uint32 count_rx_grant_wait_activated = 11;
+ optional uint32 count_rx_grant_wait_timeout = 12;
+ optional uint32 count_rx_grant_deactivated_during_request = 13;
+ optional uint32 count_rx_grant_none = 14;
+ optional uint32 rx_average_request_to_grant_time_us = 15;
+ }
+
+ optional WpanStats wpan_stats = 1;
+ optional WpanTopoFull wpan_topo_full = 2;
+ optional WpanBorderRouter wpan_border_router = 3;
+ optional WpanRcp wpan_rcp = 4;
+ optional CoexMetrics coex_metrics = 5;
+}
+
+message ThreadnetworkTopoEntryRepeated {
+ message TopoEntry {
+ // 0~15: uint16_t rloc_16
+ // 16~31: uint16_t version Thread version of the neighbor
+ optional uint32 combo_telemetry1 = 1;
+ // 0~7: uint8_t link_quality_in
+ // 8~15: int8_t average_rssi
+ // 16~23: int8_t last_rssi
+ // 24~31: uint8_t network_data_version
+ optional uint32 combo_telemetry2 = 2;
+ optional uint32 age_sec = 3;
+ // Each bit on the flag represents a bool flag
+ // 0: rx_on_when_idle
+ // 1: full_function
+ // 2: secure_data_request
+ // 3: full_network_data
+ // 4: is_child
+ optional uint32 topo_entry_flags = 4;
+ optional uint32 link_frame_counter = 5;
+ optional uint32 mle_frame_counter = 6;
+ optional uint32 timeout_sec = 7;
+ // 0~15: uint16_t frame_error_rate. Frame error rate (0xffff->100%). Requires error tracking feature.
+ // 16~31: uint16_t message_error_rate. (IPv6) msg error rate (0xffff->100%). Requires error tracking feature.
+ optional uint32 combo_telemetry3 = 8;
+ }
+
+ message TopoEntryRepeated {
+ repeated TopoEntry topo_entries = 1;
+ }
+
+ optional TopoEntryRepeated topo_entry_repeated = 1;
+}
+
+message ThreadnetworkDeviceInfoReported {
+ // OpenThread host build version.
+ optional string ot_host_version = 1;
+
+ // OpenThread RCP build version.
+ optional string ot_rcp_version = 2;
+
+ // Thread protocol version.
+ optional int32 thread_version = 3;
+
+ // Thread Daemon version.
+ optional string thread_daemon_version = 4;
+}
diff --git a/tests/android/Android.bp b/tests/android/Android.bp
new file mode 100644
index 00000000..242eec0a
--- /dev/null
+++ b/tests/android/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (c) 2023, The OpenThread Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holder nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+package {
+ default_applicable_licenses: ["external_ot-br-posix_license"],
+}
+
+android_test {
+ name: "OtDaemonUnitTests",
+ manifest: "AndroidManifest.xml",
+ srcs: ["java/**/*.java"],
+ test_suites: [
+ "general-tests",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "frameworks-base-testutils",
+ "guava",
+ "mockito-target-minus-junit4",
+ "ot-daemon-aidl-java",
+ "ot-daemon-testing",
+ "truth",
+ ],
+}
diff --git a/tests/android/AndroidManifest.xml b/tests/android/AndroidManifest.xml
new file mode 100644
index 00000000..153f0680
--- /dev/null
+++ b/tests/android/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2023, The OpenThread Authors.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.thread.openthread.unittests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.thread.openthread.unittests"
+ android:label="Unit tests for com.android.server.thread.openthread" />
+</manifest>
diff --git a/tests/android/AndroidTest.xml b/tests/android/AndroidTest.xml
new file mode 100644
index 00000000..8f898e02
--- /dev/null
+++ b/tests/android/AndroidTest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2023, The OpenThread Authors.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ -->
+
+<configuration description="Config for OT daemon unit test cases">
+ <option name="test-tag" value="OtDaemonUnitTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="OtDaemonUnitTests.apk" />
+ <option name="check-min-sdk" value="true" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.thread.openthread.unittests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ <!-- Ignores tests introduced by frameworks-base-testutils -->
+ <option name="exclude-filter" value="android.os.test.TestLooperTest"/>
+ <option name="exclude-filter" value="com.android.test.filters.SelectTestTests"/>
+ </test>
+</configuration>
diff --git a/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java b/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
new file mode 100644
index 00000000..4b845537
--- /dev/null
+++ b/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2023, The OpenThread Authors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.thread.openthread.testing;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.thread.openthread.IOtDaemonCallback;
+import com.android.server.thread.openthread.IOtStatusReceiver;
+import com.android.server.thread.openthread.OtDaemonState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Unit tests for {@link FakeOtDaemon}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FakeOtDaemonTest {
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
+ // Active Timestamp: 1
+ // Channel: 19
+ // Channel Mask: 0x07FFF800
+ // Ext PAN ID: ACC214689BC40BDF
+ // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
+ // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
+ // Network Name: OpenThread-d9a0
+ // PAN ID: 0xD9A0
+ // PSKc: A245479C836D551B9CA557F7B9D351B4
+ // Security Policy: 672 onrcb
+ private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+
+ private FakeOtDaemon mFakeOtDaemon;
+ private TestLooper mTestLooper;
+ @Mock private ParcelFileDescriptor mMockTunFd;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mFakeOtDaemon = new FakeOtDaemon(new Handler(mTestLooper.getLooper()));
+ }
+
+ @Test
+ public void initialize_succeed_tunFdIsSet() throws Exception {
+ mFakeOtDaemon.initialize(mMockTunFd);
+
+ assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
+ }
+
+ @Test
+ public void registerStateCallback_noStateChange_callbackIsInvoked() throws Exception {
+ mFakeOtDaemon.initialize(mMockTunFd);
+ final AtomicReference<OtDaemonState> stateRef = new AtomicReference<>();
+ final AtomicLong listenerIdRef = new AtomicLong();
+
+ mFakeOtDaemon.registerStateCallback(
+ new IOtDaemonCallback.Default() {
+ @Override
+ public void onStateChanged(OtDaemonState newState, long listenerId) {
+ stateRef.set(newState);
+ listenerIdRef.set(listenerId);
+ }
+ },
+ 7 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ OtDaemonState state = stateRef.get();
+ assertThat(state).isNotNull();
+ assertThat(state.isInterfaceUp).isFalse();
+ assertThat(state.deviceRole).isEqualTo(FakeOtDaemon.OT_DEVICE_ROLE_DISABLED);
+ assertThat(state.activeDatasetTlvs).isEmpty();
+ assertThat(state.pendingDatasetTlvs).isEmpty();
+ assertThat(state.multicastForwardingEnabled).isFalse();
+ assertThat(listenerIdRef.get()).isEqualTo(7);
+ }
+
+ @Test
+ public void setJoinException_joinFailsWithTheGivenException() {
+ final RemoteException joinException = new RemoteException("join() failed");
+
+ mFakeOtDaemon.setJoinException(joinException);
+
+ RemoteException thrown =
+ assertThrows(
+ RemoteException.class,
+ () ->
+ mFakeOtDaemon.join(
+ DEFAULT_ACTIVE_DATASET_TLVS,
+ new IOtStatusReceiver.Default()));
+ assertThat(thrown).isEqualTo(joinException);
+ }
+
+ @Test
+ public void join_succeed_statesAreSentBack() throws Exception {
+ final AtomicBoolean succeedRef = new AtomicBoolean(false);
+ final AtomicReference<OtDaemonState> stateRef = new AtomicReference<>();
+ mFakeOtDaemon.registerStateCallback(
+ new IOtDaemonCallback.Default() {
+ @Override
+ public void onStateChanged(OtDaemonState newState, long listenerId) {
+ stateRef.set(newState);
+ }
+ },
+ 11 /* listenerId */);
+
+ mFakeOtDaemon.join(
+ DEFAULT_ACTIVE_DATASET_TLVS,
+ new IOtStatusReceiver.Default() {
+ @Override
+ public void onSuccess() {
+ succeedRef.set(true);
+ }
+ });
+ mTestLooper.dispatchAll();
+ final OtDaemonState intermediateDetachedState = stateRef.get();
+ mTestLooper.moveTimeForward(FakeOtDaemon.JOIN_DELAY.toMillis());
+ mTestLooper.dispatchAll();
+
+ assertThat(intermediateDetachedState.isInterfaceUp).isTrue();
+ assertThat(intermediateDetachedState.deviceRole)
+ .isEqualTo(FakeOtDaemon.OT_DEVICE_ROLE_DETACHED);
+ assertThat(succeedRef.get()).isTrue();
+ final OtDaemonState state = stateRef.get();
+ assertThat(state.isInterfaceUp).isTrue();
+ assertThat(state.deviceRole).isEqualTo(FakeOtDaemon.OT_DEVICE_ROLE_LEADER);
+ assertThat(state.activeDatasetTlvs).isEqualTo(DEFAULT_ACTIVE_DATASET_TLVS);
+ assertThat(state.multicastForwardingEnabled).isTrue();
+ }
+}