blob: 577f5bc65bdc3ef4211368f01c0672411b45cd8f [file] [log] [blame]
/*
* Copyright (C) 2018 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;
import static com.android.server.wifi.util.NativeUtil.hexStringFromByteArray;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import android.net.MacAddress;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiSsid;
import android.util.Base64;
import android.util.Pair;
import androidx.test.filters.SmallTest;
import com.android.server.wifi.WifiScoreCardProto.AccessPoint;
import com.android.server.wifi.WifiScoreCardProto.Event;
import com.android.server.wifi.WifiScoreCardProto.Network;
import com.android.server.wifi.WifiScoreCardProto.NetworkList;
import com.android.server.wifi.WifiScoreCardProto.Signal;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Unit tests for {@link com.android.server.wifi.WifiScoreCard}.
*/
@SmallTest
public class WifiScoreCardTest {
static final WifiSsid TEST_SSID_1 = WifiSsid.createFromAsciiEncoded("Joe's Place");
static final WifiSsid TEST_SSID_2 = WifiSsid.createFromAsciiEncoded("Poe's Raven");
static final MacAddress TEST_BSSID_1 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
static final MacAddress TEST_BSSID_2 = MacAddress.fromString("1:2:3:4:5:6");
static final int TEST_NETWORK_AGENT_ID = 123;
static final int TEST_NETWORK_CONFIG_ID = 1492;
static final double TOL = 1e-6; // for assertEquals(double, double, tolerance)
WifiScoreCard mWifiScoreCard;
@Mock Clock mClock;
@Mock WifiScoreCard.MemoryStore mMemoryStore;
final ArrayList<String> mKeys = new ArrayList<>();
final ArrayList<WifiScoreCard.BlobListener> mBlobListeners = new ArrayList<>();
final ArrayList<byte[]> mBlobs = new ArrayList<>();
long mMilliSecondsSinceBoot;
ExtendedWifiInfo mWifiInfo;
void millisecondsPass(long ms) {
mMilliSecondsSinceBoot += ms;
when(mClock.getElapsedSinceBootMillis()).thenReturn(mMilliSecondsSinceBoot);
when(mClock.getWallClockMillis()).thenReturn(mMilliSecondsSinceBoot + 1_500_000_000_000L);
}
void secondsPass(long s) {
millisecondsPass(s * 1000);
}
/**
* Sets up for unit test
*/
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mKeys.clear();
mBlobListeners.clear();
mBlobs.clear();
mMilliSecondsSinceBoot = 0;
mWifiInfo = new ExtendedWifiInfo();
mWifiInfo.setSSID(TEST_SSID_1);
mWifiInfo.setBSSID(TEST_BSSID_1.toString());
mWifiInfo.setNetworkId(TEST_NETWORK_CONFIG_ID);
millisecondsPass(0);
mWifiScoreCard = new WifiScoreCard(mClock, "some seed");
}
/**
* Test generic update
*/
@Test
public void testUpdate() throws Exception {
mWifiInfo.setSSID(TEST_SSID_1);
mWifiInfo.setBSSID(TEST_BSSID_1.toString());
mWifiScoreCard.noteIpConfiguration(mWifiInfo);
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
assertTrue(perBssid.id > 0);
assertNotNull(perBssid.l2Key);
assertTrue("L2Key length should be more than 16.", perBssid.l2Key.length() > 16);
mWifiInfo.setBSSID(TEST_BSSID_2.toString());
mWifiScoreCard.noteIpConfiguration(mWifiInfo);
assertEquals(perBssid, mWifiScoreCard.fetchByBssid(TEST_BSSID_1));
assertNotEquals(perBssid.id, mWifiScoreCard.fetchByBssid(TEST_BSSID_2).id);
assertNotEquals(perBssid.l2Key, mWifiScoreCard.fetchByBssid(TEST_BSSID_2).l2Key);
}
/**
* Test identifiers.
*/
@Test
public void testIdentifiers() throws Exception {
mWifiInfo.setSSID(TEST_SSID_1);
mWifiInfo.setBSSID(TEST_BSSID_1.toString());
Pair<String, String> p1 = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo);
assertNotNull(p1.first);
assertNotNull(p1.second);
mWifiInfo.setBSSID(TEST_BSSID_2.toString());
Pair<String, String> p2 = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo);
assertNotEquals(p1.first, p2.first);
assertEquals(p1.second, p2.second);
mWifiInfo.setBSSID(null);
Pair<String, String> p3 = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo);
assertNull(p3.first);
assertNull(p3.second);
}
/**
* Test rssi poll updates
*/
@Test
public void testRssiPollUpdates() throws Exception {
// Start out on one frequency
mWifiInfo.setFrequency(5805);
mWifiInfo.setRssi(-77);
mWifiInfo.setLinkSpeed(12);
mWifiScoreCard.noteSignalPoll(mWifiInfo);
// Switch channels for a bit
mWifiInfo.setFrequency(5290);
mWifiInfo.setRssi(-66);
mWifiInfo.setLinkSpeed(666);
mWifiScoreCard.noteSignalPoll(mWifiInfo);
// Back to the first channel
mWifiInfo.setFrequency(5805);
mWifiInfo.setRssi(-55);
mWifiInfo.setLinkSpeed(86);
mWifiScoreCard.noteSignalPoll(mWifiInfo);
double expectSum = -77 + -55;
double expectSumSq = 77 * 77 + 55 * 55;
// Now verify
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
// Looking up the same thing twice should yield the same object.
assertTrue(perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
== perBssid.lookupSignal(Event.SIGNAL_POLL, 5805));
// Check the rssi statistics for the first channel
assertEquals(2, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805).rssi.count);
assertEquals(expectSum, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.sum, TOL);
assertEquals(expectSumSq, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.sumOfSquares, TOL);
assertEquals(-77.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.minValue, TOL);
assertEquals(-55.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.maxValue, TOL);
// Check the rssi statistics for the second channel
assertEquals(1, perBssid.lookupSignal(Event.SIGNAL_POLL, 5290).rssi.count);
// Check that the linkspeed was updated
assertEquals(666.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 5290).linkspeed.sum, TOL);
}
/**
* Statistics on time-to-connect, connection duration
*/
@Test
public void testDurationStatistics() throws Exception {
// Start out disconnected; start connecting
mWifiInfo.setBSSID(android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS);
mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
// First poll has a bad RSSI
millisecondsPass(111);
mWifiInfo.setBSSID(TEST_BSSID_1.toString());
mWifiInfo.setFrequency(5805);
mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
// A bit later, connection is complete (up through DHCP)
millisecondsPass(222);
mWifiInfo.setRssi(-55);
mWifiScoreCard.noteIpConfiguration(mWifiInfo);
millisecondsPass(666);
// Rssi polls for 99 seconds
for (int i = 0; i < 99; i += 3) {
mWifiScoreCard.noteSignalPoll(mWifiInfo);
secondsPass(3);
}
// Make sure our simulated time adds up
assertEquals(mMilliSecondsSinceBoot, 99999);
// Validation success, rather late!
mWifiScoreCard.noteValidationSuccess(mWifiInfo);
// A long while later, wifi is toggled off
secondsPass(9900);
// Second validation success should not matter.
mWifiScoreCard.noteValidationSuccess(mWifiInfo);
mWifiScoreCard.noteWifiDisabled(mWifiInfo);
// Now verify
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
assertEquals(1, perBssid.lookupSignal(Event.IP_CONFIGURATION_SUCCESS, 5805)
.elapsedMs.count);
assertEquals(333.0, perBssid.lookupSignal(Event.IP_CONFIGURATION_SUCCESS, 5805)
.elapsedMs.sum, TOL);
assertEquals(9999999.0, perBssid.lookupSignal(Event.WIFI_DISABLED, 5805)
.elapsedMs.maxValue, TOL);
assertEquals(999.0, perBssid.lookupSignal(Event.FIRST_POLL_AFTER_CONNECTION, 5805)
.elapsedMs.minValue, TOL);
assertEquals(99999.0, perBssid.lookupSignal(Event.VALIDATION_SUCCESS, 5805)
.elapsedMs.sum, TOL);
assertNull(perBssid.lookupSignal(Event.SIGNAL_POLL, 5805).elapsedMs);
}
/**
* Constructs a protobuf form of an example.
*/
private byte[] makeSerializedAccessPointExample() {
mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
millisecondsPass(10);
// Association completes, a NetworkAgent is created
mWifiScoreCard.noteNetworkAgentCreated(mWifiInfo, TEST_NETWORK_AGENT_ID);
millisecondsPass(101);
mWifiInfo.setRssi(-55);
mWifiInfo.setFrequency(5805);
mWifiInfo.setLinkSpeed(384);
mWifiScoreCard.noteIpConfiguration(mWifiInfo);
millisecondsPass(888);
mWifiScoreCard.noteSignalPoll(mWifiInfo);
millisecondsPass(1000);
mWifiInfo.setRssi(-44);
mWifiScoreCard.noteSignalPoll(mWifiInfo);
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
perBssid.lookupSignal(Event.SIGNAL_POLL, 2412).rssi.historicalMean = -42.0;
perBssid.lookupSignal(Event.SIGNAL_POLL, 2412).rssi.historicalVariance = 4.0;
checkSerializationExample("before serialization", perBssid);
// Now convert to protobuf form
byte[] serialized = perBssid.toAccessPoint().toByteArray();
return serialized;
}
/**
* Checks that the fields of the serialization example are as expected
*/
private void checkSerializationExample(String diag, WifiScoreCard.PerBssid perBssid) {
assertEquals(diag, 2, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805).rssi.count);
assertEquals(diag, -55.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.minValue, TOL);
assertEquals(diag, -44.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 5805)
.rssi.maxValue, TOL);
assertEquals(diag, 384.0, perBssid.lookupSignal(Event.FIRST_POLL_AFTER_CONNECTION, 5805)
.linkspeed.sum, TOL);
assertEquals(diag, 111.0, perBssid.lookupSignal(Event.IP_CONFIGURATION_SUCCESS, 5805)
.elapsedMs.minValue, TOL);
assertEquals(diag, 0, perBssid.lookupSignal(Event.SIGNAL_POLL, 2412).rssi.count);
assertEquals(diag, -42.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 2412)
.rssi.historicalMean, TOL);
assertEquals(diag, 4.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 2412)
.rssi.historicalVariance, TOL);
}
/**
* AccessPoint serialization
*/
@Test
public void testAccessPointSerialization() throws Exception {
byte[] serialized = makeSerializedAccessPointExample();
// Verify by parsing it and checking that we see the expected results
AccessPoint ap = AccessPoint.parseFrom(serialized);
assertEquals(4, ap.getEventStatsCount());
for (Signal signal: ap.getEventStatsList()) {
if (signal.getFrequency() == 2412) {
assertFalse(signal.getRssi().hasCount());
assertEquals(-42.0, signal.getRssi().getHistoricalMean(), TOL);
assertEquals(4.0, signal.getRssi().getHistoricalVariance(), TOL);
continue;
}
assertEquals(5805, signal.getFrequency());
switch (signal.getEvent()) {
case IP_CONFIGURATION_SUCCESS:
assertEquals(384.0, signal.getLinkspeed().getMaxValue(), TOL);
assertEquals(111.0, signal.getElapsedMs().getMinValue(), TOL);
break;
case SIGNAL_POLL:
assertEquals(2, signal.getRssi().getCount());
break;
case FIRST_POLL_AFTER_CONNECTION:
assertEquals(-55.0, signal.getRssi().getSum(), TOL);
break;
default:
fail(signal.getEvent().toString());
}
}
}
/**
* Serialization should be reproducable
*/
@Test
public void testReproducableSerialization() throws Exception {
byte[] serialized = makeSerializedAccessPointExample();
setUp();
assertArrayEquals(serialized, makeSerializedAccessPointExample());
}
/**
* Deserialization
*/
@Test
public void testDeserialization() throws Exception {
byte[] serialized = makeSerializedAccessPointExample();
setUp(); // Get back to the initial state
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.perBssidFromAccessPoint(
mWifiInfo.getSSID(),
AccessPoint.parseFrom(serialized));
// Now verify
String diag = hexStringFromByteArray(serialized);
checkSerializationExample(diag, perBssid);
}
/**
* Serialization of all internally represented networks
*/
@Test
public void testNetworksSerialization() throws Exception {
makeSerializedAccessPointExample();
byte[] serialized = mWifiScoreCard.getNetworkListByteArray(false);
byte[] cleaned = mWifiScoreCard.getNetworkListByteArray(true);
String base64Encoded = mWifiScoreCard.getNetworkListBase64(true);
setUp(); // Get back to the initial state
String diag = hexStringFromByteArray(serialized);
NetworkList networkList = NetworkList.parseFrom(serialized);
assertEquals(diag, 1, networkList.getNetworksCount());
Network network = networkList.getNetworks(0);
assertEquals(diag, 1, network.getAccessPointsCount());
AccessPoint accessPoint = network.getAccessPoints(0);
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.perBssidFromAccessPoint(network.getSsid(),
accessPoint);
checkSerializationExample(diag, perBssid);
// Leaving out the bssids should make the cleaned version shorter.
assertTrue(cleaned.length < serialized.length);
// Check the Base64 version
assertTrue(Arrays.equals(cleaned, Base64.decode(base64Encoded, Base64.DEFAULT)));
// Check that the network ids were carried over
assertEquals(TEST_NETWORK_AGENT_ID, network.getNetworkAgentId());
assertEquals(TEST_NETWORK_CONFIG_ID, network.getNetworkConfigId());
}
/**
* Installation of memory store does not crash
*/
@Test
public void testInstallationOfMemoryStoreDoesNotCrash() throws Exception {
mWifiScoreCard.installMemoryStore(mMemoryStore);
makeSerializedAccessPointExample();
mWifiScoreCard.installMemoryStore(mMemoryStore);
}
/**
* Merge of lazy reads
*/
@Test
public void testLazyReads() throws Exception {
// Install our own MemoryStore object, which records read requests
mWifiScoreCard.installMemoryStore(new WifiScoreCard.MemoryStore() {
@Override
public void read(String key, WifiScoreCard.BlobListener listener) {
mKeys.add(key);
mBlobListeners.add(listener);
}
@Override
public void write(String key, byte[] value) {
// ignore for now
}
});
// Now make some changes
byte[] serialized = makeSerializedAccessPointExample();
assertEquals(1, mKeys.size());
// Simulate the asynchronous completion of the read request
millisecondsPass(33);
mBlobListeners.get(0).onBlobRetrieved(serialized);
// Check that the historical mean and variance were updated accordingly
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.fetchByBssid(TEST_BSSID_1);
assertEquals(-42.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 2412)
.rssi.historicalMean, TOL);
assertEquals(2.0, perBssid.lookupSignal(Event.SIGNAL_POLL, 2412)
.rssi.historicalVariance, TOL);
}
/**
* Write test
*/
@Test
public void testWrites() throws Exception {
// Install our own MemoryStore object, which records write requests
mWifiScoreCard.installMemoryStore(new WifiScoreCard.MemoryStore() {
@Override
public void read(String key, WifiScoreCard.BlobListener listener) {
// Just record these, never answer
mBlobListeners.add(listener);
}
@Override
public void write(String key, byte[] value) {
mKeys.add(key);
mBlobs.add(value);
}
});
// Make some changes
byte[] serialized = makeSerializedAccessPointExample();
assertEquals(1, mBlobListeners.size());
secondsPass(33);
// There should be one changed bssid now. We may have already done some writes.
mWifiScoreCard.doWrites();
assertTrue(mKeys.size() > 0);
// The written blob should not contain the BSSID, though the full serialized version does
String writtenHex = hexStringFromByteArray(mBlobs.get(mKeys.size() - 1));
String fullHex = hexStringFromByteArray(serialized);
String bssidHex = hexStringFromByteArray(TEST_BSSID_1.toByteArray());
assertFalse(writtenHex, writtenHex.contains(bssidHex));
assertTrue(fullHex, fullHex.contains(bssidHex));
// A second write request should not find anything to write
final int beforeSize = mKeys.size();
assertEquals(0, mWifiScoreCard.doWrites());
assertEquals(beforeSize, mKeys.size());
}
/**
* Calling doWrites before installing a MemoryStore should do nothing.
*/
@Test
public void testNoWritesUntilReady() throws Exception {
makeSerializedAccessPointExample();
assertEquals(0, mWifiScoreCard.doWrites());
}
/**
* Installing a MemoryStore after startup should issue reads.
*/
@Test
public void testReadAfterDelayedMemoryStoreInstallation() throws Exception {
makeSerializedAccessPointExample();
mWifiScoreCard.installMemoryStore(mMemoryStore);
verify(mMemoryStore).read(any(), any());
}
/**
* Calling clear should forget the state.
*/
@Test
public void testClearReallyDoesClearTheState() throws Exception {
byte[] serialized = makeSerializedAccessPointExample();
assertNotEquals(0, serialized.length);
mWifiScoreCard.clear();
byte[] leftovers = mWifiScoreCard.getNetworkListByteArray(false);
assertEquals(0, leftovers.length);
}
}