Merge "Select Thread channel from preferred or supported channel mask" into main

This commit is contained in:
Zhanglong Xia 2024-02-26 02:56:30 +00:00 • committed by Gerrit Code Review
commit 427c307ca2
4 changed files with 197 additions and 22 deletions

View file

@ -83,8 +83,8 @@ public final class ThreadNetworkManager {
* This user restriction specifies if Thread network is disallowed on the device. If Thread
* network is disallowed it cannot be turned on via Settings.
*
* <p>this is a mirror of {@link UserManager#DISALLOW_THREAD_NETWORK} which is not available
* on Android U devices.
* <p>this is a mirror of {@link UserManager#DISALLOW_THREAD_NETWORK} which is not available on
* Android U devices.
*
* @hide
*/

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.thread;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
import java.util.HashSet;
import java.util.Set;
/**
* A {@link IActiveOperationalDatasetReceiver} wrapper which makes it easier to invoke the
* callbacks.
*/
final class ActiveOperationalDatasetReceiverWrapper {
private final IActiveOperationalDatasetReceiver mReceiver;
private static final Object sPendingReceiversLock = new Object();
@GuardedBy("sPendingReceiversLock")
private static final Set<ActiveOperationalDatasetReceiverWrapper> sPendingReceivers =
new HashSet<>();
public ActiveOperationalDatasetReceiverWrapper(IActiveOperationalDatasetReceiver receiver) {
this.mReceiver = receiver;
synchronized (sPendingReceiversLock) {
sPendingReceivers.add(this);
}
}
public static void onOtDaemonDied() {
synchronized (sPendingReceiversLock) {
for (ActiveOperationalDatasetReceiverWrapper receiver : sPendingReceivers) {
try {
receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
} catch (RemoteException e) {
// The client is dead, do nothing
}
}
sPendingReceivers.clear();
}
}
public void onSuccess(ActiveOperationalDataset dataset) {
synchronized (sPendingReceiversLock) {
sPendingReceivers.remove(this);
}
try {
mReceiver.onSuccess(dataset);
} catch (RemoteException e) {
// The client is dead, do nothing
}
}
public void onError(int errorCode, String errorMessage) {
synchronized (sPendingReceiversLock) {
sPendingReceivers.remove(this);
}
try {
mReceiver.onError(errorCode, errorMessage);
} catch (RemoteException e) {
// The client is dead, do nothing
}
}
}

View file

@ -109,6 +109,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceManagerWrapper;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
import com.android.server.thread.openthread.IOtStatusReceiver;
@ -157,9 +158,6 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
private final NsdPublisher mNsdPublisher;
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
// TODO(b/308310823): read supported channel from Thread dameon
private final int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
@Nullable private IOtDaemon mOtDaemon;
@Nullable private NetworkAgent mNetworkAgent;
@Nullable private NetworkAgent mTestNetworkAgent;
@ -593,26 +591,51 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
@Override
public void createRandomizedDataset(
String networkName, IActiveOperationalDatasetReceiver receiver) {
mHandler.post(
() -> {
ActiveOperationalDataset dataset =
createRandomizedDatasetInternal(
networkName,
mSupportedChannelMask,
Instant.now(),
new Random(),
new SecureRandom());
try {
receiver.onSuccess(dataset);
} catch (RemoteException e) {
// The client is dead, do nothing
}
});
ActiveOperationalDatasetReceiverWrapper receiverWrapper =
new ActiveOperationalDatasetReceiverWrapper(receiver);
mHandler.post(() -> createRandomizedDatasetInternal(networkName, receiverWrapper));
}
private static ActiveOperationalDataset createRandomizedDatasetInternal(
private void createRandomizedDatasetInternal(
String networkName, @NonNull ActiveOperationalDatasetReceiverWrapper receiver) {
checkOnHandlerThread();
try {
getOtDaemon().getChannelMasks(newChannelMasksReceiver(networkName, receiver));
} catch (RemoteException e) {
Log.e(TAG, "otDaemon.getChannelMasks failed", e);
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
}
}
private IChannelMasksReceiver newChannelMasksReceiver(
String networkName, ActiveOperationalDatasetReceiverWrapper receiver) {
return new IChannelMasksReceiver.Stub() {
@Override
public void onSuccess(int supportedChannelMask, int preferredChannelMask) {
ActiveOperationalDataset dataset =
createRandomizedDataset(
networkName,
supportedChannelMask,
preferredChannelMask,
Instant.now(),
new Random(),
new SecureRandom());
receiver.onSuccess(dataset);
}
@Override
public void onError(int errorCode, String errorMessage) {
receiver.onError(otErrorToAndroidError(errorCode), errorMessage);
}
};
}
private static ActiveOperationalDataset createRandomizedDataset(
String networkName,
int supportedChannelMask,
int preferredChannelMask,
Instant now,
Random random,
SecureRandom secureRandom) {
@ -622,6 +645,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
final SparseArray<byte[]> channelMask = new SparseArray<>(1);
channelMask.put(CHANNEL_PAGE_24_GHZ, channelMaskToByteArray(supportedChannelMask));
final int channel = selectChannel(supportedChannelMask, preferredChannelMask, random);
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
@ -632,7 +656,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
.setChannel(CHANNEL_PAGE_24_GHZ, selectRandomChannel(supportedChannelMask, random))
.setChannel(CHANNEL_PAGE_24_GHZ, channel)
.setChannelMask(channelMask)
.setPskc(newRandomBytes(secureRandom, LENGTH_PSKC))
.setNetworkKey(newRandomBytes(secureRandom, LENGTH_NETWORK_KEY))
@ -641,6 +665,18 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
.build();
}
private static int selectChannel(
int supportedChannelMask, int preferredChannelMask, Random random) {
// If the preferred channel mask is not empty, select a random channel from it, otherwise
// choose one from the supported channel mask.
preferredChannelMask = preferredChannelMask & supportedChannelMask;
if (preferredChannelMask == 0) {
preferredChannelMask = supportedChannelMask;
}
return selectRandomChannel(preferredChannelMask, random);
}
private static byte[] newRandomBytes(Random random, int length) {
byte[] result = new byte[length];
random.nextBytes(result);

View file

@ -16,6 +16,7 @@
package com.android.server.thread;
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
@ -23,6 +24,7 @@ import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
@ -30,8 +32,10 @@ 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.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@ -47,9 +51,11 @@ import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
import android.net.thread.ThreadNetworkException;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserManager;
@ -64,6 +70,8 @@ import com.android.server.thread.openthread.testing.FakeOtDaemon;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@ -95,6 +103,12 @@ public final class ThreadNetworkControllerServiceTest {
+ "B9D351B40C0402A0FFF8");
private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
private static final String DEFAULT_NETWORK_NAME = "thread-wpan0";
private static final int OT_ERROR_NONE = 0;
private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0x00000800; // channel 11
private static final int DEFAULT_SELECTED_CHANNEL = 11;
private static final byte[] DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY = base16().decode("001FFFE0");
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@ -104,10 +118,12 @@ public final class ThreadNetworkControllerServiceTest {
@Mock private ThreadPersistentSettings mMockPersistentSettings;
@Mock private NsdPublisher mMockNsdPublisher;
@Mock private UserManager mMockUserManager;
@Mock private IBinder mIBinder;
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
private ThreadNetworkControllerService mService;
@Captor private ArgumentCaptor<ActiveOperationalDataset> mActiveDatasetCaptor;
@Before
public void setUp() {
@ -281,4 +297,40 @@ public final class ThreadNetworkControllerServiceTest {
}
};
}
@Test
public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
final IActiveOperationalDatasetReceiver mockReceiver =
mock(IActiveOperationalDatasetReceiver.class);
mFakeOtDaemon.setChannelMasks(
DEFAULT_SUPPORTED_CHANNEL_MASK, DEFAULT_PREFERRED_CHANNEL_MASK);
mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_NONE);
mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
mTestLooper.dispatchAll();
verify(mockReceiver, never()).onError(anyInt(), anyString());
verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
assertThat(activeDataset.getNetworkName()).isEqualTo(DEFAULT_NETWORK_NAME);
assertThat(activeDataset.getChannelMask().size()).isEqualTo(1);
assertThat(activeDataset.getChannelMask().get(CHANNEL_PAGE_24_GHZ))
.isEqualTo(DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY);
assertThat(activeDataset.getChannel()).isEqualTo(DEFAULT_SELECTED_CHANNEL);
}
@Test
public void createRandomizedDataset_otDaemonRemoteFailure_returnsPreconditionError()
throws Exception {
final IActiveOperationalDatasetReceiver mockReceiver =
mock(IActiveOperationalDatasetReceiver.class);
mFakeOtDaemon.setChannelMasksReceiverOtError(OT_ERROR_INVALID_STATE);
when(mockReceiver.asBinder()).thenReturn(mIBinder);
mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
mTestLooper.dispatchAll();
verify(mockReceiver, never()).onSuccess(any(ActiveOperationalDataset.class));
verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
}
}