// Copyright 2017 Google Inc.
//
// 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 com.google.crypto.tink;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;

/**
 * An interface for streaming authenticated encryption with associated data.
 *
 * <p>Streaming encryption is typically used for encrypting large plaintexts such as large files.
 * Tink may eventually contain multiple interfaces for streaming encryption depending on the
 * supported properties. This interface supports a streaming interface for symmetric encryption with
 * authentication. The underlying encryption modes are selected so that partial plaintext can be
 * obtained fast by decrypting and authenticating just a part of the ciphertext.
 *
 * <h3>Security guarantees</h3>
 *
 * <p>Instances of StreamingAead must follow the nOAE definition as proposed in the paper "Online
 * Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance" by Hoang, Reyhanitabar, Rogaway
 * and Vizár https://eprint.iacr.org/2015/189.pdf
 *
 * <h3>Restrictions</h3>
 *
 * <p>Encryption must be done in one session. There is no possibility to modify an existing
 * ciphertext or append to it (other than reencrypt the whole file again). One reason for this
 * restriction is the use of AES-GCM as one cipher to implement this interface. If single segments
 * are modified then this is equivalent to reusing the same IV twice, but reusing an IV twice leaks
 * an AES-GCM key. Another reason is that implementations of this interface have no protection
 * against roll-back attacks: an attacker can always try to restore a previous version of the file
 * without detection.
 *
 * <h3>Blocking vs non-blocking I/O</h3>
 *
 * <p>A channel can be in a blocking mode (i.e. always waits until the requested number of bytes
 * have been processed) or non-blocking mode (i.e. I/O operation will never block and may transfer
 * fewer bytes than were requested or possibly no bytes at all).
 *
 * <p>If the channel provided to the streaming encryption is in blocking mode then encryption and
 * decryption have the same property. That is, encryption always processes all the plaintext passed
 * in, and waits until complete segments have been written to the ciphertext channel (incomplete
 * segment, if any, is buffered). Similarly, decryption blocks until sufficiently many bytes have
 * been read from the ciphertext channel so that all the requested plaintext can be decrypted and
 * authenticated, or until the end of the plaintext has been reached, or an IOException occurred.
 *
 * <p>If the channel provided to the streaming encryption is in non-blocking mode, then encryption
 * and decryption are also non-blocking. Since encryption and decryption is done in segments it is
 * possible that for example a call attempting to read() returns no plaintext at all even if partial
 * ciphertext was read from the underlying channel.
 *
 * <h3>Sample encryption</h3>
 *
 * <pre>{@code
 * StreamingAead s = ...
 * java.nio.channels.FileChannel ciphertextDestination =
 *     new FileOutputStream(ciphertextFile).getChannel();
 * byte[] aad = ...
 * WritableByteChannel encryptingChannel = s.newEncryptingChannel(ciphertextDestination, aad);
 * while ( ... ) {
 *   int r = encryptingChannel.write(buffer);
 *   ...
 * }
 * encryptingChannel.close();
 * }</pre>
 *
 * <h3>Sample full decryption</h3>
 *
 * <pre>{@code
 * StreamingAead s = ...
 * java.nio.channels.FileChannel ciphertextSource =
 *     new FileInputStream(ciphertextFile).getChannel();
 * byte[] aad = ...
 * ReadableByteChannel decryptingChannel = s.newDecryptingChannel(ciphertextSource, aad);
 * int chunkSize = ...
 * ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
 * do {
 *   buffer.clear();
 *   int cnt = decryptingChannel.read(buffer);
 *   if (cnt > 0) {
 *     // Process cnt bytes of plaintext.
 *   } else if (read == -1) {
 *     // End of plaintext detected.
 *     break;
 *   } else if (read == 0) {
 *     // No ciphertext is available at the moment.
 *   }
 * }
 * }</pre>
 *
 * @since 1.1.0
 */
public interface StreamingAead {

