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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// Copyright 2016  Jonas mg
// See the 'AUTHORS' file at the top-level directory for a full list of authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Generates numeric identifiers.

use std::io;
use std::time;

extern crate byteorder;
extern crate rand;

use byteorder::{ReadBytesExt, WriteBytesExt};

mod errors;
pub use errors::*;

/// Generates a Combid (Combined Identifier), a combination of a timestamp and
/// some random bits. The timestamp ensures they are ordered chronologically,
/// and the random bits ensure that each ID is unique, even if thousands of
/// people are creating IDs at the same time.
///
/// timestamp     - 5 bytes (40 bits) - 4 bytes from seconds and the other ones from nanoseconds  
/// random number - 3 bytes (24 bits) - gives us up to 16_777_216 possible values
///
/// # Examples
///
/// ```
/// extern crate combid;
/// extern crate rand;
///
/// let mut rng = rand::thread_rng();
///
/// let id = match combid::gen(&mut rng) {
///     Ok(v) => v,
///     Err(e) => panic!(e), // handle error
/// };
/// println!("combid: {}", id);
/// ```
///
pub fn gen<R: rand::Rng>(rng: &mut R) -> Result<i64, IdError> {
    let now = match time::SystemTime::now().duration_since(time::UNIX_EPOCH) {
        Ok(v) => v,
        Err(e) => return Err(IdError::Time(e)),
    };

    let mut v_time_sec: Vec<u8> = Vec::with_capacity(8);
    match v_time_sec.write_u64::<byteorder::BigEndian>(now.as_secs()) {
        Ok(_) => (),
        Err(e) => return Err(IdError::Io(e)),
    }

    let mut v_time_nsec: Vec<u8> = Vec::with_capacity(4);
    match v_time_nsec.write_u32::<byteorder::BigEndian>(now.subsec_nanos()) {
        Ok(_) => (),
        Err(e) => return Err(IdError::Io(e)),
    }

    let mut v_rand: Vec<u8> = Vec::with_capacity(4);
    match v_rand.write_u32::<byteorder::BigEndian>(rng.gen()) {
        Ok(_) => (),
        Err(e) => return Err(IdError::Io(e)),
    }

    let array: [u8; 8] = [v_time_sec[4],
                          v_time_sec[5],
                          v_time_sec[6],
                          v_time_sec[7],
                          v_time_nsec[0],
                          v_rand[0],
                          v_rand[1],
                          v_rand[2]];

    let mut rd = io::Cursor::new(array);
    match rd.read_i64::<byteorder::BigEndian>() {
        Ok(v) => Ok(v),
        Err(e) => return Err(IdError::Io(e)),
    }
}

/// Generates an identifier based in the current time.
pub fn gen_timeid() -> Result<i64, IdError> {
    let now = match time::UNIX_EPOCH.elapsed() {
        Ok(v) => v,
        Err(e) => return Err(IdError::Time(e)),
    };

    let mut v_time_sec: Vec<u8> = Vec::with_capacity(8);
    match v_time_sec.write_u64::<byteorder::BigEndian>(now.as_secs()) {
        Ok(_) => (),
        Err(e) => return Err(IdError::Io(e)),
    }

    let mut v_time_nsec: Vec<u8> = Vec::with_capacity(4);
    match v_time_nsec.write_u32::<byteorder::BigEndian>(now.subsec_nanos()) {
        Ok(_) => (),
        Err(e) => return Err(IdError::Io(e)),
    }

    let array: [u8; 8] = [v_time_sec[4],
                          v_time_sec[5],
                          v_time_sec[6],
                          v_time_sec[7],
                          v_time_nsec[0],
                          v_time_nsec[1],
                          v_time_nsec[2],
                          v_time_nsec[3]];

    let mut rdr = io::Cursor::new(array);
    match rdr.read_i64::<byteorder::BigEndian>() {
        Ok(v) => Ok(v),
        Err(e) => return Err(IdError::Io(e)),
    }
}

// == Iterators
//

/// Iterator which will generate combids using a thread-local RNG.
pub struct Generator<T> {
    rng: T,
}

impl<T: rand::Rng> Generator<T> {
    pub fn new(rng: T) -> Generator<T> {
        Generator { rng: rng }
    }
}

impl<T: rand::Rng> Iterator for Generator<T> {
    type Item = Result<i64, IdError>;

    fn next(&mut self) -> Option<Result<i64, IdError>> {
        match gen(&mut self.rng) {
            Ok(v) => Some(Ok(v)),
            Err(e) => Some(Err(e)),
        }
    }
}

/// Iterator which will generate identifiers based in the current time.
///
/// # Examples
///
/// ```
/// let mut gen_time = combid::TimeGenerator {};
///
/// let timeid = match gen_time.next() {
///     Some(v) => {
///         match v {
///             Ok(v) => v,
///             Err(e) => panic!(e), // handle error
///         }
///     },
///     None => unreachable!(),
/// };
/// println!("timeid: {}", timeid);
/// ```
///
pub struct TimeGenerator {}

impl Iterator for TimeGenerator {
    type Item = Result<i64, IdError>;

    fn next(&mut self) -> Option<Result<i64, IdError>> {
        match gen_timeid() {
            Ok(v) => Some(Ok(v)),
            Err(e) => Some(Err(e)),
        }
    }
}

// == Tests
//

#[cfg(test)]
mod tests {
    use std::time;

    use byteorder::{BigEndian, WriteBytesExt};
    use rand;

    use super::*;

    #[test]
    // Check the bytes filled at marshaling a time.
    fn check_bytes_time() {
        let now = time::UNIX_EPOCH.elapsed().unwrap();
        let mut v_time_sec: Vec<u8> = Vec::with_capacity(8);
        v_time_sec.write_u64::<BigEndian>(now.as_secs()).unwrap();

        // Should be like [0, 0, 0, 0, 87, 89, 71, 199]
        assert_eq!(v_time_sec[3], 0);
    }

    #[test]
    fn generators() {
        let mut rng = rand::thread_rng();

        let id = gen(&mut rng).unwrap();
        assert!(id > 0);

        let id = gen_timeid().unwrap();
        assert!(id > 0);
    }

    #[test]
    fn iterators() {
        let mut rng = rand::thread_rng();
        let mut gen = Generator::new(&mut rng);
        let mut result: Result<i64, IdError>;

        print!("\n== Generating Combids\n");
        for _ in 0..5 {
            result = gen.next().unwrap();
            println!("{:?}", result);
        }

        print!("\n== Generating time ids\n");
        let mut gen_time = TimeGenerator {};
        for _ in 0..5 {
            result = gen_time.next().unwrap();
            println!("{:?}", result);
        }
        println!("");
    }
}