mongodb/bson-ruby

View on GitHub
ext/bson/init.c

Summary

Maintainability
Test Coverage
/*
 * Copyright (C) 2009-2020 MongoDB Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "bson-native.h"

/**
 * The counter for incrementing object ids.
 */
uint32_t rb_bson_object_id_counter;


VALUE rb_bson_registry;

const rb_data_type_t rb_byte_buffer_data_type = {
  "bson/byte_buffer",
  { NULL, rb_bson_byte_buffer_free, rb_bson_byte_buffer_memsize }
};

VALUE _ref_str, _id_str, _db_str;

/**
 * Initialize the bson_native extension.
 */
void Init_bson_native()
{
  char rb_bson_machine_id[256];

  _ref_str = rb_str_new_cstr("$ref");
  rb_gc_register_mark_object(_ref_str);
  _id_str = rb_str_new_cstr("$id");
  rb_gc_register_mark_object(_id_str);
  _db_str = rb_str_new_cstr("$db");
  rb_gc_register_mark_object(_db_str);

  rb_require("digest/md5");

  VALUE rb_bson_module = rb_define_module("BSON");

  /* Document-class: BSON::ByteBuffer
   *
   * Stores BSON-serialized data and provides efficient serialization and
   * deserialization of common Ruby classes using native code.
   */
  VALUE rb_byte_buffer_class = rb_define_class_under(rb_bson_module, "ByteBuffer", rb_cObject);

  VALUE rb_bson_object_id_class = rb_const_get(rb_bson_module, rb_intern("ObjectId"));
  VALUE rb_bson_object_id_generator_class = rb_const_get(rb_bson_object_id_class, rb_intern("Generator"));
  VALUE rb_digest_class = rb_const_get(rb_cObject, rb_intern("Digest"));
  VALUE rb_md5_class = rb_const_get(rb_digest_class, rb_intern("MD5"));

  rb_define_alloc_func(rb_byte_buffer_class, rb_bson_byte_buffer_allocate);
  rb_define_method(rb_byte_buffer_class, "initialize", rb_bson_byte_buffer_initialize, -1);

  /*
   * call-seq:
   *   buffer.length -> Fixnum
   *
   * Returns the number of bytes available to be read in the buffer.
   *
   * When a buffer is being written to, each added byte increases its length.
   * When a buffer is being read from, each read byte decreases its length.
   */
  rb_define_method(rb_byte_buffer_class, "length", rb_bson_byte_buffer_length, 0);

  /*
   * call-seq:
   *   buffer.read_position -> Fixnum
   *
   * Returns the read position in the buffer.
   */
  rb_define_method(rb_byte_buffer_class, "read_position", rb_bson_byte_buffer_read_position, 0);

  rb_define_method(rb_byte_buffer_class, "get_byte", rb_bson_byte_buffer_get_byte, 0);
  rb_define_method(rb_byte_buffer_class, "get_bytes", rb_bson_byte_buffer_get_bytes, 1);
  rb_define_method(rb_byte_buffer_class, "get_cstring", rb_bson_byte_buffer_get_cstring, 0);
  rb_define_method(rb_byte_buffer_class, "get_decimal128_bytes", rb_bson_byte_buffer_get_decimal128_bytes, 0);
  rb_define_method(rb_byte_buffer_class, "get_double", rb_bson_byte_buffer_get_double, 0);

  /*
   * call-seq:
   *   buffer.get_hash(**options) -> Hash
   *
   * Reads a document from the byte buffer and returns it as a BSON::Document.
   *
   * @option options [ nil | :bson ] :mode Decoding mode to use.
   *
   * @return [ BSON::Document ] The decoded document.
   */
  rb_define_method(rb_byte_buffer_class, "get_hash", rb_bson_byte_buffer_get_hash, -1);

  /*
   * call-seq:
   *   buffer.get_array(**options) -> Array
   *
   * Reads an array from the byte buffer.
   *
   * @option options [ nil | :bson ] :mode Decoding mode to use.
   *
   * @return [ Array ] The decoded array.
   */
  rb_define_method(rb_byte_buffer_class, "get_array", rb_bson_byte_buffer_get_array, -1);

  rb_define_method(rb_byte_buffer_class, "get_int32", rb_bson_byte_buffer_get_int32, 0);

  /*
   * call-seq:
   *   buffer.get_uint32(buffer) -> Fixnum
   *
   * Reads an unsigned 32 bit number from the byte buffer.
   *
   * @return [ Fixnum ] The unsigned 32 bits integer from the buffer
   *
   * @api private
   */
  rb_define_method(rb_byte_buffer_class, "get_uint32", rb_bson_byte_buffer_get_uint32, 0);
  rb_define_method(rb_byte_buffer_class, "get_int64", rb_bson_byte_buffer_get_int64, 0);
  rb_define_method(rb_byte_buffer_class, "get_string", rb_bson_byte_buffer_get_string, 0);

  /*
   * call-seq:
   *   buffer.write_position -> Fixnum
   *
   * Returns the write position in the buffer.
   */
  rb_define_method(rb_byte_buffer_class, "write_position", rb_bson_byte_buffer_write_position, 0);

  /*
   * call-seq:
   *   buffer.put_byte(binary_str) -> ByteBuffer
   *
   * Writes the specified byte string, which must be of length 1,
   * to the byte buffer.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_byte", rb_bson_byte_buffer_put_byte, 1);

  /*
   * call-seq:
   *   buffer.put_bytes(binary_str) -> ByteBuffer
   *
   * Writes the specified byte string to the byte buffer.
   *
   * This method writes exactly the provided byte string - in particular, it
   * does not prepend the length, and does not append a null byte at the end.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_bytes", rb_bson_byte_buffer_put_bytes, 1);

  /*
   * call-seq:
   *   buffer.put_string(str) -> ByteBuffer
   *
   * Writes the specified string to the byte buffer as a BSON string.
   *
   * Unlike #put_bytes, this method writes the provided byte string as
   * a "BSON string" - the string is prefixed with its length and suffixed
   * with a null byte. The byte string may contain null bytes itself thus
   * the null terminator is redundant, but it is required by the BSON
   * specification.
   *
   * +str+ must either already be in UTF-8 encoding or be a string encodable
   * to UTF-8. In particular, a string in BINARY/ASCII-8BIT encoding is
   * generally not suitable for this method. +EncodingError+ will be raised
   * if +str+ cannot be encoded in UTF-8, or if +str+ claims to be encoded in
   * UTF-8 but contains bytes/byte sequences which are not valid in UTF-8.
   * Use #put_bytes to write arbitrary byte strings to the buffer.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_string", rb_bson_byte_buffer_put_string, 1);

  /**
   * call-seq:
   *   buffer.put_cstring(obj) -> ByteBuffer
   *
   * Converts +obj+ to a string, which must not contain any null bytes, and
   * which must be valid UTF-8, and writes the string to the buffer as a
   * BSON cstring. +obj+ can be an instance of String, Symbol or Fixnum.
   *
   * If the string serialization of +obj+ contains null bytes, this method
   * raises +ArgumentError+. If +obj+ is of an unsupported type, this method
   * raises +TypeError+.
   *
   * BSON cstring serialization contains no length of the string (relying
   * instead on the null terminator), unlike the BSON string serialization.
   */
  rb_define_method(rb_byte_buffer_class, "put_cstring", rb_bson_byte_buffer_put_cstring, 1);

  /**
   * call-seq:
   *   buffer.put_symbol(sym) -> ByteBuffer
   *
   * Converts +sym+ to a string and writes the resulting string to the byte
   * buffer.
   *
   * The symbol may contain null bytes.
   *
   * The symbol value is assumed to be encoded in UTF-8. If the symbol value
   * contains bytes or byte sequences that are not valid in UTF-8, this method
   * raises +EncodingError+.
   *
   * Note: due to the string conversion, a symbol written to the buffer becomes
   * indistinguishable from a string with the same value written to the buffer.
   */
  rb_define_method(rb_byte_buffer_class, "put_symbol", rb_bson_byte_buffer_put_symbol, 1);

  /*
   * call-seq:
   *   buffer.put_int32(fixnum) -> ByteBuffer
   *
   * Writes a 32-bit integer value to the buffer.
   *
   * If the argument cannot be represented in 32 bits, raises RangeError.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_int32", rb_bson_byte_buffer_put_int32, 1);

  /*
   * call-seq:
   *   buffer.put_uint32(fixnum) -> ByteBuffer
   *
   * Writes an unsigned 32-bit integer value to the buffer.
   *
   * If the argument cannot be represented in 32 bits, raises RangeError.
   *
   * Returns the modified +self+.
   *
   * @api private
   *
   */
  rb_define_method(rb_byte_buffer_class, "put_uint32", rb_bson_byte_buffer_put_uint32, 1);

  /*
   * call-seq:
   *   buffer.put_int64(fixnum) -> ByteBuffer
   *
   * Writes a 64-bit integer value to the buffer.
   *
   * If the argument cannot be represented in 64 bits, raises RangeError.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_int64", rb_bson_byte_buffer_put_int64, 1);

  /*
   * call-seq:
   *   buffer.put_double(double) -> ByteBuffer
   *
   * Writes a 64-bit floating point value to the buffer.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_double", rb_bson_byte_buffer_put_double, 1);

  /*
   * call-seq:
   *   buffer.put_decimal128(low_64bit, high_64bit) -> ByteBuffer
   *
   * Writes a 128-bit Decimal128 value to the buffer.
   *
   * +low_64bit+ and +high_64bit+ are Fixnum objects containing the low and
   * the high parts of the 128-bit Decimal128 value, respectively.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_decimal128", rb_bson_byte_buffer_put_decimal128, 2);

  /*
   * call-seq:
   *   buffer.put_hash(hash) -> ByteBuffer
   *
   * Writes a Hash into the byte buffer.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_hash", rb_bson_byte_buffer_put_hash, 1);

  /*
   * call-seq:
   *   buffer.put_array(array) -> ByteBuffer
   *
   * Writes an Array into the byte buffer.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "put_array", rb_bson_byte_buffer_put_array, 1);

  /*
   * call-seq:
   *   buffer.replace_int32(position, fixnum) -> ByteBuffer
   *
   * Replaces a 32-bit integer value at the specified position in the buffer.
   *
   * The position must be a non-negative integer, and must be completely
   * contained within the data already written. For example, if the buffer has
   * the write position of 12, the acceptable range of positions for this
   * method is 0..8.
   *
   * If the argument cannot be represented in 32 bits, raises RangeError.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "replace_int32", rb_bson_byte_buffer_replace_int32, 2);

  /*
   * call-seq:
   *   buffer.rewind! -> ByteBuffer
   *
   * Resets the read position to the beginning of the byte buffer.
   *
   * Note: +rewind!+ does not change the buffer's write position.
   *
   * Returns the modified +self+.
   */
  rb_define_method(rb_byte_buffer_class, "rewind!", rb_bson_byte_buffer_rewind, 0);

  /*
   * call-seq:
   *   buffer.to_s -> String
   *
   * Returns the contents of the buffer as a binary string.
   *
   * If the buffer is used for reading, the returned contents is the data
   * that was not yet read. If the buffer is used for writing, the returned
   * contents is the complete data that has been written so far.
   *
   * Note: this method copies the buffer's contents into a newly allocated
   * +String+ instance. It does not return a reference to the data stored in
   * the buffer itself.
   */
  rb_define_method(rb_byte_buffer_class, "to_s", rb_bson_byte_buffer_to_s, 0);

  rb_define_method(rb_bson_object_id_generator_class, "next_object_id", rb_bson_object_id_generator_next, -1);
  rb_define_method(rb_bson_object_id_generator_class, "reset_counter", rb_bson_object_id_generator_reset_counter, -1);

  // Get the object id machine id and hash it.
  rb_require("digest/md5");
  gethostname(rb_bson_machine_id, sizeof(rb_bson_machine_id));
  rb_bson_machine_id[255] = '\0';
  rb_bson_generate_machine_id(rb_md5_class, rb_bson_machine_id);

  pvt_init_rand();

  // Set the object id counter to a random 3-byte integer
  rb_bson_object_id_counter = pvt_rand() % 0xFFFFFF;

  rb_bson_registry = rb_const_get(rb_bson_module, rb_intern("Registry"));
  rb_gc_register_mark_object(rb_bson_registry);
}