bson_ext 0.20

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,158 @@
1
+ # -*- mode: ruby; -*-
2
+ require 'rubygems'
3
+ require 'rubygems/specification'
4
+ require 'fileutils'
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+ require 'rake/gempackagetask'
8
+ begin
9
+ require 'rake/contrib/rubyforgepublisher'
10
+ rescue LoadError
11
+ end
12
+ require 'rbconfig'
13
+ include Config
14
+ ENV['TEST_MODE'] = 'TRUE'
15
+
16
+ desc "Test the MongoDB Ruby driver."
17
+ task :test do
18
+ puts "\nThis option has changed."
19
+ puts "\nTo test the driver with the c-extensions:\nrake test:c\n"
20
+ puts "To test the pure ruby driver: \nrake test:ruby"
21
+ end
22
+
23
+ namespace :test do
24
+
25
+ desc "Test the driver with the c extension enabled."
26
+ task :c do
27
+ ENV['C_EXT'] = 'TRUE'
28
+ Rake::Task['test:unit'].invoke
29
+ Rake::Task['test:functional'].invoke
30
+ Rake::Task['test:bson'].invoke
31
+ Rake::Task['test:pooled_threading'].invoke
32
+ Rake::Task['test:drop_databases'].invoke
33
+ ENV['C_EXT'] = nil
34
+ end
35
+
36
+ desc "Test the driver using pure ruby (no c extension)"
37
+ task :ruby do
38
+ ENV['C_EXT'] = nil
39
+ Rake::Task['test:unit'].invoke
40
+ Rake::Task['test:functional'].invoke
41
+ Rake::Task['test:bson'].invoke
42
+ Rake::Task['test:pooled_threading'].invoke
43
+ Rake::Task['test:drop_databases'].invoke
44
+ end
45
+
46
+ Rake::TestTask.new(:unit) do |t|
47
+ t.test_files = FileList['test/unit/*_test.rb']
48
+ t.verbose = true
49
+ end
50
+
51
+ Rake::TestTask.new(:functional) do |t|
52
+ t.test_files = FileList['test/*_test.rb']
53
+ t.verbose = true
54
+ end
55
+
56
+ Rake::TestTask.new(:pooled_threading) do |t|
57
+ t.test_files = FileList['test/threading/*.rb']
58
+ t.verbose = true
59
+ end
60
+
61
+ Rake::TestTask.new(:pair_count) do |t|
62
+ t.test_files = FileList['test/replica/count_test.rb']
63
+ t.verbose = true
64
+ end
65
+
66
+ Rake::TestTask.new(:pair_insert) do |t|
67
+ t.test_files = FileList['test/replica/insert_test.rb']
68
+ t.verbose = true
69
+ end
70
+
71
+ Rake::TestTask.new(:pooled_pair_insert) do |t|
72
+ t.test_files = FileList['test/replica/pooled_insert_test.rb']
73
+ t.verbose = true
74
+ end
75
+
76
+ Rake::TestTask.new(:pair_query) do |t|
77
+ t.test_files = FileList['test/replica/query_test.rb']
78
+ t.verbose = true
79
+ end
80
+
81
+ Rake::TestTask.new(:auto_reconnect) do |t|
82
+ t.test_files = FileList['test/auxillary/autoreconnect_test.rb']
83
+ t.verbose = true
84
+ end
85
+
86
+ Rake::TestTask.new(:authentication) do |t|
87
+ t.test_files = FileList['test/auxillary/authentication_test.rb']
88
+ t.verbose = true
89
+ end
90
+
91
+ Rake::TestTask.new(:new_features) do |t|
92
+ t.test_files = FileList['test/auxillary/1.4_features.rb']
93
+ t.verbose = true
94
+ end
95
+
96
+ Rake::TestTask.new(:bson) do |t|
97
+ t.test_files = FileList['test/mongo_bson/*_test.rb']
98
+ t.verbose = true
99
+ end
100
+
101
+ task :drop_databases do |t|
102
+ puts "Dropping test database..."
103
+ require File.join(File.dirname(__FILE__), 'test', 'test_helper')
104
+ include Mongo
105
+ con = Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
106
+ ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT)
107
+ con.drop_database(MONGO_TEST_DB)
108
+ end
109
+ end
110
+
111
+ desc "Generate documentation"
112
+ task :rdoc do
113
+ version = eval(File.read("mongo-ruby-driver.gemspec")).version
114
+ out = File.join('html', version.to_s)
115
+ FileUtils.rm_rf('html')
116
+ system "rdoc --main README.rdoc --op #{out} --inline-source --quiet README.rdoc `find lib -name '*.rb'`"
117
+ end
118
+
119
+ desc "Generate YARD documentation"
120
+ task :ydoc do
121
+ require File.join(File.dirname(__FILE__), 'lib', 'mongo')
122
+ out = File.join('ydoc', Mongo::VERSION)
123
+ FileUtils.rm_rf('ydoc')
124
+ system "yardoc lib/**/*.rb lib/mongo/**/*.rb -e docs/yard_ext.rb -p docs/templates -o #{out} --title MongoRuby-#{Mongo::VERSION}"
125
+ end
126
+
127
+ desc "Publish documentation to mongo.rubyforge.org"
128
+ task :publish => [:rdoc] do
129
+ # Assumes docs are in ./html
130
+ Rake::RubyForgePublisher.new(GEM, RUBYFORGE_USER).upload
131
+ end
132
+
133
+ namespace :gem do
134
+
135
+ desc "Install the gem locally"
136
+ task :install do
137
+ sh "gem build mongo-ruby-driver.gemspec"
138
+ sh "gem install mongo-*.gem"
139
+ sh "rm mongo-*.gem"
140
+ end
141
+
142
+ desc "Install the optional c extensions"
143
+ task :install_extensions do
144
+ sh "gem build bson.gemspec"
145
+ sh "gem build bson_ext.gemspec"
146
+ sh "gem install bson-*.gem"
147
+ sh "gem install bson_ext-*.gem"
148
+ sh "rm bson-*.gem"
149
+ sh "rm bson_ext-*.gem"
150
+ end
151
+
152
+ end
153
+
154
+ task :default => :list
155
+
156
+ task :list do
157
+ system 'rake -T'
158
+ end
data/bson_ext.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ require 'lib/bson'
2
+ VERSION_HEADER = File.open(File.join(File.dirname(__FILE__), 'ext', 'cbson', 'version.h'), "r")
3
+ VERSION = VERSION_HEADER.read.scan(/VERSION\s+"(\d+\.\d+(\.\d+\w*)?)\"/)[0][0]
4
+ Gem::Specification.new do |s|
5
+ s.name = 'bson_ext'
6
+
7
+ s.version = VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.summary = 'C extensions for Ruby BSON.'
10
+ s.description = 'C extensions to accelerate the Ruby BSON serialization. For more information about BSON, see http://bsonspec.org. For information about MongoDB, see http://www.mongodb.org.'
11
+
12
+ s.require_paths = ['ext']
13
+ s.files = ['Rakefile', 'bson_ext.gemspec']
14
+ s.files += Dir['ext/**/*.rb'] + Dir['ext/**/*.c'] + Dir['ext/**/*.h']
15
+ s.test_files = []
16
+
17
+ s.has_rdoc = false
18
+ s.extensions << 'ext/cbson/extconf.rb'
19
+
20
+ s.author = 'Mike Dirolf'
21
+ s.email = 'mongodb-dev@googlegroups.com'
22
+ s.homepage = 'http://www.mongodb.org'
23
+ end
@@ -0,0 +1,135 @@
1
+ /*
2
+ * Copyright 2009-2010 10gen, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #include <stdlib.h>
18
+ #include <string.h>
19
+
20
+ #include "buffer.h"
21
+
22
+ #define INITIAL_BUFFER_SIZE 256
23
+
24
+ struct buffer {
25
+ char* buffer;
26
+ int size;
27
+ int position;
28
+ };
29
+
30
+ /* Allocate and return a new buffer.
31
+ * Return NULL on allocation failure. */
32
+ buffer_t buffer_new(void) {
33
+ buffer_t buffer;
34
+ buffer = (buffer_t)malloc(sizeof(struct buffer));
35
+ if (buffer == NULL) {
36
+ return NULL;
37
+ }
38
+
39
+ buffer->size = INITIAL_BUFFER_SIZE;
40
+ buffer->position = 0;
41
+ buffer->buffer = (char*)malloc(sizeof(char) * INITIAL_BUFFER_SIZE);
42
+ if (buffer->buffer == NULL) {
43
+ free(buffer);
44
+ return NULL;
45
+ }
46
+
47
+ return buffer;
48
+ }
49
+
50
+ /* Free the memory allocated for `buffer`.
51
+ * Return non-zero on failure. */
52
+ int buffer_free(buffer_t buffer) {
53
+ if (buffer == NULL) {
54
+ return 1;
55
+ }
56
+ free(buffer->buffer);
57
+ free(buffer);
58
+ return 0;
59
+ }
60
+
61
+ /* Grow `buffer` to at least `min_length`.
62
+ * Return non-zero on allocation failure. */
63
+ static int buffer_grow(buffer_t buffer, int min_length) {
64
+ int size = buffer->size;
65
+ char* old_buffer = buffer->buffer;
66
+ if (size >= min_length) {
67
+ return 0;
68
+ }
69
+ while (size < min_length) {
70
+ size *= 2;
71
+ }
72
+ buffer->buffer = (char*)realloc(buffer->buffer, sizeof(char) * size);
73
+ if (buffer->buffer == NULL) {
74
+ free(old_buffer);
75
+ free(buffer);
76
+ return 1;
77
+ }
78
+ buffer->size = size;
79
+ return 0;
80
+ }
81
+
82
+ /* Assure that `buffer` has at least `size` free bytes (and grow if needed).
83
+ * Return non-zero on allocation failure. */
84
+ static int buffer_assure_space(buffer_t buffer, int size) {
85
+ if (buffer->position + size <= buffer->size) {
86
+ return 0;
87
+ }
88
+ return buffer_grow(buffer, buffer->position + size);
89
+ }
90
+
91
+ /* Save `size` bytes from the current position in `buffer` (and grow if needed).
92
+ * Return offset for writing, or -1 on allocation failure. */
93
+ buffer_position buffer_save_space(buffer_t buffer, int size) {
94
+ int position = buffer->position;
95
+ if (buffer_assure_space(buffer, size) != 0) {
96
+ return -1;
97
+ }
98
+ buffer->position += size;
99
+ return position;
100
+ }
101
+
102
+ /* Write `size` bytes from `data` to `buffer` (and grow if needed).
103
+ * Return non-zero on allocation failure. */
104
+ int buffer_write(buffer_t buffer, const char* data, int size) {
105
+ if (buffer_assure_space(buffer, size) != 0) {
106
+ return 1;
107
+ }
108
+
109
+ memcpy(buffer->buffer + buffer->position, data, size);
110
+ buffer->position += size;
111
+ return 0;
112
+ }
113
+
114
+ /* Write `size` bytes from `data` to `buffer` at position `position`.
115
+ * Does not change the internal position of `buffer`.
116
+ * Return non-zero if buffer isn't large enough for write. */
117
+ int buffer_write_at_position(buffer_t buffer, buffer_position position,
118
+ const char* data, int size) {
119
+ if (position + size > buffer->size) {
120
+ buffer_free(buffer);
121
+ return 1;
122
+ }
123
+
124
+ memcpy(buffer->buffer + position, data, size);
125
+ return 0;
126
+ }
127
+
128
+
129
+ int buffer_get_position(buffer_t buffer) {
130
+ return buffer->position;
131
+ }
132
+
133
+ char* buffer_get_buffer(buffer_t buffer) {
134
+ return buffer->buffer;
135
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright 2009-2010 10gen, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #ifndef BUFFER_H
18
+ #define BUFFER_H
19
+
20
+ /* Note: if any of these functions return a failure condition then the buffer
21
+ * has already been freed. */
22
+
23
+ /* A buffer */
24
+ typedef struct buffer* buffer_t;
25
+ /* A position in the buffer */
26
+ typedef int buffer_position;
27
+
28
+ /* Allocate and return a new buffer.
29
+ * Return NULL on allocation failure. */
30
+ buffer_t buffer_new(void);
31
+
32
+ /* Free the memory allocated for `buffer`.
33
+ * Return non-zero on failure. */
34
+ int buffer_free(buffer_t buffer);
35
+
36
+ /* Save `size` bytes from the current position in `buffer` (and grow if needed).
37
+ * Return offset for writing, or -1 on allocation failure. */
38
+ buffer_position buffer_save_space(buffer_t buffer, int size);
39
+
40
+ /* Write `size` bytes from `data` to `buffer` (and grow if needed).
41
+ * Return non-zero on allocation failure. */
42
+ int buffer_write(buffer_t buffer, const char* data, int size);
43
+
44
+ /* Write `size` bytes from `data` to `buffer` at position `position`.
45
+ * Does not change the internal position of `buffer`.
46
+ * Return non-zero if buffer isn't large enough for write. */
47
+ int buffer_write_at_position(buffer_t buffer, buffer_position position, const char* data, int size);
48
+
49
+ /* Getters for the internals of a buffer_t.
50
+ * Should try to avoid using these as much as possible
51
+ * since they break the abstraction. */
52
+ buffer_position buffer_get_position(buffer_t buffer);
53
+ char* buffer_get_buffer(buffer_t buffer);
54
+
55
+ #endif
data/ext/cbson/cbson.c ADDED
@@ -0,0 +1,910 @@
1
+ /*
2
+ * Copyright 2009-2010 10gen, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /*
18
+ * This file contains C implementations of some of the functions needed by the
19
+ * bson module. If possible, these implementations should be used to speed up
20
+ * BSON encoding and decoding.
21
+ */
22
+
23
+ #include "ruby.h"
24
+
25
+ #if HAVE_RUBY_ST_H
26
+ #include "ruby/st.h"
27
+ #endif
28
+ #if HAVE_ST_H
29
+ #include "st.h"
30
+ #endif
31
+
32
+ #if HAVE_RUBY_REGEX_H
33
+ #include "ruby/regex.h"
34
+ #endif
35
+ #if HAVE_REGEX_H
36
+ #include "regex.h"
37
+ #endif
38
+
39
+ #include <string.h>
40
+ #include <math.h>
41
+ #include <unistd.h>
42
+ #include <time.h>
43
+
44
+ #include "version.h"
45
+ #include "buffer.h"
46
+ #include "encoding_helpers.h"
47
+
48
+ #define SAFE_WRITE(buffer, data, size) \
49
+ if (buffer_write((buffer), (data), (size)) != 0) \
50
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c")
51
+
52
+ #define SAFE_WRITE_AT_POS(buffer, position, data, size) \
53
+ if (buffer_write_at_position((buffer), (position), (data), (size)) != 0) \
54
+ rb_raise(rb_eRuntimeError, "invalid write at position in buffer.c")
55
+
56
+ #define MAX_HOSTNAME_LENGTH 256
57
+
58
+ static VALUE Binary;
59
+ static VALUE Time;
60
+ static VALUE ObjectID;
61
+ static VALUE DBRef;
62
+ static VALUE Code;
63
+ static VALUE MinKey;
64
+ static VALUE MaxKey;
65
+ static VALUE Regexp;
66
+ static VALUE OrderedHash;
67
+ static VALUE InvalidKeyName;
68
+ static VALUE InvalidStringEncoding;
69
+ static VALUE InvalidDocument;
70
+ static VALUE DigestMD5;
71
+
72
+ #if HAVE_RUBY_ENCODING_H
73
+ #include "ruby/encoding.h"
74
+ #define STR_NEW(p,n) rb_enc_str_new((p), (n), rb_utf8_encoding())
75
+ /* MUST call TO_UTF8 before calling write_utf8. */
76
+ #define TO_UTF8(string) rb_str_export_to_enc((string), rb_utf8_encoding())
77
+ static void write_utf8(buffer_t buffer, VALUE string, char check_null) {
78
+ result_t status = check_string(RSTRING_PTR(string), RSTRING_LEN(string),
79
+ 0, check_null);
80
+ if (status == HAS_NULL) {
81
+ buffer_free(buffer);
82
+ rb_raise(InvalidDocument, "Key names / regex patterns must not contain the NULL byte");
83
+ }
84
+ SAFE_WRITE(buffer, RSTRING_PTR(string), RSTRING_LEN(string));
85
+ }
86
+ #else
87
+ #define STR_NEW(p,n) rb_str_new((p), (n))
88
+ /* MUST call TO_UTF8 before calling write_utf8. */
89
+ #define TO_UTF8(string) (string)
90
+ static void write_utf8(buffer_t buffer, VALUE string, char check_null) {
91
+ result_t status = check_string(RSTRING_PTR(string), RSTRING_LEN(string),
92
+ 1, check_null);
93
+ if (status == HAS_NULL) {
94
+ buffer_free(buffer);
95
+ rb_raise(InvalidDocument, "Key names / regex patterns must not contain the NULL byte");
96
+ } else if (status == NOT_UTF_8) {
97
+ buffer_free(buffer);
98
+ rb_raise(InvalidStringEncoding, "String not valid UTF-8");
99
+ }
100
+ SAFE_WRITE(buffer, RSTRING_PTR(string), RSTRING_LEN(string));
101
+ }
102
+ #endif
103
+
104
+ // this sucks. but for some reason these moved around between 1.8 and 1.9
105
+ #ifdef ONIGURUMA_H
106
+ #define IGNORECASE ONIG_OPTION_IGNORECASE
107
+ #define MULTILINE ONIG_OPTION_MULTILINE
108
+ #define EXTENDED ONIG_OPTION_EXTEND
109
+ #else
110
+ #define IGNORECASE RE_OPTION_IGNORECASE
111
+ #define MULTILINE RE_OPTION_MULTILINE
112
+ #define EXTENDED RE_OPTION_EXTENDED
113
+ #endif
114
+
115
+ /* TODO we ought to check that the malloc or asprintf was successful
116
+ * and raise an exception if not. */
117
+ /* TODO maybe we can use something more portable like vsnprintf instead
118
+ * of this hack. And share it with the Python extension ;) */
119
+ #ifndef HAVE_ASPRINTF
120
+ #define INT2STRING(buffer, i) \
121
+ { \
122
+ int vslength = _scprintf("%d", i) + 1; \
123
+ *buffer = malloc(vslength); \
124
+ _snprintf(*buffer, vslength, "%d", i); \
125
+ }
126
+ #else
127
+ #define INT2STRING(buffer, i) asprintf(buffer, "%d", i);
128
+ #endif
129
+
130
+ #ifndef RREGEXP_SRC
131
+ #define RREGEXP_SRC(r) rb_str_new(RREGEXP((r))->str, RREGEXP((r))->len)
132
+ #endif
133
+
134
+ // rubinius compatibility
135
+ #ifndef RREGEXP_OPTIONS
136
+ #define RREGEXP_OPTIONS(r) RREGEXP(value)->ptr->options
137
+ #endif
138
+
139
+ static char zero = 0;
140
+ static char one = 1;
141
+
142
+ static int cmp_char(const void* a, const void* b) {
143
+ return *(char*)a - *(char*)b;
144
+ }
145
+
146
+ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id);
147
+ static int write_element_with_id(VALUE key, VALUE value, VALUE extra);
148
+ static int write_element_without_id(VALUE key, VALUE value, VALUE extra);
149
+ static VALUE elements_to_hash(const char* buffer, int max);
150
+
151
+ static VALUE pack_extra(buffer_t buffer, VALUE check_keys) {
152
+ return rb_ary_new3(2, LL2NUM((long long)buffer), check_keys);
153
+ }
154
+
155
+ static void write_name_and_type(buffer_t buffer, VALUE name, char type) {
156
+ SAFE_WRITE(buffer, &type, 1);
157
+ name = TO_UTF8(name);
158
+ write_utf8(buffer, name, 1);
159
+ SAFE_WRITE(buffer, &zero, 1);
160
+ }
161
+
162
+ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) {
163
+ buffer_t buffer = (buffer_t)NUM2LL(rb_ary_entry(extra, 0));
164
+ VALUE check_keys = rb_ary_entry(extra, 1);
165
+
166
+ if (TYPE(key) == T_SYMBOL) {
167
+ // TODO better way to do this... ?
168
+ key = rb_str_new2(rb_id2name(SYM2ID(key)));
169
+ }
170
+
171
+ if (TYPE(key) != T_STRING) {
172
+ buffer_free(buffer);
173
+ rb_raise(rb_eTypeError, "keys must be strings or symbols");
174
+ }
175
+
176
+ if (allow_id == 0 && strcmp("_id", RSTRING_PTR(key)) == 0) {
177
+ return ST_CONTINUE;
178
+ }
179
+
180
+ if (check_keys == Qtrue) {
181
+ int i;
182
+ if (RSTRING_LEN(key) > 0 && RSTRING_PTR(key)[0] == '$') {
183
+ buffer_free(buffer);
184
+ rb_raise(InvalidKeyName, "key must not start with '$'");
185
+ }
186
+ for (i = 0; i < RSTRING_LEN(key); i++) {
187
+ if (RSTRING_PTR(key)[i] == '.') {
188
+ buffer_free(buffer);
189
+ rb_raise(InvalidKeyName, "key must not contain '.'");
190
+ }
191
+ }
192
+ }
193
+
194
+ switch(TYPE(value)) {
195
+ case T_BIGNUM:
196
+ case T_FIXNUM:
197
+ {
198
+ if (rb_funcall(value, rb_intern(">"), 1, LL2NUM(9223372036854775807LL)) == Qtrue ||
199
+ rb_funcall(value, rb_intern("<"), 1, LL2NUM(-9223372036854775808ULL)) == Qtrue) {
200
+ buffer_free(buffer);
201
+ rb_raise(rb_eRangeError, "MongoDB can only handle 8-byte ints");
202
+ }
203
+ if (rb_funcall(value, rb_intern(">"), 1, INT2NUM(2147483647L)) == Qtrue ||
204
+ rb_funcall(value, rb_intern("<"), 1, INT2NUM(-2147483648L)) == Qtrue) {
205
+ long long ll_value;
206
+ write_name_and_type(buffer, key, 0x12);
207
+ ll_value = NUM2LL(value);
208
+ SAFE_WRITE(buffer, (char*)&ll_value, 8);
209
+ } else {
210
+ int int_value;
211
+ write_name_and_type(buffer, key, 0x10);
212
+ int_value = NUM2LL(value);
213
+ SAFE_WRITE(buffer, (char*)&int_value, 4);
214
+ }
215
+ break;
216
+ }
217
+ case T_TRUE:
218
+ {
219
+ write_name_and_type(buffer, key, 0x08);
220
+ SAFE_WRITE(buffer, &one, 1);
221
+ break;
222
+ }
223
+ case T_FALSE:
224
+ {
225
+ write_name_and_type(buffer, key, 0x08);
226
+ SAFE_WRITE(buffer, &zero, 1);
227
+ break;
228
+ }
229
+ case T_FLOAT:
230
+ {
231
+ double d = NUM2DBL(value);
232
+ write_name_and_type(buffer, key, 0x01);
233
+ SAFE_WRITE(buffer, (char*)&d, 8);
234
+ break;
235
+ }
236
+ case T_NIL:
237
+ {
238
+ write_name_and_type(buffer, key, 0x0A);
239
+ break;
240
+ }
241
+ case T_HASH:
242
+ {
243
+ write_name_and_type(buffer, key, 0x03);
244
+ write_doc(buffer, value, check_keys, Qfalse);
245
+ break;
246
+ }
247
+ case T_ARRAY:
248
+ {
249
+ buffer_position length_location, start_position, obj_length;
250
+ int items, i;
251
+ VALUE* values;
252
+
253
+ write_name_and_type(buffer, key, 0x04);
254
+ start_position = buffer_get_position(buffer);
255
+
256
+ // save space for length
257
+ length_location = buffer_save_space(buffer, 4);
258
+ if (length_location == -1) {
259
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
260
+ }
261
+
262
+ items = RARRAY_LEN(value);
263
+ values = RARRAY_PTR(value);
264
+ for(i = 0; i < items; i++) {
265
+ char* name;
266
+ VALUE key;
267
+ INT2STRING(&name, i);
268
+ key = rb_str_new2(name);
269
+ write_element_with_id(key, values[i], pack_extra(buffer, check_keys));
270
+ free(name);
271
+ }
272
+
273
+ // write null byte and fill in length
274
+ SAFE_WRITE(buffer, &zero, 1);
275
+ obj_length = buffer_get_position(buffer) - start_position;
276
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4);
277
+ break;
278
+ }
279
+ case T_STRING:
280
+ {
281
+ if (strcmp(rb_obj_classname(value),
282
+ "BSON::Code") == 0) {
283
+ buffer_position length_location, start_position, total_length;
284
+ int length;
285
+ write_name_and_type(buffer, key, 0x0F);
286
+
287
+ start_position = buffer_get_position(buffer);
288
+ length_location = buffer_save_space(buffer, 4);
289
+ if (length_location == -1) {
290
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
291
+ }
292
+
293
+ length = RSTRING_LEN(value) + 1;
294
+ SAFE_WRITE(buffer, (char*)&length, 4);
295
+ SAFE_WRITE(buffer, RSTRING_PTR(value), length - 1);
296
+ SAFE_WRITE(buffer, &zero, 1);
297
+ write_doc(buffer, rb_funcall(value, rb_intern("scope"), 0), Qfalse, Qfalse);
298
+
299
+ total_length = buffer_get_position(buffer) - start_position;
300
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&total_length, 4);
301
+ break;
302
+ } else {
303
+ int length;
304
+ write_name_and_type(buffer, key, 0x02);
305
+ value = TO_UTF8(value);
306
+ length = RSTRING_LEN(value) + 1;
307
+ SAFE_WRITE(buffer, (char*)&length, 4);
308
+ write_utf8(buffer, value, 0);
309
+ SAFE_WRITE(buffer, &zero, 1);
310
+ break;
311
+ }
312
+ }
313
+ case T_SYMBOL:
314
+ {
315
+ const char* str_value = rb_id2name(SYM2ID(value));
316
+ int length = strlen(str_value) + 1;
317
+ write_name_and_type(buffer, key, 0x0E);
318
+ SAFE_WRITE(buffer, (char*)&length, 4);
319
+ SAFE_WRITE(buffer, str_value, length);
320
+ break;
321
+ }
322
+ case T_OBJECT:
323
+ {
324
+ // TODO there has to be a better way to do these checks...
325
+ const char* cls = rb_obj_classname(value);
326
+ if (strcmp(cls, "BSON::Binary") == 0 ||
327
+ strcmp(cls, "ByteBuffer") == 0) {
328
+ const char subtype = strcmp(cls, "ByteBuffer") ?
329
+ (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2;
330
+ VALUE string_data = rb_funcall(value, rb_intern("to_s"), 0);
331
+ int length = RSTRING_LEN(string_data);
332
+ write_name_and_type(buffer, key, 0x05);
333
+ if (subtype == 2) {
334
+ const int other_length = length + 4;
335
+ SAFE_WRITE(buffer, (const char*)&other_length, 4);
336
+ SAFE_WRITE(buffer, &subtype, 1);
337
+ }
338
+ SAFE_WRITE(buffer, (const char*)&length, 4);
339
+ if (subtype != 2) {
340
+ SAFE_WRITE(buffer, &subtype, 1);
341
+ }
342
+ SAFE_WRITE(buffer, RSTRING_PTR(string_data), length);
343
+ break;
344
+ }
345
+ if (strcmp(cls, "BSON::ObjectID") == 0) {
346
+ VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0);
347
+ int i;
348
+ write_name_and_type(buffer, key, 0x07);
349
+ for (i = 0; i < 12; i++) {
350
+ char byte = (char)FIX2INT(RARRAY_PTR(as_array)[i]);
351
+ SAFE_WRITE(buffer, &byte, 1);
352
+ }
353
+ break;
354
+ }
355
+ if (strcmp(cls, "BSON::DBRef") == 0) {
356
+ buffer_position length_location, start_position, obj_length;
357
+ VALUE ns, oid;
358
+ write_name_and_type(buffer, key, 0x03);
359
+
360
+ start_position = buffer_get_position(buffer);
361
+
362
+ // save space for length
363
+ length_location = buffer_save_space(buffer, 4);
364
+ if (length_location == -1) {
365
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
366
+ }
367
+
368
+ ns = rb_funcall(value, rb_intern("namespace"), 0);
369
+ write_element_with_id(rb_str_new2("$ref"), ns, pack_extra(buffer, Qfalse));
370
+ oid = rb_funcall(value, rb_intern("object_id"), 0);
371
+ write_element_with_id(rb_str_new2("$id"), oid, pack_extra(buffer, Qfalse));
372
+
373
+ // write null byte and fill in length
374
+ SAFE_WRITE(buffer, &zero, 1);
375
+ obj_length = buffer_get_position(buffer) - start_position;
376
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&obj_length, 4);
377
+ break;
378
+ }
379
+ if (strcmp(cls, "BSON::MaxKey") == 0) {
380
+ write_name_and_type(buffer, key, 0x7f);
381
+ break;
382
+ }
383
+ if (strcmp(cls, "BSON::MinKey") == 0) {
384
+ write_name_and_type(buffer, key, 0xff);
385
+ break;
386
+ }
387
+ if (strcmp(cls, "DateTime") == 0 || strcmp(cls, "Date") == 0 || strcmp(cls, "ActiveSupport::TimeWithZone") == 0) {
388
+ buffer_free(buffer);
389
+ rb_raise(InvalidDocument, "%s is not currently supported; use a UTC Time instance instead.", cls);
390
+ break;
391
+ }
392
+ if(strcmp(cls, "Complex") == 0 || strcmp(cls, "Rational") == 0 || strcmp(cls, "BigDecimal") == 0) {
393
+ buffer_free(buffer);
394
+ rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls);
395
+ break;
396
+ }
397
+ buffer_free(buffer);
398
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls);
399
+ break;
400
+ }
401
+ case T_DATA:
402
+ {
403
+ const char* cls = rb_obj_classname(value);
404
+ if (strcmp(cls, "Time") == 0) {
405
+ double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0));
406
+ long long time_since_epoch = (long long)round(t * 1000);
407
+ write_name_and_type(buffer, key, 0x09);
408
+ SAFE_WRITE(buffer, (const char*)&time_since_epoch, 8);
409
+ break;
410
+ }
411
+ if(strcmp(cls, "BigDecimal") == 0) {
412
+ buffer_free(buffer);
413
+ rb_raise(InvalidDocument, "Cannot serialize the Numeric type %s as BSON; only Bignum, Fixnum, and Float are supported.", cls);
414
+ break;
415
+ }
416
+ buffer_free(buffer);
417
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s into BSON.", cls);
418
+ break;
419
+ }
420
+ case T_REGEXP:
421
+ {
422
+ VALUE pattern = RREGEXP_SRC(value);
423
+ long flags = RREGEXP_OPTIONS(value);
424
+ VALUE has_extra;
425
+
426
+ write_name_and_type(buffer, key, 0x0B);
427
+
428
+ pattern = TO_UTF8(pattern);
429
+ write_utf8(buffer, pattern, 1);
430
+ SAFE_WRITE(buffer, &zero, 1);
431
+
432
+ if (flags & IGNORECASE) {
433
+ char ignorecase = 'i';
434
+ SAFE_WRITE(buffer, &ignorecase, 1);
435
+ }
436
+ if (flags & MULTILINE) {
437
+ char multiline = 'm';
438
+ SAFE_WRITE(buffer, &multiline, 1);
439
+ }
440
+ if (flags & EXTENDED) {
441
+ char extended = 'x';
442
+ SAFE_WRITE(buffer, &extended, 1);
443
+ }
444
+
445
+ has_extra = rb_funcall(value, rb_intern("respond_to?"), 1, rb_str_new2("extra_options_str"));
446
+ if (TYPE(has_extra) == T_TRUE) {
447
+ VALUE extra = rb_funcall(value, rb_intern("extra_options_str"), 0);
448
+ buffer_position old_position = buffer_get_position(buffer);
449
+ SAFE_WRITE(buffer, RSTRING_PTR(extra), RSTRING_LEN(extra));
450
+ qsort(buffer_get_buffer(buffer) + old_position, RSTRING_LEN(extra), sizeof(char), cmp_char);
451
+ }
452
+ SAFE_WRITE(buffer, &zero, 1);
453
+
454
+ break;
455
+ }
456
+ default:
457
+ {
458
+ const char* cls = rb_obj_classname(value);
459
+ buffer_free(buffer);
460
+ rb_raise(InvalidDocument, "Cannot serialize an object of class %s (type %d) into BSON.", cls, TYPE(value));
461
+ break;
462
+ }
463
+ }
464
+ return ST_CONTINUE;
465
+ }
466
+
467
+ static int write_element_without_id(VALUE key, VALUE value, VALUE extra) {
468
+ return write_element(key, value, extra, 0);
469
+ }
470
+
471
+ static int write_element_with_id(VALUE key, VALUE value, VALUE extra) {
472
+ return write_element(key, value, extra, 1);
473
+ }
474
+
475
+ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_id) {
476
+ buffer_position start_position = buffer_get_position(buffer);
477
+ buffer_position length_location = buffer_save_space(buffer, 4);
478
+ buffer_position length;
479
+ int allow_id;
480
+ int (*write_function)(VALUE, VALUE, VALUE) = NULL;
481
+ VALUE id_str = rb_str_new2("_id");
482
+ VALUE id_sym = ID2SYM(rb_intern("_id"));
483
+
484
+ if (length_location == -1) {
485
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
486
+ }
487
+
488
+ // write '_id' first if move_id is true. then don't allow an id to be written.
489
+ if(move_id == Qtrue) {
490
+ allow_id = 0;
491
+ if (rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) {
492
+ VALUE id = rb_hash_aref(hash, id_str);
493
+ write_element_with_id(id_str, id, pack_extra(buffer, check_keys));
494
+ } else if (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue) {
495
+ VALUE id = rb_hash_aref(hash, id_sym);
496
+ write_element_with_id(id_sym, id, pack_extra(buffer, check_keys));
497
+ }
498
+ }
499
+ else {
500
+ allow_id = 1;
501
+ if (strcmp(rb_obj_classname(hash), "Hash") == 0) {
502
+ if ((rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) &&
503
+ (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue)) {
504
+ VALUE oid_sym = rb_hash_delete(hash, id_sym);
505
+ rb_funcall(hash, rb_intern("[]="), 2, id_str, oid_sym);
506
+ }
507
+ }
508
+ }
509
+
510
+ if(allow_id == 1) {
511
+ write_function = write_element_with_id;
512
+ }
513
+ else {
514
+ write_function = write_element_without_id;
515
+ }
516
+
517
+ // we have to check for an OrderedHash and handle that specially
518
+ if (strcmp(rb_obj_classname(hash), "OrderedHash") == 0) {
519
+ VALUE keys = rb_funcall(hash, rb_intern("keys"), 0);
520
+ int i;
521
+ for(i = 0; i < RARRAY_LEN(keys); i++) {
522
+ VALUE key = RARRAY_PTR(keys)[i];
523
+ VALUE value = rb_hash_aref(hash, key);
524
+
525
+ write_function(key, value, pack_extra(buffer, check_keys));
526
+ }
527
+ } else {
528
+ rb_hash_foreach(hash, write_function, pack_extra(buffer, check_keys));
529
+ }
530
+
531
+ // write null byte and fill in length
532
+ SAFE_WRITE(buffer, &zero, 1);
533
+ length = buffer_get_position(buffer) - start_position;
534
+
535
+ // make sure that length doesn't exceed 4MB
536
+ if (length > 4 * 1024 * 1024) {
537
+ buffer_free(buffer);
538
+ rb_raise(InvalidDocument, "Document too large: BSON documents are limited to 4MB.");
539
+ return;
540
+ }
541
+ SAFE_WRITE_AT_POS(buffer, length_location, (const char*)&length, 4);
542
+ }
543
+
544
+ static VALUE method_serialize(VALUE self, VALUE doc, VALUE check_keys, VALUE move_id) {
545
+ VALUE result;
546
+ buffer_t buffer = buffer_new();
547
+ if (buffer == NULL) {
548
+ rb_raise(rb_eNoMemError, "failed to allocate memory in buffer.c");
549
+ }
550
+
551
+ write_doc(buffer, doc, check_keys, move_id);
552
+
553
+ result = rb_str_new(buffer_get_buffer(buffer), buffer_get_position(buffer));
554
+ if (buffer_free(buffer) != 0) {
555
+ rb_raise(rb_eRuntimeError, "failed to free buffer");
556
+ }
557
+ return result;
558
+ }
559
+
560
+ static VALUE get_value(const char* buffer, int* position, int type) {
561
+ VALUE value;
562
+ switch (type) {
563
+ case -1:
564
+ {
565
+ value = rb_class_new_instance(0, NULL, MinKey);
566
+ break;
567
+ }
568
+ case 1:
569
+ {
570
+ double d;
571
+ memcpy(&d, buffer + *position, 8);
572
+ value = rb_float_new(d);
573
+ *position += 8;
574
+ break;
575
+ }
576
+ case 2:
577
+ case 13:
578
+ {
579
+ int value_length;
580
+ value_length = *(int*)(buffer + *position) - 1;
581
+ *position += 4;
582
+ value = STR_NEW(buffer + *position, value_length);
583
+ *position += value_length + 1;
584
+ break;
585
+ }
586
+ case 3:
587
+ {
588
+ int size;
589
+ memcpy(&size, buffer + *position, 4);
590
+ if (strcmp(buffer + *position + 5, "$ref") == 0) { // DBRef
591
+ int offset = *position + 10;
592
+ VALUE argv[2];
593
+ int collection_length = *(int*)(buffer + offset) - 1;
594
+ char id_type;
595
+ offset += 4;
596
+
597
+ argv[0] = STR_NEW(buffer + offset, collection_length);
598
+ offset += collection_length + 1;
599
+ id_type = buffer[offset];
600
+ offset += 5;
601
+ argv[1] = get_value(buffer, &offset, (int)id_type);
602
+ value = rb_class_new_instance(2, argv, DBRef);
603
+ } else {
604
+ value = elements_to_hash(buffer + *position + 4, size - 5);
605
+ }
606
+ *position += size;
607
+ break;
608
+ }
609
+ case 4:
610
+ {
611
+ int size, end;
612
+ memcpy(&size, buffer + *position, 4);
613
+ end = *position + size - 1;
614
+ *position += 4;
615
+
616
+ value = rb_ary_new();
617
+ while (*position < end) {
618
+ int type = (int)buffer[(*position)++];
619
+ int key_size = strlen(buffer + *position);
620
+ VALUE to_append;
621
+
622
+ *position += key_size + 1; // just skip the key, they're in order.
623
+ to_append = get_value(buffer, position, type);
624
+ rb_ary_push(value, to_append);
625
+ }
626
+ (*position)++;
627
+ break;
628
+ }
629
+ case 5:
630
+ {
631
+ int length, subtype;
632
+ VALUE data, st;
633
+ VALUE argv[2];
634
+ memcpy(&length, buffer + *position, 4);
635
+ subtype = (unsigned char)buffer[*position + 4];
636
+ if (subtype == 2) {
637
+ data = rb_str_new(buffer + *position + 9, length - 4);
638
+ } else {
639
+ data = rb_str_new(buffer + *position + 5, length);
640
+ }
641
+ st = INT2FIX(subtype);
642
+ argv[0] = data;
643
+ argv[1] = st;
644
+ value = rb_class_new_instance(2, argv, Binary);
645
+ *position += length + 5;
646
+ break;
647
+ }
648
+ case 6:
649
+ {
650
+ value = Qnil;
651
+ break;
652
+ }
653
+ case 7:
654
+ {
655
+ VALUE str = rb_str_new(buffer + *position, 12);
656
+ VALUE oid = rb_funcall(str, rb_intern("unpack"), 1, rb_str_new2("C*"));
657
+ value = rb_class_new_instance(1, &oid, ObjectID);
658
+ *position += 12;
659
+ break;
660
+ }
661
+ case 8:
662
+ {
663
+ value = buffer[(*position)++] ? Qtrue : Qfalse;
664
+ break;
665
+ }
666
+ case 9:
667
+ {
668
+ long long millis;
669
+ VALUE seconds, microseconds;
670
+ memcpy(&millis, buffer + *position, 8);
671
+ seconds = LL2NUM(millis / 1000);
672
+ microseconds = INT2NUM((millis % 1000) * 1000);
673
+
674
+ value = rb_funcall(Time, rb_intern("at"), 2, seconds, microseconds);
675
+ value = rb_funcall(value, rb_intern("utc"), 0);
676
+ *position += 8;
677
+ break;
678
+ }
679
+ case 10:
680
+ {
681
+ value = Qnil;
682
+ break;
683
+ }
684
+ case 11:
685
+ {
686
+ int pattern_length = strlen(buffer + *position);
687
+ VALUE pattern = STR_NEW(buffer + *position, pattern_length);
688
+ int flags_length, flags = 0, i = 0;
689
+ VALUE argv[3];
690
+ *position += pattern_length + 1;
691
+
692
+ flags_length = strlen(buffer + *position);
693
+ for (i = 0; i < flags_length; i++) {
694
+ char flag = buffer[*position + i];
695
+ if (flag == 'i') {
696
+ flags |= IGNORECASE;
697
+ }
698
+ else if (flag == 'm') {
699
+ flags |= MULTILINE;
700
+ }
701
+ else if (flag == 'x') {
702
+ flags |= EXTENDED;
703
+ }
704
+ }
705
+ argv[0] = pattern;
706
+ argv[1] = INT2FIX(flags);
707
+ value = rb_class_new_instance(2, argv, Regexp);
708
+ *position += flags_length + 1;
709
+ break;
710
+ }
711
+ case 12:
712
+ {
713
+ int collection_length;
714
+ VALUE collection, str, oid, id, argv[2];
715
+ collection_length = *(int*)(buffer + *position) - 1;
716
+ *position += 4;
717
+ collection = STR_NEW(buffer + *position, collection_length);
718
+ *position += collection_length + 1;
719
+
720
+ str = rb_str_new(buffer + *position, 12);
721
+ oid = rb_funcall(str, rb_intern("unpack"), 1, rb_str_new2("C*"));
722
+ id = rb_class_new_instance(1, &oid, ObjectID);
723
+ *position += 12;
724
+
725
+ argv[0] = collection;
726
+ argv[1] = id;
727
+ value = rb_class_new_instance(2, argv, DBRef);
728
+ break;
729
+ }
730
+ case 14:
731
+ {
732
+ int value_length;
733
+ memcpy(&value_length, buffer + *position, 4);
734
+ value = ID2SYM(rb_intern(buffer + *position + 4));
735
+ *position += value_length + 4;
736
+ break;
737
+ }
738
+ case 15:
739
+ {
740
+ int code_length, scope_size;
741
+ VALUE code, scope, argv[2];
742
+ *position += 4;
743
+ code_length = *(int*)(buffer + *position) - 1;
744
+ *position += 4;
745
+ code = STR_NEW(buffer + *position, code_length);
746
+ *position += code_length + 1;
747
+
748
+ memcpy(&scope_size, buffer + *position, 4);
749
+ scope = elements_to_hash(buffer + *position + 4, scope_size - 5);
750
+ *position += scope_size;
751
+
752
+ argv[0] = code;
753
+ argv[1] = scope;
754
+ value = rb_class_new_instance(2, argv, Code);
755
+ break;
756
+ }
757
+ case 16:
758
+ {
759
+ int i;
760
+ memcpy(&i, buffer + *position, 4);
761
+ value = LL2NUM(i);
762
+ *position += 4;
763
+ break;
764
+ }
765
+ case 17:
766
+ {
767
+ int i;
768
+ int j;
769
+ memcpy(&i, buffer + *position, 4);
770
+ memcpy(&j, buffer + *position + 4, 4);
771
+ value = rb_ary_new3(2, LL2NUM(i), LL2NUM(j));
772
+ *position += 8;
773
+ break;
774
+ }
775
+ case 18:
776
+ {
777
+ long long ll;
778
+ memcpy(&ll, buffer + *position, 8);
779
+ value = LL2NUM(ll);
780
+ *position += 8;
781
+ break;
782
+ }
783
+ case 127:
784
+ {
785
+ value = rb_class_new_instance(0, NULL, MaxKey);
786
+ break;
787
+ }
788
+ default:
789
+ {
790
+ rb_raise(rb_eTypeError, "no c decoder for this type yet (%d)", type);
791
+ break;
792
+ }
793
+ }
794
+ return value;
795
+ }
796
+
797
+ static VALUE elements_to_hash(const char* buffer, int max) {
798
+ VALUE hash = rb_class_new_instance(0, NULL, OrderedHash);
799
+ int position = 0;
800
+ while (position < max) {
801
+ int type = (int)buffer[position++];
802
+ int name_length = strlen(buffer + position);
803
+ VALUE name = STR_NEW(buffer + position, name_length);
804
+ VALUE value;
805
+ position += name_length + 1;
806
+ value = get_value(buffer, &position, type);
807
+ rb_funcall(hash, rb_intern("[]="), 2, name, value);
808
+ }
809
+ return hash;
810
+ }
811
+
812
+ static VALUE method_deserialize(VALUE self, VALUE bson) {
813
+ const char* buffer = RSTRING_PTR(bson);
814
+ int remaining = RSTRING_LEN(bson);
815
+
816
+ // NOTE we just swallow the size and end byte here
817
+ buffer += 4;
818
+ remaining -= 5;
819
+
820
+ return elements_to_hash(buffer, remaining);
821
+ }
822
+
823
+
824
+ static VALUE fast_pack(VALUE self)
825
+ {
826
+ VALUE res;
827
+ long i;
828
+ char c;
829
+
830
+ res = rb_str_buf_new(0);
831
+
832
+ for (i = 0; i < RARRAY_LEN(self); i++) {
833
+ c = FIX2LONG(RARRAY_PTR(self)[i]);
834
+ rb_str_buf_cat(res, &c, sizeof(char));
835
+ }
836
+
837
+ return res;
838
+ }
839
+
840
+
841
+ static VALUE objectid_generate(VALUE self)
842
+ {
843
+ VALUE oid, digest;
844
+ char hostname[MAX_HOSTNAME_LENGTH];
845
+ unsigned char oid_bytes[12];
846
+ unsigned long t, inc;
847
+ unsigned short pid;
848
+ int i;
849
+
850
+ t = htonl(time(NULL));
851
+ MEMCPY(&oid_bytes, &t, unsigned char, 4);
852
+
853
+ if (gethostname(hostname, MAX_HOSTNAME_LENGTH) != 0) {
854
+ rb_raise(rb_eRuntimeError, "failed to get hostname");
855
+ }
856
+ digest = rb_funcall(DigestMD5, rb_intern("digest"), 1, rb_str_new2(hostname));
857
+ MEMCPY(&oid_bytes[4], RSTRING_PTR(digest), unsigned char, 3);
858
+
859
+ pid = htons(getpid());
860
+ MEMCPY(&oid_bytes[7], &pid, unsigned char, 2);
861
+
862
+ inc = htonl(FIX2ULONG(rb_funcall(self, rb_intern("get_inc"), 0)));
863
+ MEMCPY(&oid_bytes[9], ((unsigned char*)&inc + 1), unsigned char, 3);
864
+
865
+ oid = rb_ary_new2(12);
866
+ for(i = 0; i < 12; i++) {
867
+ rb_ary_store(oid, i, INT2FIX((unsigned int)oid_bytes[i]));
868
+ }
869
+ return oid;
870
+ }
871
+
872
+
873
+ void Init_cbson() {
874
+ VALUE bson, CBson, Digest, ext_version;
875
+ Time = rb_const_get(rb_cObject, rb_intern("Time"));
876
+
877
+ bson = rb_const_get(rb_cObject, rb_intern("BSON"));
878
+ rb_require("bson/types/binary");
879
+ Binary = rb_const_get(bson, rb_intern("Binary"));
880
+ rb_require("bson/types/objectid");
881
+ ObjectID = rb_const_get(bson, rb_intern("ObjectID"));
882
+ rb_require("bson/types/dbref");
883
+ DBRef = rb_const_get(bson, rb_intern("DBRef"));
884
+ rb_require("bson/types/code");
885
+ Code = rb_const_get(bson, rb_intern("Code"));
886
+ rb_require("bson/types/min_max_keys");
887
+ MinKey = rb_const_get(bson, rb_intern("MinKey"));
888
+ MaxKey = rb_const_get(bson, rb_intern("MaxKey"));
889
+ Regexp = rb_const_get(rb_cObject, rb_intern("Regexp"));
890
+ rb_require("bson/exceptions");
891
+ InvalidKeyName = rb_const_get(bson, rb_intern("InvalidKeyName"));
892
+ InvalidStringEncoding = rb_const_get(bson, rb_intern("InvalidStringEncoding"));
893
+ InvalidDocument = rb_const_get(bson, rb_intern("InvalidDocument"));
894
+ rb_require("bson/ordered_hash");
895
+ OrderedHash = rb_const_get(rb_cObject, rb_intern("OrderedHash"));
896
+
897
+ CBson = rb_define_module("CBson");
898
+ ext_version = rb_str_new2(VERSION);
899
+ rb_define_const(CBson, "VERSION", ext_version);
900
+ rb_define_module_function(CBson, "serialize", method_serialize, 3);
901
+ rb_define_module_function(CBson, "deserialize", method_deserialize, 1);
902
+
903
+ rb_require("digest/md5");
904
+ Digest = rb_const_get(rb_cObject, rb_intern("Digest"));
905
+ DigestMD5 = rb_const_get(Digest, rb_intern("MD5"));
906
+
907
+ rb_define_method(ObjectID, "generate", objectid_generate, 0);
908
+
909
+ rb_define_method(rb_cArray, "fast_pack", fast_pack, 0);
910
+ }