144 lines
4.4 KiB
Plaintext
144 lines
4.4 KiB
Plaintext
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);
|
|
} |