blob: bd0ad321d5a428a1f4530697b1080d0e808306b6 [file] [log] [blame]
/*
* Copyright (C) 2017 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.wifi.rtt;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.hardware.wifi.V1_0.IWifiRttController;
import android.hardware.wifi.V1_0.RttBw;
import android.hardware.wifi.V1_0.RttCapabilities;
import android.hardware.wifi.V1_0.RttConfig;
import android.hardware.wifi.V1_0.RttPeerType;
import android.hardware.wifi.V1_0.RttPreamble;
import android.hardware.wifi.V1_0.RttResult;
import android.hardware.wifi.V1_0.RttStatus;
import android.hardware.wifi.V1_0.RttType;
import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
import android.hardware.wifi.V1_0.WifiStatus;
import android.hardware.wifi.V1_0.WifiStatusCode;
import android.net.MacAddress;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.ResponderConfig;
import androidx.test.filters.SmallTest;
import com.android.server.wifi.HalDeviceManager;
import org.hamcrest.core.IsNull;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
/**
* Unit test harness for the RttNative class.
*/
@SmallTest
public class RttNativeTest {
private RttNative mDut;
private WifiStatus mStatusSuccess;
private ArgumentCaptor<ArrayList> mRttConfigCaptor = ArgumentCaptor.forClass(ArrayList.class);
private ArgumentCaptor<List> mRttResultCaptor = ArgumentCaptor.forClass(List.class);
private ArgumentCaptor<HalDeviceManager.ManagerStatusListener> mHdmStatusListener =
ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class);
private ArgumentCaptor<HalDeviceManager.InterfaceRttControllerLifecycleCallback>
mRttLifecycleCbCaptor = ArgumentCaptor.forClass(
HalDeviceManager.InterfaceRttControllerLifecycleCallback.class);
private ArgumentCaptor<IWifiRttController.getCapabilitiesCallback> mGetCapCbCatpr =
ArgumentCaptor.forClass(IWifiRttController.getCapabilitiesCallback.class);
@Rule
public ErrorCollector collector = new ErrorCollector();
@Mock
public RttServiceImpl mockRttServiceImpl;
@Mock
public HalDeviceManager mockHalDeviceManager;
@Mock
public IWifiRttController mockRttController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mockHalDeviceManager.isStarted()).thenReturn(true);
mStatusSuccess = new WifiStatus();
mStatusSuccess.code = WifiStatusCode.SUCCESS;
when(mockRttController.registerEventCallback(any())).thenReturn(mStatusSuccess);
when(mockRttController.rangeRequest(anyInt(), any(ArrayList.class))).thenReturn(
mStatusSuccess);
when(mockRttController.rangeCancel(anyInt(), any(ArrayList.class))).thenReturn(
mStatusSuccess);
mDut = new RttNative(mockRttServiceImpl, mockHalDeviceManager);
mDut.start(null);
verify(mockHalDeviceManager).initialize();
verify(mockHalDeviceManager).registerRttControllerLifecycleCallback(
mRttLifecycleCbCaptor.capture(), any());
mRttLifecycleCbCaptor.getValue().onNewRttController(mockRttController);
verify(mockRttController).registerEventCallback(any());
verify(mockRttServiceImpl).enableIfPossible();
verify(mockRttController).getCapabilities(mGetCapCbCatpr.capture());
// will override capabilities (just call cb again) for specific tests
mGetCapCbCatpr.getValue().onValues(mStatusSuccess, getFullRttCapabilities());
assertTrue(mDut.isReady());
}
/**
* Validate successful ranging flow.
*/
@Test
public void testRangeRequest() throws Exception {
int cmdId = 55;
RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
// (1) issue range request
mDut.rangeRequest(cmdId, request, true);
// (2) verify HAL call and parameters
verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
// verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
collector.checkThat("number of entries", halRequest.size(),
equalTo(request.mRttPeers.size()));
RttConfig rttConfig = halRequest.get(0);
collector.checkThat("entry 0: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("00:01:02:03:04:00").toByteArray()));
collector.checkThat("entry 0: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(true));
collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(true));
rttConfig = halRequest.get(1);
collector.checkThat("entry 1: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("0A:0B:0C:0D:0E:00").toByteArray()));
collector.checkThat("entry 1: rtt type", rttConfig.type, equalTo(RttType.ONE_SIDED));
collector.checkThat("entry 1: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
collector.checkThat("entry 1: lci", rttConfig.mustRequestLci, equalTo(true));
collector.checkThat("entry 1: lcr", rttConfig.mustRequestLcr, equalTo(true));
rttConfig = halRequest.get(2);
collector.checkThat("entry 2: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
collector.checkThat("entry 2: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 2: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
collector.checkThat("entry 2: lci", rttConfig.mustRequestLci, equalTo(false));
collector.checkThat("entry 2: lcr", rttConfig.mustRequestLcr, equalTo(false));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate ranging request with a mix of Repsonders with and without IEEE 802.11mc support,
* from a non- privileged context.
*/
@Test
public void testRangeRequestNotPrivilegedNo80211mcSupportMixed() throws Exception {
int cmdId = 66;
// the request has 3 responders: first AP support 802.11mc, second AP does not, third is
// Aware (which supports 802.11mc by default)
RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
// (1) issue range request
mDut.rangeRequest(cmdId, request, false);
// (2) verify HAL call and parameters
verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
// verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
collector.checkThat("number of entries", halRequest.size(), equalTo(2));
RttConfig rttConfig = halRequest.get(0);
collector.checkThat("entry 0: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("00:01:02:03:04:00").toByteArray()));
collector.checkThat("entry 0: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(true));
collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(true));
rttConfig = halRequest.get(1);
collector.checkThat("entry 1: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
collector.checkThat("entry 1: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 1: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
collector.checkThat("entry 1: lci", rttConfig.mustRequestLci, equalTo(false));
collector.checkThat("entry 1: lcr", rttConfig.mustRequestLcr, equalTo(false));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate successful ranging flow - with privileges access but with limited capabilities:
* - No single-sided RTT
* - No LCI/LCR
* - Limited BW
* - Limited Preamble
*/
@Test
public void testRangeRequestWithLimitedCapabilities() throws Exception {
int cmdId = 55;
RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
// update capabilities to a limited set
RttCapabilities cap = getFullRttCapabilities();
cap.rttOneSidedSupported = false;
cap.lciSupported = false;
cap.lcrSupported = false;
cap.bwSupport = RttBw.BW_10MHZ | RttBw.BW_160MHZ;
cap.preambleSupport = RttPreamble.LEGACY;
mGetCapCbCatpr.getValue().onValues(mStatusSuccess, cap);
// Note: request 1: BW = 40MHz --> 10MHz, Preamble = HT (since 40MHz) -> Legacy
// (1) issue range request
mDut.rangeRequest(cmdId, request, true);
// (2) verify HAL call and parameters
verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
// verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
collector.checkThat("number of entries", halRequest.size(), equalTo(2));
RttConfig rttConfig = halRequest.get(0);
collector.checkThat("entry 0: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("00:01:02:03:04:00").toByteArray()));
collector.checkThat("entry 0: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.AP));
collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(false));
collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(false));
collector.checkThat("entry 0: channel.width", rttConfig.channel.width, equalTo(
WifiChannelWidthInMhz.WIDTH_40));
collector.checkThat("entry 0: bw", rttConfig.bw, equalTo(RttBw.BW_10MHZ));
collector.checkThat("entry 0: preamble", rttConfig.preamble, equalTo(RttPreamble.LEGACY));
rttConfig = halRequest.get(1);
collector.checkThat("entry 1: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
collector.checkThat("entry 1: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 1: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
collector.checkThat("entry 1: lci", rttConfig.mustRequestLci, equalTo(false));
collector.checkThat("entry 1: lcr", rttConfig.mustRequestLcr, equalTo(false));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate successful ranging flow - with privileges access but with limited capabilities:
* - Very limited BW
* - Very limited Preamble
*/
@Test
public void testRangeRequestWithLimitedCapabilitiesNoOverlap() throws Exception {
int cmdId = 55;
RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
// update capabilities to a limited set
RttCapabilities cap = getFullRttCapabilities();
cap.bwSupport = RttBw.BW_80MHZ;
cap.preambleSupport = RttPreamble.VHT;
mGetCapCbCatpr.getValue().onValues(mStatusSuccess, cap);
// Note: request 1: BW = 40MHz --> no overlap -> dropped
// Note: request 2: BW = 160MHz --> 160MHz, preamble = VHT (since 160MHz) -> no overlap,
// dropped
// (1) issue range request
mDut.rangeRequest(cmdId, request, true);
// (2) verify HAL call and parameters
verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
// verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
collector.checkThat("number of entries", halRequest.size(), equalTo(1));
RttConfig rttConfig = halRequest.get(0);
collector.checkThat("entry 0: MAC", rttConfig.addr,
equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
collector.checkThat("entry 0: rtt type", rttConfig.type, equalTo(RttType.TWO_SIDED));
collector.checkThat("entry 0: peer type", rttConfig.peer, equalTo(RttPeerType.NAN));
collector.checkThat("entry 0: lci", rttConfig.mustRequestLci, equalTo(false));
collector.checkThat("entry 0: lcr", rttConfig.mustRequestLcr, equalTo(false));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate ranging request with all Repsonders without IEEE 802.11mc support, from a non-
* privileged context.
*/
@Test
public void testRangeRequestNotPrivilegedNo80211mcSupportForAny() throws Exception {
int cmdId = 77;
RangingRequest request = RttTestUtils.getDummyRangingRequestNo80211mcSupport((byte) 0);
// (1) issue range request
mDut.rangeRequest(cmdId, request, false);
// (2) verify immediate result callback (empty result set)
verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
collector.checkThat("Result set", mRttResultCaptor.getValue().size(), equalTo(0));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate no range request when Wi-Fi is down
*/
@Test
public void testWifiDown() throws Exception {
int cmdId = 55;
RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
// (1) simulate Wi-Fi down and send a status change indication
mRttLifecycleCbCaptor.getValue().onRttControllerDestroyed();
verify(mockRttServiceImpl).disable();
assertFalse(mDut.isReady());
// (2) issue range request
mDut.rangeRequest(cmdId, request, true);
verifyNoMoreInteractions(mockRttServiceImpl, mockRttController);
}
/**
* Validate that we react correctly (i.e. enable/disable global RTT availability) when
* notified that the RTT controller has disappear and appeared.
*/
@Test
public void testRttControllerLifecycle() throws Exception {
// RTT controller disappears
mRttLifecycleCbCaptor.getValue().onRttControllerDestroyed();
verify(mockRttServiceImpl).disable();
assertFalse(mDut.isReady());
// RTT controller re-appears (verification is x2 since 1st time is in setup())
mRttLifecycleCbCaptor.getValue().onNewRttController(mockRttController);
verify(mockRttController, times(2)).registerEventCallback(any());
verify(mockRttServiceImpl, times(2)).enableIfPossible();
verify(mockRttController, times(2)).getCapabilities(mGetCapCbCatpr.capture());
assertTrue(mDut.isReady());
verifyNoMoreInteractions(mockRttServiceImpl, mockRttController);
}
/**
* Validate ranging cancel flow.
*/
@Test
public void testRangeCancel() throws Exception {
int cmdId = 66;
ArrayList<byte[]> macAddresses = new ArrayList<>();
byte[] mac1 = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
byte[] mac2 = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
macAddresses.add(mac1);
macAddresses.add(mac2);
// (1) issue cancel request
mDut.rangeCancel(cmdId, macAddresses);
// (2) verify HAL call and parameters
verify(mockRttController).rangeCancel(cmdId, macAddresses);
verifyNoMoreInteractions(mockRttController);
}
/**
* Validate correct result conversion from HAL to framework.
*/
@Test
public void testRangeResults() throws Exception {
int cmdId = 55;
ArrayList<RttResult> results = new ArrayList<>();
RttResult res = new RttResult();
res.addr[0] = 5;
res.addr[1] = 6;
res.addr[2] = 7;
res.addr[3] = 8;
res.addr[4] = 9;
res.addr[5] = 10;
res.status = RttStatus.SUCCESS;
res.distanceInMm = 1500;
res.timeStampInUs = 666;
results.add(res);
// (1) have HAL call native with results
mDut.onResults(cmdId, results);
// (2) verify call to framework
verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
// verify contents of the framework results
List<RttResult> rttR = mRttResultCaptor.getValue();
collector.checkThat("number of entries", rttR.size(), equalTo(1));
RttResult rttResult = rttR.get(0);
collector.checkThat("status", rttResult.status,
equalTo(RttStatus.SUCCESS));
collector.checkThat("mac", rttResult.addr,
equalTo(MacAddress.fromString("05:06:07:08:09:0A").toByteArray()));
collector.checkThat("distanceCm", rttResult.distanceInMm, equalTo(1500));
collector.checkThat("timestamp", rttResult.timeStampInUs, equalTo(666L));
verifyNoMoreInteractions(mockRttController, mockRttServiceImpl);
}
/**
* Validate correct cleanup when a null array of results is provided by HAL.
*/
@Test
public void testRangeResultsNullArray() {
int cmdId = 66;
mDut.onResults(cmdId, null);
verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
collector.checkThat("number of entries", mRttResultCaptor.getValue().size(), equalTo(0));
}
/**
* Validate correct cleanup when an array of results containing null entries is provided by HAL.
*/
@Test
public void testRangeResultsSomeNulls() {
int cmdId = 77;
ArrayList<RttResult> results = new ArrayList<>();
results.add(null);
results.add(new RttResult());
results.add(null);
results.add(null);
results.add(new RttResult());
results.add(null);
mDut.onResults(cmdId, results);
verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
List<RttResult> rttR = mRttResultCaptor.getValue();
collector.checkThat("number of entries", rttR.size(), equalTo(2));
for (int i = 0; i < rttR.size(); ++i) {
collector.checkThat("entry", rttR.get(i), IsNull.notNullValue());
}
}
/**
* Validation ranging with invalid bw and preamble combination will be ignored.
*/
@Test
public void testRangingWithInvalidParameterCombination() throws Exception {
int cmdId = 88;
RangingRequest request = new RangingRequest.Builder().build();
ResponderConfig invalidConfig = new ResponderConfig(
MacAddress.fromString("08:09:08:07:06:88"), ResponderConfig.RESPONDER_AP, true,
ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_HT);
ResponderConfig config = new ResponderConfig(MacAddress.fromString("08:09:08:07:06:89"),
ResponderConfig.RESPONDER_AP, true,
ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_VHT);
// Add a ResponderConfig with invalid parameter, should be ignored.
request.mRttPeers.add(invalidConfig);
request.mRttPeers.add(config);
mDut.rangeRequest(cmdId, request, true);
verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
assertEquals(request.mRttPeers.size() - 1, mRttConfigCaptor.getValue().size());
}
// Utilities
/**
* Return an RttCapabilities structure with all features enabled and support for all
* preambles and bandwidths. The purpose is to enable any request. The returned structure can
* then be modified to disable specific features.
*/
RttCapabilities getFullRttCapabilities() {
RttCapabilities cap = new RttCapabilities();
cap.rttOneSidedSupported = true;
cap.rttFtmSupported = true;
cap.lciSupported = true;
cap.lcrSupported = true;
cap.responderSupported = true; // unused
cap.preambleSupport = RttPreamble.LEGACY | RttPreamble.HT | RttPreamble.VHT;
cap.bwSupport =
RttBw.BW_5MHZ | RttBw.BW_10MHZ | RttBw.BW_20MHZ | RttBw.BW_40MHZ | RttBw.BW_80MHZ
| RttBw.BW_160MHZ;
cap.mcVersion = 1; // unused
return cap;
}
}