blob: f7077afd7363317c46d9255c033e3a1bb48c5798 [file] [log] [blame]
/*
* Copyright (C) 2016 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.nan;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.RttManager;
import android.net.wifi.nan.ConfigRequest;
import android.net.wifi.nan.IWifiNanDiscoverySessionCallback;
import android.net.wifi.nan.IWifiNanEventCallback;
import android.net.wifi.nan.PublishConfig;
import android.net.wifi.nan.SubscribeConfig;
import android.os.IBinder;
import android.os.Looper;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseArray;
import android.util.SparseIntArray;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Field;
/**
* Unit test harness for WifiNanStateManager.
*/
@SmallTest
public class WifiNanServiceImplTest {
private WifiNanServiceImplSpy mDut;
private int mDefaultUid = 1500;
@Mock
private Context mContextMock;
@Mock
private PackageManager mPackageManagerMock;
@Mock
private WifiNanStateManager mNanStateManagerMock;
@Mock
private IBinder mBinderMock;
@Mock
private IWifiNanEventCallback mCallbackMock;
@Mock
private IWifiNanDiscoverySessionCallback mSessionCallbackMock;
/**
* Using instead of spy to avoid native crash failures - possibly due to
* spy's copying of state.
*/
private class WifiNanServiceImplSpy extends WifiNanServiceImpl {
public int fakeUid;
WifiNanServiceImplSpy(Context context) {
super(context);
}
/**
* Return the fake UID instead of the real one: pseudo-spy
* implementation.
*/
@Override
public int getMockableCallingUid() {
return fakeUid;
}
}
/**
* Initializes mocks.
*/
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContextMock.getApplicationContext()).thenReturn(mContextMock);
when(mContextMock.getPackageManager()).thenReturn(mPackageManagerMock);
when(mPackageManagerMock.hasSystemFeature(PackageManager.FEATURE_WIFI_NAN))
.thenReturn(true);
installMockNanStateManager();
mDut = new WifiNanServiceImplSpy(mContextMock);
mDut.fakeUid = mDefaultUid;
}
/**
* Validate start() function: passes a valid looper.
*/
@Test
public void testStart() {
mDut.start();
verify(mNanStateManagerMock).start(eq(mContextMock), any(Looper.class));
}
/**
* Validate enableUsage() function
*/
@Test
public void testEnableUsage() {
mDut.enableUsage();
verify(mNanStateManagerMock).enableUsage();
}
/**
* Validate disableUsage() function
*/
@Test
public void testDisableUsage() throws Exception {
mDut.enableUsage();
doConnect();
mDut.disableUsage();
verify(mNanStateManagerMock).disableUsage();
}
/**
* Validate isUsageEnabled() function
*/
@Test
public void testIsUsageEnabled() {
mDut.isUsageEnabled();
verify(mNanStateManagerMock).isUsageEnabled();
}
/**
* Validate connect() - returns and uses a client ID.
*/
@Test
public void testConnect() {
doConnect();
}
/**
* Validate connect() when a non-null config is passed.
*/
@Test
public void testConnectWithConfig() {
ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(55).build();
String callingPackage = "com.google.somePackage";
mDut.connect(mBinderMock, callingPackage, mCallbackMock,
configRequest);
verify(mNanStateManagerMock).connect(anyInt(), anyInt(), anyInt(),
eq(callingPackage), eq(mCallbackMock), eq(configRequest));
}
/**
* Validate disconnect() - correct pass-through args.
*
* @throws Exception
*/
@Test
public void testDisconnect() throws Exception {
int clientId = doConnect();
mDut.disconnect(clientId, mBinderMock);
verify(mNanStateManagerMock).disconnect(clientId);
validateInternalStateCleanedUp(clientId);
}
/**
* Validate that security exception thrown when attempting operation using
* an invalid client ID.
*/
@Test(expected = SecurityException.class)
public void testFailOnInvalidClientId() {
mDut.disconnect(-1, mBinderMock);
}
/**
* Validate that security exception thrown when attempting operation using a
* client ID which was already cleared-up.
*/
@Test(expected = SecurityException.class)
public void testFailOnClearedUpClientId() throws Exception {
int clientId = doConnect();
mDut.disconnect(clientId, mBinderMock);
verify(mNanStateManagerMock).disconnect(clientId);
validateInternalStateCleanedUp(clientId);
mDut.disconnect(clientId, mBinderMock);
}
/**
* Validate that trying to use a client ID from a UID which is different
* from the one that created it fails - and that the internal state is not
* modified so that a valid call (from the correct UID) will subsequently
* succeed.
*/
@Test
public void testFailOnAccessClientIdFromWrongUid() throws Exception {
int clientId = doConnect();
mDut.fakeUid = mDefaultUid + 1;
/*
* Not using thrown.expect(...) since want to test that subsequent
* access works.
*/
boolean failsAsExpected = false;
try {
mDut.disconnect(clientId, mBinderMock);
} catch (SecurityException e) {
failsAsExpected = true;
}
mDut.fakeUid = mDefaultUid;
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("valid.value")
.build();
mDut.publish(clientId, publishConfig, mSessionCallbackMock);
verify(mNanStateManagerMock).publish(clientId, publishConfig, mSessionCallbackMock);
assertTrue("SecurityException for invalid access from wrong UID thrown", failsAsExpected);
}
/**
* Validates that on binder death we get a disconnect().
*/
@Test
public void testBinderDeath() throws Exception {
ArgumentCaptor<IBinder.DeathRecipient> deathRecipient = ArgumentCaptor
.forClass(IBinder.DeathRecipient.class);
int clientId = doConnect();
verify(mBinderMock).linkToDeath(deathRecipient.capture(), eq(0));
deathRecipient.getValue().binderDied();
verify(mNanStateManagerMock).disconnect(clientId);
validateInternalStateCleanedUp(clientId);
}
/**
* Validates that sequential connect() calls return increasing client IDs.
*/
@Test
public void testClientIdIncrementing() {
int loopCount = 100;
InOrder inOrder = inOrder(mNanStateManagerMock);
ArgumentCaptor<Integer> clientIdCaptor = ArgumentCaptor.forClass(Integer.class);
int prevId = 0;
for (int i = 0; i < loopCount; ++i) {
mDut.connect(mBinderMock, "", mCallbackMock, null);
inOrder.verify(mNanStateManagerMock).connect(clientIdCaptor.capture(), anyInt(),
anyInt(), anyString(), eq(mCallbackMock), any(ConfigRequest.class));
int id = clientIdCaptor.getValue();
if (i != 0) {
assertTrue("Client ID incrementing", id > prevId);
}
prevId = id;
}
}
/**
* Validate terminateSession() - correct pass-through args.
*/
@Test
public void testTerminateSession() {
int sessionId = 1024;
int clientId = doConnect();
mDut.terminateSession(clientId, sessionId);
verify(mNanStateManagerMock).terminateSession(clientId, sessionId);
}
/**
* Validate publish() - correct pass-through args.
*/
@Test
public void testPublish() {
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
.build();
int clientId = doConnect();
IWifiNanDiscoverySessionCallback mockCallback = mock(
IWifiNanDiscoverySessionCallback.class);
mDut.publish(clientId, publishConfig, mockCallback);
verify(mNanStateManagerMock).publish(clientId, publishConfig, mockCallback);
}
/**
* Validate that publish() verifies the input PublishConfig and fails on an invalid service
* name.
*/
@Test(expected = IllegalArgumentException.class)
public void testPublishBadServiceName() {
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
"Including invalid characters - spaces").build();
int clientId = doConnect();
IWifiNanDiscoverySessionCallback mockCallback = mock(
IWifiNanDiscoverySessionCallback.class);
mDut.publish(clientId, publishConfig, mockCallback);
verify(mNanStateManagerMock).publish(clientId, publishConfig, mockCallback);
}
/**
* Validate updatePublish() - correct pass-through args.
*/
@Test
public void testUpdatePublish() {
int sessionId = 1232;
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
.build();
int clientId = doConnect();
mDut.updatePublish(clientId, sessionId, publishConfig);
verify(mNanStateManagerMock).updatePublish(clientId, sessionId, publishConfig);
}
/**
* Validate subscribe() - correct pass-through args.
*/
@Test
public void testSubscribe() {
SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
.setServiceName("something.valid").build();
int clientId = doConnect();
IWifiNanDiscoverySessionCallback mockCallback = mock(
IWifiNanDiscoverySessionCallback.class);
mDut.subscribe(clientId, subscribeConfig, mockCallback);
verify(mNanStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
}
/**
* Validate that subscribe() verifies the input SubscribeConfig and fails on an invalid service
* name.
*/
@Test(expected = IllegalArgumentException.class)
public void testSubscribeBadServiceName() {
SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(
"InvalidServiceCharacters__").build();
int clientId = doConnect();
IWifiNanDiscoverySessionCallback mockCallback = mock(
IWifiNanDiscoverySessionCallback.class);
mDut.subscribe(clientId, subscribeConfig, mockCallback);
verify(mNanStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
}
/**
* Validate updateSubscribe() - correct pass-through args.
*/
@Test
public void testUpdateSubscribe() {
int sessionId = 1232;
SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
.setServiceName("something.valid").build();
int clientId = doConnect();
mDut.updateSubscribe(clientId, sessionId, subscribeConfig);
verify(mNanStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
}
/**
* Validate sendMessage() - correct pass-through args.
*/
@Test
public void testSendMessage() {
int sessionId = 2394;
int peerId = 2032;
byte[] message = new byte[23];
int messageId = 2043;
int clientId = doConnect();
mDut.sendMessage(clientId, sessionId, peerId, message, messageId, 0);
verify(mNanStateManagerMock).sendMessage(clientId, sessionId, peerId, message, messageId,
0);
}
/**
* Validate startRanging() - correct pass-through args
*/
@Test
public void testStartRanging() {
int clientId = doConnect();
int sessionId = 65345;
RttManager.ParcelableRttParams params =
new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
ArgumentCaptor.forClass(RttManager.RttParams[].class);
int rangingId = mDut.startRanging(clientId, sessionId, params);
verify(mNanStateManagerMock).startRanging(eq(clientId), eq(sessionId),
paramsCaptor.capture(), eq(rangingId));
assertArrayEquals(paramsCaptor.getValue(), params.mParams);
}
/**
* Validates that sequential startRanging() calls return increasing ranging IDs.
*/
@Test
public void testRangingIdIncrementing() {
int loopCount = 100;
int clientId = doConnect();
int sessionId = 65345;
RttManager.ParcelableRttParams params =
new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
int prevRangingId = 0;
for (int i = 0; i < loopCount; ++i) {
int rangingId = mDut.startRanging(clientId, sessionId, params);
if (i != 0) {
assertTrue("Client ID incrementing", rangingId > prevRangingId);
}
prevRangingId = rangingId;
}
}
/**
* Validates that startRanging() requires a non-empty list
*/
@Test(expected = IllegalArgumentException.class)
public void testStartRangingZeroArgs() {
int clientId = doConnect();
int sessionId = 65345;
RttManager.ParcelableRttParams params =
new RttManager.ParcelableRttParams(new RttManager.RttParams[0]);
ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
ArgumentCaptor.forClass(RttManager.RttParams[].class);
int rangingId = mDut.startRanging(clientId, sessionId, params);
}
/*
* Tests of internal state of WifiNanServiceImpl: very limited (not usually
* a good idea). However, these test that the internal state is cleaned-up
* appropriately. Alternatively would cause issues with memory leaks or
* information leak between sessions.
*/
private void validateInternalStateCleanedUp(int clientId) throws Exception {
int uidEntry = getInternalStateUid(clientId);
assertEquals(-1, uidEntry);
IBinder.DeathRecipient dr = getInternalStateDeathRecipient(clientId);
assertEquals(null, dr);
}
/*
* Utilities
*/
private int doConnect() {
String callingPackage = "com.google.somePackage";
mDut.connect(mBinderMock, callingPackage, mCallbackMock, null);
ArgumentCaptor<Integer> clientId = ArgumentCaptor.forClass(Integer.class);
verify(mNanStateManagerMock).connect(clientId.capture(), anyInt(), anyInt(),
eq(callingPackage), eq(mCallbackMock), eq(new ConfigRequest.Builder().build()));
return clientId.getValue();
}
private void installMockNanStateManager()
throws Exception {
Field field = WifiNanStateManager.class.getDeclaredField("sNanStateManagerSingleton");
field.setAccessible(true);
field.set(null, mNanStateManagerMock);
}
private int getInternalStateUid(int clientId) throws Exception {
Field field = WifiNanServiceImpl.class.getDeclaredField("mUidByClientId");
field.setAccessible(true);
@SuppressWarnings("unchecked")
SparseIntArray uidByClientId = (SparseIntArray) field.get(mDut);
return uidByClientId.get(clientId, -1);
}
private IBinder.DeathRecipient getInternalStateDeathRecipient(int clientId) throws Exception {
Field field = WifiNanServiceImpl.class.getDeclaredField("mDeathRecipientsByClientId");
field.setAccessible(true);
@SuppressWarnings("unchecked")
SparseArray<IBinder.DeathRecipient> deathRecipientsByClientId =
(SparseArray<IBinder.DeathRecipient>) field.get(mDut);
return deathRecipientsByClientId.get(clientId);
}
}