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}