Skip to content

Commit 4110055

Browse files
committed
Add oid generation
1 parent 216b906 commit 4110055

File tree

5 files changed

+417
-5
lines changed

5 files changed

+417
-5
lines changed

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ homepage = "https://github.com/zonyitoo/bson-rs"
1111
name = "bson"
1212

1313
[dependencies]
14-
chrono = "*"
15-
byteorder = "*"
16-
rustc-serialize = "*"
14+
byteorder = "0.3"
15+
chrono = "0.2"
16+
libc = "0.1"
17+
rand = "0.3"
18+
rust-crypto = "0.2.31"
19+
rustc-serialize = "0.3"
20+
time = "0.1"

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,20 @@
4242
//! }
4343
//! ```
4444
45-
extern crate rustc_serialize;
46-
extern crate chrono;
4745
extern crate byteorder;
46+
extern crate chrono;
47+
extern crate crypto;
48+
extern crate libc;
49+
extern crate rand;
50+
extern crate rustc_serialize;
51+
extern crate time;
4852

4953
pub use self::bson::{Bson, Document, Array};
5054
pub use self::encoder::{encode_document, EncoderResult, EncoderError};
5155
pub use self::decoder::{decode_document, DecoderResult, DecoderError};
5256

5357
pub mod spec;
58+
pub mod oid;
5459
mod bson;
5560
mod encoder;
5661
mod decoder;

