• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *    Copyright (c) 2023, The OpenThread Authors.
3  *    All rights reserved.
4  *
5  *    Redistribution and use in source and binary forms, with or without
6  *    modification, are permitted provided that the following conditions are met:
7  *    1. Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *    2. Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *    3. Neither the name of the copyright holder nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *    POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 package com.android.server.thread.openthread.testing;
30 
31 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_NOT_IMPLEMENTED;
32 import static com.android.server.thread.openthread.IOtDaemon.OT_EPHEMERAL_KEY_DISABLED;
33 import static com.android.server.thread.openthread.IOtDaemon.OT_EPHEMERAL_KEY_ENABLED;
34 import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLED;
35 import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
36 
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.net.thread.ChannelMaxPower;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.IBinder.DeathRecipient;
43 import android.os.ParcelFileDescriptor;
44 import android.os.RemoteException;
45 
46 import com.android.server.thread.openthread.BackboneRouterState;
47 import com.android.server.thread.openthread.IChannelMasksReceiver;
48 import com.android.server.thread.openthread.INsdPublisher;
49 import com.android.server.thread.openthread.IOtDaemon;
50 import com.android.server.thread.openthread.IOtDaemonCallback;
51 import com.android.server.thread.openthread.IOtOutputReceiver;
52 import com.android.server.thread.openthread.IOtStatusReceiver;
53 import com.android.server.thread.openthread.MeshcopTxtAttributes;
54 import com.android.server.thread.openthread.OtDaemonConfiguration;
55 import com.android.server.thread.openthread.OtDaemonState;
56 
57 import java.time.Duration;
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.NoSuchElementException;
61 
62 /** A fake implementation of the {@link IOtDaemon} AIDL API for testing. */
63 public final class FakeOtDaemon extends IOtDaemon.Stub {
64     /** The typical Thread network join / attach delay is around 8 seconds. */
65     public static final Duration JOIN_DELAY = Duration.ofSeconds(8);
66 
67     static final int OT_DEVICE_ROLE_DISABLED = 0;
68     static final int OT_DEVICE_ROLE_DETACHED = 1;
69     static final int OT_DEVICE_ROLE_CHILD = 2;
70     static final int OT_DEVICE_ROLE_ROUTER = 3;
71     static final int OT_DEVICE_ROLE_LEADER = 4;
72     static final int OT_ERROR_NONE = 0;
73 
74     private static final long PROACTIVE_LISTENER_ID = -1;
75 
76     private final Handler mHandler;
77     private OtDaemonState mState;
78     private BackboneRouterState mBbrState;
79     private boolean mIsInitialized = false;
80     private int mChannelMasksReceiverOtError = OT_ERROR_NONE;
81     private int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
82     private int mPreferredChannelMask = 0;
83     private boolean mTrelEnabled = false;
84 
85     @Nullable private DeathRecipient mDeathRecipient;
86     @Nullable private ParcelFileDescriptor mTunFd;
87     @Nullable private INsdPublisher mNsdPublisher;
88     @Nullable private MeshcopTxtAttributes mOverriddenMeshcopTxts;
89     @Nullable private IOtDaemonCallback mCallback;
90     @Nullable private Long mCallbackListenerId;
91     @Nullable private RemoteException mJoinException;
92     @Nullable private String mNat64Cidr;
93     @Nullable private RemoteException mSetNat64CidrException;
94     @Nullable private RemoteException mRunOtCtlCommandException;
95     @Nullable private String mCountryCode;
96     @Nullable private OtDaemonConfiguration mConfiguration;
97 
FakeOtDaemon(Handler handler)98     public FakeOtDaemon(Handler handler) {
99         mHandler = handler;
100         resetStates();
101     }
102 
resetStates()103     private void resetStates() {
104         mState = new OtDaemonState();
105         mState.isInterfaceUp = false;
106         mState.partitionId = -1;
107         mState.deviceRole = OT_DEVICE_ROLE_DISABLED;
108         mState.activeDatasetTlvs = new byte[0];
109         mState.pendingDatasetTlvs = new byte[0];
110         mState.threadEnabled = OT_STATE_DISABLED;
111         mState.ephemeralKeyState = OT_EPHEMERAL_KEY_DISABLED;
112         mState.ephemeralKeyPasscode = "";
113         mState.ephemeralKeyLifetimeMillis = 0;
114         mBbrState = new BackboneRouterState();
115         mBbrState.multicastForwardingEnabled = false;
116         mBbrState.listeningAddresses = new ArrayList<>();
117         mConfiguration = null;
118 
119         mTunFd = null;
120         mNsdPublisher = null;
121         mIsInitialized = false;
122 
123         mCallback = null;
124         mCallbackListenerId = null;
125     }
126 
127     @Override
asBinder()128     public IBinder asBinder() {
129         return this;
130     }
131 
132     @Override
linkToDeath(DeathRecipient recipient, int flags)133     public void linkToDeath(DeathRecipient recipient, int flags) {
134         if (mDeathRecipient != null && recipient != null) {
135             throw new IllegalStateException("IOtDaemon death recipient is already linked!");
136         }
137 
138         mDeathRecipient = recipient;
139     }
140 
141     @Override
unlinkToDeath(@onNull DeathRecipient recipient, int flags)142     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
143         if (mDeathRecipient == null || recipient != mDeathRecipient) {
144             throw new NoSuchElementException("recipient is not linked! " + recipient);
145         }
146 
147         mDeathRecipient = null;
148         return true;
149     }
150 
151     @Nullable
getDeathRecipient()152     public DeathRecipient getDeathRecipient() {
153         return mDeathRecipient;
154     }
155 
156     @Override
initialize( boolean enabled, OtDaemonConfiguration config, ParcelFileDescriptor tunFd, INsdPublisher nsdPublisher, MeshcopTxtAttributes overriddenMeshcopTxts, String countryCode, boolean trelEnabled, IOtDaemonCallback callback)157     public void initialize(
158             boolean enabled,
159             OtDaemonConfiguration config,
160             ParcelFileDescriptor tunFd,
161             INsdPublisher nsdPublisher,
162             MeshcopTxtAttributes overriddenMeshcopTxts,
163             String countryCode,
164             boolean trelEnabled,
165             IOtDaemonCallback callback)
166             throws RemoteException {
167         mIsInitialized = true;
168 
169         mState.threadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
170         setConfiguration(config, null /* receiver */);
171         mTunFd = tunFd;
172         mNsdPublisher = nsdPublisher;
173         mCountryCode = countryCode;
174         mTrelEnabled = trelEnabled;
175 
176         mOverriddenMeshcopTxts = new MeshcopTxtAttributes();
177         mOverriddenMeshcopTxts.vendorOui = overriddenMeshcopTxts.vendorOui.clone();
178         mOverriddenMeshcopTxts.vendorName = overriddenMeshcopTxts.vendorName;
179         mOverriddenMeshcopTxts.modelName = overriddenMeshcopTxts.modelName;
180         mOverriddenMeshcopTxts.nonStandardTxtEntries =
181                 List.copyOf(overriddenMeshcopTxts.nonStandardTxtEntries);
182 
183         registerStateCallback(callback, PROACTIVE_LISTENER_ID);
184     }
185 
186     /** Returns {@code true} if {@link initialize} has been called to initialize this object. */
isInitialized()187     public boolean isInitialized() {
188         return mIsInitialized;
189     }
190 
191     @Override
terminate()192     public void terminate() throws RemoteException {
193         mHandler.post(
194                 () -> {
195                     resetStates();
196                     if (mDeathRecipient != null) {
197                         mDeathRecipient.binderDied();
198                         mDeathRecipient = null;
199                     }
200                 });
201     }
202 
getEnabledState()203     public int getEnabledState() {
204         return mState.threadEnabled;
205     }
206 
getState()207     public OtDaemonState getState() {
208         return makeCopy(mState);
209     }
210 
getBackboneRouterState()211     public BackboneRouterState getBackboneRouterState() {
212         return makeCopy(mBbrState);
213     }
214 
215     /**
216      * Returns the Thread TUN interface FD sent to OT daemon or {@code null} if {@link initialize}
217      * is never called.
218      */
219     @Nullable
getTunFd()220     public ParcelFileDescriptor getTunFd() {
221         return mTunFd;
222     }
223 
224     /**
225      * Returns the INsdPublisher sent to OT daemon or {@code null} if {@link #initialize} is never
226      * called.
227      */
228     @Nullable
getNsdPublisher()229     public INsdPublisher getNsdPublisher() {
230         return mNsdPublisher;
231     }
232 
233     /**
234      * Returns the overridden MeshCoP TXT attributes that is to OT daemon or {@code null} if {@link
235      * #initialize} is never called.
236      */
237     @Nullable
getOverriddenMeshcopTxtAttributes()238     public MeshcopTxtAttributes getOverriddenMeshcopTxtAttributes() {
239         return mOverriddenMeshcopTxts;
240     }
241 
242     @Nullable
getCallback()243     public IOtDaemonCallback getCallback() {
244         return mCallback;
245     }
246 
247     @Override
setThreadEnabled(boolean enabled, IOtStatusReceiver receiver)248     public void setThreadEnabled(boolean enabled, IOtStatusReceiver receiver) {
249         mHandler.post(
250                 () -> {
251                     mState.threadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
252                     try {
253                         receiver.onSuccess();
254                     } catch (RemoteException e) {
255                         throw new AssertionError(e);
256                     }
257                 });
258     }
259 
260     @Override
registerStateCallback(IOtDaemonCallback callback, long listenerId)261     public void registerStateCallback(IOtDaemonCallback callback, long listenerId)
262             throws RemoteException {
263         mCallback = callback;
264         mCallbackListenerId = listenerId;
265 
266         mHandler.post(() -> onStateChanged(mState, mCallbackListenerId));
267         mHandler.post(() -> onBackboneRouterStateChanged(mBbrState));
268     }
269 
270     @Nullable
getStateCallback()271     public IOtDaemonCallback getStateCallback() {
272         return mCallback;
273     }
274 
275     /**
276      * Returns the country code sent to OT daemon or {@code null} if {@link #initialize} is never
277      * called.
278      */
279     @Nullable
getCountryCode()280     public String getCountryCode() {
281         return mCountryCode;
282     }
283 
284     @Override
join(byte[] activeDataset, IOtStatusReceiver receiver)285     public void join(byte[] activeDataset, IOtStatusReceiver receiver) throws RemoteException {
286         if (mJoinException != null) {
287             throw mJoinException;
288         }
289 
290         mHandler.post(
291                 () -> {
292                     mState.isInterfaceUp = true;
293                     mState.deviceRole = OT_DEVICE_ROLE_DETACHED;
294                     onStateChanged(mState, PROACTIVE_LISTENER_ID);
295                 });
296 
297         mHandler.postDelayed(
298                 () -> {
299                     mState.deviceRole = OT_DEVICE_ROLE_LEADER;
300                     mState.activeDatasetTlvs = activeDataset.clone();
301                     mBbrState.multicastForwardingEnabled = true;
302 
303                     onStateChanged(mState, PROACTIVE_LISTENER_ID);
304                     onBackboneRouterStateChanged(mBbrState);
305                     try {
306                         receiver.onSuccess();
307                     } catch (RemoteException e) {
308                         throw new AssertionError(e);
309                     }
310                 },
311                 JOIN_DELAY.toMillis());
312     }
313 
314     @Override
activateEphemeralKeyMode(long lifetimeMillis, IOtStatusReceiver receiver)315     public void activateEphemeralKeyMode(long lifetimeMillis, IOtStatusReceiver receiver) {
316         mHandler.post(
317                 () -> {
318                     mState.ephemeralKeyState = OT_EPHEMERAL_KEY_ENABLED;
319                     mState.ephemeralKeyPasscode = "123456789";
320                     mState.ephemeralKeyLifetimeMillis = lifetimeMillis;
321                     try {
322                         receiver.onSuccess();
323                     } catch (RemoteException e) {
324                         throw new AssertionError(e);
325                     }
326                 });
327     }
328 
329     @Override
deactivateEphemeralKeyMode(IOtStatusReceiver receiver)330     public void deactivateEphemeralKeyMode(IOtStatusReceiver receiver) {
331         mHandler.post(
332                 () -> {
333                     mState.ephemeralKeyState = OT_EPHEMERAL_KEY_DISABLED;
334                     mState.ephemeralKeyPasscode = "";
335                     mState.ephemeralKeyLifetimeMillis = 0;
336                     try {
337                         receiver.onSuccess();
338                     } catch (RemoteException e) {
339                         throw new AssertionError(e);
340                     }
341                 });
342     }
343 
makeCopy(OtDaemonState state)344     private OtDaemonState makeCopy(OtDaemonState state) {
345         OtDaemonState copyState = new OtDaemonState();
346         copyState.isInterfaceUp = state.isInterfaceUp;
347         copyState.deviceRole = state.deviceRole;
348         copyState.partitionId = state.partitionId;
349         copyState.activeDatasetTlvs = state.activeDatasetTlvs.clone();
350         copyState.pendingDatasetTlvs = state.pendingDatasetTlvs.clone();
351         return copyState;
352     }
353 
makeCopy(BackboneRouterState state)354     private BackboneRouterState makeCopy(BackboneRouterState state) {
355         BackboneRouterState copyState = new BackboneRouterState();
356         copyState.multicastForwardingEnabled = state.multicastForwardingEnabled;
357         copyState.listeningAddresses = new ArrayList<>(state.listeningAddresses);
358         return copyState;
359     }
360 
onStateChanged(OtDaemonState state, long listenerId)361     private void onStateChanged(OtDaemonState state, long listenerId) {
362         try {
363             // Make a copy of state so that clients won't keep a direct reference to it
364             OtDaemonState copyState = makeCopy(state);
365 
366             mCallback.onStateChanged(copyState, listenerId);
367         } catch (RemoteException e) {
368             throw new AssertionError(e);
369         }
370     }
371 
onBackboneRouterStateChanged(BackboneRouterState state)372     private void onBackboneRouterStateChanged(BackboneRouterState state) {
373         try {
374             // Make a copy of state so that clients won't keep a direct reference to it
375             BackboneRouterState copyState = makeCopy(state);
376 
377             mCallback.onBackboneRouterStateChanged(copyState);
378         } catch (RemoteException e) {
379             throw new AssertionError(e);
380         }
381     }
382 
383     /** Sets the {@link RemoteException} which will be thrown from {@link #join}. */
setJoinException(RemoteException exception)384     public void setJoinException(RemoteException exception) {
385         mJoinException = exception;
386     }
387 
388     @Override
leave(boolean eraseDataset, IOtStatusReceiver receiver)389     public void leave(boolean eraseDataset, IOtStatusReceiver receiver) throws RemoteException {
390         throw new UnsupportedOperationException("FakeOtDaemon#leave is not implemented!");
391     }
392 
393     @Override
setConfiguration(OtDaemonConfiguration config, IOtStatusReceiver receiver)394     public void setConfiguration(OtDaemonConfiguration config, IOtStatusReceiver receiver)
395             throws RemoteException {
396         mConfiguration = config;
397         // TODO: b/343814054 - Support enabling/disabling DHCPv6-PD.
398         if (mConfiguration.dhcpv6PdEnabled) {
399             receiver.onError(OT_ERROR_NOT_IMPLEMENTED, "DHCPv6-PD is not supported");
400             return;
401         }
402         if (receiver != null) {
403             receiver.onSuccess();
404         }
405     }
406 
407     /** Returns the configuration set by {@link #initialize} or {@link #setConfiguration}. */
408     @Nullable
getConfiguration()409     public OtDaemonConfiguration getConfiguration() {
410         return mConfiguration;
411     }
412 
413     @Override
setInfraLinkInterfaceName( String interfaceName, ParcelFileDescriptor fd, IOtStatusReceiver receiver)414     public void setInfraLinkInterfaceName(
415             String interfaceName, ParcelFileDescriptor fd, IOtStatusReceiver receiver)
416             throws RemoteException {
417         throw new UnsupportedOperationException(
418                 "FakeOtDaemon#setInfraLinkInterfaceName is not implemented!");
419     }
420 
421     @Override
setInfraLinkNat64Prefix(String nat64Prefix, IOtStatusReceiver receiver)422     public void setInfraLinkNat64Prefix(String nat64Prefix, IOtStatusReceiver receiver)
423             throws RemoteException {
424         throw new UnsupportedOperationException(
425                 "FakeOtDaemon#setInfraLinkNat64Prefix is not implemented!");
426     }
427 
428     /** Sets the {@link RemoteException} which will be thrown from {@link #setNat64Cidr}. */
setSetNat64CidrException(RemoteException exception)429     public void setSetNat64CidrException(RemoteException exception) {
430         mSetNat64CidrException = exception;
431     }
432 
433     @Override
setNat64Cidr(String nat64Cidr, IOtStatusReceiver receiver)434     public void setNat64Cidr(String nat64Cidr, IOtStatusReceiver receiver) throws RemoteException {
435         if (mSetNat64CidrException != null) {
436             throw mSetNat64CidrException;
437         }
438         mNat64Cidr = nat64Cidr;
439         if (receiver != null) {
440             receiver.onSuccess();
441         }
442     }
443 
444     /** Returns the NAT64 CIDR set by {@link #setNat64Cidr}. */
445     @Nullable
getNat64Cidr()446     public String getNat64Cidr() {
447         return mNat64Cidr;
448     }
449 
450     @Override
setInfraLinkDnsServers(List<String> dnsServers, IOtStatusReceiver receiver)451     public void setInfraLinkDnsServers(List<String> dnsServers, IOtStatusReceiver receiver)
452             throws RemoteException {
453         throw new UnsupportedOperationException(
454                 "FakeOtDaemon#setInfraLinkDnsServers is not implemented!");
455     }
456 
457     @Override
scheduleMigration(byte[] pendingDataset, IOtStatusReceiver receiver)458     public void scheduleMigration(byte[] pendingDataset, IOtStatusReceiver receiver)
459             throws RemoteException {
460         throw new UnsupportedOperationException(
461                 "FakeOtDaemon#scheduleMigration is not implemented!");
462     }
463 
464     @Override
setCountryCode(String countryCode, IOtStatusReceiver receiver)465     public void setCountryCode(String countryCode, IOtStatusReceiver receiver)
466             throws RemoteException {
467         throw new UnsupportedOperationException("FakeOtDaemon#setCountryCode is not implemented!");
468     }
469 
470     @Override
getChannelMasks(IChannelMasksReceiver receiver)471     public void getChannelMasks(IChannelMasksReceiver receiver) throws RemoteException {
472         mHandler.post(
473                 () -> {
474                     try {
475                         if (mChannelMasksReceiverOtError == OT_ERROR_NONE) {
476                             receiver.onSuccess(mSupportedChannelMask, mPreferredChannelMask);
477                         } else {
478                             receiver.onError(
479                                     mChannelMasksReceiverOtError, "Get channel masks failed");
480                         }
481                     } catch (RemoteException e) {
482                         throw new AssertionError(e);
483                     }
484                 });
485     }
486 
setChannelMasks(int supportedChannelMask, int preferredChannelMask)487     public void setChannelMasks(int supportedChannelMask, int preferredChannelMask) {
488         mSupportedChannelMask = supportedChannelMask;
489         mPreferredChannelMask = preferredChannelMask;
490     }
491 
setChannelMasksReceiverOtError(int otError)492     public void setChannelMasksReceiverOtError(int otError) {
493         mChannelMasksReceiverOtError = otError;
494     }
495 
496     @Override
setChannelMaxPowers(ChannelMaxPower[] channelMaxPowers, IOtStatusReceiver receiver)497     public void setChannelMaxPowers(ChannelMaxPower[] channelMaxPowers, IOtStatusReceiver receiver)
498             throws RemoteException {
499         throw new UnsupportedOperationException(
500                 "FakeOtDaemon#setChannelTargetPowers is not implemented!");
501     }
502 
503     @Override
runOtCtlCommand(String command, boolean isInteractive, IOtOutputReceiver receiver)504     public void runOtCtlCommand(String command, boolean isInteractive, IOtOutputReceiver receiver)
505             throws RemoteException {
506         if (mRunOtCtlCommandException != null) {
507             throw mRunOtCtlCommandException;
508         }
509 
510         mHandler.post(
511                 () -> {
512                     try {
513                         List<String> outputLines = new ArrayList<>();
514                         outputLines.add("leader");
515                         outputLines.add("\r\n");
516                         outputLines.add("Done");
517                         outputLines.add("\r\n");
518 
519                         for (String line : outputLines) {
520                             receiver.onOutput(line);
521                         }
522                         receiver.onComplete();
523                     } catch (RemoteException e) {
524                         throw new AssertionError(e);
525                     }
526                 });
527     }
528 
529     /** Sets the {@link RemoteException} which will be thrown from {@link #runOtCtlCommand}. */
setRunOtCtlCommandException(RemoteException exception)530     public void setRunOtCtlCommandException(RemoteException exception) {
531         mRunOtCtlCommandException = exception;
532     }
533 
isTrelEnabled()534     public boolean isTrelEnabled() {
535         return mTrelEnabled;
536     }
537 }
538