authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-29 05:53:30+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-29 05:53:30+02:00
logd4a295a9de6c2ac0cb76fa0fcfd5162cbb7031d5
treef7601d2941b0aed30fb61730860980fee198febf
parentd534cfa787cfa077b24e949b19749bd5c6e89a80
parentff3fcb0290a1ce3f938d8cdaae459157af7d3e37

Merge pull request 'std.crypto.codecs.asn1: fix. compilation after IO changes and improve correctness' (#35326) from jedisct1/zig:asn1der into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35326 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

6 files changed, 291 insertions(+), 90 deletions(-)

lib/std/crypto/codecs.zig+6
...@@ -1,3 +1,9 @@...@@ -1,3 +1,9 @@
1pub const asn1 = @import("codecs/asn1.zig");1pub const asn1 = @import("codecs/asn1.zig");
2pub const base64 = @import("codecs/base64_hex_ct.zig").base64;2pub const base64 = @import("codecs/base64_hex_ct.zig").base64;
3pub const hex = @import("codecs/base64_hex_ct.zig").hex;3pub const hex = @import("codecs/base64_hex_ct.zig").hex;
4
5test {
6 _ = asn1;
7 _ = base64;
8 _ = hex;
9}
lib/std/crypto/codecs/asn1.zig+96-37
...@@ -71,16 +71,18 @@ pub const Tag = struct {...@@ -71,16 +71,18 @@ pub const Tag = struct {
7171
72 pub fn decode(reader: *std.Io.Reader) !Tag {72 pub fn decode(reader: *std.Io.Reader) !Tag {
73 const tag1: FirstTag = @bitCast(try reader.takeByte());73 const tag1: FirstTag = @bitCast(try reader.takeByte());
74 var number: u14 = tag1.number;74 var number: std.meta.Tag(Tag.Number) = tag1.number;
7575
76 if (tag1.number == 31) {76 if (tag1.number == high_tag_marker) {
77 const tag2: NextTag = @bitCast(try reader.takeByte());77 number = 0;
78 number = tag2.number;78 for (0..max_continuations) |i| {
79 if (tag2.continues) {79 const next: NextTag = @bitCast(try reader.takeByte());
80 const tag3: NextTag = @bitCast(try reader.takeByte());80 if (i == 0 and next.number == 0) return error.InvalidEncoding;
81 number = (number << 7) + tag3.number;81 number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding;
82 if (tag3.continues) return error.EndOfStream;82 number |= next.number;
83 }83 if (!next.continues) break;
84 } else return error.InvalidEncoding;
85 if (number < high_tag_marker) return error.InvalidEncoding;
84 }86 }
8587
86 return Tag{88 return Tag{
...@@ -90,40 +92,51 @@ pub const Tag = struct {...@@ -90,40 +92,51 @@ pub const Tag = struct {
90 };92 };
91 }93 }
9294
93 pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void {95 pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 {
94 var tag1 = FirstTag{96 const n = @intFromEnum(self.number);
97 var tag1: FirstTag = .{
95 .number = undefined,98 .number = undefined,
96 .constructed = self.constructed,99 .constructed = self.constructed,
97 .class = self.class,100 .class = self.class,
98 };101 };
99102
100 var buffer: [3]u8 = undefined;103 if (n < high_tag_marker) {
101 var writer2: std.Io.Writer = .init(&buffer);104 tag1.number = @intCast(n);
105 buf[0] = @bitCast(tag1);
106 return buf[0..1];
107 }
102108
103 switch (@intFromEnum(self.number)) {109 tag1.number = high_tag_marker;
104 0...std.math.maxInt(u5) => |n| {110 buf[0] = @bitCast(tag1);
105 tag1.number = @intCast(n);111
106 writer2.writeByte(@bitCast(tag1)) catch unreachable;112 const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n);
107 },113 const len = std.math.divCeil(usize, bits_used, 7) catch unreachable;
108 std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {114
109 tag1.number = 15;115 var remaining = n;
110 const tag2 = NextTag{ .number = @intCast(n), .continues = false };116 var i = len;
111 writer2.writeByte(@bitCast(tag1)) catch unreachable;117 while (i > 0) : (i -= 1) {
112 writer2.writeByte(@bitCast(tag2)) catch unreachable;118 buf[i] = @bitCast(NextTag{
113 },119 .number = @truncate(remaining),
114 else => |n| {120 .continues = i != len,
115 tag1.number = 15;121 });
116 const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };122 remaining >>= 7;
117 const tag3 = NextTag{ .number = @truncate(n), .continues = false };
118 writer2.writeByte(@bitCast(tag1)) catch unreachable;
119 writer2.writeByte(@bitCast(tag2)) catch unreachable;
120 writer2.writeByte(@bitCast(tag3)) catch unreachable;
121 },
122 }123 }
124 return buf[0 .. 1 + len];
125 }
123126
124 _ = try writer.write(writer2.buffered());127 pub fn encode(self: Tag, writer: *std.Io.Writer) std.Io.Writer.Error!void {
128 var buf: [max_encoded_len]u8 = undefined;
129 try writer.writeAll(self.encodeToSlice(&buf));
125 }130 }
126131
132 pub const max_encoded_len = 1 + (std.math.divCeil(
133 comptime_int,
134 @bitSizeOf(std.meta.Tag(Tag.Number)),
135 7,
136 ) catch unreachable);
137 const max_continuations = max_encoded_len - 1;
138 const high_tag_marker = std.math.maxInt(u5);
139
127 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };140 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
128 const NextTag = packed struct(u8) { number: u7, continues: bool };141 const NextTag = packed struct(u8) { number: u7, continues: bool };
129142
...@@ -165,6 +178,42 @@ test Tag {...@@ -165,6 +178,42 @@ test Tag {
165 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);178 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
166}179}
167180
181test "Tag.encode produces the exact bytes from X.690" {
182 const cases = [_]struct { number: u16, expected: []const u8 }{
183 .{ .number = 0, .expected = &.{0x00} },
184 .{ .number = 30, .expected = &.{0x1e} },
185 .{ .number = 31, .expected = &.{ 0x1f, 0x1f } },
186 .{ .number = 127, .expected = &.{ 0x1f, 0x7f } },
187 .{ .number = 128, .expected = &.{ 0x1f, 0x81, 0x00 } },
188 .{ .number = 16383, .expected = &.{ 0x1f, 0xff, 0x7f } },
189 .{ .number = 16384, .expected = &.{ 0x1f, 0x81, 0x80, 0x00 } },
190 .{ .number = 65535, .expected = &.{ 0x1f, 0x83, 0xff, 0x7f } },
191 };
192 for (cases) |c| {
193 const tag = Tag.init(@enumFromInt(c.number), false, .universal);
194 var buf: [Tag.max_encoded_len]u8 = undefined;
195 try std.testing.expectEqualSlices(u8, c.expected, tag.encodeToSlice(&buf));
196 }
197}
198
199test "Tag.encode/decode round trip" {
200 for ([_]u16{ 0, 30, 31, 32, 127, 128, 16383, 16384, 65535 }) |n| {
201 const tag = Tag.init(@enumFromInt(n), false, .universal);
202 var buf: [Tag.max_encoded_len]u8 = undefined;
203 const encoded = tag.encodeToSlice(&buf);
204 var reader: std.Io.Reader = .fixed(encoded);
205 try std.testing.expectEqual(tag, try Tag.decode(&reader));
206 try std.testing.expectEqual(encoded.len, reader.seek);
207 }
208}
209
210test "Tag.decode rejects non-minimal high-tag form" {
211 for ([_][]const u8{ &.{ 0x1f, 0x1e }, &.{ 0x1f, 0x80, 0x01 } }) |bytes| {
212 var reader: std.Io.Reader = .fixed(bytes);
213 try std.testing.expectError(error.InvalidEncoding, Tag.decode(&reader));
214 }
215}
216
168/// A decoded view.217/// A decoded view.
169pub const Element = struct {218pub const Element = struct {
170 tag: Tag,219 tag: Tag,
...@@ -183,13 +232,14 @@ pub const Element = struct {...@@ -183,13 +232,14 @@ pub const Element = struct {
183 }232 }
184 };233 };
185234
186 pub const DecodeError = error{EndOfStream};235 pub const DecodeError = error{ EndOfStream, InvalidEncoding };
187236
188 /// Safely decode a DER/BER/CER element at `index`:237 /// Safely decode a DER/BER/CER element at `index`:
189 /// - Ensures length uses shortest form238 /// - Ensures length uses shortest form
190 /// - Ensures length is within `bytes`239 /// - Ensures length is within `bytes`
191 /// - Ensures length is less than `std.math.maxInt(Index)`240 /// - Ensures length is less than `std.math.maxInt(Index)`
192 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {241 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
242 if (index > bytes.len) return error.EndOfStream;
193 var reader: std.Io.Reader = .fixed(bytes[index..]);243 var reader: std.Io.Reader = .fixed(bytes[index..]);
194244
195 const tag = Tag.decode(&reader) catch |err| switch (err) {245 const tag = Tag.decode(&reader) catch |err| switch (err) {
...@@ -327,13 +377,22 @@ pub const BitString = struct {...@@ -327,13 +377,22 @@ pub const BitString = struct {
327 }377 }
328378
329 pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {379 pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
330 try encoder.writer().writeAll(self.bytes);380 try encoder.prependBytes(self.bytes);
331 try encoder.writer().writeByte(self.right_padding);381 try encoder.prependBytes(&.{self.right_padding});
332 try encoder.length(self.bytes.len + 1);382 try encoder.length(self.bytes.len + 1);
333 try encoder.tag(asn1_tag);383 try encoder.tag(asn1_tag);
334 }384 }
335};385};
336386
387test BitString {
388 const bs = BitString{ .bytes = &.{ 0x6e, 0x5d, 0xc0 }, .right_padding = 6 };
389 const allocator = std.testing.allocator;
390 const buf = try der.encode(allocator, bs);
391 defer allocator.free(buf);
392 try std.testing.expectEqualSlices(u8, &.{ 0x03, 0x04, 0x06, 0x6e, 0x5d, 0xc0 }, buf);
393 try std.testing.expectEqualDeep(bs, try der.decode(BitString, buf));
394}
395
337pub fn Opaque(comptime tag: Tag) type {396pub fn Opaque(comptime tag: Tag) type {
338 return struct {397 return struct {
339 bytes: []const u8,398 bytes: []const u8,
lib/std/crypto/codecs/asn1/Oid.zig+2-2
...@@ -47,7 +47,7 @@ test fromDot {...@@ -47,7 +47,7 @@ test fromDot {
47 }47 }
48}48}
4949
50pub fn toDot(self: Oid, writer: anytype) @TypeOf(writer).Error!void {50pub fn toDot(self: Oid, writer: *std.Io.Writer) std.Io.Writer.Error!void {
51 const encoded = self.encoded;51 const encoded = self.encoded;
52 const first = @divTrunc(encoded[0], 40);52 const first = @divTrunc(encoded[0], 40);
53 const second = encoded[0] - first * 40;53 const second = encoded[0] - first * 40;
...@@ -81,7 +81,7 @@ test toDot {...@@ -81,7 +81,7 @@ test toDot {
81 for (test_cases) |t| {81 for (test_cases) |t| {
82 var stream: std.Io.Writer = .fixed(&buf);82 var stream: std.Io.Writer = .fixed(&buf);
83 try toDot(Oid{ .encoded = t.encoded }, &stream);83 try toDot(Oid{ .encoded = t.encoded }, &stream);
84 try std.testing.expectEqualStrings(t.dot_notation, stream.written());84 try std.testing.expectEqualStrings(t.dot_notation, stream.buffered());
85 }85 }
86}86}
8787
lib/std/crypto/codecs/asn1/der.zig+42
...@@ -49,6 +49,48 @@ test decode {...@@ -49,6 +49,48 @@ test decode {
49 try std.testing.expectEqualDeep(test_case.value, decoded);49 try std.testing.expectEqualDeep(test_case.value, decoded);
50}50}
5151
52test "integer round trip across signed and unsigned boundaries" {
53 const allocator = std.testing.allocator;
54 inline for (.{ u8, u16, u32, i8, i16, i32 }) |T| {
55 const cases = comptime blk: {
56 const min = std.math.minInt(T);
57 const max = std.math.maxInt(T);
58 break :blk [_]T{ 0, 1, max, min, @divTrunc(max, 2), @divTrunc(min, 2) };
59 };
60 for (cases) |value| {
61 const buf = try encode(allocator, value);
62 defer allocator.free(buf);
63 const decoded = try decode(T, buf);
64 try std.testing.expectEqual(value, decoded);
65 }
66 }
67}
68
69test "encode skips null optional fields" {
70 const Value = struct { a: ?u8, b: u8 };
71 const allocator = std.testing.allocator;
72 const actual = try encode(allocator, Value{ .a = null, .b = 5 });
73 defer allocator.free(actual);
74
75 try std.testing.expectEqualSlices(u8, &.{ 0x30, 0x03, 0x02, 0x01, 0x05 }, actual);
76}
77
78test "encode preserves outer sequence tag after implicit field tags" {
79 const Value = struct {
80 a: u8,
81 b: u8,
82
83 pub const asn1_tags = .{
84 .a = asn1.FieldTag.initImplicit(0, .context_specific),
85 };
86 };
87 const allocator = std.testing.allocator;
88 const actual = try encode(allocator, Value{ .a = 1, .b = 2 });
89 defer allocator.free(actual);
90
91 try std.testing.expectEqualSlices(u8, &.{ 0x30, 0x06, 0x80, 0x01, 0x01, 0x02, 0x01, 0x02 }, actual);
92}
93
52test {94test {
53 _ = Decoder;95 _ = Decoder;
54 _ = Encoder;96 _ = Encoder;
lib/std/crypto/codecs/asn1/der/Decoder.zig+44-13
...@@ -111,21 +111,33 @@ pub fn view(self: Decoder, elem: Element) []const u8 {...@@ -111,21 +111,33 @@ pub fn view(self: Decoder, elem: Element) []const u8 {
111}111}
112112
113fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {113fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T {
114 if (@typeInfo(T).int.bits % 8 != 0) @compileError("T must be byte aligned");114 const info = @typeInfo(T).int;
115115 if (info.bits % 8 != 0) @compileError("T must be byte aligned");
116 var bytes = value;116
117 if (bytes.len >= 2) {117 if (value.len == 0) return error.NonCanonical;
118 if (bytes[0] == 0) {118 if (value.len >= 2) {
119 if (@clz(bytes[1]) > 0) return error.NonCanonical;119 if (value[0] == 0x00 and value[1] & 0x80 == 0) return error.NonCanonical;
120 bytes.ptr += 1;120 if (value[0] == 0xff and value[1] & 0x80 != 0) return error.NonCanonical;
121 }
122 if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
123 }121 }
124122
125 if (bytes.len > @sizeOf(T)) return error.LargeValue;123 const had_sign_byte = value.len >= 2 and value[0] == 0x00;
126 if (@sizeOf(T) == 1) return @bitCast(bytes[0]);124 const bytes = if (had_sign_byte) value[1..] else value;
125 const der_negative = !had_sign_byte and bytes[0] & 0x80 != 0;
126
127 switch (info.signedness) {
128 .unsigned => {
129 if (der_negative) return error.LargeValue;
130 if (bytes.len > @sizeOf(T)) return error.LargeValue;
131 },
132 .signed => {
133 const max_len: usize = if (had_sign_byte) @sizeOf(T) - 1 else @sizeOf(T);
134 if (bytes.len > max_len) return error.LargeValue;
135 },
136 }
127137
128 return std.mem.readVarInt(T, bytes, .big);138 var buf: [@sizeOf(T)]u8 = @splat(if (der_negative) 0xff else 0);
139 @memcpy(buf[buf.len - bytes.len ..], bytes);
140 return std.mem.readInt(T, &buf, .big);
129}141}
130142
131test int {143test int {
...@@ -135,7 +147,26 @@ test int {...@@ -135,7 +147,26 @@ test int {
135147
136 const big = [_]u8{ 0xef, 0xff };148 const big = [_]u8{ 0xef, 0xff };
137 try expectError(error.LargeValue, int(u8, &big));149 try expectError(error.LargeValue, int(u8, &big));
138 try expectEqual(0xefff, int(u16, &big));150 try expectError(error.LargeValue, int(u16, &big));
151 try expectEqual(@as(i16, -4097), try int(i16, &big));
152
153 try expectEqual(@as(u16, 255), try int(u16, &.{ 0x00, 0xff }));
154 try expectEqual(@as(u16, 0x8000), try int(u16, &.{ 0x00, 0x80, 0x00 }));
155
156 try expectEqual(@as(i8, -1), try int(i8, &.{0xff}));
157 try expectEqual(@as(i16, -1), try int(i16, &.{0xff}));
158 try expectEqual(@as(i16, -128), try int(i16, &.{0x80}));
159 try expectEqual(@as(i16, -129), try int(i16, &.{ 0xff, 0x7f }));
160 try expectEqual(@as(i16, 255), try int(i16, &.{ 0x00, 0xff }));
161 try expectEqual(@as(i32, 0x7fffffff), try int(i32, &.{ 0x7f, 0xff, 0xff, 0xff }));
162
163 try expectError(error.LargeValue, int(i8, &.{ 0x00, 0xff }));
164 try expectError(error.LargeValue, int(i16, &.{ 0x00, 0x80, 0x00 }));
165 try expectError(error.LargeValue, int(i32, &.{ 0x00, 0x80, 0x00, 0x00, 0x00 }));
166
167 try expectError(error.LargeValue, int(u8, &.{0xff}));
168 try expectError(error.LargeValue, int(u16, &.{0x80}));
169 try expectError(error.LargeValue, int(u32, &.{ 0x80, 0x00, 0x00, 0x00 }));
139}170}
140171
141test Decoder {172test Decoder {
lib/std/crypto/codecs/asn1/der/Encoder.zig+101-38
...@@ -24,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void {...@@ -24,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void {
24fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {24fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
25 const T = @TypeOf(val);25 const T = @TypeOf(val);
26 if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self);26 if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self);
27 const outer_field_tag = self.field_tag;
27 const start = self.buffer.data.len;28 const start = self.buffer.data.len;
28 const merged_tag = self.mergedTag(tag_);29 const merged_tag = self.mergedTag(tag_);
2930
...@@ -42,8 +43,9 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {...@@ -42,8 +43,9 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
42 const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: {43 const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: {
43 break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val));44 break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val));
44 } else false;45 } else false;
46 const is_null_optional = if (@typeInfo(f_type) == .optional) field_val == null else false;
4547
46 if (!is_default) {48 if (!is_default and !is_null_optional) {
47 const start2 = self.buffer.data.len;49 const start2 = self.buffer.data.len;
48 self.field_tag = field_tag;50 self.field_tag = field_tag;
49 // will merge with self.field_tag.51 // will merge with self.field_tag.
...@@ -58,6 +60,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {...@@ -58,6 +60,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
58 }60 }
59 }61 }
60 }62 }
63 self.field_tag = outer_field_tag;
61 },64 },
62 .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}),65 .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}),
63 .int => try self.int(T, val),66 .int => try self.int(T, val),
...@@ -68,7 +71,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {...@@ -68,7 +71,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
68 try self.int(e.tag_type, @intFromEnum(val));71 try self.int(e.tag_type, @intFromEnum(val));
69 }72 }
70 },73 },
71 .optional => if (val) |v| return try self.anyTag(tag_, v),74 .optional => if (val) |v| return try self.anyTag(tag_, v) else return,
72 .null => {},75 .null => {},
73 else => @compileError("cannot encode type " ++ @typeName(T)),76 else => @compileError("cannot encode type " ++ @typeName(T)),
74 }77 }
...@@ -80,7 +83,8 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {...@@ -80,7 +83,8 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
80/// Encode a tag.83/// Encode a tag.
81pub fn tag(self: *Encoder, tag_: Tag) !void {84pub fn tag(self: *Encoder, tag_: Tag) !void {
82 const t = self.mergedTag(tag_);85 const t = self.mergedTag(tag_);
83 try t.encode(self.writer());86 var buf: [Tag.max_encoded_len]u8 = undefined;
87 try self.buffer.prependSlice(t.encodeToSlice(&buf));
84}88}
8589
86fn mergedTag(self: *Encoder, tag_: Tag) Tag {90fn mergedTag(self: *Encoder, tag_: Tag) Tag {
...@@ -96,19 +100,14 @@ fn mergedTag(self: *Encoder, tag_: Tag) Tag {...@@ -96,19 +100,14 @@ fn mergedTag(self: *Encoder, tag_: Tag) Tag {
96100
97/// Encode a length.101/// Encode a length.
98pub fn length(self: *Encoder, len: usize) !void {102pub fn length(self: *Encoder, len: usize) !void {
99 const writer_ = self.writer();103 if (len < 128) return self.buffer.prependSlice(&.{@intCast(len)});
100 if (len < 128) {104 const len32 = std.math.cast(u32, len) orelse return error.InvalidLength;
101 try writer_.writeInt(u8, @intCast(len), .big);105 var buf: [@sizeOf(u32) + 1]u8 = undefined;
102 return;106 std.mem.writeInt(u32, buf[1..], len32, .big);
103 }107 var first: usize = 1;
104 inline for ([_]type{ u8, u16, u32 }) |T| {108 while (buf[first] == 0) first += 1;
105 if (len < std.math.maxInt(T)) {109 buf[first - 1] = @intCast((buf.len - first) | 0x80);
106 try writer_.writeInt(T, @intCast(len), .big);110 return self.buffer.prependSlice(buf[first - 1 ..]);
107 try writer_.writeInt(u8, @sizeOf(T) | 0x80, .big);
108 return;
109 }
110 }
111 return error.InvalidLength;
112}111}
113112
114/// Encode a tag and length-prefixed bytes.113/// Encode a tag and length-prefixed bytes.
...@@ -118,28 +117,23 @@ pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void {...@@ -118,28 +117,23 @@ pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void {
118 try self.tag(tag_);117 try self.tag(tag_);
119}118}
120119
121/// Warning: This writer writes backwards. `fn print` will NOT work as expected.120/// Write raw bytes. The encoder builds its output back-to-front, so chained
122pub fn writer(self: *Encoder) ArrayListReverse.Writer {121/// calls should be made in reverse of the desired on-wire order.
123 return self.buffer.writer();122pub fn prependBytes(self: *Encoder, bytes: []const u8) !void {
123 return self.buffer.prependSlice(bytes);
124}124}
125125
126fn int(self: *Encoder, comptime T: type, value: T) !void {126fn int(self: *Encoder, comptime T: type, value: T) !void {
127 const big = std.mem.nativeTo(T, value, .big);127 const info = @typeInfo(T).int;
128 const big_bytes = std.mem.asBytes(&big);128 const Unsigned = @Int(.unsigned, info.bits);
129129 const pad: u8 = if (info.signedness == .signed and value < 0) 0xff else 0;
130 const bits_needed = @bitSizeOf(T) - @clz(value);130 var buf: [@sizeOf(Unsigned) + 1]u8 = undefined;
131 const needs_padding: u1 = if (value == 0)131 buf[0] = pad;
132 1132 std.mem.writeInt(Unsigned, buf[1..], @bitCast(value), .big);
133 else if (bits_needed > 8) brk: {133
134 const RightShift = @Int(.unsigned, @bitSizeOf(@TypeOf(bits_needed)) - 1);134 var first: usize = 0;
135 const right_shift: RightShift = @intCast(bits_needed - 9);135 while (first + 1 < buf.len and buf[first] == pad and (buf[first + 1] ^ pad) & 0x80 == 0) first += 1;
136 break :brk if (value >> right_shift == 0x1ff) 1 else 0;136 try self.buffer.prependSlice(buf[first..]);
137 } else 0;
138 const bytes_needed = try std.math.divCeil(usize, bits_needed, 8) + needs_padding;
139
140 const writer_ = self.writer();
141 for (0..bytes_needed - needs_padding) |i| try writer_.writeByte(big_bytes[big_bytes.len - i - 1]);
142 if (needs_padding == 1) try writer_.writeByte(0);
143}137}
144138
145test int {139test int {
...@@ -148,15 +142,84 @@ test int {...@@ -148,15 +142,84 @@ test int {
148 defer encoder.deinit();142 defer encoder.deinit();
149143
150 try encoder.int(u8, 0);144 try encoder.int(u8, 0);
151 try std.testing.expectEqualSlices(u8, &[_]u8{0}, encoder.buffer.data);145 try std.testing.expectEqualSlices(u8, &.{0}, encoder.buffer.data);
152146
153 encoder.buffer.clearAndFree();147 encoder.buffer.clearAndFree();
154 try encoder.int(u16, 0x00ff);148 try encoder.int(u16, 0x00ff);
155 try std.testing.expectEqualSlices(u8, &[_]u8{0xff}, encoder.buffer.data);149 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data);
156150
157 encoder.buffer.clearAndFree();151 encoder.buffer.clearAndFree();
158 try encoder.int(u32, 0xffff);152 try encoder.int(u32, 0xffff);
159 try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0xff, 0xff }, encoder.buffer.data);153 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff, 0xff }, encoder.buffer.data);
154
155 encoder.buffer.clearAndFree();
156 try encoder.int(u32, 0x01020304);
157 try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x02, 0x03, 0x04 }, encoder.buffer.data);
158
159 encoder.buffer.clearAndFree();
160 try encoder.int(u8, 127);
161 try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data);
162
163 encoder.buffer.clearAndFree();
164 try encoder.int(u16, 128);
165 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data);
166
167 encoder.buffer.clearAndFree();
168 try encoder.int(u16, 256);
169 try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x00 }, encoder.buffer.data);
170
171 encoder.buffer.clearAndFree();
172 try encoder.int(u8, 128);
173 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data);
174
175 encoder.buffer.clearAndFree();
176 try encoder.int(u8, 255);
177 try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data);
178
179 encoder.buffer.clearAndFree();
180 try encoder.int(u16, 0x8000);
181 try std.testing.expectEqualSlices(u8, &.{ 0, 0x80, 0 }, encoder.buffer.data);
182
183 encoder.buffer.clearAndFree();
184 try encoder.int(i8, -1);
185 try std.testing.expectEqualSlices(u8, &.{0xff}, encoder.buffer.data);
186
187 encoder.buffer.clearAndFree();
188 try encoder.int(i8, -128);
189 try std.testing.expectEqualSlices(u8, &.{0x80}, encoder.buffer.data);
190
191 encoder.buffer.clearAndFree();
192 try encoder.int(i16, -129);
193 try std.testing.expectEqualSlices(u8, &.{ 0xff, 0x7f }, encoder.buffer.data);
194}
195
196test length {
197 const allocator = std.testing.allocator;
198 var encoder = Encoder.init(allocator);
199 defer encoder.deinit();
200
201 try encoder.length(127);
202 try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data);
203
204 encoder.buffer.clearAndFree();
205 try encoder.length(128);
206 try std.testing.expectEqualSlices(u8, &.{ 0x81, 0x80 }, encoder.buffer.data);
207
208 encoder.buffer.clearAndFree();
209 try encoder.length(255);
210 try std.testing.expectEqualSlices(u8, &.{ 0x81, 0xff }, encoder.buffer.data);
211
212 encoder.buffer.clearAndFree();
213 try encoder.length(256);
214 try std.testing.expectEqualSlices(u8, &.{ 0x82, 0x01, 0x00 }, encoder.buffer.data);
215
216 encoder.buffer.clearAndFree();
217 try encoder.length(65535);
218 try std.testing.expectEqualSlices(u8, &.{ 0x82, 0xff, 0xff }, encoder.buffer.data);
219
220 encoder.buffer.clearAndFree();
221 try encoder.length(65536);
222 try std.testing.expectEqualSlices(u8, &.{ 0x83, 0x01, 0x00, 0x00 }, encoder.buffer.data);
160}223}
161224
162const std = @import("std");225const std = @import("std");