[remoteauth] Implement RangingSession
* Add basic functionalities to RangingSession and related data/util classes.
Test: atest RemoteAuthUnitTests
Bug: 292549287
Change-Id: Iff6a3cb1aefc20ef69e65e5e2705e8a707874668
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
index b2b5aab..b659bf7 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/README.md
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -6,3 +6,5 @@
## Ranging
Provides the ranging manager to perform ranging with the peer devices.
+## Util
+Common utilities.
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
index 9ef6bda..4db89b3 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
@@ -16,9 +16,16 @@
package com.android.server.remoteauth.ranging;
import android.annotation.NonNull;
+import android.content.Context;
+import android.util.Log;
import androidx.annotation.IntDef;
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.util.Crypto;
+
+import com.google.common.hash.Hashing;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -35,18 +42,50 @@
* <p>Ranging method specific implementation shall be implemented in the extended class.
*/
public abstract class RangingSession {
+ private static final String TAG = "RangingSession";
/** Types of ranging error. */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
value = {
RANGING_ERROR_UNKNOWN,
+ RANGING_ERROR_INVALID_PARAMETERS,
+ RANGING_ERROR_STOPPED_BY_REQUEST,
+ RANGING_ERROR_STOPPED_BY_PEER,
+ RANGING_ERROR_FAILED_TO_START,
+ RANGING_ERROR_FAILED_TO_STOP,
+ RANGING_ERROR_SYSTEM_ERROR,
+ RANGING_ERROR_SYSTEM_TIMEOUT,
})
public @interface RangingError {}
/** Unknown ranging error type. */
public static final int RANGING_ERROR_UNKNOWN = 0x0;
+ /** Ranging error due to invalid parameters. */
+ public static final int RANGING_ERROR_INVALID_PARAMETERS = 0x1;
+
+ /** Ranging error due to stopped by calling {@link #stop}. */
+ public static final int RANGING_ERROR_STOPPED_BY_REQUEST = 0x2;
+
+ /** Ranging error due to stopped by the peer device. */
+ public static final int RANGING_ERROR_STOPPED_BY_PEER = 0x3;
+
+ /** Ranging error due to failure to start ranging. */
+ public static final int RANGING_ERROR_FAILED_TO_START = 0x4;
+
+ /** Ranging error due to failure to stop ranging. */
+ public static final int RANGING_ERROR_FAILED_TO_STOP = 0x5;
+
+ /**
+ * Ranging error due to system error cause by changes such as privacy policy, power management
+ * policy, permissions, and more.
+ */
+ public static final int RANGING_ERROR_SYSTEM_ERROR = 0x6;
+
+ /** Ranging error due to system timeout in retry attempts. */
+ public static final int RANGING_ERROR_SYSTEM_TIMEOUT = 0x7;
+
/** Interface for ranging update callbacks. */
public interface RangingCallback {
/**
@@ -55,7 +94,8 @@
* @param sessionInfo info about this ranging session.
* @param rangingReport new ranging report
*/
- void onRangingReport(SessionInfo sessionInfo, RangingReport rangingReport);
+ void onRangingReport(
+ @NonNull SessionInfo sessionInfo, @NonNull RangingReport rangingReport);
/**
* Call upon any ranging error events.
@@ -63,7 +103,49 @@
* @param sessionInfo info about this ranging session.
* @param rangingError error type
*/
- void onError(SessionInfo sessionInfo, @RangingError int rangingError);
+ void onError(@NonNull SessionInfo sessionInfo, @RangingError int rangingError);
+ }
+
+ protected Context mContext;
+ protected SessionInfo mSessionInfo;
+ protected float mLowerProximityBoundaryM;
+ protected float mUpperProximityBoundaryM;
+ protected boolean mAutoDeriveParams;
+ protected byte[] mBaseKey;
+ protected byte[] mSyncData;
+ protected int mSyncCounter;
+ protected byte[] mDerivedData;
+ protected int mDerivedDataLength;
+
+ protected RangingSession(
+ @NonNull Context context,
+ @NonNull SessionParameters sessionParameters,
+ int derivedDataLength) {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(sessionParameters);
+ mContext = context;
+ mSessionInfo =
+ new SessionInfo.Builder()
+ .setDeviceId(sessionParameters.getDeviceId())
+ .setRangingMethod(sessionParameters.getRangingMethod())
+ .build();
+ mLowerProximityBoundaryM = sessionParameters.getLowerProximityBoundaryM();
+ mUpperProximityBoundaryM = sessionParameters.getUpperProximityBoundaryM();
+ mAutoDeriveParams = sessionParameters.getAutoDeriveParams();
+ Log.i(
+ TAG,
+ "Creating a new RangingSession {info = "
+ + mSessionInfo
+ + ", autoDeriveParams = "
+ + mAutoDeriveParams
+ + "}");
+ if (mAutoDeriveParams) {
+ Preconditions.checkArgument(
+ derivedDataLength > 0, "derivedDataLength must be greater than 0");
+ mDerivedDataLength = derivedDataLength;
+ resetBaseKey(sessionParameters.getBaseKey());
+ resetSyncData(sessionParameters.getSyncData());
+ }
}
/**
@@ -75,6 +157,8 @@
* @param rangingParameters parameters to start the ranging.
* @param executor Executor to run the rangingCallback.
* @param rangingCallback callback to notify of ranging events.
+ * @throws NullPointerException if params are null.
+ * @throws IllegalArgumentException if rangingParameters is invalid.
*/
public abstract void start(
@NonNull RangingParameters rangingParameters,
@@ -94,8 +178,22 @@
* the secure connection between the devices is lost.
*
* @param baseKey new baseKey must be 16 or 32 bytes.
+ * @throws NullPointerException if baseKey is null.
+ * @throws IllegalArgumentException if baseKey has invalid length.
*/
- public void resetBaseKey(byte[] baseKey) {}
+ public void resetBaseKey(@NonNull byte[] baseKey) {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, new baseKey is ignored.");
+ return;
+ }
+ Preconditions.checkNotNull(baseKey);
+ if (baseKey.length != 16 && baseKey.length != 32) {
+ throw new IllegalArgumentException("Invalid baseKey length: " + baseKey.length);
+ }
+ mBaseKey = baseKey;
+ updateDerivedData();
+ Log.i(TAG, "resetBaseKey");
+ }
/**
* Resets the synchronization by giving a new syncData used for ranging parameters derivation.
@@ -105,6 +203,52 @@
* manner.
*
* @param syncData new syncData must be 16 bytes.
+ * @throws NullPointerException if baseKey is null.
+ * @throws IllegalArgumentException if syncData has invalid length.
*/
- public void resetSyncData(byte[] syncData) {}
+ public void resetSyncData(@NonNull byte[] syncData) {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, new syncData is ignored.");
+ return;
+ }
+ Preconditions.checkNotNull(syncData);
+ if (syncData.length != 16) {
+ throw new IllegalArgumentException("Invalid syncData length: " + syncData.length);
+ }
+ mSyncData = syncData;
+ mSyncCounter = 0;
+ updateDerivedData();
+ Log.i(TAG, "resetSyncData");
+ }
+
+ /** Update mDerivedData */
+ protected boolean updateDerivedData() {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, updateDerivedData is skipped.");
+ return false;
+ }
+ if (mBaseKey == null
+ || mBaseKey.length == 0
+ || mSyncData == null
+ || mSyncData.length == 0) {
+ Log.w(TAG, "updateDerivedData: Missing baseKey/syncData");
+ return false;
+ }
+ byte[] hashedSyncData =
+ Hashing.sha256()
+ .newHasher()
+ .putBytes(mSyncData)
+ .putInt(mSyncCounter)
+ .hash()
+ .asBytes();
+ byte[] newDerivedData = Crypto.computeHkdf(mBaseKey, hashedSyncData, mDerivedDataLength);
+ if (newDerivedData == null) {
+ Log.w(TAG, "updateDerivedData: computeHkdf failed");
+ return false;
+ }
+ mDerivedData = newDerivedData;
+ mSyncCounter++;
+ Log.i(TAG, "updateDerivedData");
+ return true;
+ }
}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
index 33c3203..2f71244 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
@@ -19,9 +19,14 @@
import android.annotation.NonNull;
+import androidx.annotation.IntDef;
+
import com.android.internal.util.Preconditions;
import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The set of parameters to create a ranging session.
*
@@ -31,9 +36,29 @@
*/
public class SessionParameters {
+ /** Ranging device role. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {
+ DEVICE_ROLE_UNKNOWN,
+ DEVICE_ROLE_INITIATOR,
+ DEVICE_ROLE_RESPONDER,
+ })
+ public @interface DeviceRole {}
+
+ /** Unknown device role. */
+ public static final int DEVICE_ROLE_UNKNOWN = 0x0;
+
+ /** Device that initiates the ranging. */
+ public static final int DEVICE_ROLE_INITIATOR = 0x1;
+
+ /** Device that responds to ranging. */
+ public static final int DEVICE_ROLE_RESPONDER = 0x2;
+
/* Required parameters */
private final String mDeviceId;
@RangingMethod private final int mRangingMethod;
+ @DeviceRole private final int mDeviceRole;
/* Optional parameters */
private final float mLowerProximityBoundaryM;
@@ -51,6 +76,11 @@
return mRangingMethod;
}
+ @DeviceRole
+ public int getDeviceRole() {
+ return mDeviceRole;
+ }
+
public float getLowerProximityBoundaryM() {
return mLowerProximityBoundaryM;
}
@@ -74,6 +104,7 @@
private SessionParameters(
String deviceId,
@RangingMethod int rangingMethod,
+ @DeviceRole int deviceRole,
float lowerProximityBoundaryM,
float upperProximityBoundaryM,
boolean autoDeriveParams,
@@ -81,6 +112,7 @@
byte[] syncData) {
mDeviceId = deviceId;
mRangingMethod = rangingMethod;
+ mDeviceRole = deviceRole;
mLowerProximityBoundaryM = lowerProximityBoundaryM;
mUpperProximityBoundaryM = upperProximityBoundaryM;
mAutoDeriveParams = autoDeriveParams;
@@ -92,6 +124,7 @@
public static final class Builder {
private String mDeviceId = new String("");
@RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN;
+ @DeviceRole private int mDeviceRole = DEVICE_ROLE_UNKNOWN;
private float mLowerProximityBoundaryM;
private float mUpperProximityBoundaryM;
private boolean mAutoDeriveParams = false;
@@ -120,6 +153,12 @@
return this;
}
+ /** Sets the {@link DeviceRole} to be used for the {@link RangingSession}. */
+ public Builder setDeviceRole(@DeviceRole int deviceRole) {
+ mDeviceRole = deviceRole;
+ return this;
+ }
+
/**
* Sets the lower proximity boundary in meters, must be greater than or equals to zero.
*
@@ -191,6 +230,7 @@
Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty.");
Preconditions.checkArgument(
mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod");
+ Preconditions.checkArgument(mDeviceRole != DEVICE_ROLE_UNKNOWN, "Unknown deviceRole");
Preconditions.checkArgument(
mLowerProximityBoundaryM >= 0,
"Negative lowerProximityBoundaryM: " + mLowerProximityBoundaryM);
@@ -212,6 +252,7 @@
return new SessionParameters(
mDeviceId,
mRangingMethod,
+ mDeviceRole,
mLowerProximityBoundaryM,
mUpperProximityBoundaryM,
mAutoDeriveParams,
diff --git a/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
new file mode 100644
index 0000000..573597f
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.remoteauth.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Utility class of cryptographic functions. */
+public final class Crypto {
+ private static final String TAG = "Crypto";
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+ /**
+ * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
+ * given size.
+ *
+ * @param ikm the input keying material.
+ * @param salt A possibly non-secret random value.
+ * @param size The length of the generated pseudorandom string in bytes. The maximal size is
+ * 255.DigestSize, where DigestSize is the size of the underlying HMAC.
+ * @return size pseudorandom bytes, null if failed.
+ */
+ // Based on
+ // google3/third_party/tink/java_src/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
+ @Nullable
+ public static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
+ Mac mac;
+ try {
+ mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e);
+ return null;
+ }
+
+ if (size > 255 * mac.getMacLength()) {
+ Log.w(TAG, "Size too large. " + size + " > " + 255 * mac.getMacLength());
+ return null;
+ }
+
+ if (ikm == null || ikm.length == 0) {
+ Log.w(TAG, "Ikm cannot be empty.");
+ return null;
+ }
+
+ if (salt == null || salt.length == 0) {
+ Log.w(TAG, "Salt cannot be empty.");
+ return null;
+ }
+
+ try {
+ mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM));
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, "Invalid key.", e);
+ return null;
+ }
+
+ byte[] prk = mac.doFinal(ikm);
+ byte[] result = new byte[size];
+ try {
+ mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM));
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, "Invalid key.", e);
+ return null;
+ }
+
+ byte[] digest = new byte[0];
+ int ctr = 1;
+ int pos = 0;
+ while (true) {
+ mac.update(digest);
+ mac.update((byte) ctr);
+ digest = mac.doFinal();
+ if (pos + digest.length < size) {
+ System.arraycopy(digest, 0, result, pos, digest.length);
+ pos += digest.length;
+ ctr++;
+ } else {
+ System.arraycopy(digest, 0, result, pos, size - pos);
+ break;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
new file mode 100644
index 0000000..0e547d6
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/** Unit test for {@link RangingSession}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+
+ private static final String TEST_DEVICE_ID = "test_device_id";
+ @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+ @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+ private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
+ private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
+ private static final byte[] TEST_BASE_KEY =
+ new byte[] {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f
+ };
+ private static final byte[] TEST_BASE_KEY2 =
+ new byte[] {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0,
+ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+ };
+ private static final byte[] TEST_SYNC_DATA =
+ new byte[] {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x00
+ };
+ private static final byte[] TEST_SYNC_DATA2 =
+ new byte[] {
+ 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x00
+ };
+
+ private static final SessionParameters TEST_SESSION_PARAMETER_WITH_AD =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+ .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+ .setAutoDeriveParams(true)
+ .setBaseKey(TEST_BASE_KEY)
+ .setSyncData(TEST_SYNC_DATA)
+ .build();
+ private static final SessionParameters TEST_SESSION_PARAMETER_WO_AD =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+ .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+ .setAutoDeriveParams(false)
+ .setBaseKey(TEST_BASE_KEY)
+ .setSyncData(TEST_SYNC_DATA)
+ .build();
+ private static final int TEST_DERIVE_DATA_LENGTH = 40;
+
+ /** Wrapper class for testing {@link RangingSession}. */
+ public static class RangingSessionWrapper extends RangingSession {
+ public RangingSessionWrapper(
+ Context context, SessionParameters sessionParameters, int derivedDataLength) {
+ super(context, sessionParameters, derivedDataLength);
+ }
+
+ @Override
+ public void start(
+ RangingParameters rangingParameters,
+ Executor executor,
+ RangingCallback rangingCallback) {}
+
+ @Override
+ public void stop() {}
+
+ @Override
+ public boolean updateDerivedData() {
+ return super.updateDerivedData();
+ }
+
+ public byte[] baseKey() {
+ return mBaseKey;
+ }
+
+ public byte[] syncData() {
+ return mSyncData;
+ }
+
+ public byte[] derivedData() {
+ return mDerivedData;
+ }
+
+ public int syncCounter() {
+ return mSyncCounter;
+ }
+ }
+
+ @Mock private Context mContext;
+
+ private RangingSessionWrapper mRangingSessionWithAD;
+ private RangingSessionWrapper mRangingSessionWithoutAD;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mRangingSessionWithAD =
+ new RangingSessionWrapper(
+ mContext, TEST_SESSION_PARAMETER_WITH_AD, TEST_DERIVE_DATA_LENGTH);
+ mRangingSessionWithoutAD =
+ new RangingSessionWrapper(mContext, TEST_SESSION_PARAMETER_WO_AD, 0);
+ }
+
+ @Test
+ public void testResetBaseKey_autoDeriveDisabled() {
+ assertNull(mRangingSessionWithoutAD.baseKey());
+ mRangingSessionWithoutAD.resetBaseKey(TEST_BASE_KEY2);
+ assertNull(mRangingSessionWithoutAD.baseKey());
+ }
+
+ @Test
+ public void testResetBaseKey_nullBaseKey() {
+ assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetBaseKey(null));
+ }
+
+ @Test
+ public void testResetBaseKey_invalidBaseKey() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mRangingSessionWithAD.resetBaseKey(new byte[] {0x1, 0x2, 0x3, 0x4}));
+ }
+
+ @Test
+ public void testResetBaseKey_success() {
+ mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY2);
+ assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY2);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+
+ mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY);
+ assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 3);
+ }
+
+ @Test
+ public void testResetSyncData_autoDeriveDisabled() {
+ assertNull(mRangingSessionWithoutAD.syncData());
+ mRangingSessionWithoutAD.resetSyncData(TEST_SYNC_DATA2);
+ assertNull(mRangingSessionWithoutAD.syncData());
+ }
+
+ @Test
+ public void testResetSyncData_nullSyncData() {
+ assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetSyncData(null));
+ }
+
+ @Test
+ public void testResetSyncData_invalidSyncData() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mRangingSessionWithAD.resetSyncData(new byte[] {0x1, 0x2, 0x3, 0x4}));
+ }
+
+ @Test
+ public void testResetSyncData_success() {
+ mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA2);
+ assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA2);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+
+ mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA);
+ assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+ }
+
+ @Test
+ public void testUpdateDerivedData_autoDeriveDisabled() {
+ assertFalse(mRangingSessionWithoutAD.updateDerivedData());
+ assertEquals(mRangingSessionWithoutAD.syncCounter(), 0);
+ }
+
+ @Test
+ public void testUpdateDerivedData_hkdfFailed() {
+ // Max derivedDataLength is 32*255
+ RangingSessionWrapper rangingSession =
+ new RangingSessionWrapper(
+ mContext, TEST_SESSION_PARAMETER_WITH_AD, /* derivedDataLength= */ 10000);
+ assertNull(rangingSession.derivedData());
+ assertFalse(rangingSession.updateDerivedData());
+ assertEquals(rangingSession.syncCounter(), 0);
+ assertNull(rangingSession.derivedData());
+ }
+
+ @Test
+ public void testUpdateDerivedData_success() {
+ assertNotNull(mRangingSessionWithAD.derivedData());
+ assertTrue(mRangingSessionWithAD.updateDerivedData());
+ assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+ assertNotNull(mRangingSessionWithAD.derivedData());
+ }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
index 357fdf9..522623e 100644
--- a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
@@ -17,6 +17,7 @@
package com.android.server.remoteauth.ranging;
import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -25,6 +26,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,6 +37,7 @@
private static final String TEST_DEVICE_ID = "test_device_id";
@RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+ @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
private static final boolean TEST_AUTO_DERIVE_PARAMS = true;
@@ -55,6 +58,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -82,6 +86,7 @@
final SessionParameters.Builder builder =
new SessionParameters.Builder()
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setBaseKey(TEST_BASE_KEY)
@@ -95,6 +100,21 @@
final SessionParameters.Builder builder =
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+ .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+ .setBaseKey(TEST_BASE_KEY)
+ .setSyncData(TEST_SYNC_DATA);
+
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuildingSessionParameters_invalidDeviceRole() {
+ final SessionParameters.Builder builder =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setBaseKey(TEST_BASE_KEY)
@@ -109,6 +129,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(-1.0f)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setBaseKey(TEST_BASE_KEY)
@@ -123,6 +144,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M - 0.1f)
.setBaseKey(TEST_BASE_KEY)
@@ -138,6 +160,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(autoDeriveParams)
@@ -154,6 +177,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -168,6 +192,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -183,6 +208,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -197,6 +223,7 @@
new SessionParameters.Builder()
.setDeviceId(TEST_DEVICE_ID)
.setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
.setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
new file mode 100644
index 0000000..eb7a8c5
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.remoteauth.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link Crypto}. */
+@RunWith(AndroidJUnit4.class)
+public class CryptoTest {
+ private static final byte[] TEST_IKM =
+ new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+ private static final byte[] TEST_SALT =
+ new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00};
+ private static final int TEST_SIZE = 40;
+
+ @Test
+ public void testComputeHkdf_exceedMaxSize() {
+ // Max size is 32*255
+ assertNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, /* size= */ 10000));
+ }
+
+ @Test
+ public void testComputeHkdf_emptySalt() {
+ assertNull(Crypto.computeHkdf(TEST_IKM, new byte[] {}, TEST_SIZE));
+ }
+
+ @Test
+ public void testComputeHkdf_emptyIkm() {
+ assertNull(Crypto.computeHkdf(new byte[] {}, TEST_SALT, TEST_SIZE));
+ }
+
+ @Test
+ public void testComputeHkdf_success() {
+ assertNotNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, TEST_SIZE));
+ }
+}