diff options
author | Peter Qiu <zqiu@google.com> | 2016-09-02 14:25:05 -0700 |
---|---|---|
committer | Peter Qiu <zqiu@google.com> | 2016-09-07 14:51:51 -0700 |
commit | 09044adabba28c56b48922d105994d30e7ab015e (patch) | |
tree | 7a4525d33ca42431303e60f55241a7bb60ae2bd9 | |
parent | c49f45c75b1db93cd80b8bac4dd5fa8930fcb242 (diff) |
passpoint: refactor PasspointEventHandler
Changes include:
- cleanup the callback interface
- remove dependency for ScanDetail
- remove unused code
- add unit tests
BUG: 31264540
TEST: build and run unit tests
Change-Id: Idab62a6e12bef7807e2ef8bc5aec4e46fbf965dc
6 files changed, 336 insertions, 222 deletions
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java index 34dcfd79f..1ee04ba99 100644 --- a/service/java/com/android/server/wifi/WifiMonitor.java +++ b/service/java/com/android/server/wifi/WifiMonitor.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import com.android.internal.util.Protocol; import com.android.internal.util.StateMachine; import com.android.server.wifi.hotspot2.IconEvent; +import com.android.server.wifi.hotspot2.WnmData; import com.android.server.wifi.hotspot2.Utils; import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus; import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData; diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java index 2b0ed9415..d24cd1592 100644 --- a/service/java/com/android/server/wifi/WifiNative.java +++ b/service/java/com/android/server/wifi/WifiNative.java @@ -45,7 +45,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.server.connectivity.KeepalivePacketData; import com.android.server.wifi.hotspot2.NetworkDetail; -import com.android.server.wifi.hotspot2.SupplicantBridge; +import com.android.server.wifi.hotspot2.PasspointEventHandler; import com.android.server.wifi.hotspot2.Utils; import com.android.server.wifi.util.FrameParser; import com.android.server.wifi.util.InformationElementUtil; @@ -654,7 +654,7 @@ public class WifiNative { line.substring(BSS_SSID_STR.length())); } else if (line.startsWith(BSS_IE_STR)) { infoElementsStr = line; - } else if (SupplicantBridge.isAnqpAttribute(line)) { + } else if (PasspointEventHandler.isAnqpAttribute(line)) { if (anqpLines == null) { anqpLines = new ArrayList<>(); } diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index 4be85428d..6bb25a944 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -7020,31 +7020,6 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss return TextUtils.join(" ", attributes); } - private void wnmFrameReceived(WnmData event) { - // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url - // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url - - Intent intent = new Intent(WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_BSSID, event.getBssid()); - intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_URL, event.getUrl()); - - if (event.isDeauthEvent()) { - intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_ESS, event.isEss()); - intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_DELAY, event.getDelay()); - } else { - intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_METHOD, event.getMethod()); - WifiConfiguration config = getCurrentWifiConfiguration(); - if (config != null && config.FQDN != null) { - // TODO (b/31065385) - // intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH, - // mWifiConfigManager.matchProviderWithCurrentNetwork(config.FQDN)); - } - } - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - } - /** * Gets the SSID from the WifiConfiguration pointed at by 'mTargetNetworkId' * This should match the network config framework is attempting to connect to. diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java index 69255d7ac..63301ebd0 100644 --- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java +++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java @@ -255,7 +255,7 @@ public class NetworkDetail { mAnqpOICount = roamingConsortium.anqpOICount; mRoamingConsortiums = roamingConsortium.roamingConsortiums; mExtendedCapabilities = extendedCapabilities; - mANQPElements = SupplicantBridge.parseANQPLines(anqpLines); + mANQPElements = PasspointEventHandler.parseANQPLines(anqpLines); //set up channel info mPrimaryFreq = freq; diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java index 08d41a1ac..f61a5079d 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java @@ -1,17 +1,28 @@ +/* + * 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.hotspot2; import android.util.Base64; import android.util.Log; -import com.android.server.wifi.ScanDetail; import com.android.server.wifi.WifiNative; import com.android.server.wifi.anqp.ANQPElement; import com.android.server.wifi.anqp.ANQPFactory; import com.android.server.wifi.anqp.Constants; -import com.android.server.wifi.anqp.eap.AuthParam; -import com.android.server.wifi.anqp.eap.EAP; -import com.android.server.wifi.anqp.eap.EAPMethod; -import com.android.server.wifi.hotspot2.pps.Credential; import java.io.BufferedReader; import java.io.IOException; @@ -19,20 +30,20 @@ import java.io.StringReader; import java.net.ProtocolException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class SupplicantBridge { +/** + * This class handles passpoint specific interactions with the AP, such as ANQP + * elements requests, passpoint icon requests, and wireless network management + * event notifications. + */ +public class PasspointEventHandler { private final WifiNative mSupplicantHook; - private final SupplicantBridgeCallbacks mCallbacks; - private final Map<Long, ScanDetail> mRequestMap = new HashMap<>(); + private final Callbacks mCallbacks; - private static final int IconChunkSize = 1400; // 2K*3/4 - overhead + private static final int ICON_CHUNK_SIZE = 1400; // 2K*3/4 - overhead private static final Map<String, Constants.ANQPElementType> sWpsNames = new HashMap<>(); static { @@ -50,35 +61,56 @@ public class SupplicantBridge { } /** - * Interface to be implemented by the client to receive callbacks from SupplicantBridge. + * Interface to be implemented by the client to receive callbacks for passpoint + * related events. */ - public interface SupplicantBridgeCallbacks { + public interface Callbacks { /** - * Response from supplicant bridge for the initiated request. - * @param scanDetail - * @param anqpElements + * Invoked on received of ANQP response. |anqpElements| will be null on failure. + * @param bssid BSSID of the AP + * @param anqpElements ANQP elements to be queried */ - void notifyANQPResponse( - ScanDetail scanDetail, - Map<Constants.ANQPElementType, ANQPElement> anqpElements); + void onANQPResponse(long bssid, + Map<Constants.ANQPElementType, ANQPElement> anqpElements); /** - * Notify failure. - * @param bssid + * Invoked on received of icon response. |filename| and |data| will be null + * on failure. + * @param bssid BSSID of the AP + * @param filename Name of the icon file + * @data icon data bytes */ - void notifyIconFailed(long bssid); - } + void onIconResponse(long bssid, String filename, byte[] data); - public static boolean isAnqpAttribute(String line) { - int split = line.indexOf('='); - return split >= 0 && sWpsNames.containsKey(line.substring(0, split)); + /** + * Invoked on received of Hotspot 2.0 Wireless Network Management frame. + * @param data Wireless Network Management frame data + */ + void onWnmFrameReceived(WnmData data); } - public SupplicantBridge(WifiNative supplicantHook, SupplicantBridgeCallbacks callbacks) { + public PasspointEventHandler(WifiNative supplicantHook, Callbacks callbacks) { mSupplicantHook = supplicantHook; mCallbacks = callbacks; } + /** + * Determine the given |line| string is an ANQP element. + * TODO(zqiu): move this to different/new class (e.g. AnqpParser). + * @param line input text + * @return true if it is an ANQP element, false otherwise + */ + public static boolean isAnqpAttribute(String line) { + int split = line.indexOf('='); + return split >= 0 && sWpsNames.containsKey(line.substring(0, split)); + } + + /** + * Parse ANQP elements. + * TODO(zqiu): move this to different/new class (e.g. AnqpParser). + * @param lines input text + * @return a map of ANQP elements + */ public static Map<Constants.ANQPElementType, ANQPElement> parseANQPLines(List<String> lines) { if (lines == null) { return null; @@ -92,157 +124,118 @@ public class SupplicantBridge { } } catch (ProtocolException pe) { - Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse ANQP: " + pe); + Log.e(Utils.hs2LogTag(PasspointEventHandler.class), + "Failed to parse ANQP: " + pe); } } return elements; } - public boolean startANQP(ScanDetail scanDetail, List<Constants.ANQPElementType> elements) { - String anqpGet = buildWPSQueryRequest(scanDetail.getNetworkDetail(), elements); + /** + * Request the specified ANQP elements |elements| from the specified AP |bssid|. + * @param bssid BSSID of the AP + * @param elements ANQP elements to be queried + * @return true if request is sent successfully, false otherwise. + */ + public boolean requestANQP(long bssid, List<Constants.ANQPElementType> elements) { + String anqpGet = buildWPSQueryRequest(bssid, elements); if (anqpGet == null) { return false; } - synchronized (mRequestMap) { - mRequestMap.put(scanDetail.getNetworkDetail().getBSSID(), scanDetail); - } String result = mSupplicantHook.doCustomSupplicantCommand(anqpGet); if (result != null && result.startsWith("OK")) { Log.d(Utils.hs2LogTag(getClass()), "ANQP initiated on " - + scanDetail + " (" + anqpGet + ")"); + + Utils.macToString(bssid) + " (" + anqpGet + ")"); return true; } else { Log.d(Utils.hs2LogTag(getClass()), "ANQP failed on " + - scanDetail + ": " + result); + Utils.macToString(bssid) + ": " + result); return false; } } - public boolean doIconQuery(long bssid, String fileName) { + /** + * Request a passpoint icon file |filename| from the specified AP |bssid|. + * @param bssid BSSID of the AP + * @param fileName name of the icon file + * @return true if request is sent successfully, false otherwise + */ + public boolean requestIcon(long bssid, String fileName) { String result = mSupplicantHook.doCustomSupplicantCommand("REQ_HS20_ICON " + Utils.macToString(bssid) + " " + fileName); return result != null && result.startsWith("OK"); } - public byte[] retrieveIcon(IconEvent iconEvent) throws IOException { - byte[] iconData = new byte[iconEvent.getSize()]; - try { - int offset = 0; - while (offset < iconEvent.getSize()) { - int size = Math.min(iconEvent.getSize() - offset, IconChunkSize); - - String command = String.format("GET_HS20_ICON %s %s %d %d", - Utils.macToString(iconEvent.getBSSID()), iconEvent.getFileName(), - offset, size); - Log.d(Utils.hs2LogTag(getClass()), "Issuing '" + command + "'"); - String response = mSupplicantHook.doCustomSupplicantCommand(command); - if (response == null) { - throw new IOException("No icon data returned"); - } - - try { - byte[] fragment = Base64.decode(response, Base64.DEFAULT); - if (fragment.length == 0) { - throw new IOException("Null data for '" + command + "': " + response); - } - if (fragment.length + offset > iconData.length) { - throw new IOException("Icon chunk exceeds image size"); - } - System.arraycopy(fragment, 0, iconData, offset, fragment.length); - offset += fragment.length; - } catch (IllegalArgumentException iae) { - throw new IOException("Failed to parse response to '" + command - + "': " + response); - } + /** + * Invoked when ANQP query is completed. + * TODO(zqiu): currently ANQP completion notification is through WifiMonitor, + * this shouldn't be needed once we switch over to wificond for ANQP requests. + * @param bssid BSSID of the AP + * @param success true if query is completed successfully, false otherwise + */ + public void notifyANQPDone(long bssid, boolean success) { + Map<Constants.ANQPElementType, ANQPElement> elements = null; + if (success) { + String bssData = + mSupplicantHook.scanResult(Utils.macToString(bssid)); + try { + elements = parseWPSData(bssData); + Log.d(Utils.hs2LogTag(getClass()), + String.format("Successful ANQP response for %012x: %s", + bssid, elements)); } - if (offset != iconEvent.getSize()) { - Log.w(Utils.hs2LogTag(getClass()), "Partial icon data: " + offset + - ", expected " + iconEvent.getSize()); + catch (IOException ioe) { + Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " + + ioe.toString() + ": " + bssData); } - } - finally { - Log.d(Utils.hs2LogTag(getClass()), "Deleting icon for " + iconEvent); - String result = mSupplicantHook.doCustomSupplicantCommand("DEL_HS20_ICON " + - Utils.macToString(iconEvent.getBSSID()) + " " + iconEvent.getFileName()); - } - - return iconData; - } - - public void notifyANQPDone(Long bssid, boolean success) { - ScanDetail scanDetail; - synchronized (mRequestMap) { - scanDetail = mRequestMap.remove(bssid); - } - - if (scanDetail == null) { - // Icon queries are not held on the request map, so a null scanDetail is very likely - // the result of an Icon query. Notify the OSU app if the query was unsuccessful, - // else bail out. - if (!success) { - mCallbacks.notifyIconFailed(bssid); + catch (RuntimeException rte) { + Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " + + rte.toString() + ": " + bssData, rte); } - return; - } else if (!success) { - // If there is an associated ScanDetail, notify of a failed regular ANQP query. - mCallbacks.notifyANQPResponse(scanDetail, null); - return; } - - String bssData = mSupplicantHook.scanResult(scanDetail.getBSSIDString()); - Map<Constants.ANQPElementType, ANQPElement> elements = null; - try { - elements = parseWPSData(bssData); - Log.d(Utils.hs2LogTag(getClass()), - String.format("Successful ANQP response for %012x: %s", bssid, elements)); - } - catch (IOException ioe) { - Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " + - ioe.toString() + ": " + bssData); - } - catch (RuntimeException rte) { - Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " + - rte.toString() + ": " + bssData, rte); - } - mCallbacks.notifyANQPResponse(scanDetail, elements); - } - - private static String escapeSSID(NetworkDetail networkDetail) { - return escapeString(networkDetail.getSSID(), networkDetail.isSSID_UTF8()); + mCallbacks.onANQPResponse(bssid, elements); } - private static String escapeString(String s, boolean utf8) { - boolean asciiOnly = true; - for (int n = 0; n < s.length(); n++) { - char ch = s.charAt(n); - if (ch > 127) { - asciiOnly = false; - break; + /** + * Invoked when icon query is completed. + * TODO(zqiu): currently icon completion notification is through WifiMonitor, + * this shouldn't be needed once we switch over to wificond for icon requests. + * @param bssid BSSID of the AP + * @param iconEvent icon event data + */ + public void notifyIconDone(long bssid, IconEvent iconEvent) { + String filename = null; + byte[] data = null; + if (iconEvent != null) { + try { + data = retrieveIcon(iconEvent); + filename = iconEvent.getFileName(); + } catch (IOException ioe) { + Log.e(Utils.hs2LogTag(getClass()), "Failed to retrieve icon: " + + ioe.toString() + ": " + iconEvent.getFileName()); } } + mCallbacks.onIconResponse(bssid, filename, data); + } - if (asciiOnly) { - return '"' + s + '"'; - } - else { - byte[] octets = s.getBytes(utf8 ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1); - - StringBuilder sb = new StringBuilder(); - for (byte octet : octets) { - sb.append(String.format("%02x", octet & Constants.BYTE_MASK)); - } - return sb.toString(); - } + /** + * Invoked when a Wireless Network Management (WNM) frame is received. + * TODO(zqiu): currently WNM frame notification is through WifiMonitor, + * this shouldn't be needed once we switch over to wificond for WNM frame monitoring. + * @param data WNM frame data + */ + public void notifyWnmFrameReceived(WnmData data) { + mCallbacks.onWnmFrameReceived(data); } /** * Build a wpa_supplicant ANQP query command - * @param networkDetail The network to query. + * @param bssid BSSID of the AP to be queried * @param querySet elements to query * @return A command string. */ - private static String buildWPSQueryRequest(NetworkDetail networkDetail, + private static String buildWPSQueryRequest(long bssid, List<Constants.ANQPElementType> querySet) { boolean baseANQPElements = Constants.hasBaseANQPElements(querySet); @@ -251,9 +244,10 @@ public class SupplicantBridge { sb.append("ANQP_GET "); } else { - sb.append("HS20_ANQP_GET "); // ANQP_GET does not work for a sole hs20:8 (OSU) query + // ANQP_GET does not work for a sole hs20:8 (OSU) query + sb.append("HS20_ANQP_GET "); } - sb.append(networkDetail.getBSSIDString()).append(' '); + sb.append(Utils.macToString(bssid)).append(' '); boolean first = true; for (Constants.ANQPElementType elementType : querySet) { @@ -280,42 +274,6 @@ public class SupplicantBridge { return sb.toString(); } - private static List<String> getWPSNetCommands(String netID, NetworkDetail networkDetail, - Credential credential) { - - List<String> commands = new ArrayList<String>(); - - EAPMethod eapMethod = credential.getEAPMethod(); - commands.add(String.format("SET_NETWORK %s key_mgmt WPA-EAP", netID)); - commands.add(String.format("SET_NETWORK %s ssid %s", netID, escapeSSID(networkDetail))); - commands.add(String.format("SET_NETWORK %s bssid %s", - netID, networkDetail.getBSSIDString())); - commands.add(String.format("SET_NETWORK %s eap %s", - netID, mapEAPMethodName(eapMethod.getEAPMethodID()))); - - AuthParam authParam = credential.getEAPMethod().getAuthParam(); - if (authParam == null) { - return null; // TLS or SIM/AKA - } - switch (authParam.getAuthInfoID()) { - case NonEAPInnerAuthType: - case InnerAuthEAPMethodType: - commands.add(String.format("SET_NETWORK %s identity %s", - netID, escapeString(credential.getUserName(), true))); - commands.add(String.format("SET_NETWORK %s password %s", - netID, escapeString(credential.getPassword(), true))); - commands.add(String.format("SET_NETWORK %s anonymous_identity \"anonymous\"", - netID)); - break; - default: // !!! Needs work. - return null; - } - commands.add(String.format("SET_NETWORK %s priority 0", netID)); - commands.add(String.format("ENABLE_NETWORK %s", netID)); - commands.add(String.format("SAVE_CONFIG")); - return commands; - } - private static Map<Constants.ANQPElementType, ANQPElement> parseWPSData(String bssInfo) throws IOException { Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>(); @@ -350,7 +308,8 @@ public class SupplicantBridge { payload = Utils.hexToBytes(text.substring(separator + 1)); } catch (NumberFormatException nfe) { - Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse hex string"); + Log.e(Utils.hs2LogTag(PasspointEventHandler.class), + "Failed to parse hex string"); return null; } return Constants.getANQPElementID(elementType) != null ? @@ -359,21 +318,50 @@ public class SupplicantBridge { ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN)); } - private static String mapEAPMethodName(EAP.EAPMethodID eapMethodID) { - switch (eapMethodID) { - case EAP_AKA: - return "AKA"; - case EAP_AKAPrim: - return "AKA'"; // eap.c:1514 - case EAP_SIM: - return "SIM"; - case EAP_TLS: - return "TLS"; - case EAP_TTLS: - return "TTLS"; - default: - throw new IllegalArgumentException("No mapping for " + eapMethodID); + private byte[] retrieveIcon(IconEvent iconEvent) throws IOException { + byte[] iconData = new byte[iconEvent.getSize()]; + try { + int offset = 0; + while (offset < iconEvent.getSize()) { + int size = Math.min(iconEvent.getSize() - offset, ICON_CHUNK_SIZE); + + String command = String.format("GET_HS20_ICON %s %s %d %d", + Utils.macToString(iconEvent.getBSSID()), iconEvent.getFileName(), + offset, size); + Log.d(Utils.hs2LogTag(getClass()), "Issuing '" + command + "'"); + String response = mSupplicantHook.doCustomSupplicantCommand(command); + if (response == null) { + throw new IOException("No icon data returned"); + } + + try { + byte[] fragment = Base64.decode(response, Base64.DEFAULT); + if (fragment.length == 0) { + throw new IOException("Null data for '" + command + "': " + response); + } + if (fragment.length + offset > iconData.length) { + throw new IOException("Icon chunk exceeds image size"); + } + System.arraycopy(fragment, 0, iconData, offset, fragment.length); + offset += fragment.length; + } catch (IllegalArgumentException iae) { + throw new IOException("Failed to parse response to '" + command + + "': " + response); + } + } + if (offset != iconEvent.getSize()) { + Log.w(Utils.hs2LogTag(getClass()), "Partial icon data: " + offset + + ", expected " + iconEvent.getSize()); + } + } + finally { + // Delete the icon file in supplicant. + Log.d(Utils.hs2LogTag(getClass()), "Deleting icon for " + iconEvent); + String result = mSupplicantHook.doCustomSupplicantCommand("DEL_HS20_ICON " + + Utils.macToString(iconEvent.getBSSID()) + " " + iconEvent.getFileName()); + Log.d(Utils.hs2LogTag(getClass()), "Result: " + result); } - } + return iconData; + } } diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java new file mode 100644 index 000000000..88e02f8d5 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java @@ -0,0 +1,150 @@ +/* + * 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.hotspot2; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.wifi.WifiNative; +import com.android.server.wifi.anqp.Constants; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointEventHandler}. + * TODO(zqiu): add more test when switch over to use wificond. + */ +@SmallTest +public class PasspointEventHandlerTest { + + private static final String TAG = "PasspointEventHandlerTest"; + + private static final long BSSID = 0x112233445566L; + private static final String BSSID_STR = "11:22:33:44:55:66"; + private static final String ICON_FILENAME = "icon.test"; + + @Mock WifiNative mWifiNative; + @Mock PasspointEventHandler.Callbacks mCallbacks; + PasspointEventHandler mHandler; + + /** Sets up test. */ + @Before + public void setUp() throws Exception { + initMocks(this); + mHandler = new PasspointEventHandler(mWifiNative, mCallbacks); + } + + /** + * Test for requesting Hotspot 2.0 R1 ANQP element. + */ + @Test + public void requestR1AnqpElement() { + List<Constants.ANQPElementType> elementToRequest = + Arrays.asList(Constants.ANQPElementType.ANQPRoamingConsortium); + String expectedCommand = "ANQP_GET " + BSSID_STR + " " + + Constants.getANQPElementID( + Constants.ANQPElementType.ANQPRoamingConsortium); + + // wpa_supplicant succeeded the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("OK"); + assertTrue(mHandler.requestANQP(BSSID, elementToRequest)); + + // wpa_supplicant failed the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("FAILED"); + assertFalse(mHandler.requestANQP(BSSID, elementToRequest)); + } + + /** + * Test for requesting Hotspot 2.0 R2 ANQP element. + */ + @Test + public void requestR2AnqpElement() { + List<Constants.ANQPElementType> elementToRequest = + Arrays.asList(Constants.ANQPElementType.HSFriendlyName); + String expectedCommand = "HS20_ANQP_GET " + BSSID_STR + " " + + Constants.getHS20ElementID(Constants.ANQPElementType.HSFriendlyName); + + // wpa_supplicant succeeded the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("OK"); + assertTrue(mHandler.requestANQP(BSSID, elementToRequest)); + + // wpa_supplicant failed the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("FAILED"); + assertFalse(mHandler.requestANQP(BSSID, elementToRequest)); + } + + /** + * Test for requesting both Hotspot 2.0 R1 and R2 ANQP elements. + */ + @Test + public void requestMixAnqpElements() { + List<Constants.ANQPElementType> elementToRequest = + Arrays.asList(Constants.ANQPElementType.ANQPRoamingConsortium, + Constants.ANQPElementType.HSFriendlyName); + + // ANQP_GET command is used when both R1 and R2 elements are present. + // |hs20:| prefix for R2 elements are needed when using ANQP_GET command + String expectedCommand = "ANQP_GET " + BSSID_STR + " " + + Constants.getANQPElementID( + Constants.ANQPElementType.ANQPRoamingConsortium) + ",hs20:" + + Constants.getHS20ElementID(Constants.ANQPElementType.HSFriendlyName); + + // wpa_supplicant succeeded the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("OK"); + assertTrue(mHandler.requestANQP(BSSID, elementToRequest)); + + // wpa_supplicant failed the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("FAILED"); + assertFalse(mHandler.requestANQP(BSSID, elementToRequest)); + } + + /** + * Test for requesting both Hotspot 2.0 R2 icon file. + */ + @Test + public void requestIconFile() { + String expectedCommand = "REQ_HS20_ICON " + BSSID_STR + " " + ICON_FILENAME; + + // wpa_supplicant succeeded the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("OK"); + assertTrue(mHandler.requestIcon(BSSID, ICON_FILENAME)); + + // wpa_supplicant failed the request. + when(mWifiNative.doCustomSupplicantCommand(expectedCommand)).thenReturn("FAILED"); + assertFalse(mHandler.requestIcon(BSSID, ICON_FILENAME)); + } + + /** + * Test for ANQP request completed with error. + */ + @Test + public void anqpRequestCompletedWithError() { + mHandler.notifyANQPDone(BSSID, false); + verify(mWifiNative, never()).scanResult(anyString()); + verify(mCallbacks).onANQPResponse(BSSID, null); + } +} |