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.
   */
  /*
   * Copyright (c) 2017, The Linux Foundation.
   */
  /*
   * Contributed by: Giesecke & Devrient GmbH.
   */
  
  package android.se.omapi;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.os.RemoteException;
  import android.os.ServiceSpecificException;
  import android.util.Log;
  
  import java.io.IOException;
  import java.util.NoSuchElementException;
  
  /**
   * Instances of this class represent a connection session to one of the Secure
   * Elements available on the device. These objects can be used to get a
   * communication channel with an Applet in the Secure Element.
   * This channel can be the basic channel or a logical channel.
   *
   * @see <a href="http://simalliance.org">SIMalliance Open Mobile API  v3.0</a>
   */
  public final class Session {
  
      private final Object mLock = new Object();
      private final SEService mService;
      private final Reader mReader;
      private final ISecureElementSession mSession;
      private static final String TAG = "OMAPI.Session";
  
      Session(@NonNull SEService service, @NonNull ISecureElementSession session,
              @NonNull Reader reader) {
          if (service == null || reader == null || session == null) {
              throw new IllegalArgumentException("Parameters cannot be null");
          }
          mService = service;
          mReader = reader;
          mSession = session;
      }
  
      /**
       * Get the reader that provides this session.
       *
       * @return The Reader object.
       */
      public @NonNull Reader getReader() {
          return mReader;
      }
  
      /**
       * Get the Answer to Reset of this Secure Element. <br>
       * The returned byte array can be null if the ATR for this Secure Element is
       * not available.
       *
       * @throws IllegalStateException if there was an error connecting to SE or
       *                               if the service was not connected.
       * @return the ATR as a byte array or null.
       */
      public @Nullable byte[] getATR() {
          if (!mService.isConnected()) {
              throw new IllegalStateException("service not connected to system");
          }
          try {
              return mSession.getAtr();
          } catch (RemoteException e) {
              throw new IllegalStateException(e.getMessage());
          }
      }
  
      /**
       * Close the connection with the Secure Element. This will close any
       * channels opened by this application with this Secure Element.
       */
      public void close() {
          if (!mService.isConnected()) {
              Log.e(TAG, "service not connected to system");
              return;
          }
          synchronized (mLock) {
              try {
                  mSession.close();
              } catch (RemoteException e) {
                  Log.e(TAG, "Error closing session", e);
              }
          }
      }
  
      /**
       * Tells if this session is closed.
       *
       * @return <code>true</code> if the session is closed, false otherwise.
       */
      public boolean isClosed() {
          try {
              return mSession.isClosed();
          } catch (RemoteException e) {
              // If there was an error here, then the session is considered close
              return true;
          }
      }
  
      /**
       * Close any channel opened on this session.
       */
      public void closeChannels() {
          if (!mService.isConnected()) {
              Log.e(TAG, "service not connected to system");
              return;
          }
  
          synchronized (mLock) {
              try {
                  mSession.closeChannels();
              } catch (RemoteException e) {
                  Log.e(TAG, "Error closing channels", e);
              }
          }
      }
  
      /**
       * Get an access to the basic channel, as defined in the ISO/IEC 7816-4 specification (the
       * one that has number 0). The obtained object is an instance of the Channel class.
       * If the AID is null, it means no Applet is to be selected on this channel and the default
       * Applet is used. If the AID is defined then the corresponding Applet is selected.
       * Once this channel has been opened by a device application, it is considered as "locked"
       * by this device application, and other calls to this method will return null, until the
       * channel is closed. Some Secure Elements (like the UICC) might always keep the basic channel
       * locked (i.e. return null to applications), to prevent access to the basic channel, while
       * some other might return a channel object implementing some kind of filtering on the
       * commands, restricting the set of accepted command to a smaller set.
       * It is recommended for the UICC to reject the opening of the basic channel to a specific
       * applet, by always answering null to such a request.
       * For other Secure Elements, the recommendation is to accept opening the basic channel
       * on the default applet until another applet is selected on the basic channel. As there is no
       * other way than a reset to select again the default applet, the implementation of the
       * transport API should guarantee that the openBasicChannel(null) command will return
       * null until a reset occurs.
       * With previous release (V2.05) it was not possible to set P2 value, this value was always
       * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
       * recommended that the device allows all values for P2, however only the following values
       * are mandatory: '00', '04', '08', '0C'(as defined in [2])
       * The implementation of the underlying SELECT command within this method shall be
       * based on ISO 7816-4 with following options:
       * <ul>
       * <li>CLA = '00'</li>
       * <li>INS = 'A4'</li>
       * <li>P1 = '04' (Select by DF name/application identifier)</li>
       * </ul>
       *
       * The select response data can be retrieved with byte[] getSelectResponse().
       * The API shall handle received status word as follow. If the status word indicates that the
       * Secure Element was able to open a channel (e.g. status word '90 00' or status words
       * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
       * channel opened and the next getSelectResponse() shall return the received status
       * word.
       * Other received status codes indicating that the Secure Element was able not to open a
       * channel shall be considered as an error and the corresponding channel shall not be
       * opened.
       * The function without P2 as parameter is provided for backwards compatibility and will
       * fall back to a select command with P2='00'.
       *
       * @param aid the AID of the Applet to be selected on this channel, as a
       *            byte array, or null if no Applet is to be selected.
       * @param p2 the P2 parameter of the SELECT APDU executed on this channel.
       * @throws IOException if there is a communication problem to the reader or
       *             the Secure Element.
       * @throws IllegalStateException if the Secure Element session is used after
       *             being closed.
       * @throws IllegalArgumentException if the aid's length is not within 5 to
       *             16 (inclusive).
       * @throws SecurityException if the calling application cannot be granted
       *             access to this AID or the default Applet on this
       *             session.
       * @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be
       *             selected.
       * @throws UnsupportedOperationException if the given P2 parameter is not
       *             supported by the device
       * @return an instance of Channel if available or null.
       */
      public @Nullable Channel openBasicChannel(@Nullable byte[] aid, @Nullable byte p2)
              throws IOException {
          if (!mService.isConnected()) {
              throw new IllegalStateException("service not connected to system");
          }
  
          synchronized (mLock) {
              try {
                  ISecureElementChannel channel = mSession.openBasicChannel(aid, p2,
                          mReader.getSEService().getListener());
                  if (channel == null) {
                      return null;
                  }
                  return new Channel(mService, this, channel);
              } catch (ServiceSpecificException e) {
                  if (e.errorCode == SEService.IO_ERROR) {
                      throw new IOException(e.getMessage());
                  } else if (e.errorCode == SEService.NO_SUCH_ELEMENT_ERROR) {
                      throw new NoSuchElementException(e.getMessage());
                  } else {
                      throw new IllegalStateException(e.getMessage());
                  }
              } catch (RemoteException e) {
                  throw new IllegalStateException(e.getMessage());
              }
          }
      }
  
      /**
       * This method is provided to ease the development of mobile application and for compliancy
       * with existing applications.
       * This method is equivalent to openBasicChannel(aid, P2=0x00)
       *
       * @param aid the AID of the Applet to be selected on this channel, as a
       *            byte array, or null if no Applet is to be selected.
       * @throws IOException if there is a communication problem to the reader or
       *             the Secure Element.
       * @throws IllegalStateException if the Secure Element session is used after
       *             being closed.
       * @throws IllegalArgumentException if the aid's length is not within 5 to
       *             16 (inclusive).
       * @throws SecurityException if the calling application cannot be granted
       *             access to this AID or the default Applet on this
       *             session.
       * @throws NoSuchElementException if the AID on the Secure Element is not available or cannot be
       *             selected.
       * @throws UnsupportedOperationException if the given P2 parameter is not
       *             supported by the device
       * @return an instance of Channel if available or null.
       */
      public @Nullable Channel openBasicChannel(@Nullable byte[] aid) throws IOException {
          return openBasicChannel(aid, (byte) 0x00);
      }
  
      /**
       * Open a logical channel with the Secure Element, selecting the Applet represented by
       * the given AID. If the AID is null, which means no Applet is to be selected on this
       * channel, the default Applet is used. It's up to the Secure Element to choose which
       * logical channel will be used.
       * With previous release (V2.05) it was not possible to set P2 value, this value was always
       * set to '00'.Except for specific needs it is recommended to keep P2 to '00'. It is
       * recommended that the device allows all values for P2, however only the following values
       * are mandatory: '00', '04', '08', '0C'(as defined in [2])
       * The implementation of the underlying SELECT command within this method shall be
       * based on ISO 7816-4 with following options:
       *
       * <ul>
       * <li>CLA = '01' to '03', '40 to 4F'</li>
       * <li>INS = 'A4'</li>
       * <li>P1 = '04' (Select by DF name/application identifier)</li>
       * </ul>
       *
       * The select response data can be retrieved with byte[] getSelectResponse().
       * The API shall handle received status word as follow. If the status word indicates that the
       * Secure Element was able to open a channel (e.g. status word '90 00' or status words
       * referencing a warning in ISO-7816-4: '62 XX' or '63 XX') the API shall keep the
       * channel opened and the next getSelectResponse() shall return the received status
       * word.
       * Other received status codes indicating that the Secure Element was able not to open a
       * channel shall be considered as an error and the corresponding channel shall not be
       * opened.
       * In case of UICC it is recommended for the API to reject the opening of the logical
       * channel without a specific AID, by always answering null to such a request.
       * The function without P2 as parameter is provided for backwards compatibility and will
       * fall back to a select command with P2=00.
       *
       * @param aid the AID of the Applet to be selected on this channel, as a
       *            byte array.
       * @param p2 the P2 parameter of the SELECT APDU executed on this channel.
       * @throws IOException if there is a communication problem to the reader or
       *             the Secure Element.
       * @throws IllegalStateException if the Secure Element is used after being
       *             closed.
       * @throws IllegalArgumentException if the aid's length is not within 5 to
       *             16 (inclusive).
       * @throws SecurityException if the calling application cannot be granted
       *             access to this AID or the default Applet on this
       *             session.
       * @throws NoSuchElementException if the AID on the Secure Element is not
       *             available or cannot be selected or a logical channel is already
       *             open to a non-multiselectable Applet.
       * @throws UnsupportedOperationException if the given P2 parameter is not
       *             supported by the device.
       * @return an instance of Channel. Null if the Secure Element is unable to
       *         provide a new logical channel.
       */
      public @Nullable Channel openLogicalChannel(@Nullable byte[] aid, @Nullable byte p2)
              throws IOException {
          if (!mService.isConnected()) {
              throw new IllegalStateException("service not connected to system");
          }
          synchronized (mLock) {
              try {
                  ISecureElementChannel channel = mSession.openLogicalChannel(
                          aid,
                          p2,
                          mReader.getSEService().getListener());
                  if (channel == null) {
                      return null;
                  }
                  return new Channel(mService, this, channel);
              } catch (ServiceSpecificException e) {
                  if (e.errorCode == SEService.IO_ERROR) {
                      throw new IOException(e.getMessage());
                  } else if (e.errorCode == SEService.NO_SUCH_ELEMENT_ERROR) {
                      throw new NoSuchElementException(e.getMessage());
                  } else {
                      throw new IllegalStateException(e.getMessage());
                  }
              } catch (RemoteException e) {
                  throw new IllegalStateException(e.getMessage());
              }
          }
      }
  
      /**
       * This method is provided to ease the development of mobile application and for compliancy
       * with existing applications.
       * This method is equivalent to openLogicalChannel(aid, P2=0x00)
       *
       * @param aid the AID of the Applet to be selected on this channel, as a
       *            byte array.
       * @throws IOException if there is a communication problem to the reader or
       *             the Secure Element.
       * @throws IllegalStateException if the Secure Element is used after being
       *             closed.
       * @throws IllegalArgumentException if the aid's length is not within 5 to
       *             16 (inclusive).
       * @throws SecurityException if the calling application cannot be granted
       *             access to this AID or the default Applet on this
       *             session.
       * @throws NoSuchElementException if the AID on the Secure Element is not
       *             available or cannot be selected or a logical channel is already
       *             open to a non-multiselectable Applet.
       * @throws UnsupportedOperationException if the given P2 parameter is not
       *             supported by the device.
       * @return an instance of Channel. Null if the Secure Element is unable to
       *         provide a new logical channel.
       */
      public @Nullable Channel openLogicalChannel(@Nullable byte[] aid) throws IOException {
          return openLogicalChannel(aid, (byte) 0x00);
      }
  }
  