1// TLV format: https://devopedia.org/tlv-format
2// Real world example: https://www.rfc-editor.org/rfc/rfc8609
3// Cool article: https://www.huy.rocks/everyday/12-11-2022-zig-using-zig-for-advent-of-code
4
5// Dummy TLV spec for encoding Linux device information
6//
7// Packet specification
8// 1 byte ... Tag
9// 1 byte ... Length
10// variable ... Value
11//
12// Available tags: { 0x00 = vendor_name, 0x01 = model_name }
13// Packet presented as a struct: Packet{ .vendor_name, .model_name }
14// This assumes that the max value length can be 255.
15//
16// Test data from `lsusb` (`lscpu`):
17// Bus 002 Device 003: ID 046d:086b Logitech, Inc. BRIO 4K Stream Edition
18// Bus 001 Device 009: ID 3434:0350 Keychron Keychron V5
19// Bus 001 Device 006: ID 2433:b200 ASETEK [NZXT Kraken X60]
20// Bus 001 Device 005: ID 046d:c548 Logitech, Inc. Logi Bolt Receiver
21
22const std = @import("std");
23
24const TLVTag = enum(u8) {
25 VENDOR_NAME = 0x00,
26 MODEL_NAME = 0x01,
27};
28
29const TLVPacket = struct {
30 tag: TLVTag,
31 length: u8,
32 value: std.ArrayList(u8),
33
34 pub fn printHexEncoded(self: *TLVPacket) void {
35 std.debug.print("\t", .{});
36 std.debug.print("{X:0>2}-", .{@intFromEnum(self.tag)});
37 std.debug.print("{X:0>2}-", .{self.length});
38
39 for (self.value.items) |val| {
40 std.debug.print("{X:0>2}", .{val});
41 }
42
43 std.debug.print("\n", .{});
44 }
45};
46
47const TLVPayload = struct {
48 packets: std.ArrayList(TLVPacket),
49 allocator: std.mem.Allocator,
50
51 pub fn init(allocator: std.mem.Allocator) TLVPayload {
52 return TLVPayload{
53 .packets = std.ArrayList(TLVPacket).init(allocator),
54 .allocator = allocator,
55 };
56 }
57
58 pub fn deinit(self: *TLVPayload) void {
59 for (self.packets.items) |*packet| {
60 packet.value.deinit();
61 }
62 self.packets.deinit();
63 }
64
65 pub fn append(self: *TLVPayload, tag: TLVTag, message: []const u8) !void {
66 var value = try std.ArrayList(u8).initCapacity(self.allocator, message.len);
67 try value.appendSlice(message);
68
69 try self.packets.append(.{
70 .tag = tag,
71 .length = @intCast(message.len),
72 .value = value,
73 });
74 }
75};
76
77test "Create TLV payload manually" {
78 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
79 defer _ = gpa.deinit();
80
81 var payload = TLVPayload.init(gpa.allocator());
82 defer payload.deinit();
83
84 try payload.append(.VENDOR_NAME, "Logitech, Inc.");
85 try payload.append(.MODEL_NAME, "BRIO 4K Stream Edition");
86
87 std.debug.print("Capacity: {d}\n", .{payload.packets.capacity});
88
89 for (payload.packets.items) |*packet| {
90 std.debug.print("Tag: {}, Length: {d}\n", .{ packet.tag, packet.length });
91 packet.printHexEncoded();
92 }
93}
94
95pub fn main() void {
96 std.debug.print("Use `make test` or `zig test main.zig` instead.\n", .{});
97}