| /* |
| * 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.aware; |
| |
| import android.Manifest; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.hardware.wifi.V1_0.NanDataPathChannelCfg; |
| import android.hardware.wifi.V1_0.NanStatusType; |
| import android.hardware.wifi.V1_2.NanDataPathChannelInfo; |
| import android.net.ConnectivityManager; |
| import android.net.IpPrefix; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.MacAddress; |
| import android.net.MatchAllNetworkSpecifier; |
| import android.net.NetworkAgent; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkInfo; |
| import android.net.NetworkRequest; |
| import android.net.NetworkSpecifier; |
| import android.net.RouteInfo; |
| import android.net.wifi.aware.WifiAwareAgentNetworkSpecifier; |
| import android.net.wifi.aware.WifiAwareManager; |
| import android.net.wifi.aware.WifiAwareNetworkSpecifier; |
| import android.net.wifi.aware.WifiAwareUtils; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.os.INetworkManagementService; |
| import android.os.Looper; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.wifi.util.WifiPermissionsUtil; |
| import com.android.server.wifi.util.WifiPermissionsWrapper; |
| |
| import libcore.util.HexEncoding; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.NetworkInterface; |
| import java.net.SocketException; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| /** |
| * Manages Aware data-path lifetime: interface creation/deletion, data-path setup and tear-down. |
| * The Aware network configuration is: |
| * - transport = TRANSPORT_WIFI_AWARE |
| * - capabilities = NET_CAPABILITY_NOT_VPN |
| * - network specifier generated by DiscoverySession.createNetworkSpecifier(...) or |
| * WifiAwareManager.createNetworkSpecifier(...). |
| */ |
| public class WifiAwareDataPathStateManager { |
| private static final String TAG = "WifiAwareDataPathStMgr"; |
| private static final boolean VDBG = false; // STOPSHIP if true |
| /* package */ boolean mDbg = false; |
| |
| private static final String AWARE_INTERFACE_PREFIX = "aware_data"; |
| private static final String NETWORK_TAG = "WIFI_AWARE_FACTORY"; |
| private static final String AGENT_TAG_PREFIX = "WIFI_AWARE_AGENT_"; |
| private static final int NETWORK_FACTORY_SCORE_AVAIL = 1; |
| private static final int NETWORK_FACTORY_BANDWIDTH_AVAIL = 1; |
| private static final int NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL = 1; |
| |
| private final WifiAwareStateManager mMgr; |
| public NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper(); |
| private static final NetworkCapabilities sNetworkCapabilitiesFilter = new NetworkCapabilities(); |
| private final Set<String> mInterfaces = new HashSet<>(); |
| private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> |
| mNetworkRequestsCache = new ArrayMap<>(); |
| private Context mContext; |
| private WifiAwareMetrics mAwareMetrics; |
| private WifiPermissionsUtil mWifiPermissionsUtil; |
| private WifiPermissionsWrapper mPermissionsWrapper; |
| private Looper mLooper; |
| private WifiAwareNetworkFactory mNetworkFactory; |
| public INetworkManagementService mNwService; |
| |
| // internal debug flag to override API check |
| /* package */ boolean mAllowNdpResponderFromAnyOverride = false; |
| |
| public WifiAwareDataPathStateManager(WifiAwareStateManager mgr) { |
| mMgr = mgr; |
| } |
| |
| /** |
| * Initialize the Aware data-path state manager. Specifically register the network factory with |
| * connectivity service. |
| */ |
| public void start(Context context, Looper looper, WifiAwareMetrics awareMetrics, |
| WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionsWrapper) { |
| if (VDBG) Log.v(TAG, "start"); |
| |
| mContext = context; |
| mAwareMetrics = awareMetrics; |
| mWifiPermissionsUtil = wifiPermissionsUtil; |
| mPermissionsWrapper = permissionsWrapper; |
| mLooper = looper; |
| |
| sNetworkCapabilitiesFilter.clearAll(); |
| sNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE); |
| sNetworkCapabilitiesFilter |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) |
| .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); |
| sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier()); |
| sNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); |
| sNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL); |
| sNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL); |
| |
| mNetworkFactory = new WifiAwareNetworkFactory(looper, context, sNetworkCapabilitiesFilter); |
| mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL); |
| mNetworkFactory.register(); |
| |
| IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); |
| mNwService = INetworkManagementService.Stub.asInterface(b); |
| } |
| |
| private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> |
| getNetworkRequestByNdpId(int ndpId) { |
| for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry : |
| mNetworkRequestsCache.entrySet()) { |
| if (entry.getValue().ndpId == ndpId) { |
| return entry; |
| } |
| } |
| |
| return null; |
| } |
| |
| private Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> |
| getNetworkRequestByCanonicalDescriptor(CanonicalConnectionInfo cci) { |
| if (VDBG) Log.v(TAG, "getNetworkRequestByCanonicalDescriptor: cci=" + cci); |
| for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry : |
| mNetworkRequestsCache.entrySet()) { |
| if (VDBG) { |
| Log.v(TAG, "getNetworkRequestByCanonicalDescriptor: entry=" + entry.getValue() |
| + " --> cci=" + entry.getValue().getCanonicalDescriptor()); |
| } |
| if (entry.getValue().getCanonicalDescriptor().matches(cci)) { |
| return entry; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Create all Aware data-path interfaces which are possible on the device - based on the |
| * capabilities of the firmware. |
| */ |
| public void createAllInterfaces() { |
| if (VDBG) Log.v(TAG, "createAllInterfaces"); |
| |
| if (mMgr.getCapabilities() == null) { |
| Log.e(TAG, "createAllInterfaces: capabilities aren't initialized yet!"); |
| return; |
| } |
| |
| for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) { |
| String name = AWARE_INTERFACE_PREFIX + i; |
| if (mInterfaces.contains(name)) { |
| Log.e(TAG, "createAllInterfaces(): interface already up, " + name |
| + ", possibly failed to delete - deleting/creating again to be safe"); |
| mMgr.deleteDataPathInterface(name); |
| |
| // critical to remove so that don't get infinite loop if the delete fails again |
| mInterfaces.remove(name); |
| } |
| |
| mMgr.createDataPathInterface(name); |
| } |
| } |
| |
| /** |
| * Delete all Aware data-path interfaces which are currently up. |
| */ |
| public void deleteAllInterfaces() { |
| if (VDBG) Log.v(TAG, "deleteAllInterfaces"); |
| onAwareDownCleanupDataPaths(); |
| |
| if (mMgr.getCapabilities() == null) { |
| Log.e(TAG, "deleteAllInterfaces: capabilities aren't initialized yet!"); |
| return; |
| } |
| |
| for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) { |
| String name = AWARE_INTERFACE_PREFIX + i; |
| mMgr.deleteDataPathInterface(name); |
| } |
| mMgr.releaseAwareInterface(); |
| } |
| |
| /** |
| * Called when firmware indicates the an interface was created. |
| */ |
| public void onInterfaceCreated(String interfaceName) { |
| if (VDBG) Log.v(TAG, "onInterfaceCreated: interfaceName=" + interfaceName); |
| |
| if (mInterfaces.contains(interfaceName)) { |
| Log.w(TAG, "onInterfaceCreated: already contains interface -- " + interfaceName); |
| } |
| |
| mInterfaces.add(interfaceName); |
| } |
| |
| /** |
| * Called when firmware indicates the an interface was deleted. |
| */ |
| public void onInterfaceDeleted(String interfaceName) { |
| if (VDBG) Log.v(TAG, "onInterfaceDeleted: interfaceName=" + interfaceName); |
| |
| if (!mInterfaces.contains(interfaceName)) { |
| Log.w(TAG, "onInterfaceDeleted: interface not on list -- " + interfaceName); |
| } |
| |
| mInterfaces.remove(interfaceName); |
| } |
| |
| /** |
| * Response to initiating data-path request. Indicates that request is successful (not |
| * complete!) and is now in progress. |
| * |
| * @param networkSpecifier The network specifier provided as part of the initiate request. |
| * @param ndpId The ID assigned to the data-path. |
| */ |
| public void onDataPathInitiateSuccess(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) { |
| if (VDBG) { |
| Log.v(TAG, |
| "onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId=" |
| + ndpId); |
| } |
| |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); |
| if (nnri == null) { |
| Log.w(TAG, "onDataPathInitiateSuccess: network request not found for networkSpecifier=" |
| + networkSpecifier); |
| mMgr.endDataPath(ndpId); |
| return; |
| } |
| |
| if (nnri.state |
| != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { |
| Log.w(TAG, "onDataPathInitiateSuccess: network request in incorrect state: state=" |
| + nnri.state); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| mMgr.endDataPath(ndpId); |
| return; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM; |
| nnri.ndpId = ndpId; |
| } |
| |
| /** |
| * Response to an attempt to set up a data-path (on the initiator side). |
| * |
| * @param networkSpecifier The network specifier provided as part of the initiate request. |
| * @param reason Failure reason. |
| */ |
| public void onDataPathInitiateFail(WifiAwareNetworkSpecifier networkSpecifier, int reason) { |
| if (VDBG) { |
| Log.v(TAG, |
| "onDataPathInitiateFail: networkSpecifier=" + networkSpecifier + ", reason=" |
| + reason); |
| } |
| |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier); |
| if (nnri == null) { |
| Log.w(TAG, "onDataPathInitiateFail: network request not found for networkSpecifier=" |
| + networkSpecifier); |
| return; |
| } |
| |
| if (nnri.state |
| != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) { |
| Log.w(TAG, "onDataPathInitiateFail: network request in incorrect state: state=" |
| + nnri.state); |
| } |
| |
| mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), nnri.startTimestamp); |
| } |
| |
| |
| /** |
| * Notification (unsolicited/asynchronous) that a peer has requested to set up a data-path |
| * connection with us. |
| * |
| * @param pubSubId The ID of the discovery session context for the data-path - or 0 if not |
| * related to a discovery session. |
| * @param mac The discovery MAC address of the peer. |
| * @param ndpId The locally assigned ID for the data-path. |
| * @return The network specifier of the data-path (or null if none/error) |
| */ |
| public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId) { |
| if (VDBG) { |
| Log.v(TAG, |
| "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf( |
| HexEncoding.encode(mac)) + ", ndpId=" + ndpId); |
| } |
| |
| WifiAwareNetworkSpecifier networkSpecifier = null; |
| AwareNetworkRequestInformation nnri = null; |
| for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry : |
| mNetworkRequestsCache.entrySet()) { |
| /* |
| * Checking that the incoming request (from the Initiator) matches the request |
| * we (the Responder) already have set up. The rules are: |
| * - The discovery session (pub/sub ID) must match. |
| * - The peer MAC address (if specified - i.e. non-null) must match. A null peer MAC == |
| * accept (otherwise matching) requests from any peer MAC. |
| * - The request must be pending (i.e. we could have completed requests for the same |
| * parameters) |
| */ |
| if (entry.getValue().pubSubId != 0 && entry.getValue().pubSubId != pubSubId) { |
| continue; |
| } |
| |
| if (entry.getValue().peerDiscoveryMac != null && !Arrays.equals( |
| entry.getValue().peerDiscoveryMac, mac)) { |
| continue; |
| } |
| |
| if (entry.getValue().state |
| != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) { |
| continue; |
| } |
| |
| networkSpecifier = entry.getKey(); |
| nnri = entry.getValue(); |
| break; |
| } |
| |
| if (nnri == null) { |
| Log.w(TAG, "onDataPathRequest: can't find a request with specified pubSubId=" + pubSubId |
| + ", mac=" + String.valueOf(HexEncoding.encode(mac))); |
| if (VDBG) { |
| Log.v(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache); |
| } |
| mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false); |
| return null; |
| } |
| |
| if (nnri.peerDiscoveryMac == null) { |
| // the "accept anyone" request is now specific |
| nnri.peerDiscoveryMac = mac; |
| } |
| nnri.interfaceName = selectInterfaceForRequest(nnri); |
| if (nnri.interfaceName == null) { |
| Log.w(TAG, |
| "onDataPathRequest: request " + networkSpecifier + " no interface available"); |
| mMgr.respondToDataPathRequest(false, ndpId, "", null, null, false); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| return null; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE; |
| nnri.ndpId = ndpId; |
| nnri.startTimestamp = SystemClock.elapsedRealtime(); |
| mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk, |
| nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand()); |
| |
| return networkSpecifier; |
| } |
| |
| /** |
| * Called on the RESPONDER when the response to data-path request has been completed. |
| * |
| * @param ndpId The ID of the data-path (NDP) |
| * @param success Whether or not the 'RespondToDataPathRequest' operation was a success. |
| */ |
| public void onRespondToDataPathRequest(int ndpId, boolean success, int reasonOnFailure) { |
| if (VDBG) { |
| Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success); |
| } |
| |
| WifiAwareNetworkSpecifier networkSpecifier = null; |
| AwareNetworkRequestInformation nnri = null; |
| for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry : |
| mNetworkRequestsCache.entrySet()) { |
| if (entry.getValue().ndpId == ndpId) { |
| networkSpecifier = entry.getKey(); |
| nnri = entry.getValue(); |
| break; |
| } |
| } |
| |
| if (nnri == null) { |
| Log.w(TAG, "onRespondToDataPathRequest: can't find a request with specified ndpId=" |
| + ndpId); |
| if (VDBG) { |
| Log.v(TAG, "onRespondToDataPathRequest: network request cache = " |
| + mNetworkRequestsCache); |
| } |
| return; |
| } |
| |
| if (!success) { |
| Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier |
| + " failed responding"); |
| mMgr.endDataPath(ndpId); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| mAwareMetrics.recordNdpStatus(reasonOnFailure, networkSpecifier.isOutOfBand(), |
| nnri.startTimestamp); |
| return; |
| } |
| |
| if (nnri.state |
| != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE) { |
| Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier |
| + " is incorrect state=" + nnri.state); |
| mMgr.endDataPath(ndpId); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| return; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM; |
| } |
| |
| /** |
| * Notification (unsolicited/asynchronous) that the data-path (which we've been setting up) |
| * is possibly (if {@code accept} is {@code true}) ready for use from the firmware's |
| * perspective - now can do L3 configuration. |
| * |
| * @param ndpId Id of the data-path |
| * @param mac The MAC address of the peer's data-path (not discovery interface). Only |
| * valid |
| * if {@code accept} is {@code true}. |
| * @param accept Indicates whether the data-path setup has succeeded (been accepted) or |
| * failed (been rejected). |
| * @param reason If {@code accept} is {@code false} provides a reason code for the |
| * rejection/failure. |
| * @param message The message provided by the peer as part of the data-path setup |
| * process. |
| * @param channelInfo Lists of channels used for this NDP. |
| * @return The network specifier of the data-path or a null if none/error. |
| */ |
| public WifiAwareNetworkSpecifier onDataPathConfirm(int ndpId, byte[] mac, boolean accept, |
| int reason, byte[] message, List<NanDataPathChannelInfo> channelInfo) { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf( |
| HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason |
| + ", channelInfo=" + channelInfo); |
| } |
| |
| Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE = |
| getNetworkRequestByNdpId(ndpId); |
| if (nnriE == null) { |
| Log.w(TAG, "onDataPathConfirm: network request not found for ndpId=" + ndpId); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return null; |
| } |
| |
| WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey(); |
| AwareNetworkRequestInformation nnri = nnriE.getValue(); |
| |
| // validate state |
| if (nnri.state != AwareNetworkRequestInformation.STATE_WAIT_FOR_CONFIRM) { |
| Log.w(TAG, "onDataPathConfirm: invalid state=" + nnri.state); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return networkSpecifier; |
| } |
| |
| if (accept) { |
| nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED; |
| nnri.peerDataMac = mac; |
| nnri.channelInfo = channelInfo; |
| |
| NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, |
| NETWORK_TAG, ""); |
| NetworkCapabilities networkCapabilities = new NetworkCapabilities( |
| sNetworkCapabilitiesFilter); |
| LinkProperties linkProperties = new LinkProperties(); |
| |
| boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri); |
| if (!interfaceUsedByAnotherNdp) { |
| try { |
| mNwService.setInterfaceUp(nnri.interfaceName); |
| mNwService.enableIpv6(nnri.interfaceName); |
| } catch (Exception e) { // NwService throws runtime exceptions for errors |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't configure network - " |
| + e); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| return networkSpecifier; |
| } |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: interface already configured: " |
| + nnri.interfaceName); |
| } |
| } |
| |
| if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentSpecifiers, ndpId, |
| networkInfo, networkCapabilities, linkProperties)) { |
| return networkSpecifier; |
| } |
| |
| nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext, |
| AGENT_TAG_PREFIX + nnri.ndpId, |
| new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TAG, ""), |
| networkCapabilities, linkProperties, NETWORK_FACTORY_SCORE_AVAIL, |
| nnri); |
| nnri.networkAgent.sendNetworkInfo(networkInfo); |
| |
| mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, networkSpecifier.isOutOfBand(), |
| nnri.startTimestamp); |
| nnri.startTimestamp = SystemClock.elapsedRealtime(); // update time-stamp for duration |
| mAwareMetrics.recordNdpCreation(nnri.uid, mNetworkRequestsCache); |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier |
| + " rejected - reason=" + reason); |
| } |
| mNetworkRequestsCache.remove(networkSpecifier); |
| mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), |
| nnri.startTimestamp); |
| } |
| |
| return networkSpecifier; |
| } |
| |
| /** |
| * Notification (unsolicited/asynchronous) from the firmware that the specified data-path has |
| * been terminated. |
| * |
| * @param ndpId The ID of the terminated data-path. |
| */ |
| public void onDataPathEnd(int ndpId) { |
| if (VDBG) Log.v(TAG, "onDataPathEnd: ndpId=" + ndpId); |
| |
| Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE = |
| getNetworkRequestByNdpId(ndpId); |
| if (nnriE == null) { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathEnd: network request not found for ndpId=" + ndpId); |
| } |
| return; |
| } |
| |
| tearDownInterfaceIfPossible(nnriE.getValue()); |
| if (nnriE.getValue().state == AwareNetworkRequestInformation.STATE_CONFIRMED |
| || nnriE.getValue().state == AwareNetworkRequestInformation.STATE_TERMINATING) { |
| mAwareMetrics.recordNdpSessionDuration(nnriE.getValue().startTimestamp); |
| } |
| mNetworkRequestsCache.remove(nnriE.getKey()); |
| |
| mNetworkFactory.tickleConnectivityIfWaiting(); |
| } |
| |
| /** |
| * Notification (unsolicited/asynchronous) from the firmware that the channel for the specified |
| * NDP ids has been updated. |
| */ |
| public void onDataPathSchedUpdate(byte[] peerMac, List<Integer> ndpIds, |
| List<NanDataPathChannelInfo> channelInfo) { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathSchedUpdate: peerMac=" + MacAddress.fromBytes(peerMac).toString() |
| + ", ndpIds=" + ndpIds + ", channelInfo=" + channelInfo); |
| } |
| |
| for (int ndpId : ndpIds) { |
| Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE = |
| getNetworkRequestByNdpId(ndpId); |
| if (nnriE == null) { |
| Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + " - not found"); |
| continue; |
| } |
| if (!Arrays.equals(peerMac, nnriE.getValue().peerDiscoveryMac)) { |
| Log.e(TAG, "onDataPathSchedUpdate: ndpId=" + ndpId + ", report NMI=" |
| + MacAddress.fromBytes(peerMac).toString() + " doesn't match NDP NMI=" |
| + MacAddress.fromBytes(nnriE.getValue().peerDiscoveryMac).toString()); |
| continue; |
| } |
| |
| nnriE.getValue().channelInfo = channelInfo; |
| } |
| } |
| |
| /** |
| * Called whenever Aware comes down. Clean up all pending and up network requests and agents. |
| */ |
| public void onAwareDownCleanupDataPaths() { |
| if (VDBG) Log.v(TAG, "onAwareDownCleanupDataPaths"); |
| |
| Iterator<Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>> it = |
| mNetworkRequestsCache.entrySet().iterator(); |
| while (it.hasNext()) { |
| tearDownInterfaceIfPossible(it.next().getValue()); |
| it.remove(); |
| } |
| } |
| |
| /** |
| * Called when timed-out waiting for confirmation of the data-path setup (i.e. |
| * onDataPathConfirm). Started on the initiator when executing the request for the data-path |
| * and on the responder when received a request for data-path (in both cases only on success |
| * - i.e. when we're proceeding with data-path setup). |
| */ |
| public void handleDataPathTimeout(NetworkSpecifier networkSpecifier) { |
| if (mDbg) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier); |
| |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier); |
| if (nnri == null) { |
| if (mDbg) { |
| Log.v(TAG, |
| "handleDataPathTimeout: network request not found for networkSpecifier=" |
| + networkSpecifier); |
| } |
| return; |
| } |
| mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, |
| nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp); |
| |
| mMgr.endDataPath(nnri.ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| } |
| |
| private class WifiAwareNetworkFactory extends NetworkFactory { |
| // Request received while waiting for confirmation that a canonically identical data-path |
| // (NDP) is in the process of being terminated |
| private boolean mWaitingForTermination = false; |
| |
| WifiAwareNetworkFactory(Looper looper, Context context, NetworkCapabilities filter) { |
| super(looper, context, NETWORK_TAG, filter); |
| } |
| |
| public void tickleConnectivityIfWaiting() { |
| if (mWaitingForTermination) { |
| if (VDBG) Log.v(TAG, "tickleConnectivityIfWaiting: was waiting!"); |
| mWaitingForTermination = false; |
| reevaluateAllRequests(); |
| } |
| } |
| |
| @Override |
| public boolean acceptRequest(NetworkRequest request, int score) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request + ", score=" |
| + score); |
| } |
| |
| if (!mMgr.isUsageEnabled()) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " -- Aware disabled"); |
| } |
| return false; |
| } |
| |
| if (mInterfaces.isEmpty()) { |
| Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " -- No Aware interfaces are up"); |
| return false; |
| } |
| |
| NetworkSpecifier networkSpecifierBase = |
| request.networkCapabilities.getNetworkSpecifier(); |
| if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) { |
| Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - not a WifiAwareNetworkSpecifier"); |
| return false; |
| } |
| |
| WifiAwareNetworkSpecifier networkSpecifier = |
| (WifiAwareNetworkSpecifier) networkSpecifierBase; |
| |
| // look up specifier - are we being called again? |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); |
| if (nnri != null) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - already in cache with state=" + nnri.state); |
| } |
| |
| if (nnri.state == AwareNetworkRequestInformation.STATE_TERMINATING) { |
| mWaitingForTermination = true; |
| return false; |
| } |
| |
| // seems to happen after a network agent is created - trying to rematch all |
| // requests again!? |
| return true; |
| } |
| |
| nnri = AwareNetworkRequestInformation.processNetworkSpecifier(networkSpecifier, mMgr, |
| mWifiPermissionsUtil, mPermissionsWrapper, mAllowNdpResponderFromAnyOverride); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - can't parse network specifier"); |
| return false; |
| } |
| |
| // check to see if a canonical version exists |
| Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> primaryRequest = |
| getNetworkRequestByCanonicalDescriptor(nnri.getCanonicalDescriptor()); |
| if (primaryRequest != null) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + ", already has a primary request=" + primaryRequest.getKey() |
| + " with state=" + primaryRequest.getValue().state); |
| } |
| |
| if (primaryRequest.getValue().state |
| == AwareNetworkRequestInformation.STATE_TERMINATING) { |
| mWaitingForTermination = true; |
| } else { |
| primaryRequest.getValue().updateToSupportNewRequest(networkSpecifier); |
| } |
| return false; |
| } |
| |
| mNetworkRequestsCache.put(networkSpecifier, nnri); |
| |
| return true; |
| } |
| |
| @Override |
| protected void needNetworkFor(NetworkRequest networkRequest, int score) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" |
| + networkRequest + ", score=" + score); |
| } |
| |
| NetworkSpecifier networkSpecifierObj = |
| networkRequest.networkCapabilities.getNetworkSpecifier(); |
| WifiAwareNetworkSpecifier networkSpecifier = null; |
| if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) { |
| networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj; |
| } |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" |
| + networkRequest + " not in cache!?"); |
| return; |
| } |
| |
| if (nnri.state != AwareNetworkRequestInformation.STATE_IDLE) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" |
| + networkRequest + " - already in progress"); |
| // TODO: understand how/when can be called again/while in progress (seems |
| // to be related to score re-calculation after a network agent is created) |
| } |
| return; |
| } |
| if (nnri.networkSpecifier.role |
| == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { |
| nnri.interfaceName = selectInterfaceForRequest(nnri); |
| if (nnri.interfaceName == null) { |
| Log.w(TAG, "needNetworkFor: request " + networkSpecifier |
| + " no interface available"); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| return; |
| } |
| |
| mMgr.initiateDataPathSetup(networkSpecifier, nnri.peerInstanceId, |
| NanDataPathChannelCfg.CHANNEL_NOT_REQUESTED, selectChannelForRequest(nnri), |
| nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk, |
| nnri.networkSpecifier.passphrase, nnri.networkSpecifier.isOutOfBand()); |
| nnri.state = |
| AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE; |
| nnri.startTimestamp = SystemClock.elapsedRealtime(); |
| } else { |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST; |
| } |
| } |
| |
| @Override |
| protected void releaseNetworkFor(NetworkRequest networkRequest) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" |
| + networkRequest); |
| } |
| |
| NetworkSpecifier networkSpecifierObj = |
| networkRequest.networkCapabilities.getNetworkSpecifier(); |
| WifiAwareNetworkSpecifier networkSpecifier = null; |
| if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) { |
| networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj; |
| } |
| |
| AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" |
| + networkRequest + " not in cache!?"); |
| return; |
| } |
| |
| if (nnri.networkAgent != null) { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" |
| + networkRequest + ", nnri=" + nnri |
| + ": agent already created - deferring ending data-path to agent" |
| + ".unwanted()"); |
| } |
| return; |
| } |
| |
| /* |
| * Since there's no agent it means we're in the process of setting up the NDP. |
| * However, it is possible that there were other equivalent requests for this NDP. We |
| * should keep going in that case. |
| */ |
| nnri.removeSupportForRequest(networkSpecifier); |
| if (nnri.equivalentSpecifiers.isEmpty()) { |
| if (VDBG) { |
| Log.v(TAG, "releaseNetworkFor: there are no further requests, networkRequest=" |
| + networkRequest); |
| } |
| if (nnri.ndpId != 0) { // 0 is never a valid ID! |
| if (VDBG) Log.v(TAG, "releaseNetworkFor: in progress NDP being terminated"); |
| mMgr.endDataPath(nnri.ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| } |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "releaseNetworkFor: equivalent requests exist - not terminating " |
| + "networkRequest=" + networkRequest); |
| } |
| } |
| } |
| } |
| |
| private class WifiAwareNetworkAgent extends NetworkAgent { |
| private NetworkInfo mNetworkInfo; |
| private AwareNetworkRequestInformation mAwareNetworkRequestInfo; |
| |
| WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, |
| NetworkCapabilities nc, LinkProperties lp, int score, |
| AwareNetworkRequestInformation anri) { |
| super(looper, context, logTag, ni, nc, lp, score); |
| |
| mNetworkInfo = ni; |
| mAwareNetworkRequestInfo = anri; |
| } |
| |
| @Override |
| protected void unwanted() { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkAgent.unwanted: request=" + mAwareNetworkRequestInfo); |
| } |
| |
| mMgr.endDataPath(mAwareNetworkRequestInfo.ndpId); |
| mAwareNetworkRequestInfo.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| |
| // Will get a callback (on both initiator and responder) when data-path actually |
| // terminated. At that point will inform the agent and will clear the cache. |
| } |
| |
| void reconfigureAgentAsDisconnected() { |
| if (VDBG) { |
| Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request=" |
| + mAwareNetworkRequestInfo); |
| } |
| |
| mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, ""); |
| sendNetworkInfo(mNetworkInfo); |
| } |
| } |
| |
| private void tearDownInterfaceIfPossible(AwareNetworkRequestInformation nnri) { |
| if (VDBG) Log.v(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri); |
| |
| if (!TextUtils.isEmpty(nnri.interfaceName)) { |
| boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri); |
| if (interfaceUsedByAnotherNdp) { |
| if (VDBG) { |
| Log.v(TAG, "tearDownInterfaceIfPossible: interfaceName=" + nnri.interfaceName |
| + ", still in use - not turning down"); |
| } |
| } else { |
| try { |
| mNwService.setInterfaceDown(nnri.interfaceName); |
| } catch (Exception e) { // NwService throws runtime exceptions for errors |
| Log.e(TAG, "tearDownInterfaceIfPossible: nnri=" + nnri |
| + ": can't bring interface down - " + e); |
| } |
| } |
| } |
| |
| if (nnri.networkAgent != null) { |
| nnri.networkAgent.reconfigureAgentAsDisconnected(); |
| } |
| } |
| |
| private boolean isInterfaceUpAndUsedByAnotherNdp(AwareNetworkRequestInformation nri) { |
| for (AwareNetworkRequestInformation lnri : mNetworkRequestsCache.values()) { |
| if (lnri == nri) { |
| continue; |
| } |
| |
| if (nri.interfaceName.equals(lnri.interfaceName) && ( |
| lnri.state == AwareNetworkRequestInformation.STATE_CONFIRMED |
| || lnri.state == AwareNetworkRequestInformation.STATE_TERMINATING)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Select one of the existing interfaces for the new network request. A request is canonical |
| * (otherwise it wouldn't be executed). |
| * |
| * Construct a list of all interfaces currently used to communicate to the peer. The remaining |
| * interfaces are available for use for this request - if none are left then the request should |
| * fail (signaled to the caller by returning a null). |
| */ |
| private String selectInterfaceForRequest(AwareNetworkRequestInformation req) { |
| SortedSet<String> potential = new TreeSet<>(mInterfaces); |
| Set<String> used = new HashSet<>(); |
| |
| if (VDBG) { |
| Log.v(TAG, "selectInterfaceForRequest: req=" + req + ", mNetworkRequestsCache=" |
| + mNetworkRequestsCache); |
| } |
| |
| for (AwareNetworkRequestInformation nnri : mNetworkRequestsCache.values()) { |
| if (nnri == req) { |
| continue; |
| } |
| |
| if (Arrays.equals(req.peerDiscoveryMac, nnri.peerDiscoveryMac)) { |
| used.add(nnri.interfaceName); |
| } |
| } |
| |
| if (VDBG) { |
| Log.v(TAG, "selectInterfaceForRequest: potential=" + potential + ", used=" + used); |
| } |
| |
| for (String ifName: potential) { |
| if (!used.contains(ifName)) { |
| return ifName; |
| } |
| } |
| |
| Log.e(TAG, "selectInterfaceForRequest: req=" + req + " - no interfaces available!"); |
| return null; |
| } |
| |
| /** |
| * Select a channel for the network request. |
| * |
| * TODO (b/38209409): The value from this function isn't currently used - the channel selection |
| * is delegated to the HAL. |
| */ |
| private int selectChannelForRequest(AwareNetworkRequestInformation req) { |
| return 2437; |
| } |
| |
| /** |
| * Aware network request. State object: contains network request information/state through its |
| * lifetime. |
| */ |
| @VisibleForTesting |
| public static class AwareNetworkRequestInformation { |
| static final int STATE_IDLE = 100; |
| static final int STATE_WAIT_FOR_CONFIRM = 101; |
| static final int STATE_CONFIRMED = 102; |
| static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 103; |
| static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 104; |
| static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 105; |
| static final int STATE_TERMINATING = 106; |
| |
| public int state; |
| |
| public int uid; |
| public String interfaceName; |
| public int pubSubId = 0; |
| public int peerInstanceId = 0; |
| public byte[] peerDiscoveryMac = null; |
| public int ndpId = 0; // 0 is never a valid ID! |
| public byte[] peerDataMac; |
| public WifiAwareNetworkSpecifier networkSpecifier; |
| public List<NanDataPathChannelInfo> channelInfo; |
| public long startTimestamp = 0; // request is made (initiator) / get request (responder) |
| |
| public WifiAwareNetworkAgent networkAgent; |
| |
| /* A collection of specifiers which are equivalent to the current request and are |
| * supported by it's agent. This list DOES include the original (first) network specifier |
| * (which is stored separately above). |
| */ |
| public Set<WifiAwareNetworkSpecifier> equivalentSpecifiers = new HashSet<>(); |
| |
| void updateToSupportNewRequest(WifiAwareNetworkSpecifier ns) { |
| if (VDBG) Log.v(TAG, "updateToSupportNewRequest: ns=" + ns); |
| if (equivalentSpecifiers.add(ns) && state == STATE_CONFIRMED) { |
| if (networkAgent == null) { |
| Log.wtf(TAG, "updateToSupportNewRequest: null agent in CONFIRMED state!?"); |
| return; |
| } |
| |
| networkAgent.sendNetworkCapabilities(getNetworkCapabilities()); |
| } |
| } |
| |
| void removeSupportForRequest(WifiAwareNetworkSpecifier ns) { |
| if (VDBG) Log.v(TAG, "removeSupportForRequest: ns=" + ns); |
| equivalentSpecifiers.remove(ns); |
| |
| // we will not update the agent: |
| // 1. this will only get called before the agent is created |
| // 2. connectivity service does not allow (WTF) updates with reduced capabilities |
| } |
| |
| private NetworkCapabilities getNetworkCapabilities() { |
| NetworkCapabilities nc = new NetworkCapabilities(sNetworkCapabilitiesFilter); |
| nc.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier(equivalentSpecifiers.toArray( |
| new WifiAwareNetworkSpecifier[equivalentSpecifiers.size()]))); |
| return nc; |
| } |
| |
| /** |
| * Returns a canonical descriptor for the network request. |
| */ |
| CanonicalConnectionInfo getCanonicalDescriptor() { |
| return new CanonicalConnectionInfo(peerDiscoveryMac, networkSpecifier.pmk, |
| networkSpecifier.sessionId, networkSpecifier.passphrase); |
| } |
| |
| static AwareNetworkRequestInformation processNetworkSpecifier(WifiAwareNetworkSpecifier ns, |
| WifiAwareStateManager mgr, WifiPermissionsUtil wifiPermissionsUtil, |
| WifiPermissionsWrapper permissionWrapper, |
| boolean allowNdpResponderFromAnyOverride) { |
| int uid, pubSubId = 0; |
| int peerInstanceId = 0; |
| byte[] peerMac = ns.peerMac; |
| |
| if (VDBG) { |
| Log.v(TAG, "processNetworkSpecifier: networkSpecifier=" + ns); |
| } |
| |
| // type: always valid |
| if (ns.type < 0 |
| || ns.type > WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_MAX_VALID) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + ", invalid 'type' value"); |
| return null; |
| } |
| |
| // role: always valid |
| if (ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR |
| && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- invalid 'role' value"); |
| return null; |
| } |
| |
| if (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR |
| && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB |
| && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- invalid 'type' value for INITIATOR (only IB and OOB are " |
| + "permitted)"); |
| return null; |
| } |
| |
| // look up network specifier information in Aware state manager |
| WifiAwareClientState client = mgr.getClient(ns.clientId); |
| if (client == null) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- not client with this id -- clientId=" + ns.clientId); |
| return null; |
| } |
| uid = client.getUid(); |
| |
| // API change post 27: no longer allow "ANY"-style responders (initiators were never |
| // permitted). |
| // Note: checks are done on the manager. This is a backup for apps which bypass the |
| // check. |
| if (!allowNdpResponderFromAnyOverride && !wifiPermissionsUtil.isLegacyVersion( |
| client.getCallingPackage(), Build.VERSION_CODES.P)) { |
| if (ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB |
| && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- no ANY specifications allowed for this API level"); |
| return null; |
| } |
| } |
| |
| // validate the role (if session ID provided: i.e. session 1xx) |
| if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB |
| || ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER) { |
| WifiAwareDiscoverySessionState session = client.getSession(ns.sessionId); |
| if (session == null) { |
| Log.e(TAG, |
| "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- no session with this id -- sessionId=" + ns.sessionId); |
| return null; |
| } |
| |
| if ((session.isPublishSession() |
| && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) || ( |
| !session.isPublishSession() && ns.role |
| != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- invalid role for session type"); |
| return null; |
| } |
| |
| if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB) { |
| pubSubId = session.getPubSubId(); |
| WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo( |
| ns.peerId); |
| if (peerInfo == null) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- no peer info associated with this peer id -- peerId=" |
| + ns.peerId); |
| return null; |
| } |
| peerInstanceId = peerInfo.mInstanceId; |
| try { |
| peerMac = peerInfo.mMac; |
| if (peerMac == null || peerMac.length != 6) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" |
| + ns + " -- invalid peer MAC address"); |
| return null; |
| } |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- invalid peer MAC address -- e=" + e); |
| return null; |
| } |
| } |
| } |
| |
| // validate UID |
| if (ns.requestorUid != uid) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() |
| + " -- UID mismatch to clientId's uid=" + uid); |
| return null; |
| } |
| |
| // validate permission if PMK is used (SystemApi) |
| if (ns.pmk != null && ns.pmk.length != 0) { |
| if (permissionWrapper.getUidPermission(Manifest.permission.CONNECTIVITY_INTERNAL, |
| ns.requestorUid) != PackageManager.PERMISSION_GRANTED) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() |
| + " -- UID doesn't have permission to use PMK API"); |
| return null; |
| } |
| } |
| |
| // validate passphrase & PMK (if provided) |
| if (!TextUtils.isEmpty(ns.passphrase)) { // non-null indicates usage |
| if (!WifiAwareUtils.validatePassphrase(ns.passphrase)) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() |
| + " -- invalid passphrase length: " + ns.passphrase.length()); |
| return null; |
| } |
| } |
| if (ns.pmk != null && !WifiAwareUtils.validatePmk(ns.pmk)) { // non-null indicates usage |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() |
| + " -- invalid pmk length: " + ns.pmk.length); |
| return null; |
| } |
| |
| // create container and populate |
| AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation(); |
| nnri.state = AwareNetworkRequestInformation.STATE_IDLE; |
| nnri.uid = uid; |
| nnri.pubSubId = pubSubId; |
| nnri.peerInstanceId = peerInstanceId; |
| nnri.peerDiscoveryMac = peerMac; |
| nnri.networkSpecifier = ns; |
| nnri.equivalentSpecifiers.add(ns); |
| |
| return nnri; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder("AwareNetworkRequestInformation: "); |
| sb.append("state=").append(state).append(", ns=").append(networkSpecifier).append( |
| ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append( |
| ", pubSubId=").append(pubSubId).append(", peerInstanceId=").append( |
| peerInstanceId).append(", peerDiscoveryMac=").append( |
| peerDiscoveryMac == null ? "" |
| : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append( |
| ", ndpId=").append(ndpId).append(", peerDataMac=").append( |
| peerDataMac == null ? "" |
| : String.valueOf(HexEncoding.encode(peerDataMac))).append( |
| ", startTimestamp=").append(startTimestamp).append(", channelInfo=").append( |
| channelInfo).append(", equivalentSpecifiers=["); |
| for (WifiAwareNetworkSpecifier ns: equivalentSpecifiers) { |
| sb.append(ns.toString()).append(", "); |
| } |
| return sb.append("]").toString(); |
| } |
| } |
| |
| /** |
| * A canonical (unique) descriptor of the peer connection. |
| */ |
| static class CanonicalConnectionInfo { |
| CanonicalConnectionInfo(byte[] peerDiscoveryMac, byte[] pmk, int sessionId, |
| String passphrase) { |
| this.peerDiscoveryMac = peerDiscoveryMac; |
| this.pmk = pmk; |
| this.sessionId = sessionId; |
| this.passphrase = passphrase; |
| } |
| |
| public final byte[] peerDiscoveryMac; |
| |
| /* |
| * Security configuration matching: |
| * - open: pmk/passphrase = null |
| * - pmk: pmk != null, passphrase = null |
| * - passphrase: passphrase != null, sessionId used (==0 for OOB), pmk=null |
| */ |
| public final byte[] pmk; |
| |
| public final int sessionId; |
| public final String passphrase; |
| |
| public boolean matches(CanonicalConnectionInfo other) { |
| return (other.peerDiscoveryMac == null || Arrays |
| .equals(peerDiscoveryMac, other.peerDiscoveryMac)) |
| && Arrays.equals(pmk, other.pmk) |
| && TextUtils.equals(passphrase, other.passphrase) |
| && (TextUtils.isEmpty(passphrase) || sessionId == other.sessionId); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder("CanonicalConnectionInfo: ["); |
| sb.append("peerDiscoveryMac=").append(peerDiscoveryMac == null ? "" |
| : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).append(", pmk=").append( |
| pmk == null ? "" : "*").append(", sessionId=").append(sessionId).append( |
| ", passphrase=").append(passphrase == null ? "" : "*").append("]"); |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Enables mocking. |
| */ |
| @VisibleForTesting |
| public class NetworkInterfaceWrapper { |
| /** |
| * Configures network agent properties: link-local address, connected status, interface |
| * name. Delegated to enable mocking. |
| */ |
| public boolean configureAgentProperties(AwareNetworkRequestInformation nnri, |
| Set<WifiAwareNetworkSpecifier> networkSpecifiers, int ndpId, |
| NetworkInfo networkInfo, NetworkCapabilities networkCapabilities, |
| LinkProperties linkProperties) { |
| // find link-local address |
| InetAddress linkLocal = null; |
| NetworkInterface ni; |
| try { |
| ni = NetworkInterface.getByName(nnri.interfaceName); |
| } catch (SocketException e) { |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't get network interface - " + e); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| return false; |
| } |
| if (ni == null) { |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't get network interface (null)"); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| return false; |
| } |
| Enumeration<InetAddress> addresses = ni.getInetAddresses(); |
| while (addresses.hasMoreElements()) { |
| InetAddress ip = addresses.nextElement(); |
| if (ip instanceof Inet6Address && ip.isLinkLocalAddress()) { |
| linkLocal = ip; |
| break; |
| } |
| } |
| |
| if (linkLocal == null) { |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses"); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| return false; |
| } |
| |
| // configure agent |
| networkInfo.setIsAvailable(true); |
| networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); |
| |
| networkCapabilities.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( |
| networkSpecifiers.toArray(new WifiAwareNetworkSpecifier[0]))); |
| |
| linkProperties.setInterfaceName(nnri.interfaceName); |
| linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64)); |
| linkProperties.addRoute( |
| new RouteInfo(new IpPrefix("fe80::/64"), null, nnri.interfaceName)); |
| |
| return true; |
| } |
| } |
| |
| /** |
| * Dump the internal state of the class. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("WifiAwareDataPathStateManager:"); |
| pw.println(" mInterfaces: " + mInterfaces); |
| pw.println(" sNetworkCapabilitiesFilter: " + sNetworkCapabilitiesFilter); |
| pw.println(" mNetworkRequestsCache: " + mNetworkRequestsCache); |
| pw.println(" mNetworkFactory:"); |
| mNetworkFactory.dump(fd, pw, args); |
| } |
| } |