Deserializer :: struct { // downcasts to []u8 count: s64; data: *u8; cursor: s64; ok := true; // cleared by any read that would run past the end; never set back } to_deserializer :: (view: $T/.[[]u8, string]) -> Deserializer #expand { return .{view.count, view.data, 0, true}; } deserializer_can_read :: inline (d: *Deserializer, byte_count: s64) -> bool { if byte_count < 0 || d.cursor < 0 || d.cursor > d.count || byte_count > d.count - d.cursor { d.ok = false; return false; } return true; } Read :: inline (d: *Deserializer, $T: Type) -> T { mem: T = ---; if !deserializer_can_read(d, size_of(T)) { Initialize(*mem); // a failed read yields a zero value return mem; } memcpy(*mem, d.data + d.cursor, size_of(T)); d.cursor += size_of(T); return mem; } ReadArrayView :: inline (d: *Deserializer, view_count: s64, $T: Type) -> []T { // Range-check the count before multiplying, so a hostile count cannot overflow it. if view_count < 0 || d.cursor < 0 || d.cursor > d.count || view_count > (d.count - d.cursor) / size_of(T) { d.ok = false; return .[]; } array: []T = .{view_count, cast(*T)(d.data + d.cursor)}; d.cursor += view_count * size_of(T); return array; } ReadSizedArray :: inline (d: *Deserializer, $T: Type, $count_type: Type = s64) -> []T { view_count := xx Read(d, count_type); return ReadArrayView(d, view_count, T); } ReadStringView :: inline (d: *Deserializer, view_count: s64) -> string { if !deserializer_can_read(d, view_count) return ""; s := string.{view_count, d.data + d.cursor}; d.cursor += view_count; return s; } ReadSizedString :: inline (d: *Deserializer, $count_type: Type = s64) -> string { view_count : s64 = xx Read(d, count_type); return ReadStringView(d, view_count); } // Use Resizable_Array APIs directly: e.g. array_reset_keeping_memory, array_free, array_reset... Serializer :: [..]u8; new_serializer :: inline (reserve_count: s64) -> Serializer { serializer: Serializer; array_reserve(*serializer, reserve_count); return serializer; } Add :: inline (s: *Serializer, item: $T) { // we use the offset, do NOT use s.data, because the pointer // can move on resize, so it is no longer valid! starting_offset := s.count; array_resize(s, s.count + size_of(T)); memcpy(s.data + starting_offset, *item, size_of(T)); } AddArray_NoSize :: inline (s: *Serializer, view: []$T) { starting_offset := s.count; array_resize(s, s.count + (view.count * size_of(T))); memcpy(s.data + starting_offset, view.data, view.count * size_of(T)); } AddSizedArray :: inline (s: *Serializer, view: []$T, $count_type: Type = s64) -> bool { // A count that does not survive count_type would be written truncated and then followed by // the full payload, so every field after it reads from the wrong offset. The round trip // catches that for any count_type; cast,no_check keeps a debug build from aborting on the // cast before this can report it. view_count := cast,no_check(count_type) view.count; if cast(s64) view_count != view.count { log_error("[AddSizedArray] a % element array does not fit a % length prefix.", view.count, count_type); return false; } Add(s, view_count); AddArray_NoSize(s, view); return true; } AddString :: inline (s: *Serializer, view: string, $count_type: Type = s64) -> bool { view_count := cast,no_check(count_type) view.count; if cast(s64) view_count != view.count { log_error("[AddString] a % byte string does not fit a % length prefix.", view.count, count_type); return false; } Add(s, view_count); AddArray_NoSize(s, cast([]u8)view); return true; } // This has specialized use cases where the offsets are saved in a separate array. e.g. SoA filesystem string storage. AddString_NoSize :: inline (s: *Serializer, data: *u8, count: $T, $offset_type: Type = s32) -> offset_type { offset := cast(offset_type) s.count; AddArray_NoSize(s, []u8.{count, data}); return offset; } AddString_PaddedTo8ByteAlignment :: (s: *Serializer, view: string) { // The purpose of this function is to zero-pad a string to // 8-bytes so we don't run into annoying unaligned copy issues. Add(s, view.count); AddArray_NoSize(s, cast([]u8)view); starting_offset := s.count; remainder := align_forward(view.count, size_of(s64)) - view.count; array_resize(s, s.count + remainder); // Fill remaining space with zeros memset(s.data + starting_offset, 0, remainder); }