summaryrefslogtreecommitdiff
path: root/src/bson.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bson.c')
-rw-r--r--src/bson.c1251
1 files changed, 1251 insertions, 0 deletions
diff --git a/src/bson.c b/src/bson.c
new file mode 100644
index 0000000..845f6de
--- /dev/null
+++ b/src/bson.c
@@ -0,0 +1,1251 @@
+/* bson.c - libmongo-client's BSON implementation
+ * Copyright 2011, 2012 Gergely Nagy <algernon@balabit.hu>
+ *
+ * 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.
+ */
+
+/** @file src/bson.c
+ * Implementation of the BSON API.
+ */
+
+#include <glib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "bson.h"
+#include "libmongo-macros.h"
+#include "libmongo-private.h"
+
+/** @internal BSON cursor structure.
+ */
+struct _bson_cursor
+{
+ const bson *obj; /**< The BSON object this is a cursor for. */
+ const gchar *key; /**< Pointer within the BSON object to the
+ current key. */
+ size_t pos; /**< Position within the BSON object, pointing at the
+ element type. */
+ size_t value_pos; /**< The start of the value within the BSON
+ object, pointing right after the end of the
+ key. */
+};
+
+/** @internal Append a byte to a BSON stream.
+ *
+ * @param b is the BSON stream to append to.
+ * @param byte is the byte to append.
+ */
+static inline void
+_bson_append_byte (bson *b, const guint8 byte)
+{
+ b->data = g_byte_array_append (b->data, &byte, sizeof (byte));
+}
+
+/** @internal Append a 32-bit integer to a BSON stream.
+ *
+ * @param b is the BSON stream to append to.
+ * @param i is the integer to append.
+ */
+static inline void
+_bson_append_int32 (bson *b, const gint32 i)
+{
+ b->data = g_byte_array_append (b->data, (const guint8 *)&i, sizeof (gint32));
+}
+
+/** @internal Append a 64-bit integer to a BSON stream.
+ *
+ * @param b is the BSON stream to append to.
+ * @param i is the integer to append.
+ */
+static inline void
+_bson_append_int64 (bson *b, const gint64 i)
+{
+ b->data = g_byte_array_append (b->data, (const guint8 *)&i, sizeof (gint64));
+}
+
+/** @internal Append an element header to a BSON stream.
+ *
+ * The element header is a single byte, signaling the type of the
+ * element, followed by a NULL-terminated C string: the key (element)
+ * name.
+ *
+ * @param b is the BSON object to append to.
+ * @param type is the element type to append.
+ * @param name is the key name.
+ *
+ * @returns TRUE on success, FALSE otherwise.
+ */
+static inline gboolean
+_bson_append_element_header (bson *b, bson_type type, const gchar *name)
+{
+ if (!name || !b)
+ return FALSE;
+
+ if (b->finished)
+ return FALSE;
+
+ _bson_append_byte (b, (guint8) type);
+ b->data = g_byte_array_append (b->data, (const guint8 *)name,
+ strlen (name) + 1);
+
+ return TRUE;
+}
+
+/** @internal Append a string-like element to a BSON object.
+ *
+ * There are a few string-like elements in the BSON spec that differ
+ * only in type, not in structure. This convenience function is used
+ * to append them with the appropriate type.
+ *
+ * @param b is the BSON object to append to.
+ * @param type is the string-like type to append.
+ * @param name is the key name.
+ * @param val is the value to append.
+ * @param length is the length of the value.
+ *
+ * @note Passing @a -1 as length will use the full length of @a
+ * val.
+ *
+ * @returns TRUE on success, FALSE otherwise.
+ */
+static gboolean
+_bson_append_string_element (bson *b, bson_type type, const gchar *name,
+ const gchar *val, gint32 length)
+{
+ size_t len;
+
+ if (!val || !length || length < -1)
+ return FALSE;
+
+ len = (length != -1) ? (size_t)length + 1: strlen (val) + 1;
+
+ if (!_bson_append_element_header (b, type, name))
+ return FALSE;
+
+ _bson_append_int32 (b, GINT32_TO_LE (len));
+
+ b->data = g_byte_array_append (b->data, (const guint8 *)val, len - 1);
+ _bson_append_byte (b, 0);
+
+ return TRUE;
+}
+
+/** @internal Append a document-like element to a BSON object.
+ *
+ * Arrays and documents are both similar, and differ very little:
+ * different type, and arrays have restrictions on key names (which
+ * are not enforced by this library).
+ *
+ * This convenience function can append both types.
+ *
+ * @param b is the BSON object to append to.
+ * @param type is the document-like type to append.
+ * @param name is the key name.
+ * @param doc is the document-like object to append.
+ *
+ * @note The @a doc must be a finished BSON object.
+ *
+ * @returns TRUE on success, FALSE otherwise.
+ */
+static gboolean
+_bson_append_document_element (bson *b, bson_type type, const gchar *name,
+ const bson *doc)
+{
+ if (bson_size (doc) < 0)
+ return FALSE;
+
+ if (!_bson_append_element_header (b, type, name))
+ return FALSE;
+
+ b->data = g_byte_array_append (b->data, bson_data (doc), bson_size (doc));
+ return TRUE;
+}
+
+/** @internal Append a 64-bit integer to a BSON object.
+ *
+ * @param b is the BSON object to append to.
+ * @param type is the int64-like type to append.
+ * @param name is the key name.
+ * @param i is the 64-bit value to append.
+ *
+ * @returns TRUE on success, FALSE otherwise.
+ */
+static inline gboolean
+_bson_append_int64_element (bson *b, bson_type type, const gchar *name,
+ gint64 i)
+{
+ if (!_bson_append_element_header (b, type, name))
+ return FALSE;
+
+ _bson_append_int64 (b, GINT64_TO_LE (i));
+ return TRUE;
+}
+
+/********************
+ * Public interface *
+ ********************/
+
+const gchar *
+bson_type_as_string (bson_type type)
+{
+ switch (type)
+ {
+ case BSON_TYPE_NONE:
+ return "BSON_TYPE_NONE";
+ case BSON_TYPE_DOUBLE:
+ return "BSON_TYPE_DOUBLE";
+ case BSON_TYPE_STRING:
+ return "BSON_TYPE_STRING";
+ case BSON_TYPE_DOCUMENT:
+ return "BSON_TYPE_DOCUMENT";
+ case BSON_TYPE_ARRAY:
+ return "BSON_TYPE_ARRAY";
+ case BSON_TYPE_BINARY:
+ return "BSON_TYPE_BINARY";
+ case BSON_TYPE_UNDEFINED:
+ return "BSON_TYPE_UNDEFINED";
+ case BSON_TYPE_OID:
+ return "BSON_TYPE_OID";
+ case BSON_TYPE_BOOLEAN:
+ return "BSON_TYPE_BOOLEAN";
+ case BSON_TYPE_UTC_DATETIME:
+ return "BSON_TYPE_UTC_DATETIME";
+ case BSON_TYPE_NULL:
+ return "BSON_TYPE_NULL";
+ case BSON_TYPE_REGEXP:
+ return "BSON_TYPE_REGEXP";
+ case BSON_TYPE_DBPOINTER:
+ return "BSON_TYPE_DBPOINTER";
+ case BSON_TYPE_JS_CODE:
+ return "BSON_TYPE_JS_CODE";
+ case BSON_TYPE_SYMBOL:
+ return "BSON_TYPE_SYMBOL";
+ case BSON_TYPE_JS_CODE_W_SCOPE:
+ return "BSON_TYPE_JS_CODE_W_SCOPE";
+ case BSON_TYPE_INT32:
+ return "BSON_TYPE_INT32";
+ case BSON_TYPE_TIMESTAMP:
+ return "BSON_TYPE_TIMESTAMP";
+ case BSON_TYPE_INT64:
+ return "BSON_TYPE_INT64";
+ case BSON_TYPE_MIN:
+ return "BSON_TYPE_MIN";
+ case BSON_TYPE_MAX:
+ return "BSON_TYPE_MAX";
+ default:
+ return NULL;
+ }
+}
+
+bson *
+bson_new (void)
+{
+ return bson_new_sized (0);
+}
+
+bson *
+bson_new_sized (gint32 size)
+{
+ bson *b = g_new0 (bson, 1);
+
+ b->data = g_byte_array_sized_new (size + sizeof (gint32) + sizeof (guint8));
+ _bson_append_int32 (b, 0);
+
+ return b;
+}
+
+bson *
+bson_new_from_data (const guint8 *data, gint32 size)
+{
+ bson *b;
+
+ if (!data || size <= 0)
+ return NULL;
+
+ b = g_new0 (bson, 1);
+ b->data = g_byte_array_sized_new (size + sizeof (guint8));
+ b->data = g_byte_array_append (b->data, data, size);
+
+ return b;
+}
+
+/** @internal Add a single element of any type to a BSON object.
+ *
+ * Used internally by bson_build() and bson_build_full(), this
+ * function adds a single element of any supported type to the target
+ * BSON object.
+ *
+ * @param b is the target BSON object.
+ * @param type is the element type to add.
+ * @param name is the key name.
+ * @param free_after signals whether to free the values after adding
+ * them.
+ * @param ap is the list of remaining parameters.
+ *
+ * @returns TRUE in @a single_result on success, FALSE otherwise.
+ */
+#define _bson_build_add_single(b,type,name,free_after,ap) \
+ { \
+ single_result = TRUE; \
+ switch (type) \
+ { \
+ case BSON_TYPE_NONE: \
+ case BSON_TYPE_UNDEFINED: \
+ case BSON_TYPE_DBPOINTER: \
+ single_result = FALSE; \
+ break; \
+ case BSON_TYPE_MIN: \
+ case BSON_TYPE_MAX: \
+ default: \
+ single_result = FALSE; \
+ break; \
+ case BSON_TYPE_DOUBLE: \
+ { \
+ gdouble d = (gdouble)va_arg (ap, gdouble); \
+ bson_append_double (b, name, d); \
+ break; \
+ } \
+ case BSON_TYPE_STRING: \
+ { \
+ gchar *s = (gchar *)va_arg (ap, gpointer); \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson_append_string (b, name, s, l); \
+ if (free_after) \
+ g_free (s); \
+ break; \
+ } \
+ case BSON_TYPE_DOCUMENT: \
+ { \
+ bson *d = (bson *)va_arg (ap, gpointer); \
+ if (free_after && bson_size (d) < 0) \
+ bson_finish (d); \
+ bson_append_document (b, name, d); \
+ if (free_after) \
+ bson_free (d); \
+ break; \
+ } \
+ case BSON_TYPE_ARRAY: \
+ { \
+ bson *d = (bson *)va_arg (ap, gpointer); \
+ if (free_after && bson_size (d) < 0) \
+ bson_finish (d); \
+ bson_append_array (b, name, d); \
+ if (free_after) \
+ bson_free (d); \
+ break; \
+ } \
+ case BSON_TYPE_BINARY: \
+ { \
+ bson_binary_subtype s = \
+ (bson_binary_subtype)va_arg (ap, guint); \
+ guint8 *d = (guint8 *)va_arg (ap, gpointer); \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson_append_binary (b, name, s, d, l); \
+ if (free_after) \
+ g_free (d); \
+ break; \
+ } \
+ case BSON_TYPE_OID: \
+ { \
+ guint8 *oid = (guint8 *)va_arg (ap, gpointer); \
+ bson_append_oid (b, name, oid); \
+ if (free_after) \
+ g_free (oid); \
+ break; \
+ } \
+ case BSON_TYPE_BOOLEAN: \
+ { \
+ gboolean v = (gboolean)va_arg (ap, guint); \
+ bson_append_boolean (b, name, v); \
+ break; \
+ } \
+ case BSON_TYPE_UTC_DATETIME: \
+ { \
+ gint64 ts = (gint64)va_arg (ap, gint64); \
+ bson_append_utc_datetime (b, name, ts); \
+ break; \
+ } \
+ case BSON_TYPE_NULL: \
+ { \
+ bson_append_null (b, name); \
+ break; \
+ } \
+ case BSON_TYPE_REGEXP: \
+ { \
+ gchar *r = (gchar *)va_arg (ap, gpointer); \
+ gchar *o = (gchar *)va_arg (ap, gpointer); \
+ bson_append_regex (b, name, r, o); \
+ if (free_after) \
+ { \
+ g_free (r); \
+ g_free (o); \
+ } \
+ break; \
+ } \
+ case BSON_TYPE_JS_CODE: \
+ { \
+ gchar *s = (gchar *)va_arg (ap, gpointer); \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson_append_javascript (b, name, s, l); \
+ if (free_after) \
+ g_free (s); \
+ break; \
+ } \
+ case BSON_TYPE_SYMBOL: \
+ { \
+ gchar *s = (gchar *)va_arg (ap, gpointer); \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson_append_symbol (b, name, s, l); \
+ if (free_after) \
+ g_free (s); \
+ break; \
+ } \
+ case BSON_TYPE_JS_CODE_W_SCOPE: \
+ { \
+ gchar *s = (gchar *)va_arg (ap, gpointer); \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson *scope = (bson *)va_arg (ap, gpointer); \
+ if (free_after && bson_size (scope) < 0) \
+ bson_finish (scope); \
+ bson_append_javascript_w_scope (b, name, s, l, scope); \
+ if (free_after) \
+ bson_free (scope); \
+ break; \
+ } \
+ case BSON_TYPE_INT32: \
+ { \
+ gint32 l = (gint32)va_arg (ap, gint32); \
+ bson_append_int32 (b, name, l); \
+ break; \
+ } \
+ case BSON_TYPE_TIMESTAMP: \
+ { \
+ gint64 ts = (gint64)va_arg (ap, gint64); \
+ bson_append_timestamp (b, name, ts); \
+ break; \
+ } \
+ case BSON_TYPE_INT64: \
+ { \
+ gint64 l = (gint64)va_arg (ap, gint64); \
+ bson_append_int64 (b, name, l); \
+ break; \
+ } \
+ } \
+ }
+
+bson *
+bson_build (bson_type type, const gchar *name, ...)
+{
+ va_list ap;
+ bson_type t;
+ const gchar *n;
+ bson *b;
+ gboolean single_result;
+
+ b = bson_new ();
+ va_start (ap, name);
+ _bson_build_add_single (b, type, name, FALSE, ap);
+
+ if (!single_result)
+ {
+ bson_free (b);
+ va_end (ap);
+ return NULL;
+ }
+
+ while ((t = (bson_type)va_arg (ap, gint)))
+ {
+ n = (const gchar *)va_arg (ap, gpointer);
+ _bson_build_add_single (b, t, n, FALSE, ap);
+ if (!single_result)
+ {
+ bson_free (b);
+ va_end (ap);
+ return NULL;
+ }
+ }
+ va_end (ap);
+
+ return b;
+}
+
+bson *
+bson_build_full (bson_type type, const gchar *name, gboolean free_after, ...)
+{
+ va_list ap;
+ bson_type t;
+ const gchar *n;
+ gboolean f;
+ bson *b;
+ gboolean single_result;
+
+ b = bson_new ();
+ va_start (ap, free_after);
+ _bson_build_add_single (b, type, name, free_after, ap);
+ if (!single_result)
+ {
+ bson_free (b);
+ va_end (ap);
+ return NULL;
+ }
+
+ while ((t = (bson_type)va_arg (ap, gint)))
+ {
+ n = (const gchar *)va_arg (ap, gpointer);
+ f = (gboolean)va_arg (ap, gint);
+ _bson_build_add_single (b, t, n, f, ap);
+ if (!single_result)
+ {
+ bson_free (b);
+ va_end (ap);
+ return NULL;
+ }
+ }
+ va_end (ap);
+
+ return b;
+}
+
+gboolean
+bson_finish (bson *b)
+{
+ gint32 *i;
+
+ if (!b)
+ return FALSE;
+
+ if (b->finished)
+ return TRUE;
+
+ _bson_append_byte (b, 0);
+
+ i = (gint32 *) (&b->data->data[0]);
+ *i = GINT32_TO_LE ((gint32) (b->data->len));
+
+ b->finished = TRUE;
+
+ return TRUE;
+}
+
+gint32
+bson_size (const bson *b)
+{
+ if (!b)
+ return -1;
+
+ if (b->finished)
+ return b->data->len;
+ else
+ return -1;
+}
+
+const guint8 *
+bson_data (const bson *b)
+{
+ if (!b)
+ return NULL;
+
+ if (b->finished)
+ return b->data->data;
+ else
+ return NULL;
+}
+
+gboolean
+bson_reset (bson *b)
+{
+ if (!b)
+ return FALSE;
+
+ b->finished = FALSE;
+ g_byte_array_set_size (b->data, 0);
+ _bson_append_int32 (b, 0);
+
+ return TRUE;
+}
+
+void
+bson_free (bson *b)
+{
+ if (!b)
+ return;
+
+ if (b->data)
+ g_byte_array_free (b->data, TRUE);
+ g_free (b);
+}
+
+gboolean
+bson_validate_key (const gchar *key, gboolean forbid_dots,
+ gboolean no_dollar)
+{
+ if (!key)
+ {
+ errno = EINVAL;
+ return FALSE;
+ }
+ errno = 0;
+
+ if (no_dollar && key[0] == '$')
+ return FALSE;
+
+ if (forbid_dots && strchr (key, '.') != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Append elements
+ */
+
+gboolean
+bson_append_double (bson *b, const gchar *name, gdouble val)
+{
+ gdouble d = GDOUBLE_TO_LE (val);
+
+ if (!_bson_append_element_header (b, BSON_TYPE_DOUBLE, name))
+ return FALSE;
+
+ b->data = g_byte_array_append (b->data, (const guint8 *)&d, sizeof (val));
+ return TRUE;
+}
+
+gboolean
+bson_append_string (bson *b, const gchar *name, const gchar *val,
+ gint32 length)
+{
+ return _bson_append_string_element (b, BSON_TYPE_STRING, name, val, length);
+}
+
+gboolean
+bson_append_document (bson *b, const gchar *name, const bson *doc)
+{
+ return _bson_append_document_element (b, BSON_TYPE_DOCUMENT, name, doc);
+}
+
+gboolean
+bson_append_array (bson *b, const gchar *name, const bson *array)
+{
+ return _bson_append_document_element (b, BSON_TYPE_ARRAY, name, array);
+}
+
+gboolean
+bson_append_binary (bson *b, const gchar *name, bson_binary_subtype subtype,
+ const guint8 *data, gint32 size)
+{
+ if (!data || !size || size <= 0)
+ return FALSE;
+
+ if (!_bson_append_element_header (b, BSON_TYPE_BINARY, name))
+ return FALSE;
+
+ _bson_append_int32 (b, GINT32_TO_LE (size));
+ _bson_append_byte (b, (guint8)subtype);
+
+ b->data = g_byte_array_append (b->data, data, size);
+ return TRUE;
+}
+
+gboolean
+bson_append_oid (bson *b, const gchar *name, const guint8 *oid)
+{
+ if (!oid)
+ return FALSE;
+
+ if (!_bson_append_element_header (b, BSON_TYPE_OID, name))
+ return FALSE;
+
+ b->data = g_byte_array_append (b->data, oid, 12);
+ return TRUE;
+}
+
+gboolean
+bson_append_boolean (bson *b, const gchar *name, gboolean value)
+{
+ if (!_bson_append_element_header (b, BSON_TYPE_BOOLEAN, name))
+ return FALSE;
+
+ _bson_append_byte (b, (guint8)value);
+ return TRUE;
+}
+
+gboolean
+bson_append_utc_datetime (bson *b, const gchar *name, gint64 ts)
+{
+ return _bson_append_int64_element (b, BSON_TYPE_UTC_DATETIME, name, ts);
+}
+
+gboolean
+bson_append_null (bson *b, const gchar *name)
+{
+ return _bson_append_element_header (b, BSON_TYPE_NULL, name);
+}
+
+gboolean
+bson_append_regex (bson *b, const gchar *name, const gchar *regexp,
+ const gchar *options)
+{
+ if (!regexp || !options)
+ return FALSE;
+
+ if (!_bson_append_element_header (b, BSON_TYPE_REGEXP, name))
+ return FALSE;
+
+ b->data = g_byte_array_append (b->data, (const guint8 *)regexp,
+ strlen (regexp) + 1);
+ b->data = g_byte_array_append (b->data, (const guint8 *)options,
+ strlen (options) + 1);
+
+ return TRUE;
+}
+
+gboolean
+bson_append_javascript (bson *b, const gchar *name, const gchar *js,
+ gint32 len)
+{
+ return _bson_append_string_element (b, BSON_TYPE_JS_CODE, name, js, len);
+}
+
+gboolean
+bson_append_symbol (bson *b, const gchar *name, const gchar *symbol,
+ gint32 len)
+{
+ return _bson_append_string_element (b, BSON_TYPE_SYMBOL, name, symbol, len);
+}
+
+gboolean
+bson_append_javascript_w_scope (bson *b, const gchar *name,
+ const gchar *js, gint32 len,
+ const bson *scope)
+{
+ gint size;
+ size_t length;
+
+ if (!js || !scope || bson_size (scope) < 0 || len < -1)
+ return FALSE;
+
+ if (!_bson_append_element_header (b, BSON_TYPE_JS_CODE_W_SCOPE, name))
+ return FALSE;
+
+ length = (len != -1) ? (size_t)len + 1: strlen (js) + 1;
+
+ size = length + sizeof (gint32) + sizeof (gint32) + bson_size (scope);
+
+ _bson_append_int32 (b, GINT32_TO_LE (size));
+
+ /* Append the JS code */
+ _bson_append_int32 (b, GINT32_TO_LE (length));
+ b->data = g_byte_array_append (b->data, (const guint8 *)js, length - 1);
+ _bson_append_byte (b, 0);
+
+ /* Append the scope */
+ b->data = g_byte_array_append (b->data, bson_data (scope),
+ bson_size (scope));
+
+ return TRUE;
+}
+
+gboolean
+bson_append_int32 (bson *b, const gchar *name, gint32 i)
+{
+ if (!_bson_append_element_header (b, BSON_TYPE_INT32, name))
+ return FALSE;
+
+ _bson_append_int32 (b, GINT32_TO_LE (i));
+ return TRUE;
+ }
+
+gboolean
+bson_append_timestamp (bson *b, const gchar *name, gint64 ts)
+{
+ return _bson_append_int64_element (b, BSON_TYPE_TIMESTAMP, name, ts);
+}
+
+gboolean
+bson_append_int64 (bson *b, const gchar *name, gint64 i)
+{
+ return _bson_append_int64_element (b, BSON_TYPE_INT64, name, i);
+}
+
+/*
+ * Find & retrieve data
+ */
+bson_cursor *
+bson_cursor_new (const bson *b)
+{
+ bson_cursor *c;
+
+ if (bson_size (b) == -1)
+ return NULL;
+
+ c = (bson_cursor *)g_new0 (bson_cursor, 1);
+ c->obj = b;
+
+ return c;
+}
+
+void
+bson_cursor_free (bson_cursor *c)
+{
+ g_free (c);
+}
+
+/** @internal Figure out the block size of a given type.
+ *
+ * Provided a #bson_type and some raw data, figures out the length of
+ * the block, counted from rigth after the element name's position.
+ *
+ * @param type is the type of object we need the size for.
+ * @param data is the raw data (starting right after the element's
+ * name).
+ *
+ * @returns The size of the block, or -1 on error.
+ */
+static gint32
+_bson_get_block_size (bson_type type, const guint8 *data)
+{
+ glong l;
+
+ switch (type)
+ {
+ case BSON_TYPE_STRING:
+ case BSON_TYPE_JS_CODE:
+ case BSON_TYPE_SYMBOL:
+ return bson_stream_doc_size (data, 0) + sizeof (gint32);
+ case BSON_TYPE_DOCUMENT:
+ case BSON_TYPE_ARRAY:
+ case BSON_TYPE_JS_CODE_W_SCOPE:
+ return bson_stream_doc_size (data, 0);
+ case BSON_TYPE_DOUBLE:
+ return sizeof (gdouble);
+ case BSON_TYPE_BINARY:
+ return bson_stream_doc_size (data, 0) +
+ sizeof (gint32) + sizeof (guint8);
+ case BSON_TYPE_OID:
+ return 12;
+ case BSON_TYPE_BOOLEAN:
+ return 1;
+ case BSON_TYPE_UTC_DATETIME:
+ case BSON_TYPE_TIMESTAMP:
+ case BSON_TYPE_INT64:
+ return sizeof (gint64);
+ case BSON_TYPE_NULL:
+ case BSON_TYPE_UNDEFINED:
+ case BSON_TYPE_MIN:
+ case BSON_TYPE_MAX:
+ return 0;
+ case BSON_TYPE_REGEXP:
+ l = strlen((gchar *)data);
+ return l + strlen((gchar *)(data + l + 1)) + 2;
+ case BSON_TYPE_INT32:
+ return sizeof (gint32);
+ case BSON_TYPE_DBPOINTER:
+ return bson_stream_doc_size (data, 0) + sizeof (gint32) + 12;
+ case BSON_TYPE_NONE:
+ default:
+ return -1;
+ }
+}
+
+gboolean
+bson_cursor_next (bson_cursor *c)
+{
+ const guint8 *d;
+ gint32 pos, bs;
+
+ if (!c)
+ return FALSE;
+
+ d = bson_data (c->obj);
+
+ if (c->pos == 0)
+ pos = sizeof (guint32);
+ else
+ {
+ bs = _bson_get_block_size (bson_cursor_type (c), d + c->value_pos);
+ if (bs == -1)
+ return FALSE;
+ pos = c->value_pos + bs;
+ }
+
+ if (pos >= bson_size (c->obj) - 1)
+ return FALSE;
+
+ c->pos = pos;
+ c->key = (gchar *) &d[c->pos + 1];
+ c->value_pos = c->pos + strlen (c->key) + 2;
+
+ return TRUE;
+}
+
+static inline gboolean
+_bson_cursor_find (const bson *b, const gchar *name, size_t start_pos,
+ gint32 end_pos, gboolean wrap_over, bson_cursor *dest_c)
+{
+ gint32 pos = start_pos, bs;
+ const guint8 *d;
+ gint32 name_len;
+
+ name_len = strlen (name);
+
+ d = bson_data (b);
+
+ while (pos < end_pos)
+ {
+ bson_type t = (bson_type) d[pos];
+ const gchar *key = (gchar *) &d[pos + 1];
+ gint32 key_len = strlen (key);
+ gint32 value_pos = pos + key_len + 2;
+
+ if (key_len == name_len && memcmp (key, name, key_len) == 0)
+ {
+ dest_c->obj = b;
+ dest_c->key = key;
+ dest_c->pos = pos;
+ dest_c->value_pos = value_pos;
+
+ return TRUE;
+ }
+ bs = _bson_get_block_size (t, &d[value_pos]);
+ if (bs == -1)
+ return FALSE;
+ pos = value_pos + bs;
+ }
+
+ if (wrap_over)
+ return _bson_cursor_find (b, name, sizeof (gint32), start_pos,
+ FALSE, dest_c);
+
+ return FALSE;
+}
+
+gboolean
+bson_cursor_find (bson_cursor *c, const gchar *name)
+{
+ if (!c || !name)
+ return FALSE;
+
+ return _bson_cursor_find (c->obj, name, c->pos, bson_size (c->obj) - 1,
+ TRUE, c);
+}
+
+gboolean
+bson_cursor_find_next (bson_cursor *c, const gchar *name)
+{
+ if (!c || !name)
+ return FALSE;
+
+ return _bson_cursor_find (c->obj, name, c->pos, bson_size (c->obj) - 1,
+ FALSE, c);
+}
+
+bson_cursor *
+bson_find (const bson *b, const gchar *name)
+{
+ bson_cursor *c;
+
+ if (bson_size (b) == -1 || !name)
+ return NULL;
+
+ c = bson_cursor_new (b);
+ if (_bson_cursor_find (b, name, sizeof (gint32), bson_size (c->obj) - 1,
+ FALSE, c))
+ return c;
+ bson_cursor_free (c);
+ return NULL;
+}
+
+bson_type
+bson_cursor_type (const bson_cursor *c)
+{
+ if (!c || c->pos < sizeof (gint32))
+ return BSON_TYPE_NONE;
+
+ return (bson_type)(bson_data (c->obj)[c->pos]);
+}
+
+const gchar *
+bson_cursor_type_as_string (const bson_cursor *c)
+{
+ if (!c || c->pos < sizeof (gint32))
+ return NULL;
+
+ return bson_type_as_string (bson_cursor_type (c));
+}
+
+const gchar *
+bson_cursor_key (const bson_cursor *c)
+{
+ if (!c)
+ return NULL;
+
+ return c->key;
+}
+
+/** @internal Convenience macro to verify a cursor's type.
+ *
+ * Verifies that the cursor's type is the same as the type requested
+ * by the caller, and returns FALSE if there is a mismatch.
+ */
+#define BSON_CURSOR_CHECK_TYPE(c,type) \
+ if (bson_cursor_type(c) != type) \
+ return FALSE;
+
+gboolean
+bson_cursor_get_string (const bson_cursor *c, const gchar **dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_STRING);
+
+ *dest = (gchar *)(bson_data (c->obj) + c->value_pos + sizeof (gint32));
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_double (const bson_cursor *c, gdouble *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_DOUBLE);
+
+ memcpy (dest, bson_data (c->obj) + c->value_pos, sizeof (gdouble));
+ *dest = GDOUBLE_FROM_LE (*dest);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_document (const bson_cursor *c, bson **dest)
+{
+ bson *b;
+ gint32 size;
+
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_DOCUMENT);
+
+ size = bson_stream_doc_size (bson_data(c->obj), c->value_pos) -
+ sizeof (gint32) - 1;
+ b = bson_new_sized (size);
+ b->data = g_byte_array_append (b->data,
+ bson_data (c->obj) + c->value_pos +
+ sizeof (gint32), size);
+ bson_finish (b);
+
+ *dest = b;
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_array (const bson_cursor *c, bson **dest)
+{
+ bson *b;
+ gint32 size;
+
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_ARRAY);
+
+ size = bson_stream_doc_size (bson_data(c->obj), c->value_pos) -
+ sizeof (gint32) - 1;
+ b = bson_new_sized (size);
+ b->data = g_byte_array_append (b->data,
+ bson_data (c->obj) + c->value_pos +
+ sizeof (gint32), size);
+ bson_finish (b);
+
+ *dest = b;
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_binary (const bson_cursor *c,
+ bson_binary_subtype *subtype,
+ const guint8 **data, gint32 *size)
+{
+ if (!subtype || !size || !data)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_BINARY);
+
+ *size = bson_stream_doc_size (bson_data(c->obj), c->value_pos);
+ *subtype = (bson_binary_subtype)(bson_data (c->obj)[c->value_pos +
+ sizeof (gint32)]);
+ *data = (guint8 *)(bson_data (c->obj) + c->value_pos + sizeof (gint32) + 1);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_oid (const bson_cursor *c, const guint8 **dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_OID);
+
+ *dest = (guint8 *)(bson_data (c->obj) + c->value_pos);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_boolean (const bson_cursor *c, gboolean *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_BOOLEAN);
+
+ *dest = (gboolean)(bson_data (c->obj) + c->value_pos)[0];
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_utc_datetime (const bson_cursor *c,
+ gint64 *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_UTC_DATETIME);
+
+ memcpy (dest, bson_data (c->obj) + c->value_pos, sizeof (gint64));
+ *dest = GINT64_FROM_LE (*dest);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_regex (const bson_cursor *c, const gchar **regex,
+ const gchar **options)
+{
+ if (!regex || !options)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_REGEXP);
+
+ *regex = (gchar *)(bson_data (c->obj) + c->value_pos);
+ *options = (gchar *)(*regex + strlen(*regex) + 1);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_javascript (const bson_cursor *c, const gchar **dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_JS_CODE);
+
+ *dest = (gchar *)(bson_data (c->obj) + c->value_pos + sizeof (gint32));
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_symbol (const bson_cursor *c, const gchar **dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_SYMBOL);
+
+ *dest = (gchar *)(bson_data (c->obj) + c->value_pos + sizeof (gint32));
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_javascript_w_scope (const bson_cursor *c,
+ const gchar **js,
+ bson **scope)
+{
+ bson *b;
+ gint32 size, docpos;
+
+ if (!js || !scope)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_JS_CODE_W_SCOPE);
+
+ docpos = bson_stream_doc_size (bson_data (c->obj),
+ c->value_pos + sizeof (gint32)) +
+ sizeof (gint32) * 2;
+ size = bson_stream_doc_size (bson_data (c->obj), c->value_pos + docpos) -
+ sizeof (gint32) - 1;
+ b = bson_new_sized (size);
+ b->data = g_byte_array_append (b->data,
+ bson_data (c->obj) + c->value_pos + docpos +
+ sizeof (gint32), size);
+ bson_finish (b);
+
+ *scope = b;
+ *js = (gchar *)(bson_data (c->obj) + c->value_pos + sizeof (gint32) * 2);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_int32 (const bson_cursor *c, gint32 *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_INT32);
+
+ memcpy (dest, bson_data (c->obj) + c->value_pos, sizeof (gint32));
+ *dest = GINT32_FROM_LE (*dest);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_timestamp (const bson_cursor *c, gint64 *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_TIMESTAMP);
+
+ memcpy (dest, bson_data (c->obj) + c->value_pos, sizeof (gint64));
+ *dest = GINT64_FROM_LE (*dest);
+
+ return TRUE;
+}
+
+gboolean
+bson_cursor_get_int64 (const bson_cursor *c, gint64 *dest)
+{
+ if (!dest)
+ return FALSE;
+
+ BSON_CURSOR_CHECK_TYPE (c, BSON_TYPE_INT64);
+
+ memcpy (dest, bson_data (c->obj) + c->value_pos, sizeof (gint64));
+ *dest = GINT64_FROM_LE (*dest);
+
+ return TRUE;
+}