diff --git a/vlib/encoding/binary/README.md b/vlib/encoding/binary/README.md index 1ef0b9c463..732d9df261 100644 --- a/vlib/encoding/binary/README.md +++ b/vlib/encoding/binary/README.md @@ -3,6 +3,9 @@ `encoding.binary` contains utility functions for converting between an array of bytes (`[]u8`) and unsigned integers of various widths (`u16`, `u32`, and `u64`). +Also, it provide functions `encode_binary[T]()` and `decode_binary[T]()` which can converting +between an array of bytes (`[]u8`) and generic type `T`. + There are two ways in which bytes can be encoded: 1. Little endian: The least significant bytes are stored first, followed by the most @@ -22,3 +25,65 @@ sequence in big endian, we get `0x12345678`. > **Note** > The functions in this module assume appropriately sized u8 arrays. If the sizes > are not valid, the functions will panic. + +For generic `T` data encoding/decoding, you can use `encode_binary[T]()` and `decode_binary[T]()`: + +```v +module main + +import encoding.binary + +struct MyStruct { + g_u8 u8 +} + +struct ComplexStruct { +mut: + f_u8 u8 + f_u32 u32 @[serialize: '-'] // this field will be skipped + f_u64 u64 + f_string string + f_structs []MyStruct + f_maps []map[string]string +} + +fn main() { + a := ComplexStruct{ + f_u8: u8(10) + f_u32: u32(1024) + f_u64: u64(2048) + f_string: 'serialize me' + f_structs: [ + MyStruct{ + g_u8: u8(1) + }, + MyStruct{ + g_u8: u8(2) + }, + MyStruct{ + g_u8: u8(3) + }, + ] + f_maps: [ + { + 'abc': 'def' + }, + { + '123': '456' + }, + { + ',./': '!@#' + }, + ] + } + + b := binary.encode_binary(a)! + mut c := binary.decode_binary[ComplexStruct](b)! + + // because there skipped field in `a`, a != c + assert a != c + + c.f_u32 = u32(1024) + assert a == c +} +``` diff --git a/vlib/encoding/binary/serialize.v b/vlib/encoding/binary/serialize.v new file mode 100644 index 0000000000..33e02d963c --- /dev/null +++ b/vlib/encoding/binary/serialize.v @@ -0,0 +1,502 @@ +module binary + +struct EncodeState { +mut: + b []u8 + // pre-allocated buffers + b2 []u8 = [u8(0), 0] + b4 []u8 = [u8(0), 0, 0, 0] + b8 []u8 = [u8(0), 0, 0, 0, 0, 0, 0, 0] + big_endian bool +} + +@[params] +pub struct EncodeConfig { +pub mut: + buffer_len int = 1024 + big_endian bool // use big endian encoding the data +} + +// encode_binary encode a T type data into u8 array. +// for encoding struct, you can use `@[serialize: '-']` to skip field. +pub fn encode_binary[T](obj T, config EncodeConfig) ![]u8 { + mut s := EncodeState{ + b: []u8{cap: config.buffer_len} + big_endian: config.big_endian + } + $if T is $array { + encode_array(mut s, obj)! + } $else $if T is $string { + encode_string(mut s, obj)! + } $else $if T is $struct { + encode_struct(mut s, obj)! + } $else $if T is $map { + encode_map(mut s, obj)! + } $else { + encode_primitive(mut s, obj)! + } + return s.b +} + +fn encode_struct[T](mut s EncodeState, obj T) ! { + $for field in T.fields { + mut is_skip := false + for attr in field.attrs { + f := attr.split_any(':') + if f.len == 2 { + match f[0].trim_space() { + 'serialize' { + // @[serialize:'-'] + if f[1].trim_space() == '-' { + is_skip = true + } + } + else {} + } + } + } + if !is_skip { + value := obj.$(field.name) + $if field.typ is $array { + encode_array(mut s, value)! + } $else $if field.typ is $string { + encode_string(mut s, value)! + } $else $if field.typ is $struct { + encode_struct(mut s, value)! + } $else $if field.typ is $map { + encode_map(mut s, value)! + } $else { + encode_primitive(mut s, value)! + } + } + } +} + +// help unions for bypass `-cstrict`/ `-Wstrict-aliasing` check. +union U32_F32 { + u u32 + f f32 +} + +union U64_F64 { + u u64 + f f64 +} + +fn encode_primitive[T](mut s EncodeState, value T) ! { + $if T is int { + // NOTE: `int` always use 64bit + s.put_u64(u64(value)) + } $else $if T is u8 { + s.put_u8(u8(value)) + } $else $if T is u16 { + s.put_u16(u16(value)) + } $else $if T is u32 { + s.put_u32(u32(value)) + } $else $if T is u64 { + s.put_u64(u64(value)) + } $else $if T is i8 { + s.put_u8(u8(value)) + } $else $if T is i16 { + s.put_u16(u16(value)) + } $else $if T is i32 { + s.put_u32(u32(value)) + } $else $if T is i64 { + s.put_u64(u64(value)) + } $else $if T is f32 { + unsafe { s.put_u32(U32_F32{ f: value }.u) } + } $else $if T is f64 { + unsafe { s.put_u64(U64_F64{ f: value }.u) } + } $else $if T is bool { + s.put_u8(u8(value)) + } $else $if T is rune { + s.put_u32(u32(value)) + } $else $if T is isize { + if sizeof(isize) == 4 { + s.put_u32(u32(value)) + } else { + s.put_u64(u64(value)) + } + } $else $if T is usize { + if sizeof(usize) == 4 { + s.put_u32(u32(value)) + } else { + s.put_u64(u64(value)) + } + } $else $if T is voidptr { + s.put_u64(u64(value)) + } $else { + // TODO: `any` type support? + return error('${@FN}unsupported type ${typeof(value).name}') + } +} + +fn encode_array[T](mut s EncodeState, arr []T) ! { + s.put_u64(u64(arr.len)) + + $if T is u8 { + // optimization for `[]u8` + s.b << arr + } $else { + for element in arr { + $if T is $string { + encode_string(mut s, element)! + } $else $if T is $struct { + encode_struct(mut s, element)! + } $else $if T is $map { + encode_map(mut s, element)! + } $else { + encode_primitive(mut s, element)! + } + } + } +} + +fn encode_string(mut s EncodeState, str string) ! { + s.put_u64(u64(str.len)) + s.b << str.bytes() +} + +fn encode_map[K, V](mut s EncodeState, m map[K]V) ! { + s.put_u64(u64(m.len)) + + for k, v in m { + // encode key first + // Maps can have keys of type string, rune, integer, float or voidptr. + $if K is $string { + encode_string(mut s, k)! + } $else { + encode_primitive(mut s, k)! + } + + // encode value + $if V is $string { + encode_string(mut s, v)! + } $else $if V is $struct { + encode_struct(mut s, v)! + } $else $if V is $map { + encode_map(mut s, v)! + } $else { + encode_primitive(mut s, v)! + } + } +} + +struct DecodeState { +mut: + b []u8 + b2 []u8 = [u8(0), 0] + b4 []u8 = [u8(0), 0, 0, 0] + b8 []u8 = [u8(0), 0, 0, 0, 0, 0, 0, 0] + offset int + big_endian bool +} + +@[params] +pub struct DecodeConfig { +pub mut: + buffer_len int = 1024 + big_endian bool // use big endian decode the data +} + +// decode_binary decode a u8 array into T type data. +// for decoding struct, you can use `@[serialize: '-']` to skip field. +pub fn decode_binary[T](b []u8, config DecodeConfig) !T { + mut s := DecodeState{ + b: b + big_endian: config.big_endian + } + $if T is $array { + return decode_array(mut s, T{})! + } $else $if T is $string { + return decode_string(mut s)! + } $else $if T is $struct { + return decode_struct(mut s, T{})! + } $else $if T is $map { + return decode_map(mut s, T{})! + } $else { + return decode_primitive(mut s, unsafe { T(0) })! + } +} + +fn decode_struct[T](mut s DecodeState, _ T) !T { + mut obj := T{} + + $for field in T.fields { + mut is_skip := false + for attr in field.attrs { + f := attr.split_any(':') + if f.len == 2 { + match f[0].trim_space() { + 'serialize' { + // @[serialize:'-'] + if f[1].trim_space() == '-' { + is_skip = true + } + } + else {} + } + } + } + if !is_skip { + $if field.typ is $array { + obj.$(field.name) = decode_array(mut s, obj.$(field.name))! + } $else $if field.typ is $string { + obj.$(field.name) = decode_string(mut s)! + } $else $if field.typ is $struct { + obj.$(field.name) = decode_struct(mut s, obj.$(field.name))! + } $else $if field.typ is $map { + obj.$(field.name) = decode_map(mut s, obj.$(field.name))! + } $else { + obj.$(field.name) = decode_primitive(mut s, obj.$(field.name))! + } + } + } + return obj +} + +fn decode_primitive[T](mut s DecodeState, value T) !T { + $if T is int { + // NOTE: int always use 64bit + return T(s.get_u64()!) + } $else $if T is u8 { + return T(s.get_u8()!) + } $else $if T is u16 { + return T(s.get_u16()!) + } $else $if T is u32 { + return T(s.get_u32()!) + } $else $if T is u64 { + return T(s.get_u64()!) + } $else $if T is i8 { + return T(s.get_u8()!) + } $else $if T is i16 { + return T(s.get_u16()!) + } $else $if T is i32 { + return T(s.get_u32()!) + } $else $if T is i64 { + return T(s.get_u64()!) + } $else $if T is f32 { + v := s.get_u32()! + return unsafe { + U32_F32{ + u: v + }.f + } + } $else $if T is f64 { + v := s.get_u64()! + return unsafe { + U64_F64{ + u: v + }.f + } + } $else $if T is bool { + return s.get_u8()! != 0 + } $else $if T is rune { + return T(s.get_u32()!) + } $else $if T is isize { + if sizeof(isize) == 4 { + return T(s.get_u32()!) + } else { + return T(s.get_u64()!) + } + } $else $if T is usize { + if sizeof(usize) == 4 { + return T(s.get_u32()!) + } else { + return T(s.get_u64()!) + } + } $else $if T is voidptr { + return T(s.get_u64()!) + } $else { + // TODO: `any` type support? + return error('${@FN}unsupported type ${typeof(value).name}') + } + return error('impossiable error') +} + +fn decode_array[T](mut s DecodeState, _ []T) ![]T { + len := int(s.get_u64()!) + if len <= 0 || s.offset + len > s.b.len { + return error('invalid array length decode from stream') + } + mut arr := []T{cap: len} + $if T is u8 { + // optimization for `[]u8` + arr << s.b[s.offset..s.offset + len] + s.offset += len + } $else { + for _ in 0 .. len { + if s.offset >= s.b.len { + return error('unexpected end of data') + } + $if T is $array { + arr << decode_array(mut s, T{})! + } $else $if T is $string { + arr << decode_string(mut s)! + } $else $if T is $struct { + arr << decode_struct(mut s, T{})! + } $else $if T is $map { + arr << decode_map(mut s, T{})! + } $else { + arr << decode_primitive(mut s, unsafe { T(0) })! + } + } + } + return arr +} + +fn decode_string(mut s DecodeState) !string { + len := int(s.get_u64()!) + if len <= 0 || s.offset + len > s.b.len { + return error('invalid string length decode from stream') + } + str := unsafe { s.b[s.offset..s.offset + len].bytestr() } + s.offset += len + return str +} + +// `Any` is a sum type that lists the possible types to be decoded and used. +type Any = int + | bool + | f64 + | f32 + | i64 + | i32 + | i16 + | i8 + | map[string]Any + | map[int]Any + | string + | u64 + | u32 + | u16 + | u8 + | rune + | isize + | usize + | []Any + +fn decode_map[K, V](mut s DecodeState, _ map[K]V) !map[K]V { + len := int(s.get_u64()!) + if len <= 0 || s.offset + len > s.b.len { + return error('invalid map length decode from stream') + } + + mut m := map[K]V{} + + for _ in 0 .. len { + // decode key first + // Maps can have keys of type string, rune, integer, float or voidptr. + mut k := Any(0) + $if K is $string { + k = decode_string(mut s)! + } $else { + k = decode_primitive(mut s, unsafe { K(0) })! + } + + // decode value + mut v := Any(0) + $if V is $string { + v = decode_string(mut s)! + } $else $if V is $struct { + v = decode_struct(mut s, V{})! + } $else $if V is $map { + v = decode_map(mut s, V{})! + } $else { + v = decode_primitive(mut s, unsafe { V(0) })! + } + m[k as K] = v as V + } + return m +} + +@[inline] +fn (mut s DecodeState) get_u64() !u64 { + if s.offset + 8 > s.b.len { + return error('bytes length is not enough for u64') + } + defer { + s.offset += 8 + } + if s.big_endian { + return big_endian_u64_at(s.b, s.offset) + } else { + return little_endian_u64_at(s.b, s.offset) + } +} + +@[inline] +fn (mut s DecodeState) get_u32() !u32 { + if s.offset + 4 > s.b.len { + return error('bytes length is not enough for u32') + } + defer { + s.offset += 4 + } + if s.big_endian { + return big_endian_u32_at(s.b, s.offset) + } else { + return little_endian_u32_at(s.b, s.offset) + } +} + +@[inline] +fn (mut s DecodeState) get_u16() !u16 { + if s.offset + 2 > s.b.len { + return error('bytes length is not enough for u16') + } + defer { + s.offset += 2 + } + if s.big_endian { + return big_endian_u16_at(s.b, s.offset) + } else { + return little_endian_u16_at(s.b, s.offset) + } +} + +@[inline] +fn (mut s DecodeState) get_u8() !u8 { + if s.offset + 1 > s.b.len { + return error('bytes length is not enough for u8') + } + defer { + s.offset += 1 + } + return s.b[s.offset] +} + +@[inline] +fn (mut s EncodeState) put_u64(value u64) { + if s.big_endian { + big_endian_put_u64(mut s.b8, value) + } else { + little_endian_put_u64(mut s.b8, value) + } + s.b << s.b8 +} + +@[inline] +fn (mut s EncodeState) put_u32(value u32) { + if s.big_endian { + big_endian_put_u32(mut s.b4, value) + } else { + little_endian_put_u32(mut s.b4, value) + } + s.b << s.b4 +} + +@[inline] +fn (mut s EncodeState) put_u16(value u16) { + if s.big_endian { + big_endian_put_u16(mut s.b2, value) + } else { + little_endian_put_u16(mut s.b2, value) + } + s.b << s.b2 +} + +@[inline] +fn (mut s EncodeState) put_u8(value u8) { + s.b << value +} diff --git a/vlib/encoding/binary/serialize_test.v b/vlib/encoding/binary/serialize_test.v new file mode 100644 index 0000000000..48460a1245 --- /dev/null +++ b/vlib/encoding/binary/serialize_test.v @@ -0,0 +1,520 @@ +module binary + +fn test_encode_decode_primitive_string() ! { + a_u8 := u8(137) + a_u16 := u16(5325) + a_u32 := u32(255421) + a_u64 := u64(2483294832) + a_i8 := i8(-11) + a_i16 := i16(-2321) + a_i32 := i32(-54322) + a_i64 := i64(-54212245) + a_int := int(-32135) + a_f32 := f32(1.37) + a_f64 := f64(-32144.3133) + a_bool := bool(true) + a_rune := `♥` + a_isize := isize(-45433) + a_usize := usize(432211) + a_string := '♥🖊dsser333100' + + b_u8 := encode_binary(a_u8)! + b_u16 := encode_binary(a_u16)! + b_u32 := encode_binary(a_u32)! + b_u64 := encode_binary(a_u64)! + b_i8 := encode_binary(a_i8)! + b_i16 := encode_binary(a_i16)! + b_i32 := encode_binary(a_i32)! + b_i64 := encode_binary(a_i64)! + b_int := encode_binary(a_int)! + b_f32 := encode_binary(a_f32)! + b_f64 := encode_binary(a_f64)! + b_bool := encode_binary(a_bool)! + b_rune := encode_binary(a_rune)! + b_isize := encode_binary(a_isize)! + b_usize := encode_binary(a_usize)! + b_string := encode_binary(a_string)! + + c_u8 := decode_binary[u8](b_u8)! + c_u16 := decode_binary[u16](b_u16)! + c_u32 := decode_binary[u32](b_u32)! + c_u64 := decode_binary[u64](b_u64)! + c_i8 := decode_binary[i8](b_i8)! + c_i16 := decode_binary[i16](b_i16)! + c_i32 := decode_binary[i32](b_i32)! + c_i64 := decode_binary[i64](b_i64)! + c_int := decode_binary[int](b_int)! + c_f32 := decode_binary[f32](b_f32)! + c_f64 := decode_binary[f64](b_f64)! + c_bool := decode_binary[bool](b_bool)! + c_rune := decode_binary[rune](b_rune)! + c_isize := decode_binary[isize](b_isize)! + c_usize := decode_binary[usize](b_usize)! + c_string := decode_binary[string](b_string)! + + assert a_u8 == c_u8 + assert a_u16 == c_u16 + assert a_u32 == c_u32 + assert a_u64 == c_u64 + assert a_i8 == c_i8 + assert a_i16 == c_i16 + assert a_i32 == c_i32 + assert a_i64 == c_i64 + assert a_int == c_int + assert a_f32 == c_f32 + assert a_f64 == c_f64 + assert a_bool == c_bool + assert a_rune == c_rune + assert a_isize == c_isize + assert a_usize == c_usize + assert a_string == c_string + + assert b_u8 == [u8(137)] + assert b_u16 == [u8(205), 20] + assert b_u32 == [u8(189), 229, 3, 0] + assert b_u64 == [u8(112), 18, 4, 148, 0, 0, 0, 0] + assert b_i8 == [u8(245)] + assert b_i16 == [u8(239), 246] + assert b_i32 == [u8(206), 43, 255, 255] + assert b_i64 == [u8(107), 201, 196, 252, 255, 255, 255, 255] + assert b_int == [u8(121), 130, 255, 255, 255, 255, 255, 255] + assert b_f32 == [u8(41), 92, 175, 63] + assert b_f64 == [u8(118), 113, 27, 13, 20, 100, 223, 192] + assert b_bool == [u8(1)] + assert b_rune == [u8(101), 38, 0, 0] + assert b_string == [u8(18), 0, 0, 0, 0, 0, 0, 0, 226, 153, 165, 240, 159, 150, 138, 100, 115, + 115, 101, 114, 51, 51, 51, 49, 48, 48] + $if x64 { + assert b_isize == [u8(135), 78, 255, 255, 255, 255, 255, 255] + assert b_usize == [u8(83), 152, 6, 0, 0, 0, 0, 0] + } $else { + assert b_isize == [u8(135), 78, 255, 255] + assert b_usize == [u8(83), 152, 6, 0] + } +} + +fn test_encode_decode_array() { + a_u8 := [u8(137), 21] + a_u16 := [u16(5325), 322] + a_u32 := [u32(255421), 34255] + a_u64 := [u64(2483294832), 321554321] + a_i8 := [i8(-11), 17] + a_i16 := [i16(-2321), 6543] + a_i32 := [i32(-54322), 23326] + a_i64 := [i64(-54212245), 54223333] + a_int := [int(-32135), 732561] + a_f32 := [f32(1.37), -5442.3] + a_f64 := [f64(-32144.3133), 432e-13] + a_bool := [bool(true), false] + a_rune := [`♥`, `🖊`] + a_isize := [isize(-45433), 24342] + a_usize := [usize(432211), 888533] + a_string := ['♥', '🖊', 'dfd21'] + + b_u8 := encode_binary(a_u8)! + b_u16 := encode_binary(a_u16)! + b_u32 := encode_binary(a_u32)! + b_u64 := encode_binary(a_u64)! + b_i8 := encode_binary(a_i8)! + b_i16 := encode_binary(a_i16)! + b_i32 := encode_binary(a_i32)! + b_i64 := encode_binary(a_i64)! + b_int := encode_binary(a_int)! + b_f32 := encode_binary(a_f32)! + b_f64 := encode_binary(a_f64)! + b_bool := encode_binary(a_bool)! + b_rune := encode_binary(a_rune)! + b_isize := encode_binary(a_isize)! + b_usize := encode_binary(a_usize)! + b_string := encode_binary(a_string)! + + c_u8 := decode_binary[[]u8](b_u8)! + c_u16 := decode_binary[[]u16](b_u16)! + c_u32 := decode_binary[[]u32](b_u32)! + c_u64 := decode_binary[[]u64](b_u64)! + c_i8 := decode_binary[[]i8](b_i8)! + c_i16 := decode_binary[[]i16](b_i16)! + c_i32 := decode_binary[[]i32](b_i32)! + c_i64 := decode_binary[[]i64](b_i64)! + c_int := decode_binary[[]int](b_int)! + c_f32 := decode_binary[[]f32](b_f32)! + c_f64 := decode_binary[[]f64](b_f64)! + c_bool := decode_binary[[]bool](b_bool)! + c_rune := decode_binary[[]rune](b_rune)! + c_isize := decode_binary[[]isize](b_isize)! + c_usize := decode_binary[[]usize](b_usize)! + c_string := decode_binary[[]string](b_string)! + + assert a_u8 == c_u8 + assert a_u16 == c_u16 + assert a_u32 == c_u32 + assert a_u64 == c_u64 + assert a_i8 == c_i8 + assert a_i16 == c_i16 + assert a_i32 == c_i32 + assert a_i64 == c_i64 + assert a_int == c_int + assert a_f32 == c_f32 + assert a_f64 == c_f64 + assert a_bool == c_bool + assert a_rune == c_rune + assert a_isize == c_isize + assert a_usize == c_usize + assert a_string == c_string + + assert b_u8 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 137, 21] + assert b_u16 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 205, 20, 66, 1] + assert b_u32 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 189, 229, 3, 0, 207, 133, 0, 0] + assert b_u64 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 112, 18, 4, 148, 0, 0, 0, 0, 145, 135, 42, 19, + 0, 0, 0, 0] + assert b_i8 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 245, 17] + assert b_i16 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 239, 246, 143, 25] + assert b_i32 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 206, 43, 255, 255, 30, 91, 0, 0] + assert b_i64 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 107, 201, 196, 252, 255, 255, 255, 255, 229, 97, + 59, 3, 0, 0, 0, 0] + assert b_int == [u8(2), 0, 0, 0, 0, 0, 0, 0, 121, 130, 255, 255, 255, 255, 255, 255, 145, 45, + 11, 0, 0, 0, 0, 0] + assert b_f32 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 41, 92, 175, 63, 102, 18, 170, 197] + assert b_f64 == [u8(2), 0, 0, 0, 0, 0, 0, 0, 118, 113, 27, 13, 20, 100, 223, 192, 253, 251, + 253, 7, 220, 191, 199, 61] + assert b_bool == [u8(2), 0, 0, 0, 0, 0, 0, 0, 1, 0] + assert b_rune == [u8(2), 0, 0, 0, 0, 0, 0, 0, 101, 38, 0, 0, 138, 245, 1, 0] + assert b_string == [u8(3), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 226, 153, 165, 4, 0, + 0, 0, 0, 0, 0, 0, 240, 159, 150, 138, 5, 0, 0, 0, 0, 0, 0, 0, 100, 102, 100, 50, 49] + $if x64 { + assert b_isize == [u8(2), 0, 0, 0, 0, 0, 0, 0, 135, 78, 255, 255, 255, 255, 255, 255, 22, + 95, 0, 0, 0, 0, 0, 0] + assert b_usize == [u8(2), 0, 0, 0, 0, 0, 0, 0, 83, 152, 6, 0, 0, 0, 0, 0, 213, 142, 13, + 0, 0, 0, 0, 0] + } $else { + assert b_isize == [u8(2), 0, 0, 0, 0, 0, 0, 0, 135, 78, 255, 255, 22, 95, 0, 0] + assert b_usize == [u8(2), 0, 0, 0, 0, 0, 0, 0, 83, 152, 6, 0, 213, 142, 13, 0] + } +} + +fn test_encode_decode_map() { + a_map_string_string := { + 'abc': 'def' + } + a_map_string_int := { + 'abc': int(21343) + } + a_map_string_u8 := { + 'abc': u8(37) + } + a_map_string_u16 := { + 'abc': u16(3347) + } + a_map_string_u32 := { + 'abc': u32(333347) + } + a_map_string_u64 := { + 'abc': u64(64423) + } + a_map_string_i8 := { + 'abc': i8(-37) + } + a_map_string_i16 := { + 'abc': i16(-3347) + } + a_map_string_i32 := { + 'abc': i32(-333347) + } + a_map_string_i64 := { + 'abc': i64(-64423) + } + a_map_string_f32 := { + 'abc': f32(1.543) + } + a_map_string_f64 := { + 'abc': f64(1.54e31) + } + a_map_string_bool := { + 'abc': true + } + a_map_string_rune := { + 'abc': `♥` + } + a_map_string_isize := { + 'abc': isize(-45433) + } + a_map_string_usize := { + 'abc': usize(432211) + } + + b_map_string_string := encode_binary(a_map_string_string)! + b_map_string_int := encode_binary(a_map_string_int)! + b_map_string_u8 := encode_binary(a_map_string_u8)! + b_map_string_u16 := encode_binary(a_map_string_u16)! + b_map_string_u32 := encode_binary(a_map_string_u32)! + b_map_string_u64 := encode_binary(a_map_string_u64)! + b_map_string_i8 := encode_binary(a_map_string_i8)! + b_map_string_i16 := encode_binary(a_map_string_i16)! + b_map_string_i32 := encode_binary(a_map_string_i32)! + b_map_string_i64 := encode_binary(a_map_string_i64)! + b_map_string_f32 := encode_binary(a_map_string_f32)! + b_map_string_f64 := encode_binary(a_map_string_f64)! + b_map_string_bool := encode_binary(a_map_string_bool)! + b_map_string_rune := encode_binary(a_map_string_rune)! + b_map_string_isize := encode_binary(a_map_string_isize)! + b_map_string_usize := encode_binary(a_map_string_usize)! + + c_map_string_string := decode_binary[map[string]string](b_map_string_string)! + c_map_string_int := decode_binary[map[string]int](b_map_string_int)! + c_map_string_u8 := decode_binary[map[string]u8](b_map_string_u8)! + c_map_string_u16 := decode_binary[map[string]u16](b_map_string_u16)! + c_map_string_u32 := decode_binary[map[string]u32](b_map_string_u32)! + c_map_string_u64 := decode_binary[map[string]u64](b_map_string_u64)! + c_map_string_i8 := decode_binary[map[string]i8](b_map_string_i8)! + c_map_string_i16 := decode_binary[map[string]i16](b_map_string_i16)! + c_map_string_i32 := decode_binary[map[string]i32](b_map_string_i32)! + c_map_string_i64 := decode_binary[map[string]i64](b_map_string_i64)! + c_map_string_f32 := decode_binary[map[string]f32](b_map_string_f32)! + c_map_string_f64 := decode_binary[map[string]f64](b_map_string_f64)! + c_map_string_bool := decode_binary[map[string]bool](b_map_string_bool)! + c_map_string_rune := decode_binary[map[string]rune](b_map_string_rune)! + c_map_string_isize := decode_binary[map[string]isize](b_map_string_isize)! + c_map_string_usize := decode_binary[map[string]usize](b_map_string_usize)! + + assert a_map_string_string == c_map_string_string + assert a_map_string_int == c_map_string_int + assert a_map_string_u8 == c_map_string_u8 + assert a_map_string_u16 == c_map_string_u16 + assert a_map_string_u32 == c_map_string_u32 + assert a_map_string_i8 == c_map_string_i8 + assert a_map_string_i16 == c_map_string_i16 + assert a_map_string_i32 == c_map_string_i32 + assert a_map_string_i64 == c_map_string_i64 + assert a_map_string_f32 == c_map_string_f32 + assert a_map_string_f64 == c_map_string_f64 + assert a_map_string_bool == c_map_string_bool + assert a_map_string_rune == c_map_string_rune + assert a_map_string_isize == c_map_string_isize + assert a_map_string_usize == c_map_string_usize + + assert b_map_string_string == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, + 99, 3, 0, 0, 0, 0, 0, 0, 0, 100, 101, 102] + assert b_map_string_int == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 95, 83, 0, 0, 0, 0, 0, 0] + assert b_map_string_u8 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 37] + assert b_map_string_u16 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 19, 13] + assert b_map_string_u32 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 35, 22, 5, 0] + assert b_map_string_u64 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 167, 251, 0, 0, 0, 0, 0, 0] + assert b_map_string_i8 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 219] + assert b_map_string_i16 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 237, 242] + assert b_map_string_i32 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 221, 233, 250, 255] + assert b_map_string_i64 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 89, 4, 255, 255, 255, 255, 255, 255] + assert b_map_string_f32 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 6, 129, 197, 63] + assert b_map_string_f64 == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 212, 186, 221, 173, 2, 76, 104, 70] + assert b_map_string_bool == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 1] + assert b_map_string_rune == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, + 101, 38, 0, 0] + $if x64 { + assert b_map_string_isize == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, + 99, 135, 78, 255, 255, 255, 255, 255, 255] + assert b_map_string_usize == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, + 99, 83, 152, 6, 0, 0, 0, 0, 0] + } $else { + assert b_map_string_isize == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, + 99, 135, 78, 255, 255] + assert b_map_string_usize == [u8(1), 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 97, 98, + 99, 83, 152, 6, 0] + } +} + +struct MyStruct { + f_u8 u8 + f_u16 u16 + f_u32 u32 + f_u64 u64 + f_i8 i8 + f_i16 i16 + f_i32 i32 + f_i64 i64 + f_int int + f_f32 f32 + f_f64 f64 + f_bool bool + f_rune rune + f_isize isize + f_usize usize + f_string string + f_array_u8 []u8 + f_array_string []string +} + +fn test_encode_decode_struct() { + a_struct := MyStruct{ + f_u8: u8(31) + f_u16: u16(57) + f_u32: u32(6432) + f_u64: u64(7896423) + f_i8: i8(-22) + f_i16: i16(-5433) + f_i32: i32(-54244) + f_i64: i64(-8322234) + f_int: int(4235) + f_f32: f32(1.5382) + f_f64: f64(22421.32) + f_bool: bool(true) + f_rune: rune(`♥`) + f_isize: isize(42323) + f_usize: usize(83842) + f_string: 'fds♥323s' + f_array_u8: [u8(32), 22, 55, 72] + f_array_string: ['dfdss', 'dfsd3', '54344'] + } + + b_struct := encode_binary(a_struct)! + c_struct := decode_binary[MyStruct](b_struct)! + + assert a_struct == c_struct +} + +struct MyStruct_SkipFields { +mut: + f_u8 u8 + f_u16 u16 @[serialize: '-'] + f_u32 u32 + f_u64 u64 + f_i8 i8 + f_i16 i16 + f_i32 i32 + f_i64 i64 + f_int int + f_f32 f32 + f_f64 f64 + f_bool bool + f_rune rune + f_isize isize + f_usize usize + f_string string + f_array_u8 []u8 @[serialize: '-'] + f_array_string []string +} + +fn test_encode_decode_struct_skip_fields() { + a_struct := MyStruct_SkipFields{ + f_u8: u8(31) + f_u16: u16(57) + f_u32: u32(6432) + f_u64: u64(7896423) + f_i8: i8(-22) + f_i16: i16(-5433) + f_i32: i32(-54244) + f_i64: i64(-8322234) + f_int: int(4235) + f_f32: f32(1.5382) + f_f64: f64(22421.32) + f_bool: bool(true) + f_rune: rune(`♥`) + f_isize: isize(42323) + f_usize: usize(83842) + f_string: 'fds♥323s' + f_array_u8: [u8(32), 22, 55, 72] + f_array_string: ['dfdss', 'dfsd3', '54344'] + } + + b_struct := encode_binary(a_struct)! + mut c_struct := decode_binary[MyStruct_SkipFields](b_struct)! + + assert a_struct != c_struct + + c_struct.f_u16 = u16(57) + c_struct.f_array_u8 = [u8(32), 22, 55, 72] + assert a_struct == c_struct +} + +struct ComplexStruct { + f_u8 u8 + f_u32 u32 + f_u64 u64 + f_string string + f_structs []MyStruct + f_maps []map[string]string +} + +fn test_encode_decode_complex() { + a_complex := ComplexStruct{ + f_u8: u8(78) + f_u32: u32(0x11223344) + f_u64: u64(0x55667788) + f_string: 'complex' + f_structs: [ + MyStruct{ + f_u8: u8(31) + f_u16: u16(57) + f_u32: u32(6432) + f_u64: u64(7896423) + f_i8: i8(-22) + f_i16: i16(-5433) + f_i32: i32(-54244) + f_i64: i64(-8322234) + f_int: int(4235) + f_f32: f32(1.5382) + f_f64: f64(22421.32) + f_bool: bool(true) + f_rune: rune(`♥`) + f_isize: isize(42323) + f_usize: usize(83842) + f_string: 'fds♥323s' + f_array_u8: [u8(32), 22, 55, 72] + f_array_string: ['dfdss', 'dfsd3', '54344'] + }, + MyStruct{ + f_u8: u8(41) + f_u16: u16(67) + f_u32: u32(7432) + f_u64: u64(8896423) + f_i8: i8(-32) + f_i16: i16(-6433) + f_i32: i32(-64244) + f_i64: i64(-9322234) + f_int: int(5235) + f_f32: f32(2.5382) + f_f64: f64(32421.32) + f_bool: bool(true) + f_rune: rune(`♥`) + f_isize: isize(52323) + f_usize: usize(93842) + f_string: 'ads♥323s' + f_array_u8: [u8(22), 22, 55, 72] + f_array_string: ['dxfdss', 'dsefsd3', 'po54344'] + }, + ] + f_maps: [ + { + 'abc': 'def' + }, + { + '123': '456' + }, + { + ',./': '!@#' + }, + ] + } + + b_complex := encode_binary(a_complex)! + c_complex := decode_binary[ComplexStruct](b_complex)! + + assert a_complex == c_complex + + // big endian test + b_complex_big_endian := encode_binary(a_complex, big_endian: true)! + c_complex_big_endian := decode_binary[ComplexStruct](b_complex_big_endian, big_endian: true)! + + assert b_complex != b_complex_big_endian + assert a_complex == c_complex_big_endian +}