/**
   * 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()));
  }