//! Utility crate that creates cryptographic keys
//!
//! This crate provides an easy API to create and work with
//! cryptographic keys. For now only Aes128 (32 byte) and Aes256 (64 byte)
//! keys are supported but more can be added easily.
//!
//! To create a key, you can either generate one from scratch or use
//! a user provided password/ name (as salt) combination.
//!
//! ```rust
//! extern crate keybob;
//! use keybob::{Key, KeyType};
//! let key = Key::new(KeyType::Aes256);
//! ```
//!
//! Additionally, all `keybob` keys are easily serialized with `serde`
//!
//! ```rust
//! extern crate serde_json;
//! extern crate keybob;
//! use keybob::{Key, KeyType};
//! let k = Key::new(KeyType::Aes128);
//! let ser = serde_json::to_string(&k).unwrap();
//! let de: Key = serde_json::from_str(&ser).unwrap();
//! assert_eq!(k, de);
//! ```
//!
//! `Key::from_pw` uses the `pbkdf2` key derivation function with
//! a `Hmac<Blake2b>` hash to expand a user password/name (salt) into
//! a key object of a certain type.
//!
//! To then use the key, use `as_slice()` or `as_mut_slice()` on the
//! key object.
//!
//! For more examples, check the [tests] directory
//!
//! [tests]: https://github.com/spacekookie/keybob/tree/master/tests

#[macro_use]
extern crate serde_derive;
extern crate base64;
extern crate blake2;
extern crate hmac;
extern crate pbkdf2;
extern crate rand;
extern crate serde;

mod random;
mod serial;

use std::fmt;

/// The type of key that should be created
///
/// Each key variant has a number value attached to it which
/// represents the length of the key it represents.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum KeyType {
    Aes128 = 32,
    Aes256 = 64,
}

/// A crytographic key of a certain type
///
/// Uses a `union` to allow key inlining without heap allocation.
/// Provides easy to use constructors for generation and derivation
/// from user input/name.
///
/// Also implements `serde::{Serialize, Deserialize}` via a custom
/// serialiser which allows easy encryption of the key itself.
#[derive(Clone)]
pub struct Key {
    tt: KeyType,
    key: KeyBuf,
}

#[derive(Clone, Copy)]
union KeyBuf {
    _32: [u8; 32],
    _64: [u8; 64],
}

impl Key {
    /// Generate a new, completely random key of a certain type
    pub fn new(tt: KeyType) -> Self {
        Self {
            tt: tt.clone(),
            key: random::generate(tt),
        }
    }

    /// Generate a new key based on a user password
    ///
    /// This function uses the pbkdf2 algorithm to deterministically
    /// expand an input pw/username combination into a key.
    pub fn from_pw(tt: KeyType, pw: &str, user: &str) -> Self {
        Key {
            tt: tt.clone(),
            key: random::expand(tt, pw, user),
        }
    }

    /// Read the key as an imutable slice
    pub fn as_slice(&self) -> &[u8] {
        unsafe {
            match self.tt {
                KeyType::Aes128 => &self.key._32,
                KeyType::Aes256 => &self.key._64,
            }
        }
    }

    /// Read the key as a mutable slice
    pub fn as_mut_slice(&mut self) -> &mut [u8] {
        unsafe {
            match self.tt {
                KeyType::Aes128 => &mut self.key._32,
                KeyType::Aes256 => &mut self.key._64,
            }
        }
    }

    /// Return the length of the key in bytes
    pub fn len(&self) -> usize {
        match self.tt {
            KeyType::Aes128 => 32,
            KeyType::Aes256 => 64,
        }
    }
}

impl PartialEq for Key {
    fn eq(&self, o: &Self) -> bool {
        if self.tt != o.tt {
            return false;
        }
        use KeyType::*;

        let len = match self.tt {
            Aes128 => 32,
            Aes256 => 64,
        };

        let mut ret = true;
        (0..len).for_each(|i| unsafe {
            if match self.tt {
                Aes128 => self.key._32[i] != o.key._32[i],
                Aes256 => self.key._64[i] != o.key._64[i],
            } {
                ret = false;
            }
        });
        ret
    }
}

impl Eq for Key {}

impl fmt::Debug for Key {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Key: type: {} – {:?}", &self.tt, unsafe {
            match &self.tt {
                KeyType::Aes128 => base64::encode(&self.key._32[..4]),
                KeyType::Aes256 => base64::encode(&self.key._64[..4]),
            }
        })
    }
}

impl fmt::Display for KeyType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                KeyType::Aes128 => "Aes128",
                KeyType::Aes256 => "Aes256",
            }
        )
    }
}
