Snap for 11685116 from 4b113655fda72a00e3d793de4cc1cec9e6e1614f to mainline-extservices-release Change-Id: I35dcbc198c4c3044d73ee7d1b947909850754aaa
diff --git a/Android.bp b/Android.bp index ae31237..f6ba8a5 100644 --- a/Android.bp +++ b/Android.bp
@@ -27,6 +27,7 @@ // package { + default_team: "trendy_team_fwk_thread_network", default_applicable_licenses: ["external_ot-br-posix_license"], } @@ -133,16 +134,27 @@ "-DOTBR_CONFIG_ANDROID_VERSION_HEADER_ENABLE=1", "-DOTBR_CONFIG_FILE=\"src/android/otbr-config-android.h\"", "-DOTBR_ENABLE_VENDOR_SERVER=1", // for OtDaemonServer + "-DOTBR_ENABLE_BACKBONE_ROUTER=1", "-DOTBR_ENABLE_BORDER_ROUTING=1", + "-DOTBR_ENABLE_BORDER_ROUTING_COUNTERS=1", "-DOTBR_ENABLE_BORDER_AGENT=1", "-DOTBR_ENABLE_PUBLISH_MESHCOP_BA_ID=1", // Used for bypassing the macro check. In fact mdnssd is not used because we don't compile // the related source files. "-DOTBR_ENABLE_MDNS_MDNSSD=1", "-DOTBR_ENABLE_SRP_ADVERTISING_PROXY=1", - "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=0", + "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=1", "-DOTBR_ENABLE_SRP_SERVER_AUTO_ENABLE_MODE=1", "-DOTBR_PACKAGE_NAME=\"OTBR_AGENT\"", + "-DOTBR_STOP_BORDER_AGENT_ON_INIT=1", + // The platform specific rules for selecting infrastructure link do not apply to Android + "-DOTBR_ENABLE_VENDOR_INFRA_LINK_SELECT=0", + + // Disable 1.4 features, they are not supported on Android yet. + "-DOTBR_ENABLE_NAT64=0", + "-DOTBR_ENABLE_DNS_UPSTREAM_QUERY=0", + "-DOTBR_ENABLE_DHCP6_PD=0", + "-DOTBR_ENABLE_TREL=0", ], srcs: [ @@ -150,6 +162,7 @@ "src/android/mdns_publisher.cpp", "src/android/otdaemon_server.cpp", "src/android/otdaemon_telemetry.cpp", + "src/backbone_router/backbone_agent.cpp", "src/border_agent/border_agent.cpp", "src/ncp/ncp_openthread.cpp", "src/sdp_proxy/advertising_proxy.cpp",
diff --git a/src/Android.bp b/src/Android.bp index d647729..b9a3a54 100644 --- a/src/Android.bp +++ b/src/Android.bp
@@ -26,6 +26,7 @@ // POSSIBILITY OF SUCH DAMAGE. package { + default_team: "trendy_team_fwk_thread_network", default_applicable_licenses: ["external_ot-br-posix_license"], }
diff --git a/src/agent/application.cpp b/src/agent/application.cpp index 73da484..16f4082 100644 --- a/src/agent/application.cpp +++ b/src/agent/application.cpp
@@ -104,8 +104,14 @@ mPublisher->Start(); #endif #if OTBR_ENABLE_BORDER_AGENT +// This is for delaying publishing the MeshCoP service until the correct +// vendor name and OUI etc. are correctly set by BorderAgent::SetMeshCopServiceValues() +#if OTBR_STOP_BORDER_AGENT_ON_INIT + mBorderAgent.SetEnabled(false); +#else mBorderAgent.SetEnabled(true); #endif +#endif #if OTBR_ENABLE_BACKBONE_ROUTER mBackboneAgent.Init(); #endif
diff --git a/src/android/aidl/Android.bp b/src/android/aidl/Android.bp index f33ad8c..bf25b72 100644 --- a/src/android/aidl/Android.bp +++ b/src/android/aidl/Android.bp
@@ -27,6 +27,7 @@ // package { + default_team: "trendy_team_fwk_thread_network", default_applicable_licenses: ["external_ot-br-posix_license"], } @@ -37,6 +38,7 @@ unstable: true, srcs: [ "com/android/**/*.aidl", + ":framework-thread-ot-daemon-shared-aidl-sources", ], backend: { java: { @@ -61,5 +63,5 @@ "//external/ot-br-posix:__subpackages__", "//packages/modules/ThreadNetwork/service:__subpackages__", "//system/tools/aidl:__subpackages__", - ] + ], }
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl new file mode 100644 index 0000000..21de2d1 --- /dev/null +++ b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl
@@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, 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; + +/** Receives the information when a service instance is found/lost. */ +oneway interface INsdDiscoverServiceCallback { + void onServiceDiscovered(in String name, + in String type, + boolean isFound); +}
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl index 17f6295..84549f9 100644 --- a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl +++ b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl
@@ -30,6 +30,8 @@ import com.android.server.thread.openthread.DnsTxtAttribute; import com.android.server.thread.openthread.INsdStatusReceiver; +import com.android.server.thread.openthread.INsdDiscoverServiceCallback; +import com.android.server.thread.openthread.INsdResolveServiceCallback; /** * The service which supports mDNS advertising and discovery by {@link NsdManager}. @@ -91,4 +93,61 @@ * identify the registration */ void unregister(in INsdStatusReceiver receiver, int listenerId); + + /** Resets the NsdPublisher, i.e. clear all registrations. */ + void reset(); + + /** + * Discovers mDNS services of a specific type. + * + * <p>To stop discovering services, the caller must pass in the same listener ID which was used + * when starting discoverying the services. + * + * @param type the service type + * @param callback the callback when a service is found/lost + * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the + * service discovery operation + */ + void discoverService(in String type, + in INsdDiscoverServiceCallback callback, + int listenerId); + + /** + * Stops discovering services of a specific type. + * + * <p>To stop discovering services, the caller must pass in the same listener ID which was used + * when starting discoverying the services. + * + * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the + * service discovery operation + */ + void stopServiceDiscovery(int listenerId); + + /** + * Resolves an mDNS service instance. + * + * <p>To stop resolving a service, the caller must pass in the same listener ID which was used + * when starting resolving the service. + * + * @param name the service instance name + * @param type the service type + * @param callback the callback when a service is updated + * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the + * service resolution operation + */ + void resolveService(in String name, + in String type, + in INsdResolveServiceCallback callback, + int listenerId); + + /** + * Stops resolving an mDNS service instance. + * + * <p>To stop resolving a service, the caller must pass in the same listener ID which was used + * when starting resolving the service. + * + * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the + * service resolution operation + */ + void stopServiceResolution(int listenerId); }
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl new file mode 100644 index 0000000..48d7b5c --- /dev/null +++ b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl
@@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, 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; + +import com.android.server.thread.openthread.DnsTxtAttribute; + +/** Receives the information of a resolved service instance. */ +oneway interface INsdResolveServiceCallback { + void onServiceResolved(in String hostname, + in String name, + in String type, + int port, + in List<String> addresses, + in List<DnsTxtAttribute> txt, + int ttlSeconds); +}
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 861983e..d07f72a 100644 --- a/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl +++ b/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
@@ -30,12 +30,14 @@ import android.os.ParcelFileDescriptor; +import android.net.thread.ChannelMaxPower; import com.android.server.thread.openthread.BorderRouterConfigurationParcel; import com.android.server.thread.openthread.IChannelMasksReceiver; import com.android.server.thread.openthread.Ipv6AddressInfo; import com.android.server.thread.openthread.IOtStatusReceiver; import com.android.server.thread.openthread.IOtDaemonCallback; import com.android.server.thread.openthread.INsdPublisher; +import com.android.server.thread.openthread.MeshcopTxtAttributes; /** * The OpenThread daemon service which provides access to the core Thread stack for @@ -72,6 +74,7 @@ OT_ERROR_BUSY = 5, OT_ERROR_PARSE = 6, OT_ERROR_ABORT = 11, + OT_ERROR_NOT_IMPLEMENTED = 12, OT_ERROR_INVALID_STATE = 13, OT_ERROR_RESPONSE_TIMEOUT = 28, OT_ERROR_REASSEMBLY_TIMEOUT = 30, @@ -79,16 +82,30 @@ } /** - * Initializes this service with Thread tunnel interface FD. + * Initializes this service. + * + * <p>This API MUST be called before all other APIs of this interface. * * @param tunFd the Thread tunnel interface FD which can be used to transmit/receive * packets to/from Thread PAN * @param enabled the Thead enabled state from Persistent Settings * @param nsdPublisher the INsdPublisher which can be used for mDNS advertisement/discovery - * on AIL by {@link NsdManager} + * on AIL by {@link NsdManager} + * @param meshcopTxts the MeshCoP TXT values set by the system_server to override the default + * ones + * @param callback the callback for receiving OtDaemonState changes + * @param countryCode 2 bytes country code (as defined in ISO 3166) to set */ - void initialize(in ParcelFileDescriptor tunFd, in boolean enabled, - in INsdPublisher nsdPublisher); + void initialize( + in ParcelFileDescriptor tunFd, + in boolean enabled, + in INsdPublisher nsdPublisher, + in MeshcopTxtAttributes meshcopTxts, + in IOtDaemonCallback callback, + in String countryCode); + + /** Terminates the ot-daemon process. */ + void terminate(); /** * Enables/disables Thread. @@ -143,7 +160,7 @@ /** * Sets the country code. * - * @param countryCode 2 byte country code (as defined in ISO 3166) to set. + * @param countryCode 2 bytes 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); @@ -165,5 +182,13 @@ */ void getChannelMasks(in IChannelMasksReceiver receiver); + /** + * Sets the max power of each channel + * + * @param channelMaxPowers an array of {@code ChannelMaxPower}. + * @param receiver the receiver to the receive result of this operation. + */ + void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOtStatusReceiver receiver); + // TODO: add Border Router APIs }
diff --git a/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl b/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl new file mode 100644 index 0000000..3ac1db8 --- /dev/null +++ b/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl
@@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, 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; + +/** + * A collection of MeshCoP TXT entries that are supplied by Android platform. + */ +parcelable MeshcopTxtAttributes { + /** + * Predefined MeshCoP TXT entry named "mn". + * + * The length must not exceed 24 UTF-8 bytes. + */ + String modelName; + + /** + * Predefined MeshCoP TXT entry named "vn". + * + * The length must not exceed 24 UTF-8 bytes. + */ + String vendorName; + + /** + * Predefined MeshCoP TXT entry named "vo". + * + * The length must be 3 bytes. + */ + byte[] vendorOui; + + // More vendor-specific (v*) TXT entries can be added here +}
diff --git a/src/android/java/Android.bp b/src/android/java/Android.bp index 89f7910..d02c726 100644 --- a/src/android/java/Android.bp +++ b/src/android/java/Android.bp
@@ -27,6 +27,7 @@ // package { + default_team: "trendy_team_fwk_thread_network", default_applicable_licenses: ["external_ot-br-posix_license"], }
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 index 96d5db0..ce50eba 100644 --- a/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java +++ b/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
@@ -33,6 +33,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.thread.ChannelMaxPower; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -46,6 +47,7 @@ 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.MeshcopTxtAttributes; import com.android.server.thread.openthread.OtDaemonState; import java.time.Duration; @@ -67,35 +69,46 @@ private static final long PROACTIVE_LISTENER_ID = -1; private final Handler mHandler; - private final OtDaemonState mState; - private final BackboneRouterState mBbrState; - private int mThreadEnabled = OT_STATE_ENABLED; + private OtDaemonState mState; + private BackboneRouterState mBbrState; + private boolean mIsInitialized = false; + private int mThreadEnabled = OT_STATE_DISABLED; private int mChannelMasksReceiverOtError = OT_ERROR_NONE; private int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26 private int mPreferredChannelMask = 0; @Nullable private DeathRecipient mDeathRecipient; - @Nullable private ParcelFileDescriptor mTunFd; - - @NonNull private INsdPublisher mNsdPublisher; - + @Nullable private INsdPublisher mNsdPublisher; + @Nullable private MeshcopTxtAttributes mOverriddenMeshcopTxts; @Nullable private IOtDaemonCallback mCallback; - @Nullable private Long mCallbackListenerId; - @Nullable private RemoteException mJoinException; + @Nullable private String mCountryCode; public FakeOtDaemon(Handler handler) { mHandler = handler; + resetStates(); + } + + private void resetStates() { mState = new OtDaemonState(); mState.isInterfaceUp = false; + mState.partitionId = -1; mState.deviceRole = OT_DEVICE_ROLE_DISABLED; mState.activeDatasetTlvs = new byte[0]; mState.pendingDatasetTlvs = new byte[0]; mBbrState = new BackboneRouterState(); mBbrState.multicastForwardingEnabled = false; mBbrState.listeningAddresses = new ArrayList<>(); + + mTunFd = null; + mThreadEnabled = OT_STATE_DISABLED; + mNsdPublisher = null; + mIsInitialized = false; + + mCallback = null; + mCallbackListenerId = null; } @Override @@ -122,23 +135,47 @@ return true; } - @Override - public void initialize(ParcelFileDescriptor tunFd, boolean enabled, INsdPublisher nsdPublisher) - throws RemoteException { - mTunFd = tunFd; - mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED; - mNsdPublisher = nsdPublisher; + @Nullable + public DeathRecipient getDeathRecipient() { + return mDeathRecipient; } @Override - public void setThreadEnabled(boolean enabled, IOtStatusReceiver receiver) { + public void initialize( + ParcelFileDescriptor tunFd, + boolean enabled, + INsdPublisher nsdPublisher, + MeshcopTxtAttributes overriddenMeshcopTxts, + IOtDaemonCallback callback, + String countryCode) + throws RemoteException { + mIsInitialized = true; + mTunFd = tunFd; + mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED; + mNsdPublisher = nsdPublisher; + mCountryCode = countryCode; + + mOverriddenMeshcopTxts = new MeshcopTxtAttributes(); + mOverriddenMeshcopTxts.vendorOui = overriddenMeshcopTxts.vendorOui.clone(); + mOverriddenMeshcopTxts.vendorName = overriddenMeshcopTxts.vendorName; + mOverriddenMeshcopTxts.modelName = overriddenMeshcopTxts.modelName; + + registerStateCallback(callback, PROACTIVE_LISTENER_ID); + } + + /** Returns {@code true} if {@link initialize} has been called to initialize this object. */ + public boolean isInitialized() { + return mIsInitialized; + } + + @Override + public void terminate() throws RemoteException { mHandler.post( () -> { - mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED; - try { - receiver.onSuccess(); - } catch (RemoteException e) { - throw new AssertionError(e); + resetStates(); + if (mDeathRecipient != null) { + mDeathRecipient.binderDied(); + mDeathRecipient = null; } }); } @@ -147,6 +184,14 @@ return mThreadEnabled; } + public OtDaemonState getState() { + return makeCopy(mState); + } + + public BackboneRouterState getBackboneRouterState() { + return makeCopy(mBbrState); + } + /** * Returns the Thread TUN interface FD sent to OT daemon or {@code null} if {@link initialize} * is never called. @@ -165,6 +210,33 @@ return mNsdPublisher; } + /** + * Returns the overridden MeshCoP TXT attributes that is to OT daemon or {@code null} if {@link + * #initialize} is never called. + */ + @Nullable + public MeshcopTxtAttributes getOverriddenMeshcopTxtAttributes() { + return mOverriddenMeshcopTxts; + } + + @Nullable + public IOtDaemonCallback getCallback() { + return mCallback; + } + + @Override + public void setThreadEnabled(boolean enabled, IOtStatusReceiver receiver) { + mHandler.post( + () -> { + mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED; + try { + receiver.onSuccess(); + } catch (RemoteException e) { + throw new AssertionError(e); + } + }); + } + @Override public void registerStateCallback(IOtDaemonCallback callback, long listenerId) throws RemoteException { @@ -180,6 +252,15 @@ return mCallback; } + /** + * Returns the country code sent to OT daemon or {@code null} if {@link #initialize} is never + * called. + */ + @Nullable + public String getCountryCode() { + return mCountryCode; + } + @Override public void join(byte[] activeDataset, IOtStatusReceiver receiver) throws RemoteException { if (mJoinException != null) { @@ -210,15 +291,27 @@ JOIN_DELAY.toMillis()); } + private OtDaemonState makeCopy(OtDaemonState state) { + 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(); + return copyState; + } + + private BackboneRouterState makeCopy(BackboneRouterState state) { + BackboneRouterState copyState = new BackboneRouterState(); + copyState.multicastForwardingEnabled = state.multicastForwardingEnabled; + copyState.listeningAddresses = new ArrayList<>(state.listeningAddresses); + return copyState; + } + private void onStateChanged(OtDaemonState state, long listenerId) { try { // Make a copy of state 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(); + OtDaemonState copyState = makeCopy(state); mCallback.onStateChanged(copyState, listenerId); } catch (RemoteException e) { @@ -229,9 +322,8 @@ private void onBackboneRouterStateChanged(BackboneRouterState state) { try { // Make a copy of state so that clients won't keep a direct reference to it - BackboneRouterState copyState = new BackboneRouterState(); - copyState.multicastForwardingEnabled = state.multicastForwardingEnabled; - copyState.listeningAddresses = new ArrayList<>(state.listeningAddresses); + BackboneRouterState copyState = makeCopy(state); + mCallback.onBackboneRouterStateChanged(copyState); } catch (RemoteException e) { throw new AssertionError(e); @@ -294,4 +386,11 @@ public void setChannelMasksReceiverOtError(int otError) { mChannelMasksReceiverOtError = otError; } + + @Override + public void setChannelMaxPowers(ChannelMaxPower[] channelMaxPowers, IOtStatusReceiver receiver) + throws RemoteException { + throw new UnsupportedOperationException( + "FakeOtDaemon#setChannelTargetPowers is not implemented!"); + } }
diff --git a/src/android/mdns_publisher.cpp b/src/android/mdns_publisher.cpp index 4bd26ae..6febecc 100644 --- a/src/android/mdns_publisher.cpp +++ b/src/android/mdns_publisher.cpp
@@ -38,9 +38,6 @@ } namespace Android { - -using Status = ::ndk::ScopedAStatus; - otbrError DnsErrorToOtbrErrorImpl(int32_t aError) { return aError == 0 ? OTBR_ERROR_NONE : OTBR_ERROR_MDNS; @@ -61,19 +58,69 @@ return Status::ok(); } +Status MdnsPublisher::NsdDiscoverServiceCallback::onServiceDiscovered(const std::string &aName, + const std::string &aType, + bool aIsFound) +{ + VerifyOrExit(aIsFound, mSubscription.mPublisher.OnServiceRemoved(0, aType, aName)); + + mSubscription.Resolve(aName, aType); + +exit: + return Status::ok(); +} + +Status MdnsPublisher::NsdResolveServiceCallback::onServiceResolved(const std::string &aHostname, + const std::string &aName, + const std::string &aType, + int aPort, + const std::vector<std::string> &aAddresses, + const std::vector<DnsTxtAttribute> &aTxt, + int aTtlSeconds) +{ + DiscoveredInstanceInfo info; + TxtList txtList; + + info.mHostName = aHostname + ".local."; + info.mName = aName; + info.mPort = aPort; + info.mTtl = std::clamp(aTtlSeconds, kMinResolvedTtl, kMaxResolvedTtl); + for (const auto &addressStr : aAddresses) + { + Ip6Address address; + int error = Ip6Address::FromString(addressStr.c_str(), address); + + if (error != OTBR_ERROR_NONE) + { + otbrLogInfo("Failed to parse resolved IPv6 address: %s", addressStr.c_str()); + continue; + } + info.mAddresses.push_back(address); + } + for (const auto &entry : aTxt) + { + txtList.emplace_back(entry.name.c_str(), entry.value.data(), entry.value.size()); + } + EncodeTxtData(txtList, info.mTxtData); + + mSubscription.mPublisher.OnServiceResolved(aType, info); + + return Status::ok(); +} + void MdnsPublisher::SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher) { otbrLogInfo("Set INsdPublisher %p", aINsdPublisher.get()); - if (aINsdPublisher) + mNsdPublisher = std::move(aINsdPublisher); + + if (mNsdPublisher != nullptr) { - mNsdPublisher = std::move(aINsdPublisher); mStateCallback(Mdns::Publisher::State::kReady); } else { - Stop(); - mNsdPublisher = std::move(aINsdPublisher); + mStateCallback(Mdns::Publisher::State::kIdle); } } @@ -92,6 +139,18 @@ return ndk::SharedRefBase::make<MdnsPublisher::NsdStatusReceiver>(std::move(aCallback)); } +std::shared_ptr<MdnsPublisher::NsdDiscoverServiceCallback> CreateNsdDiscoverServiceCallback( + MdnsPublisher::ServiceSubscription &aServiceSubscription) +{ + return ndk::SharedRefBase::make<MdnsPublisher::NsdDiscoverServiceCallback>(aServiceSubscription); +} + +std::shared_ptr<MdnsPublisher::NsdResolveServiceCallback> CreateNsdResolveServiceCallback( + MdnsPublisher::ServiceSubscription &aServiceSubscription) +{ + return ndk::SharedRefBase::make<MdnsPublisher::NsdResolveServiceCallback>(aServiceSubscription); +} + void DieForNotImplemented(const char *aFuncName) { VerifyOrDie(false, (std::string(aFuncName) + " is not implemented").c_str()); @@ -250,32 +309,62 @@ void MdnsPublisher::SubscribeService(const std::string &aType, const std::string &aInstanceName) { - OTBR_UNUSED_VARIABLE(aType); - OTBR_UNUSED_VARIABLE(aInstanceName); + auto service = MakeUnique<ServiceSubscription>(aType, aInstanceName, *this, mNsdPublisher); - DieForNotImplemented(__func__); + VerifyOrExit(IsStarted(), otbrLogWarning("No platform mDNS implementation registered!")); + + mServiceSubscriptions.push_back(std::move(service)); + + otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(), + mServiceSubscriptions.size()); + + if (aInstanceName.empty()) + { + mServiceSubscriptions.back()->Browse(); + } + else + { + mServiceSubscriptions.back()->Resolve(aInstanceName, aType); + } +exit: + return; } void MdnsPublisher::UnsubscribeService(const std::string &aType, const std::string &aInstanceName) { - OTBR_UNUSED_VARIABLE(aType); - OTBR_UNUSED_VARIABLE(aInstanceName); + ServiceSubscriptionList::iterator it; - DieForNotImplemented(__func__); + VerifyOrExit(IsStarted()); + + it = std::find_if(mServiceSubscriptions.begin(), mServiceSubscriptions.end(), + [&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) { + return aService->mType == aType && aService->mName == aInstanceName; + }); + + VerifyOrExit(it != mServiceSubscriptions.end(), + otbrLogWarning("The service %s.%s is already unsubscribed.", aInstanceName.c_str(), aType.c_str())); + + { + std::unique_ptr<ServiceSubscription> service = std::move(*it); + + mServiceSubscriptions.erase(it); + } + + otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(), + mServiceSubscriptions.size()); + +exit: + return; } void MdnsPublisher::SubscribeHost(const std::string &aHostName) { OTBR_UNUSED_VARIABLE(aHostName); - - DieForNotImplemented(__func__); } void MdnsPublisher::UnsubscribeHost(const std::string &aHostName) { OTBR_UNUSED_VARIABLE(aHostName); - - DieForNotImplemented(__func__); } void MdnsPublisher::OnServiceResolveFailedImpl(const std::string &aType, @@ -308,7 +397,9 @@ MdnsPublisher::NsdServiceRegistration::~NsdServiceRegistration(void) { - VerifyOrExit(mPublisher->IsStarted() && mNsdPublisher != nullptr); + auto nsdPublisher = mNsdPublisher.lock(); + + VerifyOrExit(mPublisher->IsStarted() && nsdPublisher != nullptr); otbrLogInfo("Unpublishing service %s.%s listener ID = %d", mName.c_str(), mType.c_str(), mListenerId); @@ -317,7 +408,7 @@ mUnregisterReceiver = CreateReceiver([](int) {}); } - mNsdPublisher->unregister(mUnregisterReceiver, mListenerId); + nsdPublisher->unregister(mUnregisterReceiver, mListenerId); exit: return; @@ -325,7 +416,9 @@ MdnsPublisher::NsdHostRegistration::~NsdHostRegistration(void) { - VerifyOrExit(mPublisher->IsStarted() && mNsdPublisher != nullptr); + auto nsdPublisher = mNsdPublisher.lock(); + + VerifyOrExit(mPublisher->IsStarted() && nsdPublisher != nullptr); otbrLogInfo("Unpublishing host %s listener ID = %d", mName.c_str(), mListenerId); @@ -334,11 +427,80 @@ mUnregisterReceiver = CreateReceiver([](int) {}); } - mNsdPublisher->unregister(mUnregisterReceiver, mListenerId); + nsdPublisher->unregister(mUnregisterReceiver, mListenerId); exit: return; } +void MdnsPublisher::ServiceSubscription::Release(void) +{ + otbrLogInfo("Browsing service type %s", mType.c_str()); + + std::vector<std::string> instanceNames; + + for (const auto &nameAndResolvers : mResolvers) + { + instanceNames.push_back(nameAndResolvers.first); + } + for (const auto &name : instanceNames) + { + RemoveServiceResolver(name); + } + + mNsdPublisher->stopServiceDiscovery(mBrowseListenerId); +} + +void MdnsPublisher::ServiceSubscription::Browse(void) +{ + VerifyOrExit(mPublisher.IsStarted()); + + otbrLogInfo("Browsing service type %s", mType.c_str()); + + mNsdPublisher->discoverService(mType, CreateNsdDiscoverServiceCallback(*this), mBrowseListenerId); + +exit: + return; +} + +void MdnsPublisher::ServiceSubscription::Resolve(const std::string &aName, const std::string &aType) +{ + ServiceResolver *resolver = new ServiceResolver(mPublisher.AllocateListenerId(), mNsdPublisher); + + VerifyOrExit(mPublisher.IsStarted()); + + otbrLogInfo("Resolving service %s.%s", aName.c_str(), aType.c_str()); + + AddServiceResolver(aName, resolver); + mNsdPublisher->resolveService(aName, aType, CreateNsdResolveServiceCallback(*this), resolver->mListenerId); + +exit: + return; +} + +void MdnsPublisher::ServiceSubscription::AddServiceResolver(const std::string &aName, ServiceResolver *aResolver) +{ + mResolvers[aName].insert(aResolver); +} +void MdnsPublisher::ServiceSubscription::RemoveServiceResolver(const std::string &aName) +{ + int numResolvers = 0; + + VerifyOrExit(mResolvers.find(aName) != mResolvers.end()); + + numResolvers = mResolvers[aName].size(); + + for (auto resolver : mResolvers[aName]) + { + delete resolver; + } + + mResolvers.erase(aName); + +exit: + otbrLogDebug("Removed %d service resolver for instance %s", numResolvers, aName.c_str()); + return; +} + } // namespace Android } // namespace otbr
diff --git a/src/android/mdns_publisher.hpp b/src/android/mdns_publisher.hpp index a547d4f..64793dc 100644 --- a/src/android/mdns_publisher.hpp +++ b/src/android/mdns_publisher.hpp
@@ -31,12 +31,18 @@ #include "mdns/mdns.hpp" +#include <aidl/com/android/server/thread/openthread/BnNsdDiscoverServiceCallback.h> +#include <aidl/com/android/server/thread/openthread/BnNsdResolveServiceCallback.h> #include <aidl/com/android/server/thread/openthread/BnNsdStatusReceiver.h> #include <aidl/com/android/server/thread/openthread/DnsTxtAttribute.h> #include <aidl/com/android/server/thread/openthread/INsdPublisher.h> +#include <set> namespace otbr { namespace Android { +using Status = ::ndk::ScopedAStatus; +using aidl::com::android::server::thread::openthread::BnNsdDiscoverServiceCallback; +using aidl::com::android::server::thread::openthread::BnNsdResolveServiceCallback; using aidl::com::android::server::thread::openthread::BnNsdStatusReceiver; using aidl::com::android::server::thread::openthread::DnsTxtAttribute; using aidl::com::android::server::thread::openthread::INsdPublisher; @@ -45,15 +51,15 @@ { public: explicit MdnsPublisher(Publisher::StateCallback aCallback) + : mStateCallback(std::move(aCallback)) + , mNextListenerId(0) + { - mNextListenerId = 0; - mStateCallback = std::move(aCallback); } ~MdnsPublisher(void) { Stop(); } - // In this Publisher implementation, SetINsdPublisher() does the job to start/stop the Publisher. That's because we - // want to ensure ot-daemon won't do any mDNS operations when Thread is disabled. + /** Sets the INsdPublisher which forwards the mDNS API requests to the NsdManager in system_server. */ void SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher); otbrError Start(void) override { return OTBR_ERROR_NONE; } @@ -62,7 +68,10 @@ { mServiceRegistrations.clear(); mHostRegistrations.clear(); - mStateCallback(Mdns::Publisher::State::kIdle); + if (mNsdPublisher != nullptr) + { + mNsdPublisher->reset(); + } } bool IsStarted(void) const override { return mNsdPublisher != nullptr; } @@ -89,14 +98,99 @@ { } - ::ndk::ScopedAStatus onSuccess(void) override; + Status onSuccess(void) override; - ::ndk::ScopedAStatus onError(int aError) override; + Status onError(int aError) override; private: Mdns::Publisher::ResultCallback mCallback; }; + struct ServiceResolver : private ::NonCopyable + { + explicit ServiceResolver(int aListenerId, std::shared_ptr<INsdPublisher> aNsdPublisher) + : mListenerId(aListenerId) + , mNsdPublisher(std::move(aNsdPublisher)) + { + } + + ~ServiceResolver(void) + { + if (mNsdPublisher) + { + mNsdPublisher->stopServiceResolution(mListenerId); + } + } + + int mListenerId; + std::shared_ptr<INsdPublisher> mNsdPublisher; + }; + + struct ServiceSubscription : private ::NonCopyable + { + explicit ServiceSubscription(std::string aType, + std::string aName, + MdnsPublisher &aPublisher, + std::shared_ptr<INsdPublisher> aNsdPublisher) + : mType(std::move(aType)) + , mName(std::move(aName)) + , mPublisher(aPublisher) + , mNsdPublisher(std::move(aNsdPublisher)) + , mBrowseListenerId(-1) + { + } + + ~ServiceSubscription(void) { Release(); } + + void Release(void); + void Browse(void); + void Resolve(const std::string &aName, const std::string &aType); + void AddServiceResolver(const std::string &aName, ServiceResolver *aResolver); + void RemoveServiceResolver(const std::string &aInstanceName); + + std::string mType; + std::string mName; + MdnsPublisher &mPublisher; + std::shared_ptr<INsdPublisher> mNsdPublisher; + int32_t mBrowseListenerId; + + std::map<std::string, std::set<ServiceResolver *>> mResolvers; + }; + + class NsdDiscoverServiceCallback : public BnNsdDiscoverServiceCallback + { + public: + explicit NsdDiscoverServiceCallback(ServiceSubscription &aSubscription) + : mSubscription(aSubscription) + { + } + + Status onServiceDiscovered(const std::string &aName, const std::string &aType, bool aIsFound); + + private: + ServiceSubscription &mSubscription; + }; + + class NsdResolveServiceCallback : public BnNsdResolveServiceCallback + { + public: + explicit NsdResolveServiceCallback(ServiceSubscription &aSubscription) + : mSubscription(aSubscription) + { + } + + Status onServiceResolved(const std::string &aHostname, + const std::string &aName, + const std::string &aType, + int aPort, + const std::vector<std::string> &aAddresses, + const std::vector<DnsTxtAttribute> &aTxt, + int aTtlSeconds); + + private: + ServiceSubscription &mSubscription; + }; + protected: otbrError PublishServiceImpl(const std::string &aHostName, const std::string &aName, @@ -120,16 +214,16 @@ class NsdServiceRegistration : public ServiceRegistration { public: - NsdServiceRegistration(const std::string &aHostName, - const std::string &aName, - const std::string &aType, - const SubTypeList &aSubTypeList, - uint16_t aPort, - const TxtData &aTxtData, - ResultCallback &&aCallback, - MdnsPublisher *aPublisher, - int32_t aListenerId, - std::shared_ptr<INsdPublisher> aINsdPublisher) + NsdServiceRegistration(const std::string &aHostName, + const std::string &aName, + const std::string &aType, + const SubTypeList &aSubTypeList, + uint16_t aPort, + const TxtData &aTxtData, + ResultCallback &&aCallback, + MdnsPublisher *aPublisher, + int32_t aListenerId, + std::weak_ptr<INsdPublisher> aINsdPublisher) : ServiceRegistration(aHostName, aName, aType, @@ -150,18 +244,18 @@ std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver; private: - std::shared_ptr<INsdPublisher> mNsdPublisher; + std::weak_ptr<INsdPublisher> mNsdPublisher; }; class NsdHostRegistration : public HostRegistration { public: - NsdHostRegistration(const std::string &aName, - const AddressList &aAddresses, - ResultCallback &&aCallback, - MdnsPublisher *aPublisher, - int32_t aListenerId, - std::shared_ptr<INsdPublisher> aINsdPublisher) + NsdHostRegistration(const std::string &aName, + const AddressList &aAddresses, + ResultCallback &&aCallback, + MdnsPublisher *aPublisher, + int32_t aListenerId, + std::weak_ptr<INsdPublisher> aINsdPublisher) : HostRegistration(aName, aAddresses, std::move(aCallback), aPublisher) , mListenerId(aListenerId) , mNsdPublisher(aINsdPublisher) @@ -174,14 +268,20 @@ std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver; private: - std::shared_ptr<INsdPublisher> mNsdPublisher; + std::weak_ptr<INsdPublisher> mNsdPublisher; }; + typedef std::vector<std::unique_ptr<ServiceSubscription>> ServiceSubscriptionList; + + static constexpr int kMinResolvedTtl = 1; + static constexpr int kMaxResolvedTtl = 10; + int32_t AllocateListenerId(void); StateCallback mStateCallback; int32_t mNextListenerId; std::shared_ptr<INsdPublisher> mNsdPublisher = nullptr; + ServiceSubscriptionList mServiceSubscriptions; }; } // namespace Android
diff --git a/src/android/otdaemon_fuzzer.cpp b/src/android/otdaemon_fuzzer.cpp index be0bc34..9eb3235 100644 --- a/src/android/otdaemon_fuzzer.cpp +++ b/src/android/otdaemon_fuzzer.cpp
@@ -41,3 +41,7 @@ fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size)); return 0; } + +extern "C" void otPlatReset(otInstance *aInstance) +{ +}
diff --git a/src/android/otdaemon_server.cpp b/src/android/otdaemon_server.cpp index 0c6930b..9ffeb7b 100644 --- a/src/android/otdaemon_server.cpp +++ b/src/android/otdaemon_server.cpp
@@ -34,14 +34,17 @@ #include <string.h> #include <android-base/file.h> +#include <android-base/stringprintf.h> #include <android/binder_manager.h> #include <android/binder_process.h> #include <openthread/border_router.h> +#include <openthread/cli.h> #include <openthread/icmp6.h> #include <openthread/ip6.h> #include <openthread/link.h> #include <openthread/openthread-system.h> #include <openthread/platform/infra_if.h> +#include <openthread/platform/radio.h> #include "agent/vendor.hpp" #include "android/otdaemon_telemetry.hpp" @@ -97,8 +100,25 @@ return addrInfo; } +static const char *ThreadEnabledStateToString(int enabledState) +{ + switch (enabledState) + { + case IOtDaemon::OT_STATE_ENABLED: + return "ENABLED"; + case IOtDaemon::OT_STATE_DISABLED: + return "DISABLED"; + case IOtDaemon::OT_STATE_DISABLING: + return "DISABLING"; + default: + assert(false); + return "UNKNOWN"; + } +} + OtDaemonServer::OtDaemonServer(Application &aApplication) - : mNcp(aApplication.GetNcp()) + : mApplication(aApplication) + , mNcp(aApplication.GetNcp()) , mBorderAgent(aApplication.GetBorderAgent()) , mMdnsPublisher(static_cast<MdnsPublisher &>(aApplication.GetPublisher())) , mBorderRouterConfiguration() @@ -131,7 +151,14 @@ { OtDaemonServer *thisServer = static_cast<OtDaemonServer *>(aBinderServer); - otbrLogCrit("Client is died, removing callbacks..."); + otbrLogCrit("system_server is dead, removing configs and callbacks..."); + + thisServer->mMeshcopTxts = {}; + thisServer->mINsdPublisher = nullptr; + + // Note that the INsdPublisher reference is held in MdnsPublisher + thisServer->mMdnsPublisher.SetINsdPublisher(nullptr); + thisServer->mCallback = nullptr; thisServer->mTunFd.set(-1); // the original FD will be closed automatically } @@ -243,8 +270,9 @@ message = otIp6NewMessage(GetOtInstance(), &settings); VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); + otMessageSetOrigin(message, OT_MESSAGE_ORIGIN_HOST_UNTRUSTED); - SuccessOrExit(error = otMessageAppend(message, packet, length)); + SuccessOrExit(error = otMessageAppend(message, packet, static_cast<uint16_t>(length))); error = otIp6Send(GetOtInstance(), message); message = nullptr; @@ -352,9 +380,12 @@ } } -Status OtDaemonServer::initialize(const ScopedFileDescriptor &aTunFd, - const bool enabled, - const std::shared_ptr<INsdPublisher> &aINsdPublisher) +Status OtDaemonServer::initialize(const ScopedFileDescriptor &aTunFd, + const bool enabled, + const std::shared_ptr<INsdPublisher> &aINsdPublisher, + const MeshcopTxtAttributes &aMeshcopTxts, + const std::shared_ptr<IOtDaemonCallback> &aCallback, + const std::string &aCountryCode) { otbrLogInfo("OT daemon is initialized by system server (tunFd=%d, enabled=%s)", aTunFd.get(), enabled ? "true" : "false"); @@ -363,14 +394,31 @@ // we can process `aTunFd` directly in front of the task. mTunFd = aTunFd.dup(); - mTaskRunner.Post([enabled, aINsdPublisher, this]() { initializeInternal(enabled, aINsdPublisher); }); + mINsdPublisher = aINsdPublisher; + mMeshcopTxts = aMeshcopTxts; + + mTaskRunner.Post([enabled, aINsdPublisher, aMeshcopTxts, aCallback, aCountryCode, this]() { + initializeInternal(enabled, mINsdPublisher, mMeshcopTxts, aCallback, aCountryCode); + }); return Status::ok(); } -void OtDaemonServer::initializeInternal(const bool enabled, const std::shared_ptr<INsdPublisher> &aINsdPublisher) +void OtDaemonServer::initializeInternal(const bool enabled, + const std::shared_ptr<INsdPublisher> &aINsdPublisher, + const MeshcopTxtAttributes &aMeshcopTxts, + const std::shared_ptr<IOtDaemonCallback> &aCallback, + const std::string &aCountryCode) { - mINsdPublisher = aINsdPublisher; + std::string instanceName = aMeshcopTxts.vendorName + " " + aMeshcopTxts.modelName; + + setCountryCodeInternal(aCountryCode, nullptr /* aReceiver */); + registerStateCallbackInternal(aCallback, -1 /* listenerId */); + + mMdnsPublisher.SetINsdPublisher(aINsdPublisher); + mBorderAgent.SetMeshCopServiceValues(instanceName, aMeshcopTxts.modelName, aMeshcopTxts.vendorName, + aMeshcopTxts.vendorOui); + mBorderAgent.SetEnabled(enabled); if (enabled) { @@ -378,27 +426,41 @@ } else { - updateThreadEnabledState(enabled, nullptr /* aReceiver */); + updateThreadEnabledState(OT_STATE_DISABLED, nullptr /* aReceiver */); } } +Status OtDaemonServer::terminate(void) +{ + mTaskRunner.Post([]() { + otbrLogWarning("Terminating ot-daemon process..."); + exit(0); + }); + return Status::ok(); +} + void OtDaemonServer::updateThreadEnabledState(const int enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver) { + VerifyOrExit(enabled != mThreadEnabled); + + otbrLogInfo("Thread enabled state changed: %s -> %s", ThreadEnabledStateToString(mThreadEnabled), + ThreadEnabledStateToString(enabled)); mThreadEnabled = enabled; + if (aReceiver != nullptr) { aReceiver->onSuccess(); } + // Enables the BorderAgent module only when Thread is enabled because it always + // publishes the MeshCoP service even when no Thread network is provisioned. switch (enabled) { case OT_STATE_ENABLED: - mMdnsPublisher.SetINsdPublisher(mINsdPublisher); + mBorderAgent.SetEnabled(true); break; case OT_STATE_DISABLED: - mMdnsPublisher.SetINsdPublisher(nullptr); - break; - default: + mBorderAgent.SetEnabled(false); break; } @@ -406,6 +468,9 @@ { mCallback->onThreadEnabledChanged(mThreadEnabled); } + +exit: + return; } void OtDaemonServer::enableThread(const std::shared_ptr<IOtStatusReceiver> &aReceiver) @@ -418,7 +483,7 @@ (void)otIp6SetEnabled(GetOtInstance(), true); (void)otThreadSetEnabled(GetOtInstance(), true); } - updateThreadEnabledState(IOtDaemon::OT_STATE_ENABLED, aReceiver); + updateThreadEnabledState(OT_STATE_ENABLED, aReceiver); } Status OtDaemonServer::setThreadEnabled(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver) @@ -435,10 +500,9 @@ VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized"); - VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY, - message = "Thread is disabling"); + VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling"); - if ((mThreadEnabled == IOtDaemon::OT_STATE_ENABLED) == enabled) + if ((mThreadEnabled == OT_STATE_ENABLED) == enabled) { aReceiver->onSuccess(); ExitNow(); @@ -450,17 +514,14 @@ } else { - mThreadEnabled = IOtDaemon::OT_STATE_DISABLING; - if (mCallback != nullptr) - { - mCallback->onThreadEnabledChanged(mThreadEnabled); - } + // `aReceiver` should not be set here because the operation isn't finished yet + updateThreadEnabledState(OT_STATE_DISABLING, nullptr /* aReceiver */); LeaveGracefully([aReceiver, this]() { // Ignore errors as those operations should always succeed (void)otThreadSetEnabled(GetOtInstance(), false); (void)otIp6SetEnabled(GetOtInstance(), false); - updateThreadEnabledState(IOtDaemon::OT_STATE_DISABLED, aReceiver); + updateThreadEnabledState(OT_STATE_DISABLED, aReceiver); }); } @@ -552,6 +613,7 @@ if (isAttached() && !mState.activeDatasetTlvs.empty() && mJoinReceiver != nullptr) { + otbrLogInfo("Join succeeded"); mJoinReceiver->onSuccess(); mJoinReceiver = nullptr; } @@ -574,10 +636,9 @@ std::string message; otOperationalDatasetTlvs datasetTlvs; - VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY, - message = "Thread is disabling"); + VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling"); - VerifyOrExit(mThreadEnabled == IOtDaemon::OT_STATE_ENABLED, + VerifyOrExit(mThreadEnabled == OT_STATE_ENABLED, error = static_cast<int>(IOtDaemon::ErrorCode::OT_ERROR_THREAD_DISABLED), message = "Thread is disabled"); @@ -587,12 +648,15 @@ if (otThreadGetDeviceRole(GetOtInstance()) != OT_DEVICE_ROLE_DISABLED) { - LeaveGracefully([aActiveOpDatasetTlvs, aReceiver, this]() { join(aActiveOpDatasetTlvs, aReceiver); }); + LeaveGracefully([aActiveOpDatasetTlvs, aReceiver, this]() { + FinishLeave(nullptr); + join(aActiveOpDatasetTlvs, aReceiver); + }); ExitNow(); } std::copy(aActiveOpDatasetTlvs.begin(), aActiveOpDatasetTlvs.end(), datasetTlvs.mTlvs); - datasetTlvs.mLength = aActiveOpDatasetTlvs.size(); + datasetTlvs.mLength = static_cast<uint8_t>(aActiveOpDatasetTlvs.size()); SuccessOrExit(error = otDatasetSetActiveTlvs(GetOtInstance(), &datasetTlvs), message = "Failed to set Active Operational Dataset"); @@ -630,20 +694,15 @@ VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized"); - VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY, - message = "Thread is disabling"); + VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling"); - if (mThreadEnabled == IOtDaemon::OT_STATE_DISABLED) + if (mThreadEnabled == OT_STATE_DISABLED) { - (void)otInstanceErasePersistentInfo(GetOtInstance()); - aReceiver->onSuccess(); + FinishLeave(aReceiver); ExitNow(); } - LeaveGracefully([aReceiver, this]() { - (void)otInstanceErasePersistentInfo(GetOtInstance()); - aReceiver->onSuccess(); - }); + LeaveGracefully([aReceiver, this]() { FinishLeave(aReceiver); }); exit: if (error != OT_ERROR_NONE) @@ -652,6 +711,17 @@ } } +void OtDaemonServer::FinishLeave(const std::shared_ptr<IOtStatusReceiver> &aReceiver) +{ + (void)otInstanceErasePersistentInfo(GetOtInstance()); + OT_UNUSED_VARIABLE(mApplication); // Avoid the unused-private-field issue. + // TODO: b/323301831 - Re-init the Application class. + if (aReceiver != nullptr) + { + aReceiver->onSuccess(); + } +} + void OtDaemonServer::LeaveGracefully(const LeaveCallback &aReceiver) { mLeaveCallbacks.push_back(aReceiver); @@ -712,10 +782,9 @@ std::string message; otOperationalDataset emptyDataset; - VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY, - message = "Thread is disabling"); + VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling"); - VerifyOrExit(mThreadEnabled == IOtDaemon::OT_STATE_ENABLED, + VerifyOrExit(mThreadEnabled == OT_STATE_ENABLED, error = static_cast<int>(IOtDaemon::ErrorCode::OT_ERROR_THREAD_DISABLED), message = "Thread is disabled"); @@ -733,7 +802,7 @@ // TODO: check supported channel mask error = otDatasetSendMgmtPendingSet(GetOtInstance(), &emptyDataset, aPendingOpDatasetTlvs.data(), - aPendingOpDatasetTlvs.size(), SendMgmtPendingSetCallback, + static_cast<uint8_t>(aPendingOpDatasetTlvs.size()), SendMgmtPendingSetCallback, /* aBinderServer= */ this); if (error != OT_ERROR_NONE) { @@ -787,7 +856,7 @@ 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]; + countryCode = static_cast<uint16_t>((aCountryCode[0] << 8) | aCountryCode[1]); SuccessOrExit(error = otLinkSetRegion(GetOtInstance(), countryCode), message = "Failed to set the country code"); exit: @@ -826,6 +895,48 @@ } } +Status OtDaemonServer::setChannelMaxPowers(const std::vector<ChannelMaxPower> &aChannelMaxPowers, + const std::shared_ptr<IOtStatusReceiver> &aReceiver) +{ + mTaskRunner.Post( + [aChannelMaxPowers, aReceiver, this]() { setChannelMaxPowersInternal(aChannelMaxPowers, aReceiver); }); + + return Status::ok(); +} + +Status OtDaemonServer::setChannelMaxPowersInternal(const std::vector<ChannelMaxPower> &aChannelMaxPowers, + const std::shared_ptr<IOtStatusReceiver> &aReceiver) +{ + otError error = OT_ERROR_NONE; + std::string message; + uint8_t channel; + int16_t maxPower; + + VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized"); + + for (ChannelMaxPower channelMaxPower : aChannelMaxPowers) + { + VerifyOrExit((channelMaxPower.channel >= OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN) && + (channelMaxPower.channel <= OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX), + error = OT_ERROR_INVALID_ARGS, message = "The channel is invalid"); + VerifyOrExit((channelMaxPower.maxPower >= INT16_MIN) && (channelMaxPower.maxPower <= INT16_MAX), + error = OT_ERROR_INVALID_ARGS, message = "The max power is invalid"); + } + + for (ChannelMaxPower channelMaxPower : aChannelMaxPowers) + { + channel = static_cast<uint8_t>(channelMaxPower.channel); + maxPower = static_cast<int16_t>(channelMaxPower.maxPower); + otbrLogInfo("Set channel max power: channel=%u, maxPower=%d", channel, maxPower); + SuccessOrExit(error = otPlatRadioSetChannelTargetPower(GetOtInstance(), channel, maxPower), + message = "Failed to set channel max power"); + } + +exit: + PropagateResult(error, message, aReceiver); + return Status::ok(); +} + Status OtDaemonServer::configureBorderRouter(const BorderRouterConfigurationParcel &aBorderRouterConfiguration, const std::shared_ptr<IOtStatusReceiver> &aReceiver) { @@ -864,7 +975,7 @@ { if (aIsBorderRoutingEnabled) { - int infraIfIndex = if_nametoindex(aInfraInterfaceName.c_str()); + unsigned int infraIfIndex = if_nametoindex(aInfraInterfaceName.c_str()); SuccessOrExit(error = otBorderRoutingSetEnabled(GetOtInstance(), false /* aEnabled */), message = "failed to disable border routing"); otSysSetInfraNetif(aInfraInterfaceName.c_str(), icmp6SocketFd); @@ -895,14 +1006,52 @@ PropagateResult(error, message, aReceiver); } +static int OutputCallback(void *aContext, const char *aFormat, va_list aArguments) +{ + std::string output; + + android::base::StringAppendV(&output, aFormat, aArguments); + + int length = output.length(); + + VerifyOrExit(android::base::WriteStringToFd(output, *(static_cast<int *>(aContext))), length = 0); + +exit: + return length; +} + +inline void DumpCliCommand(std::string aCommand, int aFd) +{ + android::base::WriteStringToFd(aCommand + '\n', aFd); + otCliInputLine(aCommand.data()); +} + binder_status_t OtDaemonServer::dump(int aFd, const char **aArgs, uint32_t aNumArgs) { OT_UNUSED_VARIABLE(aArgs); OT_UNUSED_VARIABLE(aNumArgs); - // TODO: Use ::android::base::WriteStringToFd to dump infomration. + otCliInit(GetOtInstance(), OutputCallback, &aFd); + + DumpCliCommand("state", aFd); + DumpCliCommand("srp server state", aFd); + DumpCliCommand("srp server service", aFd); + DumpCliCommand("srp server host", aFd); + DumpCliCommand("dataset active", aFd); + DumpCliCommand("leaderdata", aFd); + DumpCliCommand("eidcache", aFd); + DumpCliCommand("counters mac", aFd); + DumpCliCommand("counters mle", aFd); + DumpCliCommand("counters ip", aFd); + DumpCliCommand("router table", aFd); + DumpCliCommand("neighbor table", aFd); + DumpCliCommand("ipaddr -v", aFd); + DumpCliCommand("netdata show", aFd); + fsync(aFd); + otSysCliInitUsingDaemon(GetOtInstance()); + return STATUS_OK; }
diff --git a/src/android/otdaemon_server.hpp b/src/android/otdaemon_server.hpp index a61d19d..8124952 100644 --- a/src/android/otdaemon_server.hpp +++ b/src/android/otdaemon_server.hpp
@@ -51,6 +51,7 @@ using BinderDeathRecipient = ::ndk::ScopedAIBinder_DeathRecipient; using ScopedFileDescriptor = ::ndk::ScopedFileDescriptor; using Status = ::ndk::ScopedAStatus; +using aidl::android::net::thread::ChannelMaxPower; using aidl::com::android::server::thread::openthread::BackboneRouterState; using aidl::com::android::server::thread::openthread::BnOtDaemon; using aidl::com::android::server::thread::openthread::BorderRouterConfigurationParcel; @@ -60,6 +61,7 @@ using aidl::com::android::server::thread::openthread::IOtDaemonCallback; using aidl::com::android::server::thread::openthread::IOtStatusReceiver; using aidl::com::android::server::thread::openthread::Ipv6AddressInfo; +using aidl::com::android::server::thread::openthread::MeshcopTxtAttributes; using aidl::com::android::server::thread::openthread::OtDaemonState; class OtDaemonServer : public BnOtDaemon, public MainloopProcessor, public vendor::VendorServer @@ -91,10 +93,18 @@ // Implements IOtDaemon.aidl - Status initialize(const ScopedFileDescriptor &aTunFd, - const bool enabled, - const std::shared_ptr<INsdPublisher> &aNsdPublisher) override; - void initializeInternal(const bool enabled, const std::shared_ptr<INsdPublisher> &aINsdPublisher); + Status initialize(const ScopedFileDescriptor &aTunFd, + const bool enabled, + const std::shared_ptr<INsdPublisher> &aNsdPublisher, + const MeshcopTxtAttributes &aMeshcopTxts, + const std::shared_ptr<IOtDaemonCallback> &aCallback, + const std::string &aCountryCode) override; + void initializeInternal(const bool enabled, + const std::shared_ptr<INsdPublisher> &aINsdPublisher, + const MeshcopTxtAttributes &aMeshcopTxts, + const std::shared_ptr<IOtDaemonCallback> &aCallback, + const std::string &aCountryCode); + Status terminate(void) override; Status setThreadEnabled(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver) override; void setThreadEnabledInternal(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver); Status registerStateCallback(const std::shared_ptr<IOtDaemonCallback> &aCallback, int64_t listenerId) override; @@ -112,6 +122,10 @@ const std::shared_ptr<IOtStatusReceiver> &aReceiver); Status setCountryCode(const std::string &aCountryCode, const std::shared_ptr<IOtStatusReceiver> &aReceiver); void setCountryCodeInternal(const std::string &aCountryCode, const std::shared_ptr<IOtStatusReceiver> &aReceiver); + Status setChannelMaxPowers(const std::vector<ChannelMaxPower> &aChannelMaxPowers, + const std::shared_ptr<IOtStatusReceiver> &aReceiver); + Status setChannelMaxPowersInternal(const std::vector<ChannelMaxPower> &aChannelMaxPowers, + const std::shared_ptr<IOtStatusReceiver> &aReceiver); Status configureBorderRouter(const BorderRouterConfigurationParcel &aBorderRouterConfiguration, const std::shared_ptr<IOtStatusReceiver> &aReceiver) override; void configureBorderRouterInternal(int aIcmp6SocketFd, @@ -124,6 +138,7 @@ bool RefreshOtDaemonState(otChangedFlags aFlags); void LeaveGracefully(const LeaveCallback &aReceiver); + void FinishLeave(const std::shared_ptr<IOtStatusReceiver> &aReceiver); static void DetachGracefullyCallback(void *aBinderServer); void DetachGracefullyCallback(void); static void SendMgmtPendingSetCallback(otError aResult, void *aBinderServer); @@ -142,11 +157,13 @@ void updateThreadEnabledState(const int aEnabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver); void enableThread(const std::shared_ptr<IOtStatusReceiver> &aReceiver); - int mThreadEnabled = IOtDaemon::OT_STATE_DISABLED; + int mThreadEnabled = OT_STATE_DISABLED; + otbr::Application &mApplication; otbr::Ncp::ControllerOpenThread &mNcp; otbr::BorderAgent &mBorderAgent; MdnsPublisher &mMdnsPublisher; std::shared_ptr<INsdPublisher> mINsdPublisher; + MeshcopTxtAttributes mMeshcopTxts; TaskRunner mTaskRunner; ScopedFileDescriptor mTunFd; OtDaemonState mState; @@ -156,7 +173,7 @@ std::shared_ptr<IOtStatusReceiver> mMigrationReceiver; std::vector<LeaveCallback> mLeaveCallbacks; BorderRouterConfigurationParcel mBorderRouterConfiguration; - static constexpr Seconds kTelemetryCheckInterval = Seconds(30); // 30 seconds + static constexpr Seconds kTelemetryCheckInterval = Seconds(600); // 600 seconds static constexpr Seconds kTelemetryUploadIntervalThreshold = Seconds(60 * 60 * 12); // 12 hours };
diff --git a/src/border_agent/border_agent.cpp b/src/border_agent/border_agent.cpp index acdb92d..b6acf18 100644 --- a/src/border_agent/border_agent.cpp +++ b/src/border_agent/border_agent.cpp
@@ -69,11 +69,7 @@ namespace otbr { -static const char kVendorName[] = OTBR_VENDOR_NAME; -static const char kProductName[] = OTBR_PRODUCT_NAME; -static const char kBorderAgentServiceType[] = "_meshcop._udp"; ///< Border agent service type of mDNS -static const char kBorderAgentServiceInstanceName[] = - OTBR_MESHCOP_SERVICE_INSTANCE_NAME; ///< Border agent service name of mDNS +static const char kBorderAgentServiceType[] = "_meshcop._udp"; ///< Border agent service type of mDNS static constexpr int kBorderAgentServiceDummyPort = 49152; /** @@ -143,7 +139,32 @@ : mNcp(aNcp) , mPublisher(aPublisher) , mIsEnabled(false) + , mVendorName(OTBR_VENDOR_NAME) + , mProductName(OTBR_PRODUCT_NAME) + , mBaseServiceInstanceName(OTBR_MESHCOP_SERVICE_INSTANCE_NAME) { + mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); }); +} + +otbrError BorderAgent::SetMeshCopServiceValues(const std::string &aServiceInstanceName, + const std::string &aProductName, + const std::string &aVendorName, + const std::vector<uint8_t> &aVendorOui) +{ + otbrError error = OTBR_ERROR_NONE; + + VerifyOrExit(aProductName.size() <= kMaxProductNameLength, error = OTBR_ERROR_INVALID_ARGS); + VerifyOrExit(aVendorName.size() <= kMaxVendorNameLength, error = OTBR_ERROR_INVALID_ARGS); + VerifyOrExit(aVendorOui.empty() || aVendorOui.size() == kVendorOuiLength, error = OTBR_ERROR_INVALID_ARGS); + + mProductName = aProductName; + mVendorName = aVendorName; + mVendorOui = aVendorOui; + + mBaseServiceInstanceName = aServiceInstanceName; + +exit: + return error; } void BorderAgent::SetEnabled(bool aIsEnabled) @@ -166,8 +187,6 @@ { otbrLogInfo("Start Thread Border Agent"); - mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); }); - #if OTBR_ENABLE_DBUS_SERVER mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) { HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate)); @@ -179,7 +198,7 @@ }); #endif - mServiceInstanceName = BaseServiceInstanceName(); + mServiceInstanceName = GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName); UpdateMeshCopService(); } @@ -360,8 +379,18 @@ } #endif - txtList.emplace_back("vn", kVendorName); - txtList.emplace_back("mn", kProductName); + if (!mVendorOui.empty()) + { + txtList.emplace_back("vo", mVendorOui.data(), mVendorOui.size()); + } + if (!mVendorName.empty()) + { + txtList.emplace_back("vn", mVendorName.c_str()); + } + if (!mProductName.empty()) + { + txtList.emplace_back("mn", mProductName.c_str()); + } txtList.emplace_back("nn", networkName); txtList.emplace_back("xp", extPanId->m8, sizeof(extPanId->m8)); txtList.emplace_back("tv", mNcp.GetThreadVersion()); @@ -465,6 +494,8 @@ void BorderAgent::HandleThreadStateChanged(otChangedFlags aFlags) { + VerifyOrExit(IsEnabled()); + if (aFlags & OT_CHANGED_THREAD_ROLE) { otbrLogInfo("Thread is %s", (IsThreadStarted() ? "up" : "down")); @@ -475,6 +506,9 @@ { UpdateMeshCopService(); } + +exit: + return; } bool BorderAgent::IsThreadStarted(void) const @@ -484,12 +518,12 @@ return role == OT_DEVICE_ROLE_CHILD || role == OT_DEVICE_ROLE_ROUTER || role == OT_DEVICE_ROLE_LEADER; } -std::string BorderAgent::BaseServiceInstanceName() const +std::string BorderAgent::GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const { const otExtAddress *extAddress = otLinkGetExtendedAddress(mNcp.GetInstance()); std::stringstream ss; - ss << kBorderAgentServiceInstanceName << " #"; + ss << aServiceInstanceName << " #"; ss << std::uppercase << std::hex << std::setfill('0'); ss << std::setw(2) << static_cast<int>(extAddress->m8[6]); ss << std::setw(2) << static_cast<int>(extAddress->m8[7]); @@ -504,7 +538,7 @@ uint16_t rand = uniform_dist(engine); std::stringstream ss; - ss << BaseServiceInstanceName() << " (" << rand << ")"; + ss << GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName) << " (" << rand << ")"; return ss.str(); }
diff --git a/src/border_agent/border_agent.hpp b/src/border_agent/border_agent.hpp index c95dc3c..39a0d2c 100644 --- a/src/border_agent/border_agent.hpp +++ b/src/border_agent/border_agent.hpp
@@ -62,7 +62,7 @@ #endif #ifndef OTBR_MESHCOP_SERVICE_INSTANCE_NAME -#define OTBR_MESHCOP_SERVICE_INSTANCE_NAME OTBR_VENDOR_NAME " " OTBR_PRODUCT_NAME +#define OTBR_MESHCOP_SERVICE_INSTANCE_NAME (OTBR_VENDOR_NAME " " OTBR_PRODUCT_NAME) #endif namespace otbr { @@ -95,6 +95,28 @@ ~BorderAgent(void) = default; /** + * Overrides MeshCoP service (i.e. _meshcop._udp) instance name, product name, vendor name and vendor OUI. + * + * This method must be called before this BorderAgent is enabled by SetEnabled. + * + * @param[in] aServiceInstanceName The service instance name; suffix may be appended to this value to avoid + * name conflicts. + * @param[in] aProductName The product name; must not exceed length of kMaxProductNameLength + * and an empty string will be ignored. + * @param[in] aVendorName The vendor name; must not exceed length of kMaxVendorNameLength + * and an empty string will be ignored. + * @param[in] aVendorOui The vendor OUI; must have length of 3 bytes or be empty and ignored. + * + * @returns OTBR_ERROR_INVALID_ARGS If aVendorName, aProductName or aVendorOui exceeds the + * allowed ranges. + * @returns OTBR_ERROR_NONE If successfully set the meshcop service values. + */ + otbrError SetMeshCopServiceValues(const std::string &aServiceInstanceName, + const std::string &aProductName, + const std::string &aVendorName, + const std::vector<uint8_t> &aVendorOui = {}); + + /** * This method enables/disables the Border Agent. * * @param[in] aIsEnabled Whether to enable the Border Agent. @@ -124,7 +146,7 @@ void HandleThreadStateChanged(otChangedFlags aFlags); bool IsThreadStarted(void) const; - std::string BaseServiceInstanceName() const; + std::string GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const; std::string GetAlternativeServiceInstanceName() const; otbr::Ncp::ControllerOpenThread &mNcp; @@ -135,6 +157,20 @@ std::map<std::string, std::vector<uint8_t>> mMeshCopTxtUpdate; #endif + std::vector<uint8_t> mVendorOui; + + std::string mVendorName; + std::string mProductName; + + // The base service instance name typically consists of the vendor and product name. But it can + // also be overridden by `OTBR_MESHCOP_SERVICE_INSTANCE_NAME` or method `SetMeshCopServiceValues()`. + // For example, this value can be "OpenThread Border Router". + std::string mBaseServiceInstanceName; + + // The actual instance name advertised in the mDNS service. This is usually the value of + // `mBaseServiceInstanceName` plus the Extended Address and optional random number for avoiding + // conflicts. For example, this value can be "OpenThread Border Router #7AC3" or + // "OpenThread Border Router #7AC3 (14379)". std::string mServiceInstanceName; };
diff --git a/src/common/types.hpp b/src/common/types.hpp index 7ba0065..a5cd547 100644 --- a/src/common/types.hpp +++ b/src/common/types.hpp
@@ -414,6 +414,10 @@ uint32_t mServiceResolutionEmaLatency; ///< The EMA latency of service resolutions in milliseconds }; +static constexpr size_t kVendorOuiLength = 3; +static constexpr size_t kMaxVendorNameLength = 24; +static constexpr size_t kMaxProductNameLength = 24; + } // namespace otbr #endif // OTBR_COMMON_TYPES_HPP_
diff --git a/src/ncp/ncp_openthread.cpp b/src/ncp/ncp_openthread.cpp index efd3e7f..84a8601 100644 --- a/src/ncp/ncp_openthread.cpp +++ b/src/ncp/ncp_openthread.cpp
@@ -296,6 +296,9 @@ otSysDeinit(); mInstance = nullptr; + + mThreadStateChangedCallbacks.clear(); + mResetHandlers.clear(); } void ControllerOpenThread::HandleStateChanged(otChangedFlags aFlags)
diff --git a/tests/android/Android.bp b/tests/android/Android.bp index 242eec0..d100717 100644 --- a/tests/android/Android.bp +++ b/tests/android/Android.bp
@@ -27,6 +27,7 @@ // package { + default_team: "trendy_team_fwk_thread_network", default_applicable_licenses: ["external_ot-br-posix_license"], }
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 index adf16d2..b3758ac 100644 --- a/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java +++ b/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
@@ -31,13 +31,20 @@ import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE; import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLED; import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED; +import static com.android.server.thread.openthread.testing.FakeOtDaemon.OT_DEVICE_ROLE_DISABLED; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.os.Handler; +import android.os.IBinder.DeathRecipient; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.test.TestLooper; @@ -50,6 +57,7 @@ import com.android.server.thread.openthread.INsdPublisher; import com.android.server.thread.openthread.IOtDaemonCallback; import com.android.server.thread.openthread.IOtStatusReceiver; +import com.android.server.thread.openthread.MeshcopTxtAttributes; import com.android.server.thread.openthread.OtDaemonState; import org.junit.Before; @@ -58,6 +66,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -86,13 +95,19 @@ + "642D643961300102D9A00410A245479C836D551B9CA557F7" + "B9D351B40C0402A0FFF8"); - private static int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26 - private static int DEFAULT_PREFERRED_CHANNEL_MASK = 0; + private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26 + private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0; + private static final byte[] TEST_VENDOR_OUI = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48}; + private static final String TEST_VENDOR_NAME = "test vendor"; + private static final String TEST_MODEL_NAME = "test model"; + private static final String TEST_DEFAULT_COUNTRY_CODE = "WW"; private FakeOtDaemon mFakeOtDaemon; private TestLooper mTestLooper; @Mock private ParcelFileDescriptor mMockTunFd; @Mock private INsdPublisher mMockNsdPublisher; + @Mock private IOtDaemonCallback mMockCallback; + private MeshcopTxtAttributes mOverriddenMeshcopTxts; @Before public void setUp() { @@ -100,25 +115,51 @@ mTestLooper = new TestLooper(); mFakeOtDaemon = new FakeOtDaemon(new Handler(mTestLooper.getLooper())); + mOverriddenMeshcopTxts = new MeshcopTxtAttributes(); + mOverriddenMeshcopTxts.vendorName = TEST_VENDOR_NAME; + mOverriddenMeshcopTxts.vendorOui = TEST_VENDOR_OUI; + mOverriddenMeshcopTxts.modelName = TEST_MODEL_NAME; } @Test - public void initialize_succeed_tunFdIsSet() throws Exception { - mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher); + public void initialize_succeed_argumentsAreSetAndCallbackIsInvoked() throws Exception { + mOverriddenMeshcopTxts.vendorName = TEST_VENDOR_NAME; + mOverriddenMeshcopTxts.vendorOui = TEST_VENDOR_OUI; + mOverriddenMeshcopTxts.modelName = TEST_MODEL_NAME; + mFakeOtDaemon.initialize( + mMockTunFd, + true, + mMockNsdPublisher, + mOverriddenMeshcopTxts, + mMockCallback, + TEST_DEFAULT_COUNTRY_CODE); + mTestLooper.dispatchAll(); + + MeshcopTxtAttributes meshcopTxts = mFakeOtDaemon.getOverriddenMeshcopTxtAttributes(); + assertThat(meshcopTxts).isNotNull(); + assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME); + assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI); + assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME); assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd); - } - - @Test - public void initialize_succeed_NsdPublisherIsSet() throws Exception { - mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher); - + assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED); assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher); + assertThat(mFakeOtDaemon.getStateCallback()).isEqualTo(mMockCallback); + assertThat(mFakeOtDaemon.getCountryCode()).isEqualTo(TEST_DEFAULT_COUNTRY_CODE); + assertThat(mFakeOtDaemon.isInitialized()).isTrue(); + verify(mMockCallback, times(1)).onStateChanged(any(), anyLong()); + verify(mMockCallback, times(1)).onBackboneRouterStateChanged(any()); } @Test public void registerStateCallback_noStateChange_callbackIsInvoked() throws Exception { - mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher); + mFakeOtDaemon.initialize( + mMockTunFd, + true, + mMockNsdPublisher, + mOverriddenMeshcopTxts, + mMockCallback, + TEST_DEFAULT_COUNTRY_CODE); final AtomicReference<OtDaemonState> stateRef = new AtomicReference<>(); final AtomicLong listenerIdRef = new AtomicLong(); final AtomicReference<BackboneRouterState> bbrStateRef = new AtomicReference<>(); @@ -211,12 +252,12 @@ } @Test - public void setThreadEnabled_disableThread_succeed() throws Exception { - assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED); + public void setThreadEnabled_enableThread_succeed() throws Exception { + assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED); final AtomicBoolean succeedRef = new AtomicBoolean(false); mFakeOtDaemon.setThreadEnabled( - false, + true, new IOtStatusReceiver.Default() { @Override public void onSuccess() { @@ -226,7 +267,7 @@ mTestLooper.dispatchAll(); assertThat(succeedRef.get()).isTrue(); - assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED); + assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED); } @Test @@ -281,4 +322,36 @@ assertThat(succeedRef.get()).isFalse(); assertThat(errorRef.get()).isEqualTo(OT_ERROR_INVALID_STATE); } + + @Test + public void terminate_statesAreResetAndDeathCallbackIsInvoked() throws Exception { + DeathRecipient mockDeathRecipient = mock(DeathRecipient.class); + mFakeOtDaemon.linkToDeath(mockDeathRecipient, 0); + mFakeOtDaemon.initialize( + mMockTunFd, + true, + mMockNsdPublisher, + mOverriddenMeshcopTxts, + mMockCallback, + TEST_DEFAULT_COUNTRY_CODE); + + mFakeOtDaemon.terminate(); + mTestLooper.dispatchAll(); + + assertThat(mFakeOtDaemon.isInitialized()).isFalse(); + OtDaemonState state = mFakeOtDaemon.getState(); + assertThat(state.isInterfaceUp).isEqualTo(false); + assertThat(state.partitionId).isEqualTo(-1); + assertThat(state.deviceRole).isEqualTo(OT_DEVICE_ROLE_DISABLED); + assertThat(state.activeDatasetTlvs).isEqualTo(new byte[0]); + assertThat(state.pendingDatasetTlvs).isEqualTo(new byte[0]); + BackboneRouterState bbrState = mFakeOtDaemon.getBackboneRouterState(); + assertThat(bbrState.multicastForwardingEnabled).isFalse(); + assertThat(bbrState.listeningAddresses).isEqualTo(new ArrayList<>()); + assertThat(mFakeOtDaemon.getDeathRecipient()).isNull(); + assertThat(mFakeOtDaemon.getTunFd()).isNull(); + assertThat(mFakeOtDaemon.getNsdPublisher()).isNull(); + assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED); + verify(mockDeathRecipient, times(1)).binderDied(); + } }