Skip to content

Commit

Permalink
RUBY-3435 Don't overflow the 4-bytes allotted to an ObjectID's timest…
Browse files Browse the repository at this point in the history
…amp portion (#327)

* final tweaks for BSON 5 release

* RUBY-3435 constrain timestamp to 4-bytes
  • Loading branch information
jamis authored Apr 8, 2024
1 parent d3eab30 commit 79d5aed
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 2 deletions.
2 changes: 1 addition & 1 deletion ext/bson/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ VALUE rb_bson_object_id_generator_next(int argc, VALUE* args, VALUE self)
* obtaining the timestamp value." */

timestamp = rb_funcall(rb_bson_object_id_class, rb_intern("timestamp"), 0);
time_component = BSON_UINT32_TO_BE(NUM2INT(timestamp));
time_component = BSON_UINT32_TO_BE(NUM2UINT(timestamp));

/* "A 5-byte field consisting of a random value generated once per process.
* This random value is unique to the machine and process.
Expand Down
12 changes: 11 additions & 1 deletion lib/bson/object_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,22 @@ def repair(object)
block_given? ? yield(object) : object
end

# The largest numeric value that can be converted to an integer by MRI's
# NUM2UINT. Further, the spec dictates that the time component of an
# ObjectID must be no more than 4 bytes long, so the spec itself is
# constrained in this regard.
MAX_INTEGER = 2 ** 32

# Returns an integer timestamp (seconds since the Epoch). Primarily used
# by the generator to produce object ids.
#
# @note This value is guaranteed to be no more than 4 bytes in length. A
# time value far enough in the future to require a larger integer than
# 4 bytes will be truncated to 4 bytes.
#
# @return [ Integer ] the number of seconds since the Epoch.
def timestamp
::Time.now.to_i
::Time.now.to_i % MAX_INTEGER
end
end

Expand Down
14 changes: 14 additions & 0 deletions spec/bson/object_id_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,20 @@
end
end

context 'when the timestamp is larger than a 32-bit integer' do
let(:distant_future) { Time.at(2 ** 32) }

before do
allow(Time).to receive(:now).and_return(distant_future)
end

let(:object_id) { BSON::ObjectId.new }

it 'wraps the timestamp to 0' do
expect(object_id.to_time).to be == Time.at(0)
end
end

context 'when fork changes the pid' do
before do
skip 'requires Process.fork' unless Process.respond_to?(:fork)
Expand Down

0 comments on commit 79d5aed

Please sign in to comment.