content: /*
   * Copyright (C) 2017 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.rtt;
  
  import android.annotation.Nullable;
  import android.hardware.wifi.V1_0.IWifiRttController;
  import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
  import android.hardware.wifi.V1_0.RttBw;
  import android.hardware.wifi.V1_0.RttCapabilities;
  import android.hardware.wifi.V1_0.RttConfig;
  import android.hardware.wifi.V1_0.RttPeerType;
  import android.hardware.wifi.V1_0.RttPreamble;
  import android.hardware.wifi.V1_0.RttResult;
  import android.hardware.wifi.V1_0.RttType;
  import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
  import android.hardware.wifi.V1_0.WifiStatus;
  import android.hardware.wifi.V1_0.WifiStatusCode;
  import android.net.wifi.rtt.RangingRequest;
  import android.net.wifi.rtt.ResponderConfig;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.server.wifi.HalDeviceManager;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.ListIterator;
  
  /**
   * TBD
   */
  public class RttNative extends IWifiRttControllerEventCallback.Stub {
      private static final String TAG = "RttNative";
      private static final boolean VDBG = false; // STOPSHIP if true
      /* package */ boolean mDbg = false;
  
      private final RttServiceImpl mRttService;
      private final HalDeviceManager mHalDeviceManager;
  
      private Object mLock = new Object();
  
      private volatile IWifiRttController mIWifiRttController;
      private volatile RttCapabilities mRttCapabilities;
  
      private final HalDeviceManager.InterfaceRttControllerLifecycleCallback mRttLifecycleCb =
              new HalDeviceManager.InterfaceRttControllerLifecycleCallback() {
                  @Override
                  public void onNewRttController(IWifiRttController controller) {
                      if (mDbg) Log.d(TAG, "onNewRttController: controller=" + controller);
                      synchronized (mLock) {
                          try {
                              controller.registerEventCallback(RttNative.this);
                          } catch (RemoteException e) {
                              Log.e(TAG, "onNewRttController: exception registering callback: " + e);
                              if (mIWifiRttController != null) {
                                  mIWifiRttController = null;
                                  mRttService.disable();
                              }
                              return;
                          }
  
                          mIWifiRttController = controller;
                          mRttService.enableIfPossible();
                          updateRttCapabilities();
                      }
                  }
  
                  @Override
                  public void onRttControllerDestroyed() {
                      if (mDbg) Log.d(TAG, "onRttControllerDestroyed");
                      synchronized (mLock) {
                          mIWifiRttController = null;
                          mRttCapabilities = null;
                          mRttService.disable();
                      }
                  }
              };
  
      public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager) {
          mRttService = rttService;
          mHalDeviceManager = halDeviceManager;
      }
  
      /**
       * Initialize the object - registering with the HAL device manager.
       */
      public void start(Handler handler) {
          synchronized (mLock) {
              mHalDeviceManager.initialize();
              mHalDeviceManager.registerStatusListener(() -> {
                  if (VDBG) Log.d(TAG, "hdm.onStatusChanged");
                  if (mHalDeviceManager.isStarted()) {
                      mHalDeviceManager.registerRttControllerLifecycleCallback(mRttLifecycleCb,
                              handler);
                  }
              }, handler);
              if (mHalDeviceManager.isStarted()) {
                  mHalDeviceManager.registerRttControllerLifecycleCallback(mRttLifecycleCb, handler);
              }
          }
      }
  
      /**
       * Returns true if Wi-Fi is ready for RTT requests, false otherwise.
       */
      public boolean isReady() {
          return mIWifiRttController != null;
      }
  
      /**
       * Returns the RTT capabilities. Will only be null when disabled (e.g. no STA interface
       * available - not necessarily up).
       */
      public @Nullable RttCapabilities getRttCapabilities() {
          return mRttCapabilities;
      }
  
      /**
       * Updates the RTT capabilities.
       */
      void updateRttCapabilities() {
          if (mIWifiRttController == null) {
              Log.e(TAG, "updateRttCapabilities: but a RTT controll is NULL!?");
              return;
          }
          if (mRttCapabilities != null) {
              return;
          }
          if (mDbg) Log.v(TAG, "updateRttCapabilities");
  
          synchronized (mLock) {
              try {
                  mIWifiRttController.getCapabilities(
                          (status, capabilities) -> {
                              if (status.code != WifiStatusCode.SUCCESS) {
                                  Log.e(TAG, "updateRttCapabilities: error requesting capabilities "
                                          + "-- code=" + status.code);
                                  return;
                              }
                              if (mDbg) {
                                  Log.v(TAG, "updateRttCapabilities: RTT capabilities="
                                          + capabilities);
                              }
                              mRttCapabilities = capabilities;
                          });
              } catch (RemoteException e) {
                  Log.e(TAG, "updateRttCapabilities: exception requesting capabilities: " + e);
              }
  
              if (mRttCapabilities != null && !mRttCapabilities.rttFtmSupported) {
                  Log.wtf(TAG, "Firmware indicates RTT is not supported - but device supports RTT - "
                          + "ignored!?");
              }
          }
      }
  
      /**
       * Issue a range request to the HAL.
       *
       * @param cmdId Command ID for the request. Will be used in the corresponding
       * {@link #onResults(int, ArrayList)}.
       * @param request Range request.
       * @param isCalledFromPrivilegedContext Indicates whether privileged APIs are permitted,
       *                                      initially: support for one-sided RTT.
       *
       * @return Success status: true for success, false for failure.
       */
      public boolean rangeRequest(int cmdId, RangingRequest request,
              boolean isCalledFromPrivilegedContext) {
          if (mDbg) {
              Log.v(TAG,
                      "rangeRequest: cmdId=" + cmdId + ", # of requests=" + request.mRttPeers.size());
          }
          if (VDBG) Log.v(TAG, "rangeRequest: request=" + request);
          synchronized (mLock) {
              if (!isReady()) {
                  Log.e(TAG, "rangeRequest: RttController is null");
                  return false;
              }
              updateRttCapabilities();
  
              ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request,
                      isCalledFromPrivilegedContext, mRttCapabilities);
              if (rttConfig == null) {
                  Log.e(TAG, "rangeRequest: invalid request parameters");
                  return false;
              }
              if (rttConfig.size() == 0) {
                  Log.e(TAG, "rangeRequest: all requests invalidated");
                  mRttService.onRangingResults(cmdId, new ArrayList<>());
                  return true;
              }
  
              try {
                  WifiStatus status = mIWifiRttController.rangeRequest(cmdId, rttConfig);
                  if (status.code != WifiStatusCode.SUCCESS) {
                      Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
                  return false;
              }
  
              return true;
          }
      }
  
      /**
       * Cancel an outstanding ranging request: no guarantees of execution - we will ignore any
       * results which are returned for the canceled request.
       *
       * @param cmdId The cmdId issued with the original rangeRequest command.
       * @param macAddresses A list of MAC addresses for which to cancel the operation.
       * @return Success status: true for success, false for failure.
       */
      public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
          if (mDbg) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
          synchronized (mLock) {
              if (!isReady()) {
                  Log.e(TAG, "rangeCancel: RttController is null");
                  return false;
              }
  
              try {
                  WifiStatus status = mIWifiRttController.rangeCancel(cmdId, macAddresses);
                  if (status.code != WifiStatusCode.SUCCESS) {
                      Log.e(TAG, "rangeCancel: cannot issue range cancel -- code=" + status.code);
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "rangeCancel: exception issuing range cancel: " + e);
                  return false;
              }
  
              return true;
          }
      }
  
      /**
       * Callback from HAL with range results.
       *
       * @param cmdId Command ID specified in the original request
       * {@link #rangeRequest(int, RangingRequest, boolean)}.
       * @param halResults A list of range results.
       */
      @Override
      public void onResults(int cmdId, ArrayList<RttResult> halResults) {
          if (mDbg) Log.v(TAG, "onResults: cmdId=" + cmdId + ", # of results=" + halResults.size());
  
          // sanitize HAL results
          if (halResults == null) {
              halResults = new ArrayList<>();
          }
          ListIterator<RttResult> lit = halResults.listIterator();
          while (lit.hasNext()) {
              if (lit.next() == null) {
                  lit.remove();
              }
          }
          mRttService.onRangingResults(cmdId, halResults);
      }
  
      private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request,
              boolean isCalledFromPrivilegedContext, RttCapabilities cap) {
          ArrayList<RttConfig> rttConfigs = new ArrayList<>(request.mRttPeers.size());
  
          // Skipping any configurations which have an error (printing out a message).
          // The caller will only get results for valid configurations.
          for (ResponderConfig responder: request.mRttPeers) {
              if (!isCalledFromPrivilegedContext) {
                  if (!responder.supports80211mc) {
                      Log.e(TAG, "Invalid responder: does not support 802.11mc");
                      continue;
                  }
              }
  
              RttConfig config = new RttConfig();
  
              System.arraycopy(responder.macAddress.toByteArray(), 0, config.addr, 0,
                      config.addr.length);
  
              try {
                  config.type = responder.supports80211mc ? RttType.TWO_SIDED : RttType.ONE_SIDED;
                  if (config.type == RttType.ONE_SIDED && cap != null && !cap.rttOneSidedSupported) {
                      Log.w(TAG, "Device does not support one-sided RTT");
                      continue;
                  }
  
                  config.peer = halRttPeerTypeFromResponderType(responder.responderType);
                  config.channel.width = halChannelWidthFromResponderChannelWidth(
                          responder.channelWidth);
                  config.channel.centerFreq = responder.frequency;
                  config.channel.centerFreq0 = responder.centerFreq0;
                  config.channel.centerFreq1 = responder.centerFreq1;
                  config.bw = halRttChannelBandwidthFromResponderChannelWidth(responder.channelWidth);
                  config.preamble = halRttPreambleFromResponderPreamble(responder.preamble);
                  validateBwAndPreambleCombination(config.bw, config.preamble);
  
                  if (config.peer == RttPeerType.NAN) {
                      config.mustRequestLci = false;
                      config.mustRequestLcr = false;
                      config.burstPeriod = 0;
                      config.numBurst = 0;
                      config.numFramesPerBurst = 5;
                      config.numRetriesPerRttFrame = 0; // irrelevant for 2-sided RTT
                      config.numRetriesPerFtmr = 3;
                      config.burstDuration = 9;
                  } else { // AP + all non-NAN requests
                      config.mustRequestLci = true;
                      config.mustRequestLcr = true;
                      config.burstPeriod = 0;
                      config.numBurst = 0;
                      config.numFramesPerBurst = 8;
                      config.numRetriesPerRttFrame = (config.type == RttType.TWO_SIDED ? 0 : 3);
                      config.numRetriesPerFtmr = 3;
                      config.burstDuration = 9;
  
                      if (cap != null) { // constrain parameters per device capabilities
                          config.mustRequestLci = config.mustRequestLci && cap.lciSupported;
                          config.mustRequestLcr = config.mustRequestLcr && cap.lcrSupported;
                          config.bw = halRttChannelBandwidthCapabilityLimiter(config.bw, cap);
                          config.preamble = halRttPreambleCapabilityLimiter(config.preamble, cap);
                      }
                  }
              } catch (IllegalArgumentException e) {
                  Log.e(TAG, "Invalid configuration: " + e.getMessage());
                  continue;
              }
  
              rttConfigs.add(config);
          }
  
          return rttConfigs;
      }
  
      private static void validateBwAndPreambleCombination(int bw, int preamble) {
          if (bw <= RttBw.BW_20MHZ) {
              return;
          }
          if (bw == RttBw.BW_40MHZ && preamble >= RttPreamble.HT) {
              return;
          }
          if (bw >= RttBw.BW_80MHZ && preamble >= RttPreamble.VHT) {
              return;
          }
          throw new IllegalArgumentException(
                  "bw and preamble combination is invalid, bw: " + bw + " preamble: " + preamble);
      }
  
      private static int halRttPeerTypeFromResponderType(int responderType) {
          switch (responderType) {
              case ResponderConfig.RESPONDER_AP:
                  return RttPeerType.AP;
              case ResponderConfig.RESPONDER_STA:
                  return RttPeerType.STA;
              case ResponderConfig.RESPONDER_P2P_GO:
                  return RttPeerType.P2P_GO;
              case ResponderConfig.RESPONDER_P2P_CLIENT:
                  return RttPeerType.P2P_CLIENT;
              case ResponderConfig.RESPONDER_AWARE:
                  return RttPeerType.NAN;
              default:
                  throw new IllegalArgumentException(
                          "halRttPeerTypeFromResponderType: bad " + responderType);
          }
      }
  
      private static int halChannelWidthFromResponderChannelWidth(int responderChannelWidth) {
          switch (responderChannelWidth) {
              case ResponderConfig.CHANNEL_WIDTH_20MHZ:
                  return WifiChannelWidthInMhz.WIDTH_20;
              case ResponderConfig.CHANNEL_WIDTH_40MHZ:
                  return WifiChannelWidthInMhz.WIDTH_40;
              case ResponderConfig.CHANNEL_WIDTH_80MHZ:
                  return WifiChannelWidthInMhz.WIDTH_80;
              case ResponderConfig.CHANNEL_WIDTH_160MHZ:
                  return WifiChannelWidthInMhz.WIDTH_160;
              case ResponderConfig.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
                  return WifiChannelWidthInMhz.WIDTH_80P80;
              default:
                  throw new IllegalArgumentException(
                          "halChannelWidthFromResponderChannelWidth: bad " + responderChannelWidth);
          }
      }
  
      private static int halRttChannelBandwidthFromResponderChannelWidth(int responderChannelWidth) {
          switch (responderChannelWidth) {
              case ResponderConfig.CHANNEL_WIDTH_20MHZ:
                  return RttBw.BW_20MHZ;
              case ResponderConfig.CHANNEL_WIDTH_40MHZ:
                  return RttBw.BW_40MHZ;
              case ResponderConfig.CHANNEL_WIDTH_80MHZ:
                  return RttBw.BW_80MHZ;
              case ResponderConfig.CHANNEL_WIDTH_160MHZ:
                  return RttBw.BW_160MHZ;
              case ResponderConfig.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
                  return RttBw.BW_160MHZ;
              default:
                  throw new IllegalArgumentException(
                          "halRttChannelBandwidthFromHalBandwidth: bad " + responderChannelWidth);
          }
      }
  
      private static int halRttPreambleFromResponderPreamble(int responderPreamble) {
          switch (responderPreamble) {
              case ResponderConfig.PREAMBLE_LEGACY:
                  return RttPreamble.LEGACY;
              case ResponderConfig.PREAMBLE_HT:
                  return RttPreamble.HT;
              case ResponderConfig.PREAMBLE_VHT:
                  return RttPreamble.VHT;
              default:
                  throw new IllegalArgumentException(
                          "halRttPreambleFromResponderPreamble: bad " + responderPreamble);
          }
      }
  
      /**
       * Check to see whether the selected RTT channel bandwidth is supported by the device.
       * If not supported: return the next lower bandwidth which is supported
       * If none: throw an IllegalArgumentException.
       *
       * Note: the halRttChannelBandwidth is a single bit flag of the ones used in cap.bwSupport (HAL
       * specifications).
       */
      private static int halRttChannelBandwidthCapabilityLimiter(int halRttChannelBandwidth,
              RttCapabilities cap) {
          while ((halRttChannelBandwidth != 0) && ((halRttChannelBandwidth & cap.bwSupport) == 0)) {
              halRttChannelBandwidth >>= 1;
          }
  
          if (halRttChannelBandwidth != 0) {
              return halRttChannelBandwidth;
          }
  
          throw new IllegalArgumentException(
                  "RTT BW=" + halRttChannelBandwidth + ", not supported by device capabilities=" + cap
                          + " - and no supported alternative");
      }
  
      /**
       * Check to see whether the selected RTT preamble is supported by the device.
       * If not supported: return the next "lower" preamble which is supported
       * If none: throw an IllegalArgumentException.
       *
       * Note: the halRttPreamble is a single bit flag of the ones used in cap.preambleSupport (HAL
       * specifications).
       */
      private static int halRttPreambleCapabilityLimiter(int halRttPreamble, RttCapabilities cap) {
          while ((halRttPreamble != 0) && ((halRttPreamble & cap.preambleSupport) == 0)) {
              halRttPreamble >>= 1;
          }
  
          if (halRttPreamble != 0) {
              return halRttPreamble;
          }
  
          throw new IllegalArgumentException(
                  "RTT Preamble=" + halRttPreamble + ", not supported by device capabilities=" + cap
                          + " - and no supported alternative");
      }
  
  
      /**
       * Dump the internal state of the class.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("RttNative:");
          pw.println("  mHalDeviceManager: " + mHalDeviceManager);
          pw.println("  mIWifiRttController: " + mIWifiRttController);
          pw.println("  mRttCapabilities: " + mRttCapabilities);
      }
  }
  