#![deny(missing_docs)]
#![cfg_attr(feature = "bench", feature(test))]
extern crate byteorder;
extern crate rand;
extern crate resize_slice;
extern crate time;
mod base62;
use std::io;
use std::ascii::AsciiExt;
use byteorder::{ByteOrder, BigEndian};
use time::{Timespec, Duration};
use rand::{Rng, Rand};
pub const EPOCH: Timespec = Timespec {sec: 1_400_000_000, nsec: 0};
const LEN: usize = 20;
const EMPTY: [u8; LEN] = [0; LEN];
const BASE62_LEN: usize = 27;
const HEX_LEN: usize = 40;
const HEX_DIGITS: &[u8] = b"0123456789ABCDEF";
const MAX_BASE62_KSUID: &[u8] = b"aWgEPTl1tmebfsQzFP4bxwgy80V";
fn hex_digit(c: u8) -> io::Result<u8> {
HEX_DIGITS.iter()
.position(|d| c.eq_ignore_ascii_case(d))
.map(|idx| idx as u8)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid hex character in input"))
}
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
pub struct Ksuid([u8; LEN]);
impl Ksuid {
pub fn new(timestamp: u32, payload: [u8; 16]) -> Self {
let mut ret = Ksuid(EMPTY);
ret.set_timestamp(timestamp);
ret.set_payload(payload);
ret
}
pub fn with_payload(payload: [u8; 16]) -> Self {
let elapsed = time::get_time() - EPOCH;
let ts = elapsed.num_seconds() as u32;
Self::new(ts, payload)
}
pub fn generate() -> Self {
rand::random()
}
pub fn from_base62(s: &str) -> io::Result<Self> {
let bytes = s.as_bytes();
if bytes.len() != BASE62_LEN || bytes > MAX_BASE62_KSUID {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid id"));
}
let mut ret = Ksuid(EMPTY);
let mut scratch = [0; BASE62_LEN];
scratch.clone_from_slice(bytes);
base62::decode_raw(scratch.as_mut(), ret.0.as_mut())?;
Ok(ret)
}
pub fn from_hex(hex: &str) -> io::Result<Self> {
if hex.len() != HEX_LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Hex string must be 40 bytes long"));
}
let mut ret = Ksuid(EMPTY);
for (pair, place) in hex.as_bytes().chunks(2).zip(ret.0.iter_mut()) {
let upper = hex_digit(pair[0])?;
let lower = hex_digit(pair[1])?;
*place = (upper * 16 + lower) as u8;
}
Ok(ret)
}
pub fn from_bytes(raw: &[u8]) -> io::Result<Self> {
if raw.len() != LEN {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Ksuids are 20 bytes long"));
}
let mut ret = Ksuid(EMPTY);
ret.0.copy_from_slice(raw);
Ok(ret)
}
pub fn to_base62(&self) -> String {
let mut scratch = self.0;
let mut out = vec![0; BASE62_LEN];
base62::encode_raw(scratch.as_mut(), out.as_mut());
unsafe { String::from_utf8_unchecked(out) }
}
pub fn to_hex(&self) -> String {
let mut ret = Vec::with_capacity(HEX_LEN);
for b in self.as_bytes() {
ret.push(HEX_DIGITS[(b / 16) as usize]);
ret.push(HEX_DIGITS[(b % 16) as usize]);
}
unsafe { String::from_utf8_unchecked(ret) }
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
pub fn timestamp(&self) -> u32 {
BigEndian::read_u32(self.0.as_ref())
}
pub fn set_timestamp(&mut self, timestamp: u32) {
BigEndian::write_u32(self.0.as_mut(), timestamp);
}
pub fn time(&self) -> Timespec {
EPOCH + Duration::seconds(self.timestamp().into())
}
pub fn set_time(&mut self, time: Timespec) {
let dur = time - EPOCH;
self.set_timestamp(dur.num_seconds() as u32);
}
pub fn payload(&self) -> &[u8] {
&self.0[4..]
}
pub fn set_payload(&mut self, payload: [u8; 16]) {
(&mut self.0[4..]).copy_from_slice(payload.as_ref());
}
}
impl Rand for Ksuid {
fn rand<R: Rng>(rng: &mut R) -> Self {
Self::with_payload(rng.gen())
}
}
#[cfg(feature = "bench")]
#[cfg(test)]
mod tests {
extern crate test;
use super::*;
#[bench]
fn bench_from_base62(b: &mut test::Bencher) {
let encoded = ::std::str::from_utf8(MAX_BASE62_KSUID).unwrap();
b.iter(|| {
Ksuid::from_base62(encoded)
})
}
#[bench]
fn bench_to_base62(b: &mut test::Bencher) {
let ksuid = Ksuid::from_bytes(&[255; LEN]).unwrap();
b.iter(|| {
ksuid.to_base62()
})
}
#[bench]
fn bench_to_hex(b: &mut test::Bencher) {
let ksuid = Ksuid::from_bytes(&[255; LEN]).unwrap();
b.iter(|| {
ksuid.to_hex()
})
}
#[bench]
fn bench_from_hex(b: &mut test::Bencher) {
b.iter(|| {
Ksuid::from_hex("ffffffffffffffffffffffffffffffffffffffff")
})
}
#[bench]
fn bench_gen(b: &mut test::Bencher) {
b.iter(|| {
Ksuid::generate()
})
}
#[bench]
fn bench_gen_lock_rng(b: &mut test::Bencher) {
let mut rng = rand::thread_rng();
b.iter(|| {
rng.gen::<Ksuid>()
})
}
}