/* * 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.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.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 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. */ public class RttNativeTest { private RttNative mDut; private WifiStatus mStatusSuccess; private ArgumentCaptor mRttConfigCaptor = ArgumentCaptor.forClass(ArrayList.class); private ArgumentCaptor mRttResultCaptor = ArgumentCaptor.forClass(List.class); private ArgumentCaptor mHdmStatusListener = ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class); private ArgumentCaptor 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); when(mockHalDeviceManager.createRttController()).thenReturn(mockRttController); 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).registerStatusListener(mHdmStatusListener.capture(), any()); 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 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 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)); 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 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 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) configure Wi-Fi down and send a status change indication when(mockHalDeviceManager.isStarted()).thenReturn(false); mHdmStatusListener.getValue().onStatusChanged(); verify(mockRttServiceImpl).disable(); assertFalse(mDut.isReady()); // (2) issue range request mDut.rangeRequest(cmdId, request, true); verifyNoMoreInteractions(mockRttServiceImpl, mockRttController); } /** * Validate ranging cancel flow. */ @Test public void testRangeCancel() throws Exception { int cmdId = 66; ArrayList 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 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 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 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 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()); } } // 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; } }