class WebPushHybridDecrypt implements HybridDecrypt {

  @Override
  public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo /* unused */)
      throws GeneralSecurityException {
    if (contextInfo != null) {
      throw new GeneralSecurityException("contextInfo must be null because it is unused");
    }

    if (ciphertext.length < WebPushConstants.CIPHERTEXT_OVERHEAD) {
      throw new GeneralSecurityException("ciphertext too short");
    }

    // A push service is not required to support more than 4096 octets of
    // payload body. See https://tools.ietf.org/html/rfc8291#section-4.0.
    if (ciphertext.length > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
      throw new GeneralSecurityException("ciphertext too long");
    }

    // Unpacking.
    ByteBuffer record = ByteBuffer.wrap(ciphertext);
    byte[] salt = new byte[WebPushConstants.SALT_SIZE];
    record.get(salt);

    int recordSize = record.getInt();
    if (recordSize != this.recordSize
        || recordSize < ciphertext.length
        || recordSize > WebPushConstants.MAX_CIPHERTEXT_SIZE) {
      throw new GeneralSecurityException("invalid record size: " + recordSize);
    }

    int publicKeySize = (int) record.get();
    if (publicKeySize != WebPushConstants.PUBLIC_KEY_SIZE) {
      throw new GeneralSecurityException("invalid ephemeral public key size: " + publicKeySize);
    }

    byte[] asPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
    record.get(asPublicKey);
    ECPoint asPublicPoint =
        EllipticCurves.pointDecode(
            WebPushConstants.NIST_P256_CURVE_TYPE,
            WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
            asPublicKey);

    byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
    record.get(payload);

    // See https://tools.ietf.org/html/rfc8291#section-3.4.
    byte[] ecdhSecret = EllipticCurves.computeSharedSecret(recipientPrivateKey, asPublicPoint);
    byte[] ikm = WebPushUtil.computeIkm(ecdhSecret, authSecret, recipientPublicKey, asPublicKey);
    byte[] cek = WebPushUtil.computeCek(ikm, salt);
    byte[] nonce = WebPushUtil.computeNonce(ikm, salt);

    return decrypt(cek, nonce, payload);
  }

  private  WebPushHybridDecrypt(Builder builder);

}