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