2025-02-17 15:54:51 -08:00

270 lines
7.2 KiB
GDScript

# COPYRIGHT 2025 Colormatic Studios and contributors.
# This file is the BSON serializer for the Godot Engine,
# published under the MIT license. https://opensource.org/license/MIT
class_name BSON
static func to_bson(data: Dictionary) -> PackedByteArray:
var document := dictionary_to_bytes(data)
document.append(0x00)
var buffer := PackedByteArray()
buffer.append_array(int32_to_bytes(document.size() + 4))
buffer.append_array(document)
return buffer
static func get_byte_type(value: Variant) -> int:
match typeof(value):
TYPE_STRING:
return 0x02
TYPE_INT:
if abs(value as int) < 2147483647: # 32 bit signed integer limit
return 0x10
else:
return 0x12
TYPE_FLOAT:
return 0x01
TYPE_ARRAY:
return 0x04
TYPE_DICTIONARY:
return 0x03
TYPE_BOOL:
return 0x08
_:
push_error("BSON serialization error: Unsupported type: ", typeof(value))
return 0x00
static func int16_to_bytes(value: int) -> PackedByteArray:
var buffer := PackedByteArray()
buffer.resize(2)
buffer.encode_s16(0, value)
return buffer
static func int32_to_bytes(value: int) -> PackedByteArray:
var buffer := PackedByteArray()
buffer.resize(4)
buffer.encode_s32(0, value)
return buffer
static func int64_to_bytes(value: int) -> PackedByteArray:
var buffer := PackedByteArray()
buffer.resize(8)
buffer.encode_s64(0, value)
return buffer
static func float_to_bytes(value: float) -> PackedByteArray:
var buffer := PackedByteArray()
buffer.resize(4)
buffer.encode_float(0, value)
return buffer
static func double_to_bytes(value: float) -> PackedByteArray:
var buffer := PackedByteArray()
buffer.resize(8)
buffer.encode_double(0, value)
return buffer
static func dictionary_to_bytes(dict: Dictionary) -> PackedByteArray:
var buffer := PackedByteArray()
for key: String in dict.keys():
buffer.append(get_byte_type(dict[key]))
var key_string_bytes := key.to_utf8_buffer()
buffer.append_array(key_string_bytes)
buffer.append(0x00)
buffer.append_array(serialize_variant(dict[key]))
return buffer
static func array_to_bytes(array: Array[Variant]) -> PackedByteArray:
var buffer := PackedByteArray()
for index: int in range(array.size()):
buffer.append(get_byte_type(array[index]))
# For whatever reason, BSON wants array indexes to be strings. This makes no sense.
var s_index := str(index)
buffer.append_array(s_index.to_utf8_buffer())
buffer.append(0x00)
buffer.append_array(serialize_variant(array[index]))
return buffer
static func serialize_variant(data: Variant) -> PackedByteArray:
var buffer := PackedByteArray()
match typeof(data):
TYPE_DICTIONARY:
var document := dictionary_to_bytes(data as Dictionary)
buffer.append_array(int32_to_bytes(document.size()))
buffer.append_array(document)
buffer.append(0x00)
TYPE_ARRAY:
var b_array := array_to_bytes(data as Array[Variant])
buffer.append_array(int32_to_bytes(b_array.size()))
buffer.append_array(b_array)
buffer.append(0x00)
TYPE_STRING:
var str_as_bytes := (data as String).to_utf8_buffer()
buffer.append_array(int32_to_bytes(str_as_bytes.size() + 1))
buffer.append_array(str_as_bytes)
buffer.append(0x00)
TYPE_INT:
if abs(data as int) < 2147483647: # 32 bit signed integer limit
buffer.append_array(int32_to_bytes(data as int))
else:
buffer.append_array(int64_to_bytes(data as int))
TYPE_FLOAT:
buffer.append_array(double_to_bytes(data as float))
TYPE_BOOL:
buffer.append((data as bool) if 0x01 else 0x00)
_:
buffer.append(0x00)
return buffer
static func from_bson(data: PackedByteArray) -> Dictionary:
return Deserializer.new(data).read_dictionary()
class Deserializer:
var buffer: PackedByteArray
var read_position := 0
func _init(buffer: PackedByteArray):
self.buffer = buffer
func get_int8() -> int:
var value := buffer[read_position]
read_position += 1
return value
func get_int16() -> int:
var value := buffer.decode_s16(read_position)
read_position += 2
return value
func get_int32() -> int:
var value := buffer.decode_s32(read_position)
read_position += 4
return value
func get_int64() -> int:
var value := buffer.decode_s64(read_position)
read_position += 8
return value
func get_float() -> float:
var value := buffer.decode_float(read_position)
read_position += 4
return value
func get_double() -> float:
var value := buffer.decode_double(read_position)
read_position += 8
return value
func get_string() -> String:
var expected_size = get_int32()
var s_value: String
var iter := 0
while true:
iter += 1
var b_char := get_int8()
if b_char == 0x00: break
s_value += char(b_char)
if expected_size != iter: # Check if the string is terminated with 0x00
push_error("BSON deserialization error: String was the wrong size."
+ " Position: "
+ str(read_position - iter)
+ ", stated size: "
+ str(expected_size)
+ ", actual size: "
+ str(iter))
return s_value
func get_bool() -> bool:
return (get_int8() == 1)
func read_dictionary() -> Dictionary:
var object = {}
var expected_size := get_int32()
var iter := 0
while true:
iter += 1
var type := get_int8()
if type == 0x00: break
var key := ""
while true:
var k_char := get_int8()
if k_char == 0x00: break
key += char(k_char)
match type:
0x02: object[key] = get_string()
0x10: object[key] = get_int32()
0x12: object[key] = get_int64()
0x01: object[key] = get_double()
0x08: object[key] = get_bool()
0x04: object[key] = read_array()
0x03: object[key] = read_dictionary()
_:
push_error("BSON deserialization error: Unsupported type "
+ str(type)
+ " at byte "
+ str(read_position - 1))
if iter > expected_size:
push_warning("BSON deserialization warning: Dictionary is the wrong length."
+ " Expected dictionary length: "
+ str(expected_size)
+ ", Actual dictionary length: "
+ str(iter))
return object
func read_array() -> Array:
var array: Array
var expected_size := get_int32()
var iter := 0
while true:
iter += 1
var type := get_int8()
if type == 0x00: break
var key: String
while true:
var k_char := get_int8()
if k_char == 0x00: break
key += char(k_char)
# IMPORTANT: Since the array is being deserialized sequentially, we can
# use the Array.append() function. It would be better to set the index
# directly, but that is not possible. (It *could* cause null holes)
# The BSON specification unfortunately allows for null holes, but
# this deserializer will just remove any gaps. This could cause an
# index desynchronization between two programs communicating with BSON,
# but that means the other program has a buggy serializer.
match type:
0x02: array.append(get_string())
0x10: array.append(get_int32())
0x12: array.append(get_int64())
0x01: array.append(get_double())
0x08: array.append(get_bool())
0x04: array.append(read_array())
0x03: array.append(read_dictionary())
_: push_error("BSON deserialization error: Unsupported type: " + str(type))
if iter > expected_size:
push_warning("BSON deserialization warning: Array is the wrong length."
+ " Expected array length: "
+ str(expected_size)
+ ", Actual array length: "
+ str(iter))
return array