| /* |
| * 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 static android.net.RouteInfo.RTN_UNICAST; |
| |
| import android.content.Context; |
| 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.NetworkAgentConfig; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkProvider; |
| import android.net.NetworkRequest; |
| import android.net.NetworkSpecifier; |
| import android.net.RouteInfo; |
| import android.net.wifi.aware.TlvBufferUtils; |
| import android.net.wifi.aware.WifiAwareAgentNetworkSpecifier; |
| import android.net.wifi.aware.WifiAwareManager; |
| import android.net.wifi.aware.WifiAwareNetworkInfo; |
| import android.net.wifi.aware.WifiAwareNetworkSpecifier; |
| import android.net.wifi.aware.WifiAwareUtils; |
| import android.net.wifi.util.HexEncoding; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.wifi.Clock; |
| import com.android.server.wifi.util.NetdWrapper; |
| import com.android.server.wifi.util.WifiPermissionsUtil; |
| import com.android.server.wifi.util.WifiPermissionsWrapper; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.net.DatagramSocket; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.NetworkInterface; |
| import java.net.SocketException; |
| import java.net.UnknownHostException; |
| import java.nio.ByteOrder; |
| 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 |
| private 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; |
| |
| @VisibleForTesting |
| public static final int ADDRESS_VALIDATION_RETRY_INTERVAL_MS = 1_000; // 1 second |
| @VisibleForTesting |
| public static final int ADDRESS_VALIDATION_TIMEOUT_MS = 5_000; // 5 seconds |
| |
| private final WifiAwareStateManager mMgr; |
| private final Clock mClock; |
| public NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper(); |
| private static final NetworkCapabilities sNetworkCapabilitiesFilter = |
| makeNetworkCapabilitiesFilter(); |
| 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 Handler mHandler; |
| private WifiAwareNetworkFactory mNetworkFactory; |
| public NetdWrapper mNetdWrapper; |
| |
| // internal debug flag to override API check |
| /* package */ boolean mAllowNdpResponderFromAnyOverride = false; |
| |
| public WifiAwareDataPathStateManager(WifiAwareStateManager mgr, Clock clock) { |
| mMgr = mgr; |
| mClock = clock; |
| } |
| |
| private static NetworkCapabilities makeNetworkCapabilitiesFilter() { |
| return new NetworkCapabilities.Builder() |
| .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) |
| .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) |
| .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) |
| .setNetworkSpecifier(new MatchAllNetworkSpecifier()) |
| .setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL) |
| .setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL) |
| .setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL) |
| .build(); |
| } |
| |
| /** |
| * 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, |
| NetdWrapper netdWrapper) { |
| if (VDBG) Log.v(TAG, "start"); |
| |
| mContext = context; |
| mAwareMetrics = awareMetrics; |
| mWifiPermissionsUtil = wifiPermissionsUtil; |
| mPermissionsWrapper = permissionsWrapper; |
| mNetdWrapper = netdWrapper; |
| mLooper = looper; |
| mHandler = new Handler(mLooper); |
| |
| |
| mNetworkFactory = new WifiAwareNetworkFactory(looper, context, sNetworkCapabilitiesFilter); |
| mNetworkFactory.setScoreFilter(NETWORK_FACTORY_SCORE_AVAIL); |
| mNetworkFactory.register(); |
| } |
| |
| /** |
| * Enable verbose logging. |
| */ |
| public void enableVerboseLogging(boolean verbose) { |
| mDbg = verbose | VDBG; |
| } |
| |
| 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 (mDbg) 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 (mDbg) 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 (mDbg) 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 (mDbg) 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 (mDbg) { |
| 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); |
| declareUnfullfillableAndEndDp(nnri, 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 (mDbg) { |
| 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; |
| } |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| |
| 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. |
| * @param message The app_info HAL field (peer's info: binary blob) |
| * @return The network specifier of the data-path (or null if none/error) |
| */ |
| public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId, |
| byte[] message) { |
| if (mDbg) { |
| 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; |
| } |
| |
| // it is also possible that this is an initiator-side data-path request indication (which |
| // happens when the Responder responds). In such a case it will be matched by the NDP ID. |
| Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE = |
| getNetworkRequestByNdpId(ndpId); |
| if (nnriE != null) { |
| if (VDBG) { |
| Log.v(TAG, |
| "onDataPathRequest: initiator-side indication for " + nnriE.getValue()); |
| } |
| |
| // potential transmission mechanism for port/transport-protocol information from |
| // Responder (alternative to confirm message) |
| NetworkInformationData.ParsedResults peerServerInfo = NetworkInformationData.parseTlv( |
| message); |
| if (peerServerInfo != null) { |
| if (peerServerInfo.port != 0) { |
| nnriE.getValue().peerPort = peerServerInfo.port; |
| } |
| if (peerServerInfo.transportProtocol != -1) { |
| nnriE.getValue().peerTransportProtocol = peerServerInfo.transportProtocol; |
| } |
| if (peerServerInfo.ipv6Override != null) { |
| nnriE.getValue().peerIpv6Override = peerServerInfo.ipv6Override; |
| } |
| } |
| |
| return null; // ignore this for NDP set up flow: it is used to obtain app_info from Resp |
| } |
| |
| 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, 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, null, false); |
| mNetworkRequestsCache.remove(networkSpecifier); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| return null; |
| } |
| |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE; |
| nnri.ndpId = ndpId; |
| nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); |
| mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk, |
| nnri.networkSpecifier.passphrase, |
| NetworkInformationData.buildTlv(nnri.networkSpecifier.port, |
| nnri.networkSpecifier.transportProtocol), |
| 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 (mDbg) { |
| 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); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| 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); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| 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 (mDbg) { |
| Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf( |
| HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason |
| + ", message.length=" + ((message == null) ? 0 : message.length) |
| + ", 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); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| if (accept) { |
| mMgr.endDataPath(ndpId); |
| } |
| return networkSpecifier; |
| } |
| |
| if (accept) { |
| nnri.state = AwareNetworkRequestInformation.STATE_CONFIRMED; |
| nnri.peerDataMac = mac; |
| nnri.channelInfo = channelInfo; |
| |
| if (!isInterfaceUpAndUsedByAnotherNdp(nnri)) { |
| try { |
| mNetdWrapper.setInterfaceUp(nnri.interfaceName); |
| mNetdWrapper.enableIpv6(nnri.interfaceName); |
| } catch (Exception e) { // NwService throws runtime exceptions for errors |
| Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't configure network - " |
| + e); |
| declareUnfullfillableAndEndDp(nnri, ndpId); |
| return networkSpecifier; |
| } |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: interface already configured: " |
| + nnri.interfaceName); |
| } |
| } |
| |
| // only relevant for the initiator |
| if (nnri.networkSpecifier.role |
| == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { |
| NetworkInformationData.ParsedResults peerServerInfo = |
| NetworkInformationData.parseTlv(message); |
| if (peerServerInfo != null) { |
| if (peerServerInfo.port != 0) { |
| nnri.peerPort = peerServerInfo.port; |
| } |
| if (peerServerInfo.transportProtocol != -1) { |
| nnri.peerTransportProtocol = peerServerInfo.transportProtocol; |
| } |
| if (peerServerInfo.ipv6Override != null) { |
| nnri.peerIpv6Override = peerServerInfo.ipv6Override; |
| } |
| } |
| } |
| |
| nnri.startValidationTimestamp = mClock.getElapsedSinceBootMillis(); |
| handleAddressValidation(nnri, ndpId, networkSpecifier.isOutOfBand(), mac); |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier |
| + " rejected - reason=" + reason); |
| } |
| mNetworkRequestsCache.remove(networkSpecifier); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| mAwareMetrics.recordNdpStatus(reason, networkSpecifier.isOutOfBand(), |
| nnri.startTimestamp); |
| } |
| |
| return networkSpecifier; |
| } |
| |
| private void getInet6Address(AwareNetworkRequestInformation nnri, byte[] mac) { |
| try { |
| byte[] addr; |
| if (nnri.peerIpv6Override == null) { |
| addr = MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress(); |
| } else { |
| addr = new byte[16]; |
| addr[0] = (byte) 0xfe; |
| addr[1] = (byte) 0x80; |
| addr[8] = nnri.peerIpv6Override[0]; |
| addr[9] = nnri.peerIpv6Override[1]; |
| addr[10] = nnri.peerIpv6Override[2]; |
| addr[11] = nnri.peerIpv6Override[3]; |
| addr[12] = nnri.peerIpv6Override[4]; |
| addr[13] = nnri.peerIpv6Override[5]; |
| addr[14] = nnri.peerIpv6Override[6]; |
| addr[15] = nnri.peerIpv6Override[7]; |
| } |
| nnri.peerIpv6 = Inet6Address.getByAddress(null, addr, |
| NetworkInterface.getByName(nnri.interfaceName)); |
| } catch (SocketException | UnknownHostException e) { |
| if (mDbg) { |
| Log.d(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e); |
| } |
| nnri.peerIpv6 = null; |
| } |
| } |
| |
| private void handleAddressValidation(AwareNetworkRequestInformation nnri, int ndpId, |
| boolean isOutOfBand, byte[] mac) { |
| final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder( |
| sNetworkCapabilitiesFilter); |
| LinkProperties linkProperties = new LinkProperties(); |
| getInet6Address(nnri, mac); |
| if (!(nnri.peerIpv6 != null && mNiWrapper.configureAgentProperties(nnri, |
| nnri.equivalentRequests, ndpId, ncBuilder, linkProperties) |
| && mNiWrapper.isAddressUsable(linkProperties))) { |
| if (VDBG) { |
| Log.d(TAG, "Failed address validation"); |
| } |
| if (!isAddressValidationExpired(nnri, ndpId)) { |
| mHandler.postDelayed(() -> { |
| handleAddressValidation(nnri, ndpId, isOutOfBand, mac); |
| }, ADDRESS_VALIDATION_RETRY_INTERVAL_MS); |
| } |
| return; |
| } |
| final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo( |
| nnri.peerIpv6, nnri.peerPort, nnri.peerTransportProtocol); |
| ncBuilder.setTransportInfo(ni); |
| if (VDBG) { |
| Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni); |
| } |
| final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder() |
| .setLegacyType(ConnectivityManager.TYPE_NONE) |
| .setLegacyTypeName(NETWORK_TAG) |
| .build(); |
| nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext, |
| AGENT_TAG_PREFIX + nnri.ndpId, ncBuilder.build(), linkProperties, |
| NETWORK_FACTORY_SCORE_AVAIL, naConfig, mNetworkFactory.getProvider(), nnri); |
| mNiWrapper.setConnected(nnri.networkAgent); |
| mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, nnri.startTimestamp); |
| nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); // update time-stamp |
| mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache); |
| } |
| |
| private boolean isAddressValidationExpired(AwareNetworkRequestInformation nnri, int ndpId) { |
| if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp |
| > ADDRESS_VALIDATION_TIMEOUT_MS) { |
| Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable"); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| declareUnfullfillableAndEndDp(nnri, ndpId); |
| return true; |
| } |
| return false; |
| } |
| |
| private void declareUnfullfillableAndEndDp(AwareNetworkRequestInformation nnri, int ndpId) { |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| mMgr.endDataPath(ndpId); |
| nnri.state = AwareNetworkRequestInformation.STATE_TERMINATING; |
| } |
| |
| /** |
| * 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 (mDbg) 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 (mDbg) { |
| 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 (mDbg) 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 (VDBG) { |
| Log.v(TAG, |
| "handleDataPathTimeout: network request not found for networkSpecifier=" |
| + networkSpecifier); |
| } |
| return; |
| } |
| mAwareMetrics.recordNdpStatus(NanStatusType.INTERNAL_FAILURE, |
| nnri.networkSpecifier.isOutOfBand(), nnri.startTimestamp); |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| |
| 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.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(request, networkSpecifier, |
| mMgr, mWifiPermissionsUtil, mPermissionsWrapper, |
| mAllowNdpResponderFromAnyOverride); |
| if (nnri == null) { |
| Log.e(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request |
| + " - can't parse network specifier"); |
| releaseRequestAsUnfulfillableByAnyFactory(request); |
| 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(request); |
| } |
| return false; |
| } |
| |
| mNetworkRequestsCache.put(networkSpecifier, nnri); |
| |
| return true; |
| } |
| |
| @Override |
| protected void needNetworkFor(NetworkRequest networkRequest, int score) { |
| if (mDbg) { |
| Log.v(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest=" |
| + networkRequest + ", score=" + score); |
| } |
| |
| NetworkSpecifier networkSpecifierObj = networkRequest.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); |
| letAppKnowThatRequestsAreUnavailable(nnri); |
| 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(), |
| null); |
| nnri.state = |
| AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE; |
| nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); |
| } else { |
| nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST; |
| } |
| } |
| |
| @Override |
| protected void releaseNetworkFor(NetworkRequest networkRequest) { |
| if (mDbg) { |
| Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest=" |
| + networkRequest); |
| } |
| |
| NetworkSpecifier networkSpecifierObj = networkRequest.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(networkRequest); |
| if (nnri.equivalentRequests.isEmpty()) { |
| if (mDbg) { |
| 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 { |
| mNetworkRequestsCache.remove(networkSpecifier); |
| if (nnri.networkAgent != null) { |
| letAppKnowThatRequestsAreUnavailable(nnri); |
| } |
| } |
| } else { |
| if (VDBG) { |
| Log.v(TAG, "releaseNetworkFor: equivalent requests exist - not terminating " |
| + "networkRequest=" + networkRequest); |
| } |
| } |
| } |
| |
| void letAppKnowThatRequestsAreUnavailable(AwareNetworkRequestInformation nnri) { |
| for (NetworkRequest nr : nnri.equivalentRequests) { |
| releaseRequestAsUnfulfillableByAnyFactory(nr); |
| } |
| } |
| } |
| |
| /** |
| * Network agent for Wi-Fi Aware. |
| */ |
| @VisibleForTesting |
| public class WifiAwareNetworkAgent extends NetworkAgent { |
| private AwareNetworkRequestInformation mAwareNetworkRequestInfo; |
| |
| WifiAwareNetworkAgent(Looper looper, Context context, String logTag, |
| NetworkCapabilities nc, LinkProperties lp, int score, |
| NetworkAgentConfig config, NetworkProvider provider, |
| AwareNetworkRequestInformation anri) { |
| super(context, looper, logTag, nc, lp, score, config, provider); |
| mAwareNetworkRequestInfo = anri; |
| register(); |
| } |
| |
| @Override |
| public void onNetworkUnwanted() { |
| if (mDbg) { |
| 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 (mDbg) { |
| Log.v(TAG, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: request=" |
| + mAwareNetworkRequestInfo); |
| } |
| unregister(); |
| } |
| } |
| |
| 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 (mDbg) { |
| Log.v(TAG, "tearDownInterfaceIfPossible: interfaceName=" + nnri.interfaceName |
| + ", still in use - not turning down"); |
| } |
| } else { |
| try { |
| mNetdWrapper.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) { |
| mNetworkFactory.letAppKnowThatRequestsAreUnavailable(nnri); |
| } else { |
| 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 (mDbg) { |
| 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 packageName; |
| 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 Inet6Address peerIpv6; |
| public int peerPort = 0; // uninitialized (invalid) value |
| public int peerTransportProtocol = -1; // uninitialized (invalid) value |
| public byte[] peerIpv6Override = null; |
| public WifiAwareNetworkSpecifier networkSpecifier; |
| public List<NanDataPathChannelInfo> channelInfo; |
| public long startTimestamp = 0; // request is made (initiator) / get request (responder) |
| public long startValidationTimestamp = 0; // NDP created and starting to validate IPv6 addr |
| |
| public WifiAwareNetworkAgent networkAgent; |
| |
| /* A collection of request which are equivalent to the current request and are |
| * supported by it's agent. This list DOES include the original (first) network request |
| * (whose specifier is also stored separately above). |
| */ |
| public Set<NetworkRequest> equivalentRequests = new HashSet<>(); |
| |
| void updateToSupportNewRequest(NetworkRequest ns) { |
| if (VDBG) Log.v(TAG, "updateToSupportNewRequest: ns=" + ns); |
| if (equivalentRequests.add(ns) && state == STATE_CONFIRMED) { |
| if (networkAgent == null) { |
| Log.wtf(TAG, "updateToSupportNewRequest: null agent in CONFIRMED state!?"); |
| return; |
| } |
| |
| networkAgent.sendNetworkCapabilities(getNetworkCapabilities()); |
| } |
| } |
| |
| void removeSupportForRequest(NetworkRequest ns) { |
| if (VDBG) Log.v(TAG, "removeSupportForRequest: ns=" + ns); |
| equivalentRequests.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() { |
| final NetworkCapabilities.Builder builder = |
| new NetworkCapabilities.Builder(sNetworkCapabilitiesFilter); |
| builder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( |
| equivalentRequests.stream() |
| .map(NetworkRequest::getNetworkSpecifier) |
| .toArray(WifiAwareNetworkSpecifier[]::new))); |
| if (peerIpv6 != null) { |
| builder.setTransportInfo( |
| new WifiAwareNetworkInfo(peerIpv6, peerPort, peerTransportProtocol)); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Returns a canonical descriptor for the network request. |
| */ |
| CanonicalConnectionInfo getCanonicalDescriptor() { |
| return new CanonicalConnectionInfo(peerDiscoveryMac, networkSpecifier.pmk, |
| networkSpecifier.sessionId, networkSpecifier.passphrase); |
| } |
| |
| static AwareNetworkRequestInformation processNetworkSpecifier(NetworkRequest request, |
| WifiAwareNetworkSpecifier ns, WifiAwareStateManager mgr, |
| WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionWrapper, |
| boolean allowNdpResponderFromAnyOverride) { |
| int uid, pubSubId = 0; |
| int peerInstanceId = 0; |
| String packageName = null; |
| 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(); |
| packageName = client.getCallingPackage(); |
| |
| // 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.isTargetSdkLessThan( |
| client.getCallingPackage(), Build.VERSION_CODES.P, uid)) { |
| 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 port & transportProtocol |
| if (ns.port < 0 || ns.transportProtocol < -1) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- invalid port/transportProtocol"); |
| return null; |
| } |
| if (ns.port != 0 || ns.transportProtocol != -1) { |
| if (ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- port/transportProtocol can only be specified on responder"); |
| return null; |
| } |
| if (TextUtils.isEmpty(ns.passphrase) && ns.pmk == null) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns |
| + " -- port/transportProtocol can only be specified on secure ndp"); |
| 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 && package name |
| if (request.getRequestorUid() != uid |
| || !TextUtils.equals(request.getRequestorPackageName(), packageName)) { |
| Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns.toString() |
| + " -- UID or package name mismatch to clientId's uid=" + uid |
| + ", packageName=" + packageName); |
| 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.packageName = packageName; |
| nnri.pubSubId = pubSubId; |
| nnri.peerInstanceId = peerInstanceId; |
| nnri.peerDiscoveryMac = peerMac; |
| nnri.networkSpecifier = ns; |
| nnri.equivalentRequests.add(request); |
| |
| 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(", packageName=").append(packageName) |
| .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(", peerIpv6=").append(peerIpv6).append(", peerPort=").append( |
| peerPort).append(", peerTransportProtocol=").append( |
| peerTransportProtocol).append(", startTimestamp=").append( |
| startTimestamp).append(", channelInfo=").append( |
| channelInfo).append(", equivalentSpecifiers=["); |
| for (NetworkRequest nr : equivalentRequests) { |
| sb.append(nr.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<NetworkRequest> networkRequests, int ndpId, |
| NetworkCapabilities.Builder ncBuilder, LinkProperties linkProperties) { |
| // find link-local address |
| InetAddress linkLocal = null; |
| NetworkInterface ni; |
| try { |
| ni = NetworkInterface.getByName(nnri.interfaceName); |
| } catch (SocketException e) { |
| Log.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't get network interface - " + e); |
| return false; |
| } |
| if (ni == null) { |
| Log.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri |
| + ": can't get network interface (null)"); |
| 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.v(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": no link local addresses"); |
| return false; |
| } |
| |
| ncBuilder.setRequestorUid(nnri.uid); |
| ncBuilder.setRequestorPackageName(nnri.packageName); |
| ncBuilder.setNetworkSpecifier(new WifiAwareAgentNetworkSpecifier( |
| networkRequests.stream() |
| .map(NetworkRequest::getNetworkSpecifier) |
| .toArray(WifiAwareNetworkSpecifier[]::new))); |
| |
| linkProperties.setInterfaceName(nnri.interfaceName); |
| linkProperties.addLinkAddress(new LinkAddress(linkLocal, 64)); |
| linkProperties.addRoute( |
| new RouteInfo(new IpPrefix("fe80::/64"), null, nnri.interfaceName, |
| RTN_UNICAST)); |
| |
| return true; |
| } |
| |
| /** |
| * Tries binding to the input address to check whether it is configured (and therefore |
| * usable). |
| */ |
| public boolean isAddressUsable(LinkProperties linkProperties) { |
| InetAddress address = linkProperties.getLinkAddresses().get(0).getAddress(); |
| DatagramSocket testDatagramSocket = null; |
| try { |
| testDatagramSocket = new DatagramSocket(0, address); |
| } catch (SocketException e) { |
| if (mDbg) { |
| Log.v(TAG, "Can't create socket on address " + address + " -- " + e); |
| } |
| return false; |
| } finally { |
| if (testDatagramSocket != null) { |
| testDatagramSocket.close(); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Tell the network agent the network is now connected. |
| */ |
| public void setConnected(WifiAwareNetworkAgent networkAgent) { |
| networkAgent.markConnected(); |
| } |
| } |
| |
| /** |
| * Utility (hence static) class encapsulating the data structure used to communicate Wi-Fi Aware |
| * specific network capabilities. The TLV is defined as part of the NANv3 spec: |
| * |
| * - Generic Service Protocol |
| * - Port |
| * - Transport protocol |
| */ |
| @VisibleForTesting |
| public static class NetworkInformationData { |
| // All package visible to allow usage in unit testing |
| /* package */ static final int IPV6_LL_TYPE = 0x00; // Table 82 |
| /* package */ static final int SERVICE_INFO_TYPE = 0x01; // Table 83 |
| /* package */ static final byte[] WFA_OUI = {0x50, 0x6F, (byte) 0x9A}; // Table 83 |
| /* package */ static final int GENERIC_SERVICE_PROTOCOL_TYPE = 0x02; // Table 50 |
| /* package */ static final int SUB_TYPE_PORT = 0x00; // Table 127 |
| /* package */ static final int SUB_TYPE_TRANSPORT_PROTOCOL = 0x01; // Table 128 |
| |
| /** |
| * Construct the TLV. |
| */ |
| public static byte[] buildTlv(int port, int transportProtocol) { |
| if (port == 0 && transportProtocol == -1) { |
| return null; |
| } |
| |
| TlvBufferUtils.TlvConstructor tlvc = new TlvBufferUtils.TlvConstructor(1, 2); |
| tlvc.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| tlvc.allocate(20); // safe size for now |
| |
| tlvc.putRawByteArray(WFA_OUI); |
| tlvc.putRawByte((byte) GENERIC_SERVICE_PROTOCOL_TYPE); |
| |
| if (port != 0) { |
| tlvc.putShort(SUB_TYPE_PORT, (short) port); |
| } |
| if (transportProtocol != -1) { |
| tlvc.putByte(SUB_TYPE_TRANSPORT_PROTOCOL, (byte) transportProtocol); |
| } |
| |
| byte[] subTypes = tlvc.getArray(); |
| |
| tlvc.allocate(20); |
| tlvc.putByteArray(SERVICE_INFO_TYPE, subTypes); |
| |
| return tlvc.getArray(); |
| } |
| |
| static class ParsedResults { |
| ParsedResults(int port, int transportProtocol, byte[] ipv6Override) { |
| this.port = port; |
| this.transportProtocol = transportProtocol; |
| this.ipv6Override = ipv6Override; |
| } |
| |
| public int port = 0; |
| public int transportProtocol = -1; |
| public byte[] ipv6Override = null; |
| } |
| |
| /** |
| * Parse the TLV and returns: |
| * - Null on parsing error |
| * - <port | 0, transport-protocol | -1, ipv6-override | null> otherwise |
| */ |
| public static ParsedResults parseTlv(byte[] tlvs) { |
| int port = 0; |
| int transportProtocol = -1; |
| byte[] ipv6Override = null; |
| |
| try { |
| TlvBufferUtils.TlvIterable tlvi = new TlvBufferUtils.TlvIterable(1, 2, tlvs); |
| tlvi.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| for (TlvBufferUtils.TlvElement tlve : tlvi) { |
| switch (tlve.type) { |
| case IPV6_LL_TYPE: |
| if (tlve.length != 8) { // 8 bytes in IPv6 address |
| Log.e(TAG, "NetworkInformationData: invalid IPv6 TLV -- length: " |
| + tlve.length); |
| return null; |
| } |
| ipv6Override = tlve.getRawData(); |
| break; |
| case SERVICE_INFO_TYPE: |
| Pair<Integer, Integer> serviceInfo = parseServiceInfoTlv( |
| tlve.getRawData()); |
| if (serviceInfo == null) { |
| return null; |
| } |
| port = serviceInfo.first; |
| transportProtocol = serviceInfo.second; |
| break; |
| default: |
| Log.w(TAG, |
| "NetworkInformationData: ignoring unknown T -- " + tlve.type); |
| break; |
| } |
| } |
| } catch (Exception e) { |
| Log.e(TAG, "NetworkInformationData: error parsing TLV -- " + e); |
| return null; |
| } |
| return new ParsedResults(port, transportProtocol, ipv6Override); |
| } |
| |
| /** |
| * Parse the Service Info TLV: |
| * - Returns null on error |
| * - Returns <port | 0, transport-protocol | -1> otherwise |
| */ |
| private static Pair<Integer, Integer> parseServiceInfoTlv(byte[] tlv) { |
| int port = 0; |
| int transportProtocol = -1; |
| |
| if (tlv.length < 4) { |
| Log.e(TAG, "NetworkInformationData: invalid SERVICE_INFO_TYPE length"); |
| return null; |
| } |
| if (tlv[0] != WFA_OUI[0] || tlv[1] != WFA_OUI[1] || tlv[2] != WFA_OUI[2]) { |
| Log.e(TAG, "NetworkInformationData: unexpected OUI"); |
| return null; |
| } |
| if (tlv[3] != GENERIC_SERVICE_PROTOCOL_TYPE) { |
| Log.e(TAG, "NetworkInformationData: invalid type -- " + tlv[3]); |
| return null; |
| } |
| TlvBufferUtils.TlvIterable subTlvi = new TlvBufferUtils.TlvIterable(1, |
| 2, Arrays.copyOfRange(tlv, 4, tlv.length)); |
| subTlvi.setByteOrder(ByteOrder.LITTLE_ENDIAN); |
| for (TlvBufferUtils.TlvElement subTlve : subTlvi) { |
| switch (subTlve.type) { |
| case SUB_TYPE_PORT: |
| if (subTlve.length != 2) { |
| Log.e(TAG, |
| "NetworkInformationData: invalid port TLV " |
| + "length -- " + subTlve.length); |
| return null; |
| } |
| port = subTlve.getShort(); |
| if (port < 0) { |
| port += -2 * (int) Short.MIN_VALUE; |
| } |
| if (port == 0) { |
| Log.e(TAG, "NetworkInformationData: invalid port " |
| + port); |
| return null; |
| } |
| break; |
| case SUB_TYPE_TRANSPORT_PROTOCOL: |
| if (subTlve.length != 1) { |
| Log.e(TAG, "NetworkInformationData: invalid transport " |
| + "protocol TLV length -- " + subTlve.length); |
| return null; |
| } |
| transportProtocol = subTlve.getByte(); |
| if (transportProtocol < 0) { |
| transportProtocol += -2 * (int) Byte.MIN_VALUE; |
| } |
| break; |
| default: |
| Log.w(TAG, "NetworkInformationData: ignoring unknown " |
| + "SERVICE_INFO.T -- " + subTlve.type); |
| break; |
| } |
| } |
| return Pair.create(port, transportProtocol); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |