bson 4.15.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +4 -4
  4. data/Rakefile +5 -0
  5. data/ext/bson/bson-native.h +12 -4
  6. data/ext/bson/extconf.rb +8 -3
  7. data/ext/bson/init.c +11 -11
  8. data/ext/bson/read.c +21 -6
  9. data/ext/bson/util.c +168 -16
  10. data/ext/bson/write.c +30 -39
  11. data/lib/bson/active_support.rb +1 -0
  12. data/lib/bson/array.rb +57 -31
  13. data/lib/bson/big_decimal.rb +16 -6
  14. data/lib/bson/binary.rb +255 -128
  15. data/lib/bson/boolean.rb +1 -0
  16. data/lib/bson/code.rb +9 -11
  17. data/lib/bson/code_with_scope.rb +8 -10
  18. data/lib/bson/config.rb +1 -27
  19. data/lib/bson/date.rb +2 -1
  20. data/lib/bson/date_time.rb +2 -1
  21. data/lib/bson/db_pointer.rb +11 -12
  22. data/lib/bson/dbref.rb +11 -9
  23. data/lib/bson/decimal128/builder.rb +9 -8
  24. data/lib/bson/decimal128.rb +24 -110
  25. data/lib/bson/document.rb +1 -0
  26. data/lib/bson/environment.rb +1 -0
  27. data/lib/bson/error/bson_decode_error.rb +11 -0
  28. data/lib/bson/error/ext_json_parse_error.rb +11 -0
  29. data/lib/bson/error/illegal_key.rb +23 -0
  30. data/lib/bson/error/invalid_binary_type.rb +37 -0
  31. data/lib/bson/error/invalid_dbref_argument.rb +12 -0
  32. data/lib/bson/error/invalid_decimal128_argument.rb +25 -0
  33. data/lib/bson/error/invalid_decimal128_range.rb +27 -0
  34. data/lib/bson/error/invalid_decimal128_string.rb +26 -0
  35. data/lib/bson/error/invalid_key.rb +24 -0
  36. data/lib/bson/error/invalid_object_id.rb +11 -0
  37. data/lib/bson/error/invalid_regexp_pattern.rb +13 -0
  38. data/lib/bson/error/unrepresentable_precision.rb +19 -0
  39. data/lib/bson/error/unserializable_class.rb +13 -0
  40. data/lib/bson/error/unsupported_binary_subtype.rb +12 -0
  41. data/lib/bson/error/unsupported_type.rb +11 -0
  42. data/lib/bson/error.rb +16 -28
  43. data/lib/bson/ext_json.rb +1 -0
  44. data/lib/bson/false_class.rb +2 -1
  45. data/lib/bson/float.rb +2 -1
  46. data/lib/bson/hash.rb +127 -72
  47. data/lib/bson/int32.rb +16 -4
  48. data/lib/bson/int64.rb +16 -4
  49. data/lib/bson/integer.rb +3 -4
  50. data/lib/bson/json.rb +1 -0
  51. data/lib/bson/max_key.rb +7 -9
  52. data/lib/bson/min_key.rb +7 -9
  53. data/lib/bson/nil_class.rb +1 -0
  54. data/lib/bson/object.rb +5 -25
  55. data/lib/bson/object_id.rb +75 -121
  56. data/lib/bson/open_struct.rb +3 -2
  57. data/lib/bson/regexp.rb +35 -64
  58. data/lib/bson/registry.rb +2 -6
  59. data/lib/bson/specialized.rb +2 -1
  60. data/lib/bson/string.rb +4 -27
  61. data/lib/bson/symbol.rb +22 -19
  62. data/lib/bson/time.rb +2 -1
  63. data/lib/bson/time_with_zone.rb +13 -1
  64. data/lib/bson/timestamp.rb +2 -1
  65. data/lib/bson/true_class.rb +2 -1
  66. data/lib/bson/undefined.rb +14 -0
  67. data/lib/bson/version.rb +2 -1
  68. data/lib/bson.rb +3 -2
  69. data/spec/bson/array_spec.rb +19 -60
  70. data/spec/bson/big_decimal_spec.rb +16 -4
  71. data/spec/bson/binary_spec.rb +83 -74
  72. data/spec/bson/binary_uuid_spec.rb +1 -0
  73. data/spec/bson/boolean_spec.rb +1 -0
  74. data/spec/bson/byte_buffer_read_spec.rb +1 -0
  75. data/spec/bson/byte_buffer_spec.rb +1 -0
  76. data/spec/bson/byte_buffer_write_spec.rb +1 -0
  77. data/spec/bson/code_spec.rb +5 -3
  78. data/spec/bson/code_with_scope_spec.rb +5 -3
  79. data/spec/bson/config_spec.rb +1 -35
  80. data/spec/bson/date_spec.rb +1 -0
  81. data/spec/bson/date_time_spec.rb +1 -0
  82. data/spec/bson/dbref_legacy_spec.rb +20 -3
  83. data/spec/bson/dbref_spec.rb +9 -9
  84. data/spec/bson/decimal128_spec.rb +40 -20
  85. data/spec/bson/document_as_spec.rb +1 -0
  86. data/spec/bson/document_spec.rb +1 -1
  87. data/spec/bson/ext_json_parse_spec.rb +1 -0
  88. data/spec/bson/false_class_spec.rb +8 -0
  89. data/spec/bson/float_spec.rb +8 -3
  90. data/spec/bson/hash_as_spec.rb +1 -0
  91. data/spec/bson/hash_spec.rb +87 -75
  92. data/spec/bson/int32_spec.rb +21 -6
  93. data/spec/bson/int64_spec.rb +21 -6
  94. data/spec/bson/integer_spec.rb +45 -13
  95. data/spec/bson/json_spec.rb +1 -0
  96. data/spec/bson/max_key_spec.rb +5 -3
  97. data/spec/bson/min_key_spec.rb +5 -3
  98. data/spec/bson/nil_class_spec.rb +1 -0
  99. data/spec/bson/object_id_spec.rb +43 -4
  100. data/spec/bson/object_spec.rb +2 -1
  101. data/spec/bson/open_struct_spec.rb +14 -71
  102. data/spec/bson/raw_spec.rb +9 -15
  103. data/spec/bson/regexp_spec.rb +4 -3
  104. data/spec/bson/registry_spec.rb +2 -1
  105. data/spec/bson/string_spec.rb +13 -38
  106. data/spec/bson/symbol_raw_spec.rb +25 -0
  107. data/spec/bson/symbol_spec.rb +15 -18
  108. data/spec/bson/time_spec.rb +1 -0
  109. data/spec/bson/time_with_zone_spec.rb +1 -0
  110. data/spec/bson/timestamp_spec.rb +1 -0
  111. data/spec/bson/true_class_spec.rb +8 -0
  112. data/spec/bson/undefined_spec.rb +27 -0
  113. data/spec/bson_spec.rb +1 -0
  114. data/spec/runners/common_driver.rb +6 -5
  115. data/spec/runners/corpus.rb +6 -0
  116. data/spec/runners/corpus_legacy.rb +1 -0
  117. data/spec/shared/lib/mrss/constraints.rb +8 -16
  118. data/spec/shared/lib/mrss/docker_runner.rb +30 -3
  119. data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
  120. data/spec/shared/lib/mrss/event_subscriber.rb +15 -5
  121. data/spec/shared/lib/mrss/lite_constraints.rb +48 -1
  122. data/spec/shared/lib/mrss/server_version_registry.rb +16 -23
  123. data/spec/shared/lib/mrss/session_registry.rb +69 -0
  124. data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
  125. data/spec/shared/lib/mrss/utils.rb +28 -6
  126. data/spec/shared/share/Dockerfile.erb +68 -85
  127. data/spec/shared/shlib/config.sh +27 -0
  128. data/spec/shared/shlib/server.sh +73 -24
  129. data/spec/shared/shlib/set_env.sh +39 -1
  130. data/spec/spec_helper.rb +1 -0
  131. data/spec/spec_tests/common_driver_spec.rb +9 -4
  132. data/spec/spec_tests/corpus_legacy_spec.rb +1 -0
  133. data/spec/spec_tests/corpus_spec.rb +13 -3
  134. data/spec/spec_tests/data/corpus/binary.json +5 -0
  135. data/spec/spec_tests/data/corpus/code.json +13 -13
  136. data/spec/spec_tests/data/corpus/decimal128-4.json +48 -0
  137. data/spec/spec_tests/data/corpus/decimal128-6.json +12 -0
  138. data/spec/spec_tests/data/corpus/decimal128-7.json +4 -0
  139. data/spec/spec_tests/data/corpus/document.json +20 -0
  140. data/spec/spec_tests/data/corpus/symbol.json +7 -7
  141. data/spec/spec_tests/data/corpus/top.json +18 -3
  142. data/spec/support/shared_examples.rb +28 -5
  143. data/spec/support/spec_config.rb +1 -0
  144. data/spec/support/utils.rb +49 -1
  145. data.tar.gz.sig +0 -0
  146. metadata +167 -144
  147. metadata.gz.sig +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d0c420e8204cb354acfc6adadb5097b71f4a8346620a5461153c6461aece1e2
