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 android.net.lowpan;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.net.IpPrefix;
  import android.net.LinkAddress;
  import android.os.DeadObjectException;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.RemoteException;
  import android.os.ServiceSpecificException;
  import android.util.Log;
  import java.util.HashMap;
  
  /**
   * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
   *
   * @hide
   */
  // @SystemApi
  public class LowpanInterface {
      private static final String TAG = LowpanInterface.class.getSimpleName();
  
      /** Detached role. The interface is not currently attached to a network. */
      public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
  
      /** End-device role. End devices do not route traffic for other nodes. */
      public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
  
      /** Router role. Routers help route traffic around the mesh network. */
      public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
  
      /**
       * Sleepy End-Device role.
       *
       * <p>End devices with this role are nominally asleep, waking up periodically to check in with
       * their parent to see if there are packets destined for them. Such devices are capable of
       * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
       * seconds(depending on how the node is configured).
       */
      public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
  
      /**
       * Sleepy-router role.
       *
       * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
       * routers and their children.
       */
      public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
  
      /** TODO: doc */
      public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
  
      /** TODO: doc */
      public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
  
      /**
       * Offline state.
       *
       * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
       * this state the NCP is idle and not connected to any network.
       *
       * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
       * <code>setUp(false)</code>, with the later two only working if we were not previously in the
       * {@link #STATE_FAULT} state.
       *
       * @see #getState()
       * @see #STATE_FAULT
       */
      public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
  
      /**
       * Commissioning state.
       *
       * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
       * state may only be entered directly from the {@link #STATE_OFFLINE} state.
       *
       * @see #startCommissioningSession()
       * @see #getState()
       * @hide
       */
      public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
  
      /**
       * Attaching state.
       *
       * <p>The interface enters this state when it starts the process of trying to find other nodes
       * so that it can attach to any pre-existing network fragment, or when it is in the process of
       * calculating the optimal values for unspecified parameters when forming a new network.
       *
       * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
       * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
       * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
       * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
       * create their own network fragments.
       *
       * @see #STATE_ATTACHED
       * @see #getState()
       */
      public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
  
      /**
       * Attached state.
       *
       * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
       * participating on a network fragment.
       *
       * @see #STATE_ATTACHING
       * @see #getState()
       */
      public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
  
      /**
       * Fault state.
       *
       * <p>The interface will enter this state when the driver has detected some sort of problem from
       * which it was not immediately able to recover.
       *
       * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
       * cause the device to return to the {@link #STATE_OFFLINE} state.
       *
       * @see #getState
       * @see #STATE_OFFLINE
       */
      public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
  
      /**
       * Network type for Thread 1.x networks.
       *
       * @see android.net.lowpan.LowpanIdentity#getType
       * @see #getLowpanIdentity
       * @hide
       */
      public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
  
      public static final String EMPTY_PARTITION_ID = "";
  
      /**
       * Callback base class for LowpanInterface
       *
       * @hide
       */
      // @SystemApi
      public abstract static class Callback {
          public void onConnectedChanged(boolean value) {}
  
          public void onEnabledChanged(boolean value) {}
  
          public void onUpChanged(boolean value) {}
  
          public void onRoleChanged(@NonNull String value) {}
  
          public void onStateChanged(@NonNull String state) {}
  
          public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
  
          public void onLinkNetworkAdded(IpPrefix prefix) {}
  
          public void onLinkNetworkRemoved(IpPrefix prefix) {}
  
          public void onLinkAddressAdded(LinkAddress address) {}
  
          public void onLinkAddressRemoved(LinkAddress address) {}
      }
  
      private final ILowpanInterface mBinder;
      private final Looper mLooper;
      private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
  
      /**
       * Create a new LowpanInterface instance. Applications will almost always want to use {@link
       * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
       *
       * @param context the application context
       * @param service the Binder interface
       * @param looper the Binder interface
       * @hide
       */
      public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
          /* We aren't currently using the context, but if we need
           * it later on we can easily add it to the class.
           */
  
          mBinder = service;
          mLooper = looper;
      }
  
      /**
       * Returns the ILowpanInterface object associated with this interface.
       *
       * @hide
       */
      public ILowpanInterface getService() {
          return mBinder;
      }
  
      // Public Actions
  
      /**
       * Form a new network with the given network information optional credential. Unspecified fields
       * in the network information will be filled in with reasonable values. If the network
       * credential is unspecified, one will be generated automatically.
       *
       * <p>This method will block until either the network was successfully formed or an error
       * prevents the network form being formed.
       *
       * <p>Upon success, the interface will be up and attached to the newly formed network.
       *
       * @see #join(LowpanProvision)
       */
      public void form(@NonNull LowpanProvision provision) throws LowpanException {
          try {
              mBinder.form(provision);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Attempts to join a new network with the given network information. This method will block
       * until either the network was successfully joined or an error prevented the network from being
       * formed. Upon success, the interface will be up and attached to the newly joined network.
       *
       * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
       * device to be present in order for the operation to complete successfully.
       */
      public void join(@NonNull LowpanProvision provision) throws LowpanException {
          try {
              mBinder.join(provision);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Attaches to the network described by identity and credential. This is similar to {@link
       * #join}, except that (assuming the identity and credential are valid) it will always succeed
       * and provision the interface, even if there are no peers nearby.
       *
       * <p>This method will block execution until the operation has completed.
       */
      public void attach(@NonNull LowpanProvision provision) throws LowpanException {
          try {
              mBinder.attach(provision);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Bring down the network interface and forget all non-volatile details about the current
       * network.
       *
       * <p>This method will block execution until the operation has completed.
       */
      public void leave() throws LowpanException {
          try {
              mBinder.leave();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Start a new commissioning session. Will fail if the interface is attached to a network or if
       * the interface is disabled.
       */
      public @NonNull LowpanCommissioningSession startCommissioningSession(
              @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
          try {
              mBinder.startCommissioningSession(beaconInfo);
  
              return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Reset this network interface as if it has been power cycled. Will bring the network interface
       * down if it was previously up. Will not erase any non-volatile settings.
       *
       * <p>This method will block execution until the operation has completed.
       *
       * @hide
       */
      public void reset() throws LowpanException {
          try {
              mBinder.reset();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      // Public Getters and Setters
  
      /** Returns the name of this network interface. */
      @NonNull
      public String getName() {
          try {
              return mBinder.getName();
  
          } catch (DeadObjectException x) {
              return "";
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Indicates if the interface is enabled or disabled.
       *
       * @see #setEnabled
       * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
       */
      public boolean isEnabled() {
          try {
              return mBinder.isEnabled();
  
          } catch (DeadObjectException x) {
              return false;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
       * low-power state and all commands that require the NCP to be queried will fail with {@link
       * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
       *
       * @see #isEnabled
       * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
       * @hide
       */
      public void setEnabled(boolean enabled) throws LowpanException {
          try {
              mBinder.setEnabled(enabled);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Indicates if the network interface is up or down.
       *
       * @hide
       */
      public boolean isUp() {
          try {
              return mBinder.isUp();
  
          } catch (DeadObjectException x) {
              return false;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Indicates if there is at least one peer in range.
       *
       * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
       *     otherwise.
       */
      public boolean isConnected() {
          try {
              return mBinder.isConnected();
  
          } catch (DeadObjectException x) {
              return false;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Indicates if this interface is currently commissioned onto an existing network. If the
       * interface is commissioned, the interface may be brought up using setUp().
       */
      public boolean isCommissioned() {
          try {
              return mBinder.isCommissioned();
  
          } catch (DeadObjectException x) {
              return false;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /**
       * Get interface state
       *
       * <h3>State Diagram</h3>
       *
       * <img src="LowpanInterface-1.png" />
       *
       * @return The current state of the interface.
       * @see #STATE_OFFLINE
       * @see #STATE_COMMISSIONING
       * @see #STATE_ATTACHING
       * @see #STATE_ATTACHED
       * @see #STATE_FAULT
       */
      public String getState() {
          try {
              return mBinder.getState();
  
          } catch (DeadObjectException x) {
              return STATE_FAULT;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /** Get network partition/fragment identifier. */
      public String getPartitionId() {
          try {
              return mBinder.getPartitionId();
  
          } catch (DeadObjectException x) {
              return EMPTY_PARTITION_ID;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /** TODO: doc */
      public LowpanIdentity getLowpanIdentity() {
          try {
              return mBinder.getLowpanIdentity();
  
          } catch (DeadObjectException x) {
              return new LowpanIdentity();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /** TODO: doc */
      @NonNull
      public String getRole() {
          try {
              return mBinder.getRole();
  
          } catch (DeadObjectException x) {
              return ROLE_DETACHED;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      /** TODO: doc */
      @Nullable
      public LowpanCredential getLowpanCredential() {
          try {
              return mBinder.getLowpanCredential();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
      }
  
      public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
          try {
              return mBinder.getSupportedNetworkTypes();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
          try {
              return mBinder.getSupportedChannels();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      // Listener Support
  
      /**
       * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
       *
       * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
       * @param handler If not <code>null</code>, events will be dispatched via the given handler
       *     object. If <code>null</code>, the thread upon which events will be dispatched is
       *     unspecified.
       * @see #registerCallback(Callback)
       * @see #unregisterCallback(Callback)
       */
      public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
          ILowpanInterfaceListener.Stub listenerBinder =
                  new ILowpanInterfaceListener.Stub() {
                      private Handler mHandler;
  
                      {
                          if (handler != null) {
                              mHandler = handler;
                          } else if (mLooper != null) {
                              mHandler = new Handler(mLooper);
                          } else {
                              mHandler = new Handler();
                          }
                      }
  
                      @Override
                      public void onEnabledChanged(boolean value) {
                          mHandler.post(() -> cb.onEnabledChanged(value));
                      }
  
                      @Override
                      public void onConnectedChanged(boolean value) {
                          mHandler.post(() -> cb.onConnectedChanged(value));
                      }
  
                      @Override
                      public void onUpChanged(boolean value) {
                          mHandler.post(() -> cb.onUpChanged(value));
                      }
  
                      @Override
                      public void onRoleChanged(String value) {
                          mHandler.post(() -> cb.onRoleChanged(value));
                      }
  
                      @Override
                      public void onStateChanged(String value) {
                          mHandler.post(() -> cb.onStateChanged(value));
                      }
  
                      @Override
                      public void onLowpanIdentityChanged(LowpanIdentity value) {
                          mHandler.post(() -> cb.onLowpanIdentityChanged(value));
                      }
  
                      @Override
                      public void onLinkNetworkAdded(IpPrefix value) {
                          mHandler.post(() -> cb.onLinkNetworkAdded(value));
                      }
  
                      @Override
                      public void onLinkNetworkRemoved(IpPrefix value) {
                          mHandler.post(() -> cb.onLinkNetworkRemoved(value));
                      }
  
                      @Override
                      public void onLinkAddressAdded(String value) {
                          LinkAddress la;
                          try {
                              la = new LinkAddress(value);
                          } catch (IllegalArgumentException x) {
                              Log.e(
                                      TAG,
                                      "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
                              return;
                          }
                          mHandler.post(() -> cb.onLinkAddressAdded(la));
                      }
  
                      @Override
                      public void onLinkAddressRemoved(String value) {
                          LinkAddress la;
                          try {
                              la = new LinkAddress(value);
                          } catch (IllegalArgumentException x) {
                              Log.e(
                                      TAG,
                                      "onLinkAddressRemoved: Bad LinkAddress \""
                                              + value
                                              + "\", "
                                              + x);
                              return;
                          }
                          mHandler.post(() -> cb.onLinkAddressRemoved(la));
                      }
  
                      @Override
                      public void onReceiveFromCommissioner(byte[] packet) {
                          // This is only used by the LowpanCommissioningSession.
                      }
                  };
          try {
              mBinder.addListener(listenerBinder);
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
          }
  
          synchronized (mListenerMap) {
              mListenerMap.put(System.identityHashCode(cb), listenerBinder);
          }
      }
  
      /**
       * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
       *
       * <p>The thread upon which events will be dispatched is unspecified.
       *
       * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
       * @see #registerCallback(Callback, Handler)
       * @see #unregisterCallback(Callback)
       */
      public void registerCallback(Callback cb) {
          registerCallback(cb, null);
      }
  
      /**
       * Unregisters a previously registered callback class.
       *
       * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
       *     receive events.
       * @see #registerCallback(Callback, Handler)
       * @see #registerCallback(Callback)
       */
      public void unregisterCallback(Callback cb) {
          int hashCode = System.identityHashCode(cb);
          synchronized (mListenerMap) {
              ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
  
              if (listenerBinder != null) {
                  mListenerMap.remove(hashCode);
  
                  try {
                      mBinder.removeListener(listenerBinder);
                  } catch (DeadObjectException x) {
                      // We ignore a dead object exception because that
                      // pretty clearly means our callback isn't registered.
                  } catch (RemoteException x) {
                      throw x.rethrowAsRuntimeException();
                  }
              }
          }
      }
  
      // Active and Passive Scanning
  
      /**
       * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
       *
       * <p>This method allocates a new unique object for each call.
       *
       * @see android.net.lowpan.LowpanScanner
       */
      public @NonNull LowpanScanner createScanner() {
          return new LowpanScanner(mBinder);
      }
  
      // Route Management
  
      /**
       * Makes a copy of the internal list of LinkAddresses.
       *
       * @hide
       */
      public LinkAddress[] getLinkAddresses() throws LowpanException {
          try {
              String[] linkAddressStrings = mBinder.getLinkAddresses();
              LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
              int i = 0;
              for (String str : linkAddressStrings) {
                  ret[i++] = new LinkAddress(str);
              }
              return ret;
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Makes a copy of the internal list of networks reachable on via this link.
       *
       * @hide
       */
      public IpPrefix[] getLinkNetworks() throws LowpanException {
          try {
              return mBinder.getLinkNetworks();
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Advertise the given IP prefix as an on-mesh prefix.
       *
       * @hide
       */
      public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
          try {
              mBinder.addOnMeshPrefix(prefix, flags);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
       * prefixes.
       *
       * @hide
       */
      public void removeOnMeshPrefix(IpPrefix prefix) {
          try {
              mBinder.removeOnMeshPrefix(prefix);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              // Catch and ignore all service exceptions
              Log.e(TAG, x.toString());
          }
      }
  
      /**
       * Advertise this device to other devices on the mesh network as having a specific route to the
       * given network. This device will then receive forwarded traffic for that network.
       *
       * @hide
       */
      public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
          try {
              mBinder.addExternalRoute(prefix, flags);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              throw LowpanException.rethrowFromServiceSpecificException(x);
          }
      }
  
      /**
       * Revoke a previously advertised specific route to the given network.
       *
       * @hide
       */
      public void removeExternalRoute(IpPrefix prefix) {
          try {
              mBinder.removeExternalRoute(prefix);
  
          } catch (RemoteException x) {
              throw x.rethrowAsRuntimeException();
  
          } catch (ServiceSpecificException x) {
              // Catch and ignore all service exceptions
              Log.e(TAG, x.toString());
          }
      }
  }
  