/* * 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 mKeys = new ArrayList<>(); final ArrayList mBlobListeners = new ArrayList<>(); final ArrayList 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 p1 = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo); assertNotNull(p1.first); assertNotNull(p1.second); mWifiInfo.setBSSID(TEST_BSSID_2.toString()); Pair p2 = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo); assertNotEquals(p1.first, p2.first); assertEquals(p1.second, p2.second); mWifiInfo.setBSSID(null); Pair 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); } }