4
- data.tar.gz: ce5e7172180e7e9f5c60d2af3207b50e0ead96283d36828e75a118cc53cdd7fa
3
+ metadata.gz: 12132ecbf12cd3f9d72a97a4131985275206bd493afd61b38f723111e4f3d564
4
+ data.tar.gz: bbde2fa0d1b0cf9114a9c03ae971ecda873508602270d6ce4e9cc07052a73ba1
5
5
  SHA512:
6
- metadata.gz: 990f1be935132660ad51dab98c9233e12f9af5d03eac5f52a5bef82a78af2d258c12721564bfaf042977766c65d9adc7dbc7d88241283207213fe9ac15c9a024
7
- data.tar.gz: 8004b3cd0e51a769df76fd9027058f403efe6b5a565eff39aca3e43f700f0b77a7a670793dfb043d0cf963537eda0426e8a885dd4044adc2800934beaeb59286
6
+ metadata.gz: 75d4fc461601cb54b33371441414a35762578c4620ebd28301649bd8ff564fe41a495055c6cda98b2b61f7ef3678f6375e3e89eebeea23cd0736f7ed4b93a525
7
+ data.tar.gz: 5a64b2fca65a1117fbac73d68144387d5f0a4bc6737f0533926f2e2af5ac02a6bf9393f0307bdf17ca085380d1b14234fad1bba3103e8f64e1868bda2840491d
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -10,19 +10,19 @@ An implementation of the BSON specification in Ruby.
10
10
  Compatibility
