///|
let object_id_initial : (Bytes, UInt)? = None
///|
let object_id_state : Ref[(Bytes, UInt)?] = @ref.new(object_id_initial)
///|
/// Generate an ObjectId using an OS/Web Crypto secure random process value and
/// counter seed. Portable Wasm without a host entropy provider raises
/// `UnsupportedEntropy`; it never falls back to a deterministic PRNG.
pub fn ObjectId::new() -> ObjectId raise BsonError {
let timestamp = (@env.now() / 1000UL).to_uint()
let (process_unique, counter) = match object_id_state.val {
Some(state) => state
None => {
let entropy = object_id_secure_random(8)
let process_unique = entropy[0:5].to_owned()
let counter = (entropy[5].to_uint() << 16) |
(entropy[6].to_uint() << 8) |
entropy[7].to_uint()
object_id_state.val = Some((process_unique, counter))
(process_unique, counter)
}
}
object_id_state.val = Some((process_unique, (counter + 1U) & 0xFFFFFFU))
ObjectId::from_valid_bytes(
object_id_bytes(timestamp, process_unique, counter),
)
}
///|
/// Explicit alias for callers that want to document the entropy requirement.
pub fn ObjectId::new_secure() -> ObjectId raise BsonError {
ObjectId::new()
}
///|
pub fn ObjectId::from_parts(
timestamp : UInt,
process_unique : Bytes,
counter : UInt,
) -> ObjectId raise BsonError {
if process_unique.length() != 5 {
raise bson_error(
InvalidObjectId,
-1,
"$",
"ObjectId process-unique value must contain five bytes",
)
}
if counter > 0xFFFFFFU {
raise bson_error(
InvalidObjectId,
-1,
"$",
"ObjectId counter must fit in 24 bits",
)
}
ObjectId::from_valid_bytes(
object_id_bytes(timestamp, process_unique, counter),
)
}
///|
pub fn ObjectId::timestamp(self : ObjectId) -> DateTime {
let bytes = self.bytes()
let seconds = (bytes[0].to_uint() << 24) |
(bytes[1].to_uint() << 16) |
(bytes[2].to_uint() << 8) |
bytes[3].to_uint()
DateTime::from_millis(seconds.to_int64() * 1000L)
}
///|
fn object_id_bytes(
timestamp : UInt,
process_unique : Bytes,
counter : UInt,
) -> Bytes {
Bytes::makei(12, index => {
match index {
0 => (timestamp >> 24).to_byte()
1 => (timestamp >> 16).to_byte()
2 => (timestamp >> 8).to_byte()
3 => timestamp.to_byte()
4..=8 => process_unique[index - 4]
9 => (counter >> 16).to_byte()
10 => (counter >> 8).to_byte()
_ => counter.to_byte()
}
})
}