package de.tum.p2p.onion.auth;

import de.tum.p2p.Peer;
import lombok.val;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Optional;

/**
 * {@code OnionAuthorizer} encapsulates authentication and encryption
 * mechanisms used while building onion tunnels. It implements establishing
 * session keys given hostkeys of hops, and onion layer-encryption &
 * decryption of payload data.
 *
 * @see <a href="https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange">
 * Wikipedia - Diffie–Hellman key exchange</a>
 */
public interface OnionAuthorizer {

    /**
     * Returns all active {@link Session}s that passed Diffie–Hellman key
     * exchange
     *
     * @return list of active sessions
     */
    List<Session> sessions();

    /**
     * Creates and instance of {@link SessionFactory} that is used to perform
     * Diffie–Hellman key exchange and create new {@link Session} (shared secret)
     *
     * @return a new SessionFactory instance
     */
    SessionFactory sessionFactory();

    /**
     * Queries all active {@link Session}s for one with specific origin and
     * destination {@link Peer}s
     *
     * @param origin      a data Tunnel's origin {@link Peer}
     * @param destination a data Tunnel's destination {@link Peer}
     * @return a corresponding {@link Session}
     */
    default Optional<Session> findSession(Peer origin, Peer destination) {
        return sessions().stream().filter(s -> s.origin().equals(origin))
                                  .filter(s -> s.destination().equals(destination))
                                  .findFirst();
    }

    /**
     * Queries all active {@link Session}s for one with specific origin {@link Peer}
     *
     * @param destination a data Tunnel's destination {@link Peer}
     * @return a corresponding {@link Session}
     */
    default Optional<Session> findSession(Peer destination) {
        return sessions().stream().filter(s -> s.destination().equals(destination))
                                  .findAny();
    }

    /**
     * Encapsulates plaintext in layers of encryption, analogous to layers of an onion.
     *
     * @param plaintext a plaintext to encrypt
     * @param session   {@link Session}s used for encryption
     * @param sessions  additional {@link Session}s used for layered encryption
     * @return a ciphertext
     * @throws OnionEncryptionException in case of problems during data encryption
     */
    Ciphertext encrypt(ByteBuffer plaintext, Session session, Session... sessions) throws OnionEncryptionException;

    default Ciphertext encrypt(byte[] plaintext, Session session, Session... sessions) throws OnionEncryptionException {
        return encrypt(ByteBuffer.wrap(plaintext), session, sessions);
    }

    default Ciphertext encrypt(ByteBuffer plaintext, List<Session> sessions) throws OnionEncryptionException {
        if (sessions.isEmpty())
            throw new IllegalArgumentException("At least one session is required for encryption");

        if (sessions.size() == 1)
            return encrypt(plaintext, sessions.get(0));

        val sessionArg = sessions.get(0);
        val sessionsArg = sessions.stream().skip(1).toArray(Session[]::new);

        return encrypt(plaintext, sessionArg, sessionsArg);
    }

    default Ciphertext encrypt(byte[] plaintext, List<Session> sessions) throws OnionEncryptionException {
        return encrypt(ByteBuffer.wrap(plaintext), sessions);
    }

    /**
     * Peels away a single layer of encryption made by
     * {@link OnionAuthorizer#encrypt(ByteBuffer, Session, Session...)}
     *
     * @param ciphertext a ciphertext to decrypt
     * @param session    a {@link Session} used for decryption one layer
     * @return decrypted ciphertext
     * @throws OnionDecryptionException in case of problems during data encryption
     */
    Deciphertext decrypt(ByteBuffer ciphertext, Session session) throws OnionDecryptionException;

    default Deciphertext decrypt(Ciphertext ciphertext, Session session) throws OnionDecryptionException {
        return decrypt(ciphertext.bytesBuffer(), session);
    }

    default Deciphertext decrypt(byte[] ciphertext, Session session) throws OnionDecryptionException {
        return decrypt(ByteBuffer.wrap(ciphertext), session);
    }
}

@@ -146,16 +146,63 @@
 ptional;
+%0Aimport java.util.concurrent.CompletableFuture;
 %0A%0A/**%0A *
@@ -2500,16 +2500,34 @@
  */%0A    
+CompletableFuture%3C
 Cipherte
@@ -2520,32 +2520,33 @@
 uture%3CCiphertext
+%3E
  encrypt(ByteBuf
@@ -2589,32 +2589,44 @@
 ion... sessions)
+%0A           
  throws OnionEnc
@@ -2648,32 +2648,50 @@
 n;%0A%0A    default 
+CompletableFuture%3C
 Ciphertext encry
@@ -2676,32 +2676,33 @@
 uture%3CCiphertext
+%3E
  encrypt(byte%5B%5D 
@@ -2741,32 +2741,44 @@
 ion... sessions)
+%0A           
  throws OnionEnc
@@ -2788,32 +2788,33 @@
 tionException %7B%0A
+%0A
         return e
@@ -2879,32 +2879,50 @@
  %7D%0A%0A    default 
+CompletableFuture%3C
 Ciphertext encry
@@ -2907,32 +2907,33 @@
 uture%3CCiphertext
+%3E
  encrypt(ByteBuf
@@ -2962,32 +2962,44 @@
 ssion%3E sessions)
+%0A           
  throws OnionEnc
@@ -3009,32 +3009,33 @@
 tionException %7B%0A
+%0A
         if (sess
@@ -3440,16 +3440,34 @@
 default 
+CompletableFuture%3C
 Cipherte
@@ -3468,16 +3468,17 @@
 phertext
+%3E
  encrypt
@@ -3519,16 +3519,28 @@
 essions)
+%0A           
  throws 
@@ -3558,32 +3558,33 @@
 tionException %7B%0A
+%0A
         return e
@@ -4031,16 +4031,34 @@
  */%0A    
+CompletableFuture%3C
 Decipher
@@ -4053,32 +4053,33 @@
 ure%3CDeciphertext
+%3E
  decrypt(ByteBuf
@@ -4149,32 +4149,50 @@
 n;%0A%0A    default 
+CompletableFuture%3C
 Deciphertext dec
@@ -4179,32 +4179,33 @@
 ure%3CDeciphertext
+%3E
  decrypt(Ciphert
@@ -4228,32 +4228,44 @@
 Session session)
+%0A           
  throws OnionDec
@@ -4275,32 +4275,33 @@
 tionException %7B%0A
+%0A
         return d
@@ -4362,16 +4362,34 @@
 default 
+CompletableFuture%3C
 Decipher
@@ -4392,16 +4392,17 @@
 phertext
+%3E
  decrypt
@@ -4429,32 +4429,44 @@
 Session session)
+%0A           
  throws OnionDec
@@ -4484,16 +4484,17 @@
 ption %7B%0A
+%0A
         

Use Futures for encrypt/decrypt in Onion Auth (#48)