diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-10 18:50:48 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-10 18:50:48 +0000 |
commit | d1f85e7a82a6a98fa8021fa87d4e76612530629e (patch) | |
tree | 924ee99440d2870dad726ca994704d4cc2f76194 | |
parent | 6131f7f0eca36793a80c7cb78cba903a7ecf710d (diff) | |
parent | 44f9321fb512b447aff1b623463e182bc5c35455 (diff) | |
download | ot-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.bp | 44 | ||||
-rw-r--r-- | TEST_MAPPING | 5 | ||||
-rwxr-xr-x | script/make-java-pretty | 8 | ||||
-rw-r--r-- | src/Android.bp | 44 | ||||
-rw-r--r-- | src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl | 8 | ||||
-rw-r--r-- | src/android/java/Android.bp | 50 | ||||
-rw-r--r-- | src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java | 213 | ||||
-rw-r--r-- | src/android/otdaemon_server.cpp | 38 | ||||
-rw-r--r-- | src/android/otdaemon_server.hpp | 5 | ||||
-rw-r--r-- | src/android/otdaemon_telemetry.cpp | 671 | ||||
-rw-r--r-- | src/android/otdaemon_telemetry.hpp | 39 | ||||
-rw-r--r-- | src/proto/threadnetwork_atoms.proto | 470 | ||||
-rw-r--r-- | tests/android/Android.bp | 54 | ||||
-rw-r--r-- | tests/android/AndroidManifest.xml | 42 | ||||
-rw-r--r-- | tests/android/AndroidTest.xml | 48 | ||||
-rw-r--r-- | tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java | 179 |
16 files changed, 1918 insertions, 0 deletions
@@ -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(); + } +} |