lib/trace/src/store/format/codec.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const event = @import("../../root.zig").event;
3
4 pub const header_bytes: usize = 92;
5
6 pub const Error = error{
7 CapacityOverflow,
8 InvalidEvent,
9 OutputTooSmall,
10 };
11
12 const magic = [_]u8{ 't', 't', 'm', 'e' };
13 const version: u8 = 1;
14 const operation_flag: u8 = 1 << 0;
15 const label_flag: u8 = 1 << 1;
16 const data_flag: u8 = 1 << 2;
17 const known_flags = operation_flag | label_flag | data_flag;
18
19 const version_offset: usize = 4;
20 const kind_offset: usize = 5;
21 const flags_offset: usize = 6;
22 const reserved_offset: usize = 7;
23 const epoch_offset: usize = 8;
24 const thread_offset: usize = 16;
25 const sequence_offset: usize = 20;
26 const function_offset: usize = 28;
27 const site_offset: usize = 36;
28 const stack_map_offset: usize = 44;
29 const object_offset: usize = 52;
30 const size_offset: usize = 60;
31 const alignment_offset: usize = 68;
32 const status_offset: usize = 72;
33 const operation_length_offset: usize = 80;
34 const label_length_offset: usize = 84;
35 const data_length_offset: usize = 88;
36
37 pub fn encodedSize(item: event.Event) Error!usize {
38 var total = header_bytes;
39 inline for (.{ item.operation, item.label, item.data }) |value| {
40 if (value) |bytes| {
41 if (bytes.len > std.math.maxInt(u32)) return error.CapacityOverflow;
42 total = std.math.add(usize, total, bytes.len) catch return error.CapacityOverflow;
43 }
44 }
45 return total;
46 }
47
48 pub fn encode(target: []u8, item: event.Event) Error![]u8 {
49 const needed = try encodedSize(item);
50 if (target.len < needed) return error.OutputTooSmall;
51 const output = target[0..needed];
52 @memset(output, 0);
53 @memcpy(output[0..magic.len], &magic);
54 output[version_offset] = version;
55 output[kind_offset] = @backingInt(item.kind);
56 var flags: u8 = 0;
57 if (item.operation != null) flags |= operation_flag;
58 if (item.label != null) flags |= label_flag;
59 if (item.data != null) flags |= data_flag;
60 output[flags_offset] = flags;
61 output[reserved_offset] = 0;
62 std.mem.writeInt(u64, output[epoch_offset..][0..8], item.timepoint.epoch, .big);
63 std.mem.writeInt(u32, output[thread_offset..][0..4], item.timepoint.thread_id, .big);
64 std.mem.writeInt(u64, output[sequence_offset..][0..8], item.timepoint.seq, .big);
65 std.mem.writeInt(u64, output[function_offset..][0..8], item.function_id, .big);
66 std.mem.writeInt(u64, output[site_offset..][0..8], item.site_id, .big);
67 std.mem.writeInt(u64, output[stack_map_offset..][0..8], item.stack_map_id, .big);
68 std.mem.writeInt(u64, output[object_offset..][0..8], item.object_id, .big);
69 std.mem.writeInt(u64, output[size_offset..][0..8], item.size, .big);
70 std.mem.writeInt(u32, output[alignment_offset..][0..4], item.alignment, .big);
71 std.mem.writeInt(i64, output[status_offset..][0..8], item.status, .big);
72 writeLength(output, operation_length_offset, item.operation);
73 writeLength(output, label_length_offset, item.label);
74 writeLength(output, data_length_offset, item.data);
75 var offset = header_bytes;
76 inline for (.{ item.operation, item.label, item.data }) |value| {
77 if (value) |bytes| {
78 @memcpy(output[offset..][0..bytes.len], bytes);
79 offset += bytes.len;
80 }
81 }
82 std.debug.assert(offset == output.len);
83 return output;
84 }
85
86 pub fn decode(bytes: []u8) Error!event.Event {
87 if (bytes.len < header_bytes) return error.InvalidEvent;
88 if (!std.mem.eql(u8, bytes[0..magic.len], &magic)) return error.InvalidEvent;
89 if (bytes[version_offset] != version) return error.InvalidEvent;
90 if (bytes[flags_offset] & ~known_flags != 0 or bytes[reserved_offset] != 0) return error.InvalidEvent;
91 const kind = std.enums.fromInt(event.EventKind, bytes[kind_offset]) orelse return error.InvalidEvent;
92 const operation_length = readLength(bytes, operation_length_offset);
93 const label_length = readLength(bytes, label_length_offset);
94 const data_length = readLength(bytes, data_length_offset);
95 try validateOptional(bytes[flags_offset], operation_flag, operation_length);
96 try validateOptional(bytes[flags_offset], label_flag, label_length);
97 try validateOptional(bytes[flags_offset], data_flag, data_length);
98 const operation_end = std.math.add(usize, header_bytes, operation_length) catch return error.InvalidEvent;
99 const label_end = std.math.add(usize, operation_end, label_length) catch return error.InvalidEvent;
100 const data_end = std.math.add(usize, label_end, data_length) catch return error.InvalidEvent;
101 if (data_end != bytes.len) return error.InvalidEvent;
102 return .{
103 .kind = kind,
104 .timepoint = .{
105 .epoch = std.mem.readInt(u64, bytes[epoch_offset..][0..8], .big),
106 .thread_id = std.mem.readInt(u32, bytes[thread_offset..][0..4], .big),
107 .seq = std.mem.readInt(u64, bytes[sequence_offset..][0..8], .big),
108 },
109 .function_id = std.mem.readInt(u64, bytes[function_offset..][0..8], .big),
110 .site_id = std.mem.readInt(u64, bytes[site_offset..][0..8], .big),
111 .stack_map_id = std.mem.readInt(u64, bytes[stack_map_offset..][0..8], .big),
112 .object_id = std.mem.readInt(u64, bytes[object_offset..][0..8], .big),
113 .size = std.mem.readInt(u64, bytes[size_offset..][0..8], .big),
114 .alignment = std.mem.readInt(u32, bytes[alignment_offset..][0..4], .big),
115 .status = std.mem.readInt(i64, bytes[status_offset..][0..8], .big),
116 .operation = if (bytes[flags_offset] & operation_flag != 0) bytes[header_bytes..operation_end] else null,
117 .label = if (bytes[flags_offset] & label_flag != 0) bytes[operation_end..label_end] else null,
118 .data = if (bytes[flags_offset] & data_flag != 0) bytes[label_end..data_end] else null,
119 };
120 }
121
122 fn writeLength(target: []u8, offset: usize, value: ?[]const u8) void {
123 std.mem.writeInt(u32, target[offset..][0..4], if (value) |bytes| @intCast(bytes.len) else 0, .big);
124 }
125
126 fn readLength(bytes: []const u8, offset: usize) usize {
127 return std.mem.readInt(u32, bytes[offset..][0..4], .big);
128 }
129
130 fn validateOptional(flags: u8, flag: u8, length: usize) Error!void {
131 if (flags & flag == 0 and length != 0) return error.InvalidEvent;
132 }
133
134 test "binary event codec preserves every replay field" {
135 const original = event.Event{
136 .kind = .boundary,
137 .timepoint = .{
138 .epoch = std.math.maxInt(u64),
139 .thread_id = std.math.maxInt(u32),
140 .seq = std.math.maxInt(u64) - 1,
141 },
142 .function_id = std.math.maxInt(u64),
143 .site_id = 2,
144 .stack_map_id = 3,
145 .object_id = 4,
146 .size = std.math.maxInt(u64),
147 .alignment = std.math.maxInt(u32),
148 .status = std.math.minInt(i64),
149 .operation = "env.TEST",
150 .label = "",
151 .data = "value\x00bytes",
152 };
153 var storage: [256]u8 = undefined;
154 const encoded = try encode(&storage, original);
155 const parsed = try decode(encoded);
156 try std.testing.expect(original.eqlForReplay(parsed));
157 }
158
159 test "binary event codec rejects truncation and noncanonical optionals" {
160 const original = event.Event.user(.{}, "label", "payload");
161 var storage: [256]u8 = undefined;
162 const encoded = try encode(&storage, original);
163 try std.testing.expectError(error.InvalidEvent, decode(encoded[0 .. encoded.len - 1]));
164 encoded[flags_offset] &= ~label_flag;
165 try std.testing.expectError(error.InvalidEvent, decode(encoded));
166 }