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 }