src/oid.rs

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
use libc;
2+
3+
use crypto::digest::Digest;
4+
use crypto::md5::Md5;
5+
6+
use byteorder::{ByteOrder, BigEndian, LittleEndian};
7+
use rand::{Rng, OsRng};
8+
use rustc_serialize::hex::{self, FromHex};
9+
use time;
10+
11+
use std::{fmt, io, error, result};
12+
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
13+
14+
const TIMESTAMP_SIZE: usize = 4;
15+
const MACHINE_ID_SIZE: usize = 3;
16+
const PROCESS_ID_SIZE: usize = 2;
17+
const COUNTER_SIZE: usize = 3;
18+
19+
const TIMESTAMP_OFFSET: usize = 0;
20+
const MACHINE_ID_OFFSET: usize = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
21+
const PROCESS_ID_OFFSET: usize = MACHINE_ID_OFFSET + MACHINE_ID_SIZE;
22+
const COUNTER_OFFSET: usize = PROCESS_ID_OFFSET + PROCESS_ID_SIZE;
23+
24+
const MAX_U24: usize = 0xFFFFFF;
25+
26+
static OID_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
27+
static mut MACHINE_BYTES: [u8; 3] = [0; 3];
28+
29+
extern {
30+
fn gethostname(name: *mut libc::c_char, size: libc::size_t) -> libc::c_int;
31+
}
32+
33+
#[derive(Debug)]
34+
pub enum OIDError {
35+
ArgumentError(String),
36+
FromHexError(hex::FromHexError),
37+
IoError(io::Error),
38+
HostnameError,
39+
}
40+
41+
impl From<hex::FromHexError> for OIDError {
42+
fn from(err: hex::FromHexError) -> OIDError {
43+
OIDError::FromHexError(err)
44+
}
45+
}
46+
47+
impl From<io::Error> for OIDError {
48+
fn from(err: io::Error) -> OIDError {
49+
OIDError::IoError(err)
50+
}
51+
}
52+
53+
pub type Result<T> = result::Result<T, OIDError>;
54+
55+
impl fmt::Display for OIDError {
56+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
57+
match self {
58+
&OIDError::ArgumentError(ref inner) => inner.fmt(fmt),
59+
&OIDError::FromHexError(ref inner) => inner.fmt(fmt),
60+
&OIDError::IoError(ref inner) => inner.fmt(fmt),
61+
&OIDError::HostnameError => write!(fmt, "Failed to retrieve hostname for OID generation."),
62+
}
63+
}
64+
}
65+
66+
impl error::Error for OIDError {
67+
fn description(&self) -> &str {
68+
match self {
69+
&OIDError::ArgumentError(ref inner) => &inner,
70+
&OIDError::FromHexError(ref inner) => inner.description(),
71+
&OIDError::IoError(ref inner) => inner.description(),
72+
&OIDError::HostnameError => "Failed to retrieve hostname for OID generation.",
73+
}
74+
}
75+
76+
fn cause(&self) -> Option<&error::Error> {
77+
match self {
78+
&OIDError::ArgumentError(ref inner) => None,
79+
&OIDError::FromHexError(ref inner) => Some(inner),
80+
&OIDError::IoError(ref inner) => Some(inner),
81+
&OIDError::HostnameError => None,
82+
}
83+
}
84+
}
85+
86+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
87+
pub struct ObjectId {
88+
id: [u8; 12],
89+
}
90+
91+
impl ObjectId {
92+
/// Generates a new ObjectID, represented in bytes.
93+
/// See the [docs](http://docs.mongodb.org/manual/reference/object-id/)
94+
/// for more information.
95+
pub fn new() -> Result<ObjectId> {
96+
let timestamp = ObjectId::gen_timestamp();
97+
let machine_id = try!(ObjectId::gen_machine_id());
98+
let process_id = ObjectId::gen_process_id();
99+
let counter = try!(ObjectId::gen_count());
100+
101+
let mut buf: [u8; 12] = [0; 12];
102+
for i in 0..TIMESTAMP_SIZE { buf[TIMESTAMP_OFFSET + i] = timestamp[i]; }
103+
for i in 0..MACHINE_ID_SIZE { buf[MACHINE_ID_OFFSET + i] = machine_id[i]; }
104+
for i in 0..PROCESS_ID_SIZE { buf[PROCESS_ID_OFFSET + i] = process_id[i]; }
105+
for i in 0..COUNTER_SIZE { buf[COUNTER_OFFSET +i] = counter[i]; }
106+
107+
Ok(ObjectId::with_bytes(buf))
108+
}
109+
110+
pub fn with_bytes(bytes: [u8; 12]) -> ObjectId {
111+
ObjectId {
112+
id: bytes,
113+
}
114+
}
115+
116+
/// Creates an ObjectID using a 12-byte (24-char) hexadecimal string.
117+
pub fn with_string(s: &str) -> Result<ObjectId> {
118+
let bytes = try!(s.from_hex());
119+
if bytes.len() != 12 {
120+
Err(OIDError::ArgumentError("Provided string must be a 12-byte hexadecimal string.".to_owned()))
121+
} else {
122+
let mut byte_array: [u8; 12] = [0; 12];
123+
for i in 0..12 {
124+
byte_array[i] = bytes[i];
125+
}
126+
Ok(ObjectId::with_bytes(byte_array))
127+
}
128+
}
129+
130+
/// Creates a dummy ObjectId with a specific generation time.
131+
/// This method should only be used to do range queries on a field
132+
/// containing ObjectId instances.
133+
pub fn with_timestamp(time: u32) -> ObjectId {
134+
let mut buf: [u8; 12] = [0; 12];
135+
BigEndian::write_u32(&mut buf, time);
136+
ObjectId::with_bytes(buf)
137+
}
138+
139+
pub fn bytes(&self) -> [u8; 12] {
140+
self.id
141+
}
142+
143+
/// Retrieves the timestamp (seconds since epoch) from an ObjectId.
144+
pub fn timestamp(&self) -> u32 {
145+
BigEndian::read_u32(&self.id)
146+
}
147+
148+
/// Retrieves the machine id associated with an ObjectId.
149+
pub fn machine_id(&self) -> u32 {
150+
let mut buf: [u8; 4] = [0; 4];
151+
for i in 0..MACHINE_ID_SIZE {
152+
buf[i] = self.id[MACHINE_ID_OFFSET+i];
153+
}
154+
LittleEndian::read_u32(&buf)
155+
}
156+
157+
/// Retrieves the process id associated with an ObjectId.
158+
pub fn process_id(&self) -> u16 {
159+
LittleEndian::read_u16(&self.id[PROCESS_ID_OFFSET..])
160+
}
161+
162+
/// Retrieves the increment counter from an ObjectId.
163+
pub fn counter(&self) -> u32 {
164+
let mut buf: [u8; 4] = [0; 4];
165+
for i in 0..COUNTER_SIZE {
166+
buf[i+1] = self.id[COUNTER_OFFSET+i];
167+
}
168+
BigEndian::read_u32(&buf)
169+
}
170+
171+
// Generates a new timestamp representing the current seconds since epoch.
172+
// Represented in Big Endian.
173+
fn gen_timestamp() -> [u8; 4] {
174+
let timespec = time::get_time();
175+
let timestamp = timespec.sec as u32;
176+
177+
let mut buf: [u8; 4] = [0; 4];
178+
BigEndian::write_u32(&mut buf,timestamp);
179+
buf
180+
}
181+
182+
// Generates a new machine id represented as an MD5-hashed 3-byte-encoded hostname string.
183+
// Represented in Little Endian.
184+
fn gen_machine_id() -> Result<[u8; 3]> {
185+
// Short-circuit if machine id has already been calculated.
186+
// Since the generated machine id is not variable, arising race conditions
187+
// will have the same MACHINE_BYTES result.
188+
unsafe {
189+
if MACHINE_BYTES[0] != 0 || MACHINE_BYTES[1] != 0 || MACHINE_BYTES[2] != 0 {
190+
return Ok(MACHINE_BYTES);
191+
}
192+
}
193+
194+
// Retrieve hostname through libc
195+
let len = 255;
196+
let mut buf = Vec::<u8>::with_capacity(len);
197+
let ptr = buf.as_mut_ptr();
198+
let err = unsafe { gethostname(ptr as *mut libc::c_char, len as libc::size_t) } as i32;
199+
200+
if err != 0 {
201+
return Err(OIDError::HostnameError);
202+
}
203+
204+
// Convert bytes into string
205+
let s = String::from_utf8_lossy(&buf);
206+
207+
// Hash hostname string
208+
let mut md5 = Md5::new();
209+
md5.input_str(&s.into_owned()[..]);
210+
let hash = md5.result_str();
211+
212+
// Re-convert string to bytes and grab first three
213+
let mut bytes = hash.bytes();
214+
let mut vec: [u8; 3] = [0; 3];
215+
for i in 0..MACHINE_ID_SIZE {
216+
match bytes.next() {
217+
Some(b) => vec[i] = b,
218+
None => break,
219+
}
220+
}
221+
222+
unsafe { MACHINE_BYTES = vec };
223+
Ok(vec)
224+
}
225+
226+
// Gets the process ID and returns it as a 2-byte array.
227+
// Represented in Little Endian.
228+
fn gen_process_id() -> [u8; 2] {
229+
let pid = unsafe { libc::getpid() as u16 };
230+
let mut buf: [u8; 2] = [0; 2];
231+
LittleEndian::write_u16(&mut buf, pid);
232+
buf
233+
}
234+
235+
// Gets an incremental 3-byte count.
236+
// Represented in Big Endian.
237+
fn gen_count() -> Result<[u8; 3]> {
238+
// Init oid counter
239+
if OID_COUNTER.load(Ordering::SeqCst) == 0 {
240+
let mut rng = try!(OsRng::new());
241+
let start = rng.gen_range(0, MAX_U24 + 1);
242+
OID_COUNTER.store(start, Ordering::SeqCst);
243+
}
244+
245+
let u_counter = OID_COUNTER.fetch_add(1, Ordering::SeqCst);
246+
247+
// Mod result instead of OID_COUNTER to prevent threading issues.
248+
// Static mutexes are currently unstable; once they have been
249+
// stabilized, one should be used to access OID_COUNTER and
250+
// perform multiple operations atomically.
251+
let u = u_counter % MAX_U24;
252+
253+
// Convert usize to writable u64, then extract the first three bytes.
254+
let u_int = u as u64;
255+
256+
let mut buf: [u8; 8] = [0; 8];
257+
BigEndian::write_u64(&mut buf, u_int);
258+
let buf_u24: [u8; 3] = [buf[5], buf[6], buf[7]];
259+
Ok(buf_u24)
260+
}
261+
}
262+
263+
#[test]
264+
fn pid_generation() {
265+
let pid = unsafe { libc::getpid() as u16 };
266+
let generated = ObjectId::gen_process_id();
267+
assert_eq!(pid, LittleEndian::read_u16(&generated));
268+
}
269+
270+
#[test]
271+
fn count_generation() {
272+
let start = 52222;
273+
OID_COUNTER.store(start, Ordering::SeqCst);
274+
let count_res = ObjectId::gen_count();
275+
assert!(count_res.is_ok());
276+
let count_bytes = count_res.unwrap();
277+
278+
let mut buf: [u8; 4] = [0; 4];
279+
for i in 0..COUNTER_SIZE {
280+
buf[i+1] = count_bytes[i];
281+
}
282+
283+
let count = BigEndian::read_u32(&buf);
284+
assert_eq!(start as u32, count);
285+
}
286+
287+
#[test]
288+
fn count_is_big_endian() {
289+
let start = 1122867;
290+
OID_COUNTER.store(start, Ordering::SeqCst);
291+
let oid_res = ObjectId::new();
292+
assert!(oid_res.is_ok());
293+
let oid = oid_res.unwrap();
294+
295+
assert_eq!(0x11u8, oid.bytes()[COUNTER_OFFSET]);
296+
assert_eq!(0x22u8, oid.bytes()[COUNTER_OFFSET + 1]);
297+
assert_eq!(0x33u8, oid.bytes()[COUNTER_OFFSET + 2]);
298+
}

tests/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extern crate bson;
2+
extern crate rustc_serialize;
3+
4+
mod oid;

0 commit comments

Comments
 (0)