/**
  * Returns the sender's entity identity. The identity will be unknown if the local entity is a
  * trusted network client and the message was sent by a trusted network server using the local
  * entity's master token.
  *
  * @return the sender's entity identity or null if unknown.
  * @throws MslCryptoException if there is a crypto error accessing the entity identity;
  */
 public String getIdentity() throws MslCryptoException {
   final MessageHeader messageHeader = getMessageHeader();
   if (messageHeader != null) {
     final MasterToken masterToken = messageHeader.getMasterToken();
     if (masterToken != null) return masterToken.getIdentity();
     return messageHeader.getEntityAuthenticationData().getIdentity();
   }
   final ErrorHeader errorHeader = getErrorHeader();
   return errorHeader.getEntityAuthenticationData().getIdentity();
 }
  /**
   * Return the crypto context resulting from key response data contained in the provided header.
   *
   * <p>The {@link MslException}s thrown by this method will not have the entity or user set.
   *
   * @param ctx MSL context.
   * @param header header.
   * @param keyRequestData key request data for key exchange.
   * @return the crypto context or null if the header does not contain key response data or is for
   *     an error message.
   * @throws MslKeyExchangeException if there is an error with the key request data or key response
   *     data or the key exchange scheme is not supported.
   * @throws MslCryptoException if the crypto context cannot be created.
   * @throws MslEncodingException if there is an error parsing the JSON.
   * @throws MslMasterTokenException if the master token is not trusted and needs to be.
   * @throws MslEntityAuthException if there is a problem with the master token identity.
   */
  private static ICryptoContext getKeyxCryptoContext(
      final MslContext ctx, final MessageHeader header, final Set<KeyRequestData> keyRequestData)
      throws MslCryptoException, MslKeyExchangeException, MslEncodingException,
          MslMasterTokenException, MslEntityAuthException {
    // Pull the header data.
    final MessageHeader messageHeader = (MessageHeader) header;
    final MasterToken masterToken = messageHeader.getMasterToken();
    final KeyResponseData keyResponse = messageHeader.getKeyResponseData();

    // If there is no key response data then return null.
    if (keyResponse == null) return null;

    // If the key response data master token is decrypted then use the
    // master token keys to create the crypto context.
    final MasterToken keyxMasterToken = keyResponse.getMasterToken();
    if (keyxMasterToken.isDecrypted()) return new SessionCryptoContext(ctx, keyxMasterToken);

    // Perform the key exchange.
    final KeyExchangeScheme responseScheme = keyResponse.getKeyExchangeScheme();
    final KeyExchangeFactory factory = ctx.getKeyExchangeFactory(responseScheme);
    if (factory == null)
      throw new MslKeyExchangeException(MslError.KEYX_FACTORY_NOT_FOUND, responseScheme.name());

    // Attempt the key exchange but if it fails then try with the next
    // key request data before giving up.
    MslException keyxException = null;
    final Iterator<KeyRequestData> keyRequests = keyRequestData.iterator();
    while (keyRequests.hasNext()) {
      final KeyRequestData keyRequest = keyRequests.next();
      final KeyExchangeScheme requestScheme = keyRequest.getKeyExchangeScheme();

      // Skip incompatible key request data.
      if (!responseScheme.equals(requestScheme)) continue;

      try {
        return factory.getCryptoContext(ctx, keyRequest, keyResponse, masterToken);
      } catch (final MslKeyExchangeException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslEncodingException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslMasterTokenException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      } catch (final MslEntityAuthException e) {
        if (!keyRequests.hasNext()) throw e;
        keyxException = e;
      }
    }

    // We did not perform a successful key exchange. If we caught an
    // exception then throw that exception now.
    if (keyxException != null) {
      if (keyxException instanceof MslKeyExchangeException)
        throw (MslKeyExchangeException) keyxException;
      if (keyxException instanceof MslEncodingException) throw (MslEncodingException) keyxException;
      if (keyxException instanceof MslMasterTokenException)
        throw (MslMasterTokenException) keyxException;
      if (keyxException instanceof MslEntityAuthException)
        throw (MslEntityAuthException) keyxException;
      throw new MslInternalException(
          "Unexpected exception caught during key exchange.", keyxException);
    }

    // If we did not perform a successful key exchange then the
    // payloads will not decrypt properly. Throw an exception.
    throw new MslKeyExchangeException(
        MslError.KEYX_RESPONSE_REQUEST_MISMATCH, Arrays.toString(keyRequestData.toArray()));
  }
  /**
   * Construct a new message input stream. The header is parsed.
   *
   * <p>If key request data is provided and a matching key response data is found in the message
   * header the key exchange will be performed to process the message payloads.
   *
   * <p>Service tokens will be decrypted and verified with the provided crypto contexts identified
   * by token name. A default crypto context may be provided by using the empty string as the token
   * name; if a token name is not explcitly mapped onto a crypto context, the default crypto context
   * will be used.
   *
   * @param ctx MSL context.
   * @param source MSL input stream.
   * @param charset input stream character set encoding.
   * @param keyRequestData key request data to use when processing key response data.
   * @param cryptoContexts the map of service token names onto crypto contexts used to decrypt and
   *     verify service tokens.
   * @throws MslEncodingException if there is an error parsing the message.
   * @throws MslCryptoException if there is an error decrypting or verifying the header or creating
   *     the message payload crypto context.
   * @throws MslEntityAuthException if unable to create the entity authentication data.
   * @throws MslUserAuthException if unable to create the user authentication data.
   * @throws MslMessageException if the message master token is expired and the message is not
   *     renewable.
   * @throws MslMasterTokenException if the master token is not trusted and needs to be or if it has
   *     been revoked.
   * @throws MslUserIdTokenException if the user ID token has been revoked.
   * @throws MslKeyExchangeException if there is an error with the key request data or key response
   *     data or the key exchange scheme is not supported.
   * @throws MslMessageException if the message master token is expired and the message is not
   *     renewable.
   * @throws MslException if the message does not contain an entity authentication data or a master
   *     token, or a token is improperly bound to another token.
   */
  public MessageInputStream(
      final MslContext ctx,
      final InputStream source,
      final Charset charset,
      final Set<KeyRequestData> keyRequestData,
      final Map<String, ICryptoContext> cryptoContexts)
      throws MslEncodingException, MslEntityAuthException, MslCryptoException, MslUserAuthException,
          MslMessageException, MslKeyExchangeException, MslMasterTokenException,
          MslUserIdTokenException, MslMessageException, MslException {
    // Parse the header.
    this.source = source;
    this.tokener = new JSONTokener(new InputStreamReader(source, charset));
    final JSONObject jo;
    try {
      if (!this.tokener.more()) throw new MslEncodingException(MslError.MESSAGE_DATA_MISSING);
      final Object o = this.tokener.nextValue();
      if (!(o instanceof JSONObject)) throw new MslEncodingException(MslError.MESSAGE_FORMAT_ERROR);
      jo = (JSONObject) o;
    } catch (final JSONException e) {
      throw new MslEncodingException(MslError.JSON_PARSE_ERROR, "header", e);
    }
    this.header = Header.parseHeader(ctx, jo, cryptoContexts);

    try {
      // For error messages there are no key exchange or payload crypto
      // contexts.
      if (this.header instanceof ErrorHeader) {
        this.keyxCryptoContext = null;
        this.cryptoContext = null;
        return;
      }

      // Grab the key exchange crypto context, if any.
      final MessageHeader messageHeader = (MessageHeader) this.header;
      this.keyxCryptoContext = getKeyxCryptoContext(ctx, messageHeader, keyRequestData);

      // In peer-to-peer mode or in trusted network mode with no key
      // exchange the payload crypto context equals the header crypto
      // context.
      if (ctx.isPeerToPeer() || this.keyxCryptoContext == null)
        this.cryptoContext = messageHeader.getCryptoContext();

      // Otherwise the payload crypto context equals the key exchange
      // crypto context.
      else this.cryptoContext = this.keyxCryptoContext;

      // If this is a handshake message but it is not renewable or does
      // not contain key request data then reject the message.
      if (messageHeader.isHandshake()
          && (!messageHeader.isRenewable() || messageHeader.getKeyRequestData().isEmpty())) {
        throw new MslMessageException(
            MslError.HANDSHAKE_DATA_MISSING, messageHeader.toJSONString());
      }

      // If I am in peer-to-peer mode or the master token is verified
      // (i.e. issued by the local entity which is therefore a trusted
      // network server) then perform the master token checks.
      final MasterToken masterToken = messageHeader.getMasterToken();
      if (masterToken != null && (ctx.isPeerToPeer() || masterToken.isVerified())) {
        // If the master token has been revoked then reject the
        // message.
        final TokenFactory factory = ctx.getTokenFactory();
        final MslError revoked = factory.isMasterTokenRevoked(ctx, masterToken);
        if (revoked != null) throw new MslMasterTokenException(revoked, masterToken);

        // If the user ID token has been revoked then reject the
        // message. We know the master token is not null and that it is
        // verified so we assume the user ID token is as well.
        final UserIdToken userIdToken = messageHeader.getUserIdToken();
        if (userIdToken != null) {
          final MslError uitRevoked = factory.isUserIdTokenRevoked(ctx, masterToken, userIdToken);
          if (uitRevoked != null) throw new MslUserIdTokenException(uitRevoked, userIdToken);
        }

        // If the master token is expired...
        if (masterToken.isExpired(null)) {
          // If the message is not renewable or does not contain key
          // request data then reject the message.
          if (!messageHeader.isRenewable() || messageHeader.getKeyRequestData().isEmpty())
            throw new MslMessageException(MslError.MESSAGE_EXPIRED, messageHeader.toJSONString());

          // If the master token will not be renewed by the token
          // factory then reject the message.
          //
          // This throws an exception if the master token is not
          // renewable.
          final MslError notRenewable = factory.isMasterTokenRenewable(ctx, masterToken);
          if (notRenewable != null)
            throw new MslMessageException(
                notRenewable, "Master token is expired and not renewable.");
        }
      }

      // TODO: This is the old non-replayable logic for backwards
      // compatibility. It should be removed once all MSL stacks have
      // migrated to the newer non-replayable ID logic.
      //
      // If the message is non-replayable (it is not from a trusted
      // network server).
      if (messageHeader.isNonReplayable()) {
        // ...and not also renewable with key request data and a
        // master token then reject the message.
        if (!messageHeader.isRenewable()
            || messageHeader.getKeyRequestData().isEmpty()
            || masterToken == null) {
          throw new MslMessageException(
              MslError.INCOMPLETE_NONREPLAYABLE_MESSAGE, messageHeader.toJSONString());
        }

        // If the message does not have the newest master token
        // then notify the sender.
        final TokenFactory factory = ctx.getTokenFactory();
        if (!factory.isNewestMasterToken(ctx, masterToken))
          throw new MslMessageException(MslError.MESSAGE_REPLAYED, messageHeader.toJSONString());
      }

      // If the message is non-replayable (it is not from a trusted
      // network server).
      final Long nonReplayableId = messageHeader.getNonReplayableId();
      if (nonReplayableId != null) {
        // ...and does not include a master token then reject the
        // message.
        if (masterToken == null)
          throw new MslMessageException(
              MslError.INCOMPLETE_NONREPLAYABLE_MESSAGE, messageHeader.toJSONString());

        // If the non-replayable ID is not accepted then notify the
        // sender.
        final TokenFactory factory = ctx.getTokenFactory();
        final MslError replayed = factory.acceptNonReplayableId(ctx, masterToken, nonReplayableId);
        if (replayed != null) throw new MslMessageException(replayed, messageHeader.toJSONString());
      }
    } catch (final MslException e) {
      if (this.header instanceof MessageHeader) {
        final MessageHeader messageHeader = (MessageHeader) this.header;
        e.setEntity(messageHeader.getMasterToken());
        e.setEntity(messageHeader.getEntityAuthenticationData());
        e.setUser(messageHeader.getUserIdToken());
        e.setUser(messageHeader.getUserAuthenticationData());
        e.setMessageId(messageHeader.getMessageId());
      } else {
        final ErrorHeader errorHeader = (ErrorHeader) this.header;
        e.setEntity(errorHeader.getEntityAuthenticationData());
        e.setMessageId(errorHeader.getMessageId());
      }
      throw e;
    }
  }