  /**
   * Returns a WritableByteChannel for plaintext.  Any data written to the returned
   * channel will be encrypted and the resulting ciphertext written to the provided
   * {@code ciphertextDestination}
   *
   * @param ciphertextDestination the channel to which the ciphertext is written.
   * @param associatedData data associated with the plaintext. This data is authenticated
   *     but not encrypted. It must be passed into the decryption.
   */
  WritableByteChannel newEncryptingChannel(
      WritableByteChannel ciphertextDestination, byte[] associatedData)
      throws GeneralSecurityException, IOException;

  /**
   * Returns a SeekableByteChannel that allows to access the plaintext.
   *
   * <p>This method does not work on Android Marshmallow (API level 23) or older because these
   * Android versions don't have the java.nio.channels.SeekableByteChannel interface.
   *
   * @param ciphertextSource the ciphertext
   * @param associatedData the data associated with the ciphertext.
   * @return {@link SeekableByteChannel} that allows random read access to the plaintext. The
   *     following methods of SeekableByteChannel are implemented:
   *     <ul>
   *       <li>{@code long position()} Returns the channel's position in the plaintext.
   *       <li>{@code SeekableByteChannel position(long newPosition)} Sets the channel's position.
   *           Setting the position to a value greater than the plaintext size is legal. A later
   *           attempt to read byte will immediately return an end-of-file indication.
   *       <li>{@code int read(ByteBuffer dst)} Bytes are read starting at the channel's position,
   *           and then the position is updated with the number of bytes actually read. All bytes
   *           returned have been authenticated. If the end of the stream has been reached -1 is
   *           returned. A result of -1 is authenticated (e.g. by checking the MAC of the last
   *           ciphertext chunk.) A call to this function attempts to fill dst, but it may return
   *           fewer bytes than requested, e.g. if the underlying ciphertextSource does not provide
   *           the requested number of bytes or if the plaintext ended.
   *           <p>Throws {@link IOException} if a MAC verification failed. TODO: Should we extend
   *           the interface with read(ByteBuffer dst, long position) to avoid race conditions?
   *       <li>{@code long size()} Returns the size of the plaintext. TODO: Decide whether the
   *           result should be authenticated)
   *       <li>{@code SeekableByteChannel truncate(long size)} throws {@link
   *           java.nio.channels.NonWritableChannelException } because the channel is read-only.
   *       <li>{@code int write(ByteBuffer src)} throws {@link
   *           java.nio.channels.NonWritableChannelException } because the channel is read-only.
   *       <li>{@code close()} closes the channel
   *       <li>{@code isOpen()}
   *     </ul>
   *
   * @throws GeneralSecurityException if the header of the ciphertext is corrupt or if
   *     associatedData is not correct.
   * @throws IOException if an IOException occurred while reading from ciphertextDestination.
   */
  SeekableByteChannel newSeekableDecryptingChannel(
      SeekableByteChannel ciphertextSource, byte[] associatedData)
      throws GeneralSecurityException, IOException;

  ReadableByteChannel newDecryptingChannel(
      ReadableByteChannel ciphertextSource, byte[] associatedData)
      throws GeneralSecurityException, IOException;

  /**
   * Returns a wrapper around {@code ciphertextDestination}, such that any write-operation via
   * the wrapper results in AEAD-encryption of the written data, using {@code associatedData}
   * as associated authenticated data. The associated data is not included in the ciphertext
   * and has to be passed in as parameter for decryption.
   */
  OutputStream newEncryptingStream(OutputStream ciphertextDestination, byte[] associatedData)
      throws GeneralSecurityException, IOException;

  /**
   * Returns a wrapper around {@code ciphertextSource}, such that any read-operation
   * via the wrapper results in AEAD-decryption of the underlying ciphertext,
   * using {@code associatedData} as associated authenticated data.
   *
   * <p>The returned InputStream may support {@code mark()}/{@code reset()},
   *    but does not have to do it -- {@code markSupported()} provides the corresponding info.
   *
   * <p>The returned InputStream supports {@code skip()}, yet possibly in an inefficient way,
   *    i.e. by reading a sequence of blocks until the desired position. If a more efficient
   *    {@code skip()}-functionality is needed, the Channel-based API can be used.
   */
  InputStream newDecryptingStream(InputStream ciphertextSource, byte[] associatedData)
      throws GeneralSecurityException, IOException;
}
