270 lines
7.2 KiB
GDScript
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
|