content: /*
   * 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.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.os.Build;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.INetworkManagementService;
  import android.os.Looper;
  import android.os.ServiceManager;
  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.WifiPermissionsUtil;
  import com.android.server.wifi.util.WifiPermissionsWrapper;
  
  import libcore.util.HexEncoding;
  
  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;
  import java.util.stream.Collectors;
  
  /**
   * 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;
  
      @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 = 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 Handler mHandler;
      private WifiAwareNetworkFactory mNetworkFactory;
      public INetworkManagementService mNwService;
  
      // internal debug flag to override API check
      /* package */ boolean mAllowNdpResponderFromAnyOverride = false;
  
      public WifiAwareDataPathStateManager(WifiAwareStateManager mgr, Clock clock) {
          mMgr = mgr;
          mClock = clock;
      }
  
      /**
       * 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;
          mHandler = new Handler(mLooper);
  
          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);
              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 (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;
          }
          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 (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;
          }
  
          // 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 (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);
              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 (VDBG) {
              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;
  
              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);
                      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;
                      }
                  }
              }
  
              try {
                  if (nnri.peerIpv6Override == null) {
                      nnri.peerIpv6 = Inet6Address.getByAddress(null,
                              MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress(),
                              NetworkInterface.getByName(nnri.interfaceName));
                  } else {
                      byte[] 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) {
                  Log.e(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e);
                  nnri.peerIpv6 = null;
              }
  
              if (nnri.peerIpv6 != null) {
                  networkCapabilities.setTransportInfo(
                          new WifiAwareNetworkInfo(nnri.peerIpv6, nnri.peerPort,
                                  nnri.peerTransportProtocol));
              }
              if (VDBG) {
                  Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo="
                          + networkCapabilities.getTransportInfo());
              }
  
              if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentRequests, ndpId,
                      networkInfo, networkCapabilities, linkProperties)) {
                  declareUnfullfillableAndEndDp(nnri, ndpId);
                  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.startValidationTimestamp = mClock.getElapsedSinceBootMillis();
              handleAddressValidation(nnri, linkProperties, networkInfo, ndpId,
                      networkSpecifier.isOutOfBand());
          } 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 handleAddressValidation(AwareNetworkRequestInformation nnri,
              LinkProperties linkProperties, NetworkInfo networkInfo, int ndpId,
              boolean isOutOfBand) {
          if (mNiWrapper.isAddressUsable(linkProperties)) {
              mNiWrapper.sendAgentNetworkInfo(nnri.networkAgent, networkInfo);
  
              mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, nnri.startTimestamp);
              nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); // update time-stamp
              mAwareMetrics.recordNdpCreation(nnri.uid, mNetworkRequestsCache);
          } else {
              if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp
                      > ADDRESS_VALIDATION_TIMEOUT_MS) {
                  Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable");
  
                  declareUnfullfillableAndEndDp(nnri, ndpId);
                  return;
              }
              if (mDbg) {
                  Log.d(TAG, "Failed address validation");
              }
              mHandler.postDelayed(() -> {
                  handleAddressValidation(nnri, linkProperties, networkInfo, ndpId, isOutOfBand);
              }, ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
          }
      }
  
      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 (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);
          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.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(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 (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);
                      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 (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(networkRequest);
              if (nnri.equivalentRequests.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 {
                      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 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) {
              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 (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 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() {
              NetworkCapabilities nc = new NetworkCapabilities(sNetworkCapabilitiesFilter);
              nc.setNetworkSpecifier(
                      new WifiAwareAgentNetworkSpecifier(equivalentRequests.stream().map(
                              nr -> nr.networkCapabilities.getNetworkSpecifier()).collect(
                              Collectors.toList()).toArray(new WifiAwareNetworkSpecifier[0])));
              if (peerIpv6 != null) {
                  nc.setTransportInfo(
                          new WifiAwareNetworkInfo(peerIpv6, peerPort, peerTransportProtocol));
              }
              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(NetworkRequest request,
                  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.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
              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.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(", 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,
                  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(networkRequests.stream().map(
                              nr -> nr.networkCapabilities.getNetworkSpecifier()).collect(
                              Collectors.toList()).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;
          }
  
          /**
           * 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.d(TAG, "Can't create socket on address " + address + " -- " + e);
                  }
                  return false;
              } finally {
                  if (testDatagramSocket != null) {
                      testDatagramSocket.close();
                  }
              }
              return true;
          }
  
          /**
           * Send updated network information to the agent.
           */
          public void sendAgentNetworkInfo(WifiAwareNetworkAgent networkAgent,
                  NetworkInfo networkInfo) {
              networkAgent.sendNetworkInfo(networkInfo);
          }
      }
  
      /**
       * 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);
      }
  }
  