1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
use std::io::Cursor;
use std::ops::Deref;
use std::fmt;
use std::result;
use std::error;

use base64;
use chrono;
use rand::{self, Rng};
use byteorder::{LittleEndian, WriteBytesExt};

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
    Short,
    Divisable,
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Short => fmt.write_str("Length must be >= 16"),
            Error::Divisable => fmt.write_str("Length must be divisable by 4")
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Short => "Length must be >= 16.",
            Error::Divisable => "Length must be divisable by 4."
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        None
    }
}

#[derive(Clone, PartialEq, Debug)]
pub struct TextNonce(pub String);

impl TextNonce {

    pub fn new() -> TextNonce {
        TextNonce::sized(32).unwrap()
    }

    pub fn sized(length: usize) -> Result<TextNonce> {
        TextNonce::sized_configured(length, base64::STANDARD)
    }

    pub fn sized_urlsafe(length: usize) -> Result<TextNonce> {
        TextNonce::sized_configured(length, base64::URL_SAFE)
    }

    pub fn sized_configured(length: usize, config: base64::Config) -> Result<TextNonce> {
        if length < 16 {
            return Err(Error::Short);
        }

        if length % 4 != 0 {
            return Err(Error::Divisable);
        }

        let bytelength: usize = (length / 4) * 3;

        let mut raw: Vec<u8> = Vec::with_capacity(bytelength);
        unsafe { raw.set_len(bytelength); }

        {
            let now = chrono::Utc::now();
            let secs: i64 = now.timestamp();
            let nsece: u32 = now.timestamp_subsec_nanos();

            let mut cursor = Cursor::new(&mut *raw);
            cursor.write_u32::<LittleEndian>(nsece).unwrap();
            cursor.write_i64::<LittleEndian>(secs).unwrap();
        }

        match rand::OsRng::new() {
            Ok(mut g) => g.fill_bytes(&mut raw[12..bytelength]),
            Err(_) => rand::thread_rng().fill_bytes(&mut raw[12..bytelength])
        }

        Ok(TextNonce(base64::encode_config(&raw, config)))
    }

    pub fn into_string(self) -> String {
        let TextNonce(s) = self;
        s
    }
}

impl fmt::Display for TextNonce {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl Deref for TextNonce {
    type Target = str;
    fn deref<'a>(&'a self) -> &'a str {
        &*self.0
    }
}