1//! A buffered DER encoder.
2//!
3//! Prefers calling container's `fn encodeDer(self: @This(), encoder: *der.Encoder)`.
4//! That function should encode values, lengths, then tags.
5buffer: ArrayListReverse,
6/// The field tag set by a parent container.
7/// This is needed because we might visit an implicitly tagged container with a `fn encodeDer`.
8field_tag: ?FieldTag = null,
9
10pub fn init(allocator: std.mem.Allocator) Encoder {
11 return Encoder{ .buffer = ArrayListReverse.init(allocator) };
12}
13
14pub fn deinit(self: *Encoder) void {
15 self.buffer.deinit();
16}
17
18/// Encode any value.
19pub fn any(self: *Encoder, val: anytype) !void {
20 const T = @TypeOf(val);
21 try self.anyTag(Tag.fromZig(T), val);
22}
23
24test any {
25 const Enum = enum(u8) {
26 x = 0,
27 };
28
29 try checkAnyEncoding(u16, 0, &.{ 0x02, 0x01, 0x00 });
30 try checkAnyEncoding(Enum, .x, &.{ 0x0a, 0x01, 0x00 });
31}
32
33fn checkAnyEncoding(T: type, value: T, expected: []const u8) !void {
34 var encoder: @This() = .init(std.testing.allocator);
35 defer encoder.deinit();
36
37 try encoder.any(value);
38 const encoded = try encoder.buffer.toOwnedSlice();
39 defer std.testing.allocator.free(encoded);
40
41 try std.testing.expectEqualSlices(u8, expected, encoded);
42}
43
44fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
45 const T = @TypeOf(val);
46 if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self);
47 const outer_field_tag = self.field_tag;
48 const start = self.buffer.data.len;
49 const merged_tag = self.mergedTag(tag_);
50
51 switch (@typeInfo(T)) {
52 .@"struct" => |info| {
53 inline for (0..info.field_names.len) |i| {
54 const f_idx = info.field_names.len - i - 1;
55 const f_name = info.field_names[f_idx];
56 const f_type = info.field_types[f_idx];
57 const f_attrs = info.field_attrs[f_idx];
58 const field_val = @field(val, f_name);
59 const field_tag = FieldTag.fromContainer(T, f_name);
60
61 // > The encoding of a set value or sequence value shall not include an encoding for any
62 // > component value which is equal to its default value.
63 const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: {
64 break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val));
65 } else false;
66 const is_null_optional = if (@typeInfo(f_type) == .optional) field_val == null else false;
67
68 if (!is_default and !is_null_optional) {
69 const start2 = self.buffer.data.len;
70 self.field_tag = field_tag;
71 // will merge with self.field_tag.
72 // may mutate self.field_tag.
73 try self.anyTag(Tag.fromZig(f_type), field_val);
74 if (field_tag) |ft| {
75 if (ft.explicit) {
76 try self.length(self.buffer.data.len - start2);
77 try self.tag(ft.toTag());
78 self.field_tag = null;
79 }
80 }
81 }
82 }
83 self.field_tag = outer_field_tag;
84 },
85 .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}),
86 .int => try self.int(T, val),
87 .@"enum" => |e| {
88 if (@hasDecl(T, "oids")) {
89 return self.any(T.oids.enumToOid(val));
90 } else {
91 try self.int(e.tag_type, @backingInt(val));
92 }
93 },
94 .optional => if (val) |v| return try self.anyTag(tag_, v) else return,
95 .null => {},
96 else => @compileError("cannot encode type " ++ @typeName(T)),
97 }
98
99 try self.length(self.buffer.data.len - start);
100 try self.tag(merged_tag);
101}
102
103/// Encode a tag.
104pub fn tag(self: *Encoder, tag_: Tag) !void {
105 const t = self.mergedTag(tag_);
106 var buf: [Tag.max_encoded_len]u8 = undefined;
107 try self.buffer.prependSlice(t.encodeToSlice(&buf));
108}
109
110fn mergedTag(self: *Encoder, tag_: Tag) Tag {
111 var res = tag_;
112 if (self.field_tag) |ft| {
113 if (!ft.explicit) {
114 res.number = @fromBackingInt(@intCast(ft.number));
115 res.class = ft.class;
116 }
117 }
118 return res;
119}
120
121/// Encode a length.
122pub fn length(self: *Encoder, len: usize) !void {
123 if (len < 128) return self.buffer.prependSlice(&.{@intCast(len)});
124 const len32 = std.math.cast(u32, len) orelse return error.InvalidLength;
125 var buf: [@sizeOf(u32) + 1]u8 = undefined;
126 std.mem.writeInt(u32, buf[1..], len32, .big);
127 var first: usize = 1;
128 while (buf[first] == 0) first += 1;
129 buf[first - 1] = @intCast((buf.len - first) | 0x80);
130 return self.buffer.prependSlice(buf[first - 1 ..]);
131}
132
133/// Encode a tag and length-prefixed bytes.
134pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void {
135 try self.buffer.prependSlice(bytes);
136 try self.length(bytes.len);
137 try self.tag(tag_);
138}
139
140/// Write raw bytes. The encoder builds its output back-to-front, so chained
141/// calls should be made in reverse of the desired on-wire order.
142pub fn prependBytes(self: *Encoder, bytes: []const u8) !void {
143 return self.buffer.prependSlice(bytes);
144}
145
146fn int(self: *Encoder, comptime T: type, value: T) !void {
147 const info = @typeInfo(T).int;
148 const Unsigned = @Int(.unsigned, info.bits);
149 const pad: u8 = if (info.signedness == .signed and value < 0) 0xff else 0;
150 var buf: [@sizeOf(Unsigned) + 1]u8 = undefined;
151 buf[0] = pad;
152 std.mem.writeInt(Unsigned, buf[1..], @bitCast(value), .big);
153
154 var first: usize = 0;
155 while (first + 1 < buf.len and buf[first] == pad and (buf[first + 1] ^ pad) & 0x80 == 0) first += 1;
156 try self.buffer.prependSlice(buf[first..]);
157}
158
159test int {
160 const allocator = std.testing.allocator;
161 var encoder = Encoder.init(allocator);
162 defer encoder.deinit();
163
164 try encoder.int(u8, 0);
165 try std.testing.expectEqualSlices(u8, &.{0}, encoder.buffer.data);
166
167 encoder.buffer.clearAndFree();
168 try encoder.int(u16, 0x00ff);
169 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data);
170
171 encoder.buffer.clearAndFree();
172 try encoder.int(u32, 0xffff);
173 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff, 0xff }, encoder.buffer.data);
174
175 encoder.buffer.clearAndFree();
176 try encoder.int(u32, 0x01020304);
177 try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x02, 0x03, 0x04 }, encoder.buffer.data);
178
179 encoder.buffer.clearAndFree();
180 try encoder.int(u8, 127);
181 try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data);
182
183 encoder.buffer.clearAndFree();
184 try encoder.int(u16, 128);
185 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data);
186
187 encoder.buffer.clearAndFree();
188 try encoder.int(u16, 256);
189 try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x00 }, encoder.buffer.data);
190
191 encoder.buffer.clearAndFree();
192 try encoder.int(u8, 128);
193 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data);
194
195 encoder.buffer.clearAndFree();
196 try encoder.int(u8, 255);
197 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data);
198
199 encoder.buffer.clearAndFree();
200 try encoder.int(u16, 0x8000);
201 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80, 0 }, encoder.buffer.data);
202
203 encoder.buffer.clearAndFree();
204 try encoder.int(i8, -1);
205 try std.testing.expectEqualSlices(u8, &.{0xff}, encoder.buffer.data);
206
207 encoder.buffer.clearAndFree();
208 try encoder.int(i8, -128);
209 try std.testing.expectEqualSlices(u8, &.{0x80}, encoder.buffer.data);
210
211 encoder.buffer.clearAndFree();
212 try encoder.int(i16, -129);
213 try std.testing.expectEqualSlices(u8, &.{ 0xff, 0x7f }, encoder.buffer.data);
214}
215
216test length {
217 const allocator = std.testing.allocator;
218 var encoder = Encoder.init(allocator);
219 defer encoder.deinit();
220
221 try encoder.length(127);
222 try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data);
223
224 encoder.buffer.clearAndFree();
225 try encoder.length(128);
226 try std.testing.expectEqualSlices(u8, &.{ 0x81, 0x80 }, encoder.buffer.data);
227
228 encoder.buffer.clearAndFree();
229 try encoder.length(255);
230 try std.testing.expectEqualSlices(u8, &.{ 0x81, 0xff }, encoder.buffer.data);
231
232 encoder.buffer.clearAndFree();
233 try encoder.length(256);
234 try std.testing.expectEqualSlices(u8, &.{ 0x82, 0x01, 0x00 }, encoder.buffer.data);
235
236 encoder.buffer.clearAndFree();
237 try encoder.length(65535);
238 try std.testing.expectEqualSlices(u8, &.{ 0x82, 0xff, 0xff }, encoder.buffer.data);
239
240 encoder.buffer.clearAndFree();
241 try encoder.length(65536);
242 try std.testing.expectEqualSlices(u8, &.{ 0x83, 0x01, 0x00, 0x00 }, encoder.buffer.data);
243}
244
245const std = @import("std");
246const Oid = @import("../Oid.zig");
247const asn1 = @import("../../asn1.zig");
248const ArrayListReverse = @import("./ArrayListReverse.zig");
249const Tag = asn1.Tag;
250const FieldTag = asn1.FieldTag;
251const Encoder = @This();