11
11
  -------------
12
12
 
13
- BSON is tested against MRI (2.5) and JRuby (9.2+).
13
+ BSON is tested against MRI (2.6) and JRuby (9.2+).
14
14
 
15
15
  Documentation
16
16
  -------------
17
17
 
18
18
  Current documentation can be found
19
- [here](http://docs.mongodb.org/ecosystem/tutorial/ruby-bson-tutorial/#ruby-bson-tutorial).
19
+ [here](https://www.mongodb.com/docs/ruby-driver/current/bson-tutorials/).
20
20
 
21
21
  API Documentation
22
22
  -----------------
23
23
 
24
- The [API Documentation](https://api.mongodb.com/bson-ruby/current/) is
25
- located at api.mongodb.com.
24
+ The [API Documentation](https://www.mongodb.com/docs/ruby-driver/master/api/) is
25
+ located at mongodb.com/docs.
26
26
 
27
27
  BSON Specification
28
28
  ------------------
data/Rakefile CHANGED
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:todo all
3
+
1
4
  # Copyright (C) 2009-2013 MongoDB Inc.
2
5
  #
3
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,6 +35,8 @@ if jruby?
32
35
  ext.name = "bson-ruby"
33
36
  ext.ext_dir = "src"
34
37
  ext.lib_dir = "lib"
38
+ ext.target_version = ENV['TARGET_VERSION'] if ENV['TARGET_VERSION']
39
+ ext.source_version = ENV['SOURCE_VERSION'] if ENV['SOURCE_VERSION']
35
40
  end
36
41
  else
37
42
  require "rake/extensiontask"
@@ -17,6 +17,7 @@
17
17
  #include <ruby.h>
18
18
  #include <stdbool.h>
19
19
  #include <stdint.h>
20
+ #include <stdlib.h>
20
21
  #include <unistd.h>
21
22
  #include <time.h>
22
23
  #include "bson-endian.h"
@@ -91,14 +92,16 @@ VALUE rb_bson_byte_buffer_put_uint32(VALUE self, VALUE i);
91
92
  VALUE rb_bson_byte_buffer_put_int64(VALUE self, VALUE i);
92
93
  VALUE rb_bson_byte_buffer_put_string(VALUE self, VALUE string);
93
94
  VALUE rb_bson_byte_buffer_put_symbol(VALUE self, VALUE symbol);
94
- VALUE rb_bson_byte_buffer_put_hash(VALUE self, VALUE hash, VALUE validating_keys);
95
- VALUE rb_bson_byte_buffer_put_array(VALUE self, VALUE array, VALUE validating_keys);
95
+ VALUE rb_bson_byte_buffer_put_hash(VALUE self, VALUE hash);
96
+ VALUE rb_bson_byte_buffer_put_array(VALUE self, VALUE array);
96
97
  VALUE rb_bson_byte_buffer_read_position(VALUE self);
97
98
  VALUE rb_bson_byte_buffer_replace_int32(VALUE self, VALUE index, VALUE i);
98
99
  VALUE rb_bson_byte_buffer_rewind(VALUE self);
99
100
  VALUE rb_bson_byte_buffer_write_position(VALUE self);
100
101
  VALUE rb_bson_byte_buffer_to_s(VALUE self);
102
+
101
103
  VALUE rb_bson_object_id_generator_next(int argc, VALUE* args, VALUE self);
104
+ VALUE rb_bson_object_id_generator_reset_counter(int argc, VALUE* args, VALUE self);
102
105
 
103
106
  size_t rb_bson_byte_buffer_memsize(const void *ptr);
104
107
  void rb_bson_byte_buffer_free(void *ptr);
@@ -113,6 +116,13 @@ VALUE pvt_const_get_3(const char *c1, const char *c2, const char *c3);
113
116
 
114
117
  int pvt_get_mode_option(int argc, VALUE *argv);
115
118
 
119
+ #define BSON_OBJECT_ID_RANDOM_VALUE_LENGTH ( 5 )
120
+
121
+ uint8_t* pvt_get_object_id_random_value();
122
+ void pvt_init_rand();
123
+ void pvt_rand_buf(uint8_t* bytes, int len, int pid);
124
+ int pvt_rand();
125
+
116
126
  /**
117
127
  * The counter for incrementing object ids.
118
128
  */
@@ -120,8 +130,6 @@ extern uint32_t rb_bson_object_id_counter;
120
130
 
121
131
  extern VALUE rb_bson_registry;
122
132
 
123
- extern VALUE rb_bson_illegal_key;
124
-
125
133
  extern const rb_data_type_t rb_byte_buffer_data_type;
126
134
 
127
135
  extern VALUE _ref_str, _id_str, _db_str;
data/ext/bson/extconf.rb CHANGED
@@ -1,3 +1,8 @@
1
- require "mkmf"
2
- $CFLAGS << " -Wall -g -std=c99"
3
- create_makefile("bson_native")
1
+ # frozen_string_literal: true
2
+ # rubocop:disable all
3
+
4
+ require 'mkmf'
5
+
6
+ $CFLAGS << ' -Wall -g -std=c99'
7
+
8
+ create_makefile('bson_native')
data/ext/bson/init.c CHANGED
@@ -23,8 +23,6 @@ uint32_t rb_bson_object_id_counter;
23
23
 
24
24
  VALUE rb_bson_registry;
25
25
 
26
- VALUE rb_bson_illegal_key;
27
-
28
26
  const rb_data_type_t rb_byte_buffer_data_type = {
29
27
  "bson/byte_buffer",
30
28
  { NULL, rb_bson_byte_buffer_free, rb_bson_byte_buffer_memsize }
@@ -46,6 +44,8 @@ void Init_bson_native()
46
44
  _db_str = rb_str_new_cstr("$db");
47
45
  rb_gc_register_mark_object(_db_str);
48
46
 
47
+ rb_require("digest/md5");
48
+
49
49
  VALUE rb_bson_module = rb_define_module("BSON");
50
50
 
51
51
  /* Document-class: BSON::ByteBuffer
@@ -60,9 +60,6 @@ void Init_bson_native()
60
60
  VALUE rb_digest_class = rb_const_get(rb_cObject, rb_intern("Digest"));
61
61
  VALUE rb_md5_class = rb_const_get(rb_digest_class, rb_intern("MD5"));
62
62
 
63
- rb_bson_illegal_key = rb_const_get(rb_const_get(rb_bson_module, rb_intern("String")),rb_intern("IllegalKey"));
64
- rb_gc_register_mark_object(rb_bson_illegal_key);
65
-
66
63
  rb_define_alloc_func(rb_byte_buffer_class, rb_bson_byte_buffer_allocate);
67
64
  rb_define_method(rb_byte_buffer_class, "initialize", rb_bson_byte_buffer_initialize, -1);
68
65
 
@@ -116,7 +113,7 @@ void Init_bson_native()
116
113
  rb_define_method(rb_byte_buffer_class, "get_array", rb_bson_byte_buffer_get_array, -1);
117
114
 
118
115
  rb_define_method(rb_byte_buffer_class, "get_int32", rb_bson_byte_buffer_get_int32, 0);
119
-
116
+
120
117
  /*
121
118
  * call-seq:
122
119
  * buffer.get_uint32(buffer) -> Fixnum
@@ -285,13 +282,13 @@ void Init_bson_native()
285
282
 
286
283
  /*
287
284
  * call-seq:
288
- * buffer.put_hash(hash, validating_keys) -> ByteBuffer
285
+ * buffer.put_hash(hash) -> ByteBuffer
289
286
  *
290
287
  * Writes a Hash into the byte buffer.
291
288
  *
292
289
  * Returns the modified +self+.
293
290
  */
294
- rb_define_method(rb_byte_buffer_class, "put_hash", rb_bson_byte_buffer_put_hash, 2);
291
+ rb_define_method(rb_byte_buffer_class, "put_hash", rb_bson_byte_buffer_put_hash, 1);
295
292
 
296
293
  /*
297
294
  * call-seq:
@@ -301,7 +298,7 @@ void Init_bson_native()
301
298
  *
302
299
  * Returns the modified +self+.
303
300
  */
304
- rb_define_method(rb_byte_buffer_class, "put_array", rb_bson_byte_buffer_put_array, 2);
301
+ rb_define_method(rb_byte_buffer_class, "put_array", rb_bson_byte_buffer_put_array, 1);
305
302
 
306
303
  /*
307
304
  * call-seq:
@@ -349,6 +346,7 @@ void Init_bson_native()
349
346
  rb_define_method(rb_byte_buffer_class, "to_s", rb_bson_byte_buffer_to_s, 0);
350
347
 
351
348
  rb_define_method(rb_bson_object_id_generator_class, "next_object_id", rb_bson_object_id_generator_next, -1);
349
+ rb_define_method(rb_bson_object_id_generator_class, "reset_counter", rb_bson_object_id_generator_reset_counter, -1);
352
350
 
353
351
  // Get the object id machine id and hash it.
354
352
  rb_require("digest/md5");
@@ -356,8 +354,10 @@ void Init_bson_native()
356
354
  rb_bson_machine_id[255] = '\0';
357
355
  rb_bson_generate_machine_id(rb_md5_class, rb_bson_machine_id);
358
356
 
359
- // Set the object id counter to a random number
360
- rb_bson_object_id_counter = FIX2INT(rb_funcall(rb_mKernel, rb_intern("rand"), 1, INT2FIX(0x1000000)));
357
+ pvt_init_rand();
358
+
359
+ // Set the object id counter to a random 3-byte integer
360
+ rb_bson_object_id_counter = pvt_rand() % 0xFFFFFF;
361
361
 
362
362
  rb_bson_registry = rb_const_get(rb_bson_module, rb_intern("Registry"));
363
363
  rb_gc_register_mark_object(rb_bson_registry);
data/ext/bson/read.c CHANGED
@@ -80,8 +80,23 @@ VALUE pvt_read_field(byte_buffer_t *b, VALUE rb_buffer, uint8_t type, int argc,
80
80
  case BSON_TYPE_BOOLEAN: return pvt_get_boolean(b);
81
81
  default:
82
82
  {
83
- VALUE klass = rb_funcall(rb_bson_registry,rb_intern("get"),1, INT2FIX(type));
84
- VALUE value = rb_funcall(klass, rb_intern("from_bson"),1, rb_buffer);
83
+ VALUE klass = rb_funcall(rb_bson_registry, rb_intern("get"), 1, INT2FIX(type));
84
+ VALUE value;
85
+ if (argc > 1) {
86
+ rb_raise(rb_eArgError, "At most one argument is allowed");
87
+ } else if (argc > 0) {
88
+ VALUE call_args[2];
89
+ call_args[0] = rb_buffer;
90
+ Check_Type(argv[0], T_HASH);
91
+ call_args[1] = argv[0];
92
+ #ifdef RB_PASS_KEYWORDS /* Ruby 2.7+ */
93
+ value = rb_funcallv_kw(klass, rb_intern("from_bson"), 2, call_args, RB_PASS_KEYWORDS);
94
+ #else /* Ruby 2.6 and below */
95
+ value = rb_funcallv(klass, rb_intern("from_bson"), 2, call_args);
96
+ #endif
97
+ } else {
98
+ value = rb_funcall(klass, rb_intern("from_bson"), 1, rb_buffer);
99
+ }
85
100
  RB_GC_GUARD(klass);
86
101
  return value;
87
102
  }
@@ -366,17 +381,17 @@ static int pvt_is_dbref(VALUE doc) {
366
381
  if (NIL_P(ref) || !RB_TYPE_P(ref, T_STRING)) {
367
382
  return 0;
368
383
  }
369
-
384
+
370
385
  id = rb_hash_aref(doc, _id_str);
371
386
  if (NIL_P(id)) {
372
387
  return 0;
373
388
  }
374
-
389
+
375
390
  db = rb_hash_aref(doc, _db_str);
376
391
  if (!NIL_P(db) && !RB_TYPE_P(db, T_STRING)) {
377
392
  return 0;
378
393
  }
379
-
394
+
380
395
  return 1;
381
396
  }
382
397
 
@@ -404,7 +419,7 @@ VALUE rb_bson_byte_buffer_get_hash(int argc, VALUE *argv, VALUE self){
404
419
  if (READ_PTR(b) - start_ptr != length) {
405
420
  pvt_raise_decode_error(rb_sprintf("Expected to read %d bytes for the hash but read %ld bytes", length, READ_PTR(b) - start_ptr));
406
421
  }
407
-
422
+
408
423
  if (pvt_is_dbref(doc)) {
409
424
  VALUE cDBRef = pvt_const_get_2("BSON", "DBRef");
410
425
  doc = rb_funcall(cDBRef, rb_intern("new"), 1, doc);
data/ext/bson/util.c CHANGED
@@ -21,6 +21,18 @@
21
21
  */
22
22
  static char rb_bson_machine_id_hash[HOST_NAME_HASH_MAX];
23
23
 
24
+ /**
25
+ * Holds a reference to the SecureRandom module, or Qnil if the modle is
26
+ * not available.
27
+ */
28
+ static VALUE pvt_SecureRandom = Qnil;
29
+
30
+ /**
31
+ * Indicates whether or not the SecureRandom module responds to the
32
+ * `random_number` method (depends on Ruby version).
33
+ */
34
+ static int pvt_has_random_number = 0;
35
+
24
36
  void rb_bson_generate_machine_id(VALUE rb_md5_class, char *rb_bson_machine_id)
25
37
  {
26
38
  VALUE digest = rb_funcall(rb_md5_class, rb_intern("digest"), 1, rb_str_new2(rb_bson_machine_id));
@@ -29,31 +41,69 @@ void rb_bson_generate_machine_id(VALUE rb_md5_class, char *rb_bson_machine_id)
29
41
 
30
42
  /**
31
43
  * Generate the next object id.
44
+ *
45
+ * Specification:
46
+ * https://github.com/mongodb/specifications/blob/master/source/objectid.rst
47
+ *
48
+ * The ObjectID BSON type is a 12-byte value consisting of three different portions (fields):
49
+ * * a 4-byte value representing the seconds since the Unix epoch in the highest order bytes,
50
+ * * a 5-byte random number unique to a machine and process,
51
+ * * a 3-byte counter, starting with a random value.
32
52
  */
33
53
  VALUE rb_bson_object_id_generator_next(int argc, VALUE* args, VALUE self)
34
54
  {
35
55
  char bytes[12];
36
- uint32_t t;
37
- uint32_t c;
38
- uint16_t pid = BSON_UINT16_TO_BE(getpid());
56
+ uint32_t time_component;
57
+ uint8_t* random_component;
58
+ uint32_t counter_component;
59
+ VALUE timestamp;
60
+ VALUE rb_bson_object_id_class;
39
61
 
40
- if (argc == 0 || (argc == 1 && *args == Qnil)) {
41
- t = BSON_UINT32_TO_BE((int) time(NULL));
42
- }
43
- else {
44
- t = BSON_UINT32_TO_BE(NUM2ULONG(rb_funcall(*args, rb_intern("to_i"), 0)));
45
- }
62
+ rb_bson_object_id_class = pvt_const_get_2("BSON", "ObjectId");
63
+
64
+ /* "Drivers SHOULD have an accessor method on an ObjectID class for
65
+ * obtaining the timestamp value." */
66
+
67
+ timestamp = rb_funcall(rb_bson_object_id_class, rb_intern("timestamp"), 0);
68
+ time_component = BSON_UINT32_TO_BE(NUM2INT(timestamp));
69
+
70
+ /* "A 5-byte field consisting of a random value generated once per process.
71
+ * This random value is unique to the machine and process.
72
+ *
73
+ * "Drivers MUST NOT have an accessor method on an ObjectID class for
74
+ * obtaining this value."
75
+ */
46
76
 
47
- c = BSON_UINT32_TO_BE(rb_bson_object_id_counter << 8);
77
+ random_component = pvt_get_object_id_random_value();
78
+
79
+ /* shift left 8 bits, so that the first three bytes of the result are
80
+ * the meaningful ones */
81
+ counter_component = BSON_UINT32_TO_BE(rb_bson_object_id_counter << 8);
82
+
83
+ memcpy(&bytes, &time_component, 4);
84
+ memcpy(&bytes[4], random_component, 5);
85
+ memcpy(&bytes[9], &counter_component, 3);
86
+
87
+ rb_bson_object_id_counter = (rb_bson_object_id_counter + 1) % 0x1000000;
48
88
 
49
- memcpy(&bytes, &t, 4);
50
- memcpy(&bytes[4], rb_bson_machine_id_hash, 3);
51
- memcpy(&bytes[7], &pid, 2);
52
- memcpy(&bytes[9], &c, 3);
53
- rb_bson_object_id_counter++;
54
89
  return rb_str_new(bytes, 12);
55
90
  }
56
91
 
92
+ /**
93
+ * Reset the counter. This is purely as an aid for testing.
94
+ *
95
+ * @param [ Integer ] i the value to set the counter to (default is 0)
96
+ */
97
+ VALUE rb_bson_object_id_generator_reset_counter(int argc, VALUE* args, VALUE self) {
98
+ switch(argc) {
99
+ case 0: rb_bson_object_id_counter = 0; break;
100
+ case 1: rb_bson_object_id_counter = FIX2INT(args[0]); break;
101
+ default: rb_raise(rb_eArgError, "Expected 0 or 1 arguments, got %d", argc);
102
+ }
103
+
104
+ return T_NIL;
105
+ }
106
+
57
107
  /**
58
108
  * Returns a Ruby constant nested one level, e.g. BSON::Document.
59
109
  */
@@ -77,7 +127,7 @@ VALUE pvt_const_get_3(const char *c1, const char *c2, const char *c3) {
77
127
  int pvt_get_mode_option(int argc, VALUE *argv) {
78
128
  VALUE opts;
79
129
  VALUE mode;
80
-
130
+
81
131
  rb_scan_args(argc, argv, ":", &opts);
82
132
  if (NIL_P(opts)) {
83
133
  return BSON_MODE_DEFAULT;
@@ -93,3 +143,105 @@ int pvt_get_mode_option(int argc, VALUE *argv) {
93
143
  }
94
144
  }
95
145
  }
146
+
147
+ /**
148
+ * Returns the random number associated with this host and process. If the
149
+ * process ID changes (e.g. via fork), this will detect the change and
150
+ * generate another random number.
151
+ */
152
+ uint8_t* pvt_get_object_id_random_value() {
153
+ static pid_t remembered_pid = 0;
154
+ static uint8_t remembered_value[BSON_OBJECT_ID_RANDOM_VALUE_LENGTH] = {0};
155
+ pid_t pid = getpid();
156
+
157
+ if (remembered_pid != pid) {
158
+ remembered_pid = pid;
159
+ pvt_rand_buf(remembered_value, BSON_OBJECT_ID_RANDOM_VALUE_LENGTH, pid);
160
+ }
161
+
162
+ return remembered_value;
163
+ }
164
+
165
+ /**
166
+ * Attempts to load the SecureRandom module
167
+ */
168
+ VALUE pvt_load_secure_random(VALUE _arg) {
169
+ rb_require("securerandom");
170
+ pvt_SecureRandom = rb_const_get(rb_cObject, rb_intern("SecureRandom"));
171
+ pvt_has_random_number = rb_respond_to(pvt_SecureRandom, rb_intern("random_number"));
172
+
173
+ return Qnil;
174
+ }
175
+
176
+ /**
177
+ * The fallback, if loading `securerandom` fails.
178
+ */
179
+ VALUE pvt_rescue_load_secure_random(VALUE _arg, VALUE _exception) {
180
+ pvt_SecureRandom = Qnil;
181
+
182
+ return Qnil;
183
+ }
184
+
185
+ /**
186
+ * Initializes the RNG.
187
+ */
188
+ void pvt_init_rand() {
189
+ // SecureRandom may fail to load because it's not present (LoadError), or
190
+ // because it can't find a random device (NotImplementedError).
191
+ rb_rescue2(pvt_load_secure_random, Qnil, pvt_rescue_load_secure_random, Qnil,
192
+ rb_eLoadError, rb_eNotImpError, 0);
193
+ }
194
+
195
+ /**
196
+ * Fills the buffer with random bytes. It prefers to use SecureRandom for
197
+ * this, but in the very unlikely event that SecureRandom is not available,
198
+ * it will fall back to a much-less-ideal generator using srand/rand.
199
+ *
200
+ * The `pid` argument is only used by the fallback, if SecureRandom is not
201
+ * available.
202
+ */
203
+ void pvt_rand_buf(uint8_t* bytes, int len, int pid) {
204
+ if (pvt_SecureRandom != Qnil) {
205
+ VALUE rb_bytes = rb_funcall(pvt_SecureRandom, rb_intern("bytes"), 1, INT2NUM(len));
206
+ memcpy(bytes, StringValuePtr(rb_bytes), len);
207
+
208
+ } else {
209
+ time_t t;
210
+ uint32_t seed;
211
+ int ofs = 0;
212
+
213
+ t = time(NULL);
214
+ seed = ((uint32_t)t << 16) + ((uint32_t)pid % 0xFFFF);
215
+ srand(seed);
216
+
217
+ while (ofs < len) {
218
+ int n = rand();
219
+ unsigned remaining = len - ofs;
220
+
221
+ if (remaining > sizeof(n)) remaining = sizeof(n);
222
+ memcpy(bytes+ofs, &n, remaining);
223
+
224
+ ofs += remaining;
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Returns a random integer between 0 and INT_MAX.
231
+ */
232
+ int pvt_rand() {
233
+ if (pvt_has_random_number) {
234
+ VALUE result = rb_funcall(pvt_SecureRandom, rb_intern("random_number"), 1, INT2NUM(INT_MAX));
235
+ return NUM2INT(result);
236
+
237
+ } else if (pvt_SecureRandom != Qnil) {
238
+ int result;
239
+ VALUE rb_result = rb_funcall(pvt_SecureRandom, rb_intern("bytes"), 1, INT2NUM(sizeof(result)));
240
+ memcpy(&result, StringValuePtr(rb_result), sizeof(result));
241
+ return result;
242
+
243
+ } else {
244
+ srand((unsigned)time(NULL));
245
+ return rand();
246
+ }
247
+ }