diff options
Diffstat (limited to 'src/zenserver/frontend/html/compactbinary.js')
| -rw-r--r-- | src/zenserver/frontend/html/compactbinary.js | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/zenserver/frontend/html/compactbinary.js b/src/zenserver/frontend/html/compactbinary.js new file mode 100644 index 000000000..c648e6052 --- /dev/null +++ b/src/zenserver/frontend/html/compactbinary.js @@ -0,0 +1,440 @@ +"use strict"; + +//////////////////////////////////////////////////////////////////////////////// +class VarInt +{ +} + +//////////////////////////////////////////////////////////////////////////////// +VarInt.measure = function(data_view) +{ + var value = data_view[0]; + var ret = 1; + for (; value & 0x80; value <<= 1, ++ret); + return ret; +} + +//////////////////////////////////////////////////////////////////////////////// +VarInt.read_uint = function(data_view, return_type=Number) +{ + const length = VarInt.measure(data_view); + var value = return_type(data_view[0] & (0xff >> length)); + for (var i = 1; i < length; ++i) + { + value <<= return_type(8); + value |= return_type(data_view[i]); + } + return [value, length]; +} + +//////////////////////////////////////////////////////////////////////////////// +VarInt.read_int = function(data_view, return_type=Number) +{ + var [value, length] = VarInt.read_uint(data_view, return_type); + value = -(value & return_type(1)) ^ (value >> return_type(1)); + return [value, length]; +} + + + +//////////////////////////////////////////////////////////////////////////////// +function cb_assert(expr_result) +{ + if (Boolean(expr_result) == false) + throw Error("compactbinary error"); +} + + + +//////////////////////////////////////////////////////////////////////////////// +const CbFieldType = { + None : 0x00, + Null : 0x01, + Object : 0x02, + UniformObject : 0x03, + Array : 0x04, + UniformArray : 0x05, + Binary : 0x06, + String : 0x07, + IntegerPositive : 0x08, + IntegerNegative : 0x09, + Float32 : 0x0a, + Float64 : 0x0b, + BoolFalse : 0x0c, + BoolTrue : 0x0d, + ObjectAttachment : 0x0e, + BinaryAttachment : 0x0f, + Hash : 0x10, + Uuid : 0x11, + DateTime : 0x12, + TimeSpan : 0x13, + ObjectId : 0x14, + CustomById : 0x1e, + CustomByName : 0x1f, + Reserved : 0x20, + HasFieldType : 0x40, + HasFieldName : 0x80, +} + +//////////////////////////////////////////////////////////////////////////////// +class CbFieldTypeOps +{ + static SerializedTypeMask = 0b10111111; + static TypeMask = 0b00111111; + static ObjectMask = 0b00111110; + static ObjectBase = 0b00000010; + static ArrayMask = 0b00111110; + static ArrayBase = 0b00000100; + static IntegerMask = 0b00111110; + static IntegerBase = 0b00001000; + static FloatMask = 0b00111100; + static FloatBase = 0b00001000; + static BoolMask = 0b00111110; + static BoolBase = 0b00001100; + static AttachmentMask = 0b00111110; + static AttachmentBase = 0b00001110; + + static get_type(type) { return type & CbFieldTypeOps.TypeMask; } + static get_serialized_type(type) { return type & CbFieldTypeOps.SerializedTypeMask; } + static has_field_type(type) { return (type & CbFieldType.HasFieldType) != 0; } + static has_field_name(type) { return (type & CbFieldType.HasFieldName) != 0; } + static is_none(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.None; } + static is_null(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.Null; } + static is_object(type) { return (type & CbFieldTypeOps.ObjectMask) == CbFieldTypeOps.ObjectBase; } + static is_array(type) { return (type & CbFieldTypeOps.ArrayMask) == CbFieldTypeOps.ArrayBase; } + static is_binary(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.Binary; } + static is_string(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.String; } + static is_integer(type) { return (type & CbFieldTypeOps.IntegerMask) == CbFieldTypeOps.IntegerBase; } + static is_float(type) { return (type & CbFieldTypeOps.FloatMask) == CbFieldTypeOps.FloatBase; } + static is_bool(type) { return (type & CbFieldTypeOps.BoolMask) == CbFieldTypeOps.BoolBase; } + static is_object_attachment(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.ObjectAttachment; } + static is_binary_attachment(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.BinaryAttachment; } + static is_attachment(type) { return (type & CbFieldTypeOps.AttachmentMask) == CbFieldTypeOps.AttachmentBase; } + static is_uuid(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.Uuid; } + static is_object_id(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.ObjectId; } + static is_custom_by_id(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.CustomById; } + static is_custom_by_name(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.CustomByName; } + static is_date_time(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.DateTime; } + static is_time_span(type) { return CbFieldTypeOps.get_type(type) == CbFieldType.TimeSpan; } + static is_hash(type) { var t = CbFieldTypeOps.get_type(type); return t >= CbFieldType.ObjectAttachment && t <= CbFieldType.Hash; } + static may_contain_attachments(type){ var t = CbFieldTypeOps.get_type(type); return is_object(t) || is_array(t) || is_attachement(t); } +} + + + +//////////////////////////////////////////////////////////////////////////////// +class CbFieldView +{ + constructor() + { + this._type = CbFieldType.None; + this._name = ""; + this._data_view = undefined; + } +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype._from_field = function(field) +{ + this._type = field._type; + this._name = field._name; + this._data_view = field._data_view; + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype._from_data = function(data_view, type=CbFieldType.HasFieldType) +{ + if (CbFieldTypeOps.has_field_type(type)) + { + type = data_view[0] | CbFieldType.HasFieldType; + data_view = data_view.subarray(1); + } + + if (CbFieldTypeOps.has_field_name(type)) + { + const [n, varint_len] = VarInt.read_uint(data_view); + this._name = new TextDecoder().decode(data_view.subarray(varint_len, n + varint_len)); + data_view = data_view.subarray(n + varint_len); + } + + this._type = type; + this._data_view = data_view; + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView._iterate = function*(data_view, uniform_type) +{ + while (data_view.length > 0) + { + const field = new CbFieldView()._from_data(data_view, uniform_type); + yield field; + + const field_size = field.get_payload_size(); + cb_assert(field_size <= data_view.length); + data_view = field.get_payload().subarray(field_size); + } +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.get_type = function() +{ + return this._type; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.get_name = function() +{ + return this._name; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.get_payload = function() +{ + return this._data_view; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.get_payload_size = function() +{ + switch (CbFieldTypeOps.get_type(this.get_type())) + { + case CbFieldType.None: + case CbFieldType.Null: + return 0; + case CbFieldType.Object: + case CbFieldType.UniformObject: + case CbFieldType.Array: + case CbFieldType.UniformArray: + case CbFieldType.Binary: + case CbFieldType.String: + const [value, varint_len] = VarInt.read_uint(this._data_view); + return value + varint_len; + case CbFieldType.IntegerPositive: + case CbFieldType.IntegerNegative: + return VarInt.measure(this._data_view); + case CbFieldType.Float32: + return 4; + case CbFieldType.Float64: + return 8; + case CbFieldType.BoolFalse: + case CbFieldType.BoolTrue: + return 0; + case CbFieldType.ObjectAttachment: + case CbFieldType.BinaryAttachment: + case CbFieldType.Hash: + return 20; + case CbFieldType.Uuid: + return 16; + case CbFieldType.ObjectId: + return 12; + case CbFieldType.DateTime: + case CbFieldType.TimeSpan: + return 8; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype._is = function(func) { return func(this.get_type()); } +CbFieldView.prototype.is_null = function() { return this._is(CbFieldTypeOps.is_null); } +CbFieldView.prototype.is_object = function() { return this._is(CbFieldTypeOps.is_object); } +CbFieldView.prototype.is_array = function() { return this._is(CbFieldTypeOps.is_array); } +CbFieldView.prototype.is_binary = function() { return this._is(CbFieldTypeOps.is_binary); } +CbFieldView.prototype.is_string = function() { return this._is(CbFieldTypeOps.is_string); } +CbFieldView.prototype.is_integer = function() { return this._is(CbFieldTypeOps.is_integer); } +CbFieldView.prototype.is_float = function() { return this._is(CbFieldTypeOps.is_float); } +CbFieldView.prototype.is_bool = function() { return this._is(CbFieldTypeOps.is_bool); } +CbFieldView.prototype.is_object_attachment = function() { return this._is(CbFieldTypeOps.is_object_attachment); } +CbFieldView.prototype.is_binary_attachment = function() { return this._is(CbFieldTypeOps.is_binary_attachment); } +CbFieldView.prototype.is_attachment = function() { return this._is(CbFieldTypeOps.is_attachment); } +CbFieldView.prototype.is_hash = function() { return this._is(CbFieldTypeOps.is_hash); } +CbFieldView.prototype.is_uuid = function() { return this._is(CbFieldTypeOps.is_uuid); } +CbFieldView.prototype.is_object_id = function() { return this._is(CbFieldTypeOps.is_object_id); } +CbFieldView.prototype.is_date_time = function() { return this._is(CbFieldTypeOps.is_date_time); } +CbFieldView.prototype.is_time_span = function() { return this._is(CbFieldTypeOps.is_time_span); } + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.as_object = function() +{ + cb_assert(CbFieldTypeOps.is_object(this.get_type())); + return new CbObjectView()._from_field(this); +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.as_array = function() +{ + cb_assert(CbFieldTypeOps.is_array(this.get_type())); + return new CbArrayView()._from_field(this); +} + +//////////////////////////////////////////////////////////////////////////////// +CbFieldView.prototype.as_value = function(int_type=Number) +{ + switch (CbFieldTypeOps.get_type(this.get_type())) + { + case CbFieldType.None: return undefined; + case CbFieldType.Null: return null; + + case CbFieldType.Object: + case CbFieldType.UniformObject: return this.as_object(); + + case CbFieldType.Array: + case CbFieldType.UniformArray: return this.as_array(); + + case CbFieldType.Binary: { + const [n, vn] = VarInt.read_uint(this._data_view); + return this._data_view.subarray(vn, n + vn); + } + + case CbFieldType.String: { + const [n, vn] = VarInt.read_uint(this._data_view); + return new TextDecoder().decode(this._data_view.subarray(vn, n + vn)); + } + + case CbFieldType.IntegerPositive: return VarInt.read_uint(this._data_view, int_type)[0]; + case CbFieldType.IntegerNegative: return VarInt.read_int(this._data_view, int_type)[0]; + + case CbFieldType.Float32: return new DataView(this._data_view.subarray(0, 4)).getFloat32(0, false); + case CbFieldType.Float64: return new DataView(this._data_view.subarray(0, 8)).getFloat64(0, false); + case CbFieldType.BoolFalse: return false; + case CbFieldType.BoolTrue: return true; + + case CbFieldType.ObjectAttachment: + case CbFieldType.BinaryAttachment: + case CbFieldType.Hash: return this._data_view.subarray(0, 20); + + case CbFieldType.Uuid: return this._data_view.subarray(0, 16); + case CbFieldType.ObjectId: return this._data_view.subarray(0, 12); + + case CbFieldType.DateTime: + case CbFieldType.TimeSpan: return this._data_view.subarray(0, 8); + } + + cb_assert(false); +} + + + +//////////////////////////////////////////////////////////////////////////////// +class CbObjectView extends CbFieldView +{ +} + +//////////////////////////////////////////////////////////////////////////////// +CbObjectView.prototype[Symbol.iterator] = function() +{ + var data_view = this.get_payload(); + + const [payload_size, varint_len] = VarInt.read_uint(data_view); + if (payload_size == 0) + return {}; + data_view = data_view.subarray(varint_len, payload_size + varint_len); + + var uniform_type = CbFieldType.HasFieldType; + if (CbFieldTypeOps.get_type(this.get_type()) == CbFieldType.UniformObject) + { + uniform_type = data_view[0]; + data_view = data_view.subarray(1); + } + + return CbFieldView._iterate(data_view, uniform_type); +} + +//////////////////////////////////////////////////////////////////////////////// +CbObjectView.prototype.to_js_object = function() +{ + const impl = function(node) + { + if (node.is_object()) + { + const ret = {}; + for (var item of node.as_object()) + ret[item.get_name()] = impl(item); + return ret; + } + + if (node.is_array()) + { + const ret = []; + for (var item of node.as_array()) + ret.push(impl(item)); + return ret; + } + + if (node.is_string()) return node.as_value(); + if (node.is_float()) return node.as_value(); + if (node.is_integer()) return node.as_value(); + + var ret = node.as_value(); + if (ret instanceof Uint8Array) + { + ret = ""; + for (var x of node.as_value()) + ret += x.toString(16).padStart(2, "0"); + } + return ret; + }; + + return impl(this); +} + +//////////////////////////////////////////////////////////////////////////////// +CbObjectView.prototype.find = function(name) +{ + for (const field of this) + if (field.get_name() == name) + return field; +} + + + +//////////////////////////////////////////////////////////////////////////////// +class CbArrayView extends CbFieldView +{ +} + +//////////////////////////////////////////////////////////////////////////////// +CbArrayView.prototype[Symbol.iterator] = function() +{ + var data_view = this.get_payload(); + + const [payload_size, varint_len] = VarInt.read_uint(data_view); + data_view = data_view.subarray(varint_len, payload_size + varint_len); + + const item_count_bytes = VarInt.measure(data_view); + if (item_count_bytes >= payload_size) + return {}; + data_view = data_view.subarray(item_count_bytes); + + var uniform_type = CbFieldType.HasFieldType; + if (CbFieldTypeOps.get_type(this.get_type()) == CbFieldType.UniformArray) + { + uniform_type = data_view[0]; + data_view = data_view.subarray(1); + } + + return CbFieldView._iterate(data_view, uniform_type); +} + +//////////////////////////////////////////////////////////////////////////////// +CbArrayView.prototype.num = function() +{ + var data_view = this._data_view; + const [n, n_len] = VarInt.read_uint(data_view); + data_view = data_view.subarray(n_len); + return VarInt.read_uint(data_view)[0]; +} + + + +//////////////////////////////////////////////////////////////////////////////// +class CbObject extends CbFieldView +{ + constructor(uint8_array) + { + super(); + this._from_data(uint8_array); + } +} |