
#ifndef INCLUDED_BOBCAT_DIFFIEHELLMAN_
#define INCLUDED_BOBCAT_DIFFIEHELLMAN_

#include <cstdint>
#include <iosfwd>

#include <bobcat/bigint>
#include <openssl/dh.h>

// For openssl1.1.0 see, e.g. DH_get0_pqg

namespace FBB
{

class DiffieHellman
{
    DH *d_dh;
    BIGNUM *d_otherPubKey;      // public key of the other party
    BigInt d_prime;

    mutable BIGNUM const *d_pubKey; // assigned by DH_get0_key: no need
    mutable BIGNUM const *d_privKey;// to use BN_free

    static char const *s_header;

    public:
        enum SecretKey
        {
            DONT_SAVE_SECRET_KEY,
            SAVE_SECRET_KEY
        };

            // The initiator calls this constructor to compute the common
            // DH parameters
        DiffieHellman(size_t primeLength = 1024, size_t generator = 5, // 1
                      bool progress = false);

            // The initiator saves the public info (prime (p), generator (g) 
            // and the initiator's public key (g^k % p) on basename.pub
            // and, by providing 'SAVE_SECRET_KEY, the initiator's secret key 
            // (k) on basename.sec 
        void save(std::string const &basename, SecretKey action = 
                                                DONT_SAVE_SECRET_KEY) const;

            // The initiator sends basename.pub to the peer, who reads
            // basename.pub using this constructor:
        DiffieHellman(std::string const &initiatorPublicFileName);

            // Alternatively, use this constructor expecting an istream:
        DiffieHellman(std::istream &initiatorPublicStream);

            // The peer now saves *his/her* public and (optionally) private
            // info: by calling save, providing a basename and optionally a
            // SecretKey argument.  Next, the peer sends his/her public info
            // to the initiator. 

            // The peer can already now determine the symmetric encryption
            // key (the shared key), since he/she has 
            //  - The DH prime and generator (received from the initiator)
            //  - The initiator's public key (received from the initiator)
            //  - His/her own secret key.
            // The shared key is returned in a string by calling
        std::string key() const;                      // 1

            // The initiator has two options: - After calling save and
            // transmitting the public data to the peer the DiffieHellman
            // object is kept, and the initiator waits for the peer's public
            // key to become available. In that case the initiator's private
            // key doesn't have to be saved, and ephemeral DH is
            // obtained. After receiving the peer's public parameters the
            // initiator calls either of these overloaded versions of key to
            // obtain the symmetric key:
        std::string key(std::string const &peerPublicFileName); // 2
        std::string key(std::istream &peerPublicStream);        // 3

            // Otherwise the initiator creates another DiffieHellman object, 
            // using the 4th or 5th constructor, and then calls either of the
            // last two key members to obtain the symmetric key.

        DiffieHellman(std::string const &initiatorPublicFileName,
                      std::string const &initiatorPrivateFileName);
            // Alternatively, use this constructor expecting istreams:
        DiffieHellman(std::istream &initiatorPublicStream,      // 5
                      std::istream &initiatorPrivateStream);

        ~DiffieHellman();

    private:
        void checkDHparameters();

        bool load(std::istream &publicData, BIGNUM **pub_key);  // *pub_key is
                                            // allocated inside this function
                        

        void write(std::ostream &out, BIGNUM const *bn, char *buffer, 
                                                    uint32_t nBytes) const;

        bool read(std::istream &in, BIGNUM **dest);

        static void skip(std::istream &in, size_t count);
        static int callback(int, int, BN_GENCB *);

        std::string publicKey() const;
        std::string sharedKey(BigInt const &peersPublicKey);
};

} // FBB        
#endif
