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 @@
11pub const asn1 = @import("codecs/asn1.zig");
22pub const base64 = @import("codecs/base64_hex_ct.zig").base64;
33pub 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 {
7171
7272 pub fn decode(reader: *std.Io.Reader) !Tag {
7373 const tag1: FirstTag = @bitCast(try reader.takeByte());
74 var number: u14 = tag1.number;
75
76 if (tag1.number == 31) {
77 const tag2: NextTag = @bitCast(try reader.takeByte());
78 number = tag2.number;
79 if (tag2.continues) {
80 const tag3: NextTag = @bitCast(try reader.takeByte());
81 number = (number << 7) + tag3.number;
82 if (tag3.continues) return error.EndOfStream;
83 }
74 var number: std.meta.Tag(Tag.Number) = tag1.number;
75
76 if (tag1.number == high_tag_marker) {
77 number = 0;
78 for (0..max_continuations) |i| {
79 const next: NextTag = @bitCast(try reader.takeByte());
80 if (i == 0 and next.number == 0) return error.InvalidEncoding;
81 number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding;
82 number |= next.number;
83 if (!next.continues) break;
84 } else return error.InvalidEncoding;
85 if (number < high_tag_marker) return error.InvalidEncoding;
8486 }
8587
8688 return Tag{
......@@ -90,40 +92,51 @@ pub const Tag = struct {
9092 };
9193 }
9294
93 pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void {
94 var tag1 = FirstTag{
95 pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 {
96 const n = @intFromEnum(self.number);
97 var tag1: FirstTag = .{
9598 .number = undefined,
9699 .constructed = self.constructed,
97100 .class = self.class,
98101 };
99102
100 var buffer: [3]u8 = undefined;
101 var writer2: std.Io.Writer = .init(&buffer);
103 if (n < high_tag_marker) {
104 tag1.number = @intCast(n);
105 buf[0] = @bitCast(tag1);
106 return buf[0..1];
107 }
102108
103 switch (@intFromEnum(self.number)) {
104 0...std.math.maxInt(u5) => |n| {
105 tag1.number = @intCast(n);
106 writer2.writeByte(@bitCast(tag1)) catch unreachable;
107 },
108 std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| {
109 tag1.number = 15;
110 const tag2 = NextTag{ .number = @intCast(n), .continues = false };
111 writer2.writeByte(@bitCast(tag1)) catch unreachable;
112 writer2.writeByte(@bitCast(tag2)) catch unreachable;
113 },
114 else => |n| {
115 tag1.number = 15;
116 const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true };
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 },
109 tag1.number = high_tag_marker;
110 buf[0] = @bitCast(tag1);
111
112 const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n);
113 const len = std.math.divCeil(usize, bits_used, 7) catch unreachable;
114
115 var remaining = n;
116 var i = len;
117 while (i > 0) : (i -= 1) {
118 buf[i] = @bitCast(NextTag{
119 .number = @truncate(remaining),
120 .continues = i != len,
121 });
122 remaining >>= 7;
122123 }
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));
125130 }
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
127140 const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
128141 const NextTag = packed struct(u8) { number: u7, continues: bool };
129142
......@@ -165,6 +178,42 @@ test Tag {
165178 try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t);
166179}
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
168217/// A decoded view.
169218pub const Element = struct {
170219 tag: Tag,
......@@ -183,13 +232,14 @@ pub const Element = struct {
183232 }
184233 };
185234
186 pub const DecodeError = error{EndOfStream};
235 pub const DecodeError = error{ EndOfStream, InvalidEncoding };
187236
188237 /// Safely decode a DER/BER/CER element at `index`:
189238 /// - Ensures length uses shortest form
190239 /// - Ensures length is within `bytes`
191240 /// - Ensures length is less than `std.math.maxInt(Index)`
192241 pub fn decode(bytes: []const u8, index: Index) DecodeError!Element {
242 if (index > bytes.len) return error.EndOfStream;
193243 var reader: std.Io.Reader = .fixed(bytes[index..]);
194244
195245 const tag = Tag.decode(&reader) catch |err| switch (err) {
......@@ -327,13 +377,22 @@ pub const BitString = struct {
327377 }
328378
329379 pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void {
330 try encoder.writer().writeAll(self.bytes);
331 try encoder.writer().writeByte(self.right_padding);
380 try encoder.prependBytes(self.bytes);
381 try encoder.prependBytes(&.{self.right_padding});
332382 try encoder.length(self.bytes.len + 1);
333383 try encoder.tag(asn1_tag);
334384 }
335385};
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
337396pub fn Opaque(comptime tag: Tag) type {
338397 return struct {
339398 bytes: []const u8,
lib/std/crypto/codecs/asn1/Oid.zig+2-2
......@@ -47,7 +47,7 @@ test fromDot {
4747 }
4848}
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 {
5151 const encoded = self.encoded;
5252 const first = @divTrunc(encoded[0], 40);
5353 const second = encoded[0] - first * 40;
......@@ -81,7 +81,7 @@ test toDot {
8181 for (test_cases) |t| {
8282 var stream: std.Io.Writer = .fixed(&buf);
8383 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());
8585 }
8686}
8787
lib/std/crypto/codecs/asn1/der.zig+42
......@@ -49,6 +49,48 @@ test decode {
4949 try std.testing.expectEqualDeep(test_case.value, decoded);
5050}
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
5294test {
5395 _ = Decoder;
5496 _ = Encoder;
lib/std/crypto/codecs/asn1/der/Decoder.zig+44-13
......@@ -111,21 +111,33 @@ pub fn view(self: Decoder, elem: Element) []const u8 {
111111}
112112
113113fn 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");
115
116 var bytes = value;
117 if (bytes.len >= 2) {
118 if (bytes[0] == 0) {
119 if (@clz(bytes[1]) > 0) return error.NonCanonical;
120 bytes.ptr += 1;
121 }
122 if (bytes[0] == 0xff and @clz(bytes[1]) == 0) return error.NonCanonical;
114 const info = @typeInfo(T).int;
115 if (info.bits % 8 != 0) @compileError("T must be byte aligned");
116
117 if (value.len == 0) return error.NonCanonical;
118 if (value.len >= 2) {
119 if (value[0] == 0x00 and value[1] & 0x80 == 0) return error.NonCanonical;
120 if (value[0] == 0xff and value[1] & 0x80 != 0) return error.NonCanonical;
123121 }
124122
125 if (bytes.len > @sizeOf(T)) return error.LargeValue;
126 if (@sizeOf(T) == 1) return @bitCast(bytes[0]);
123 const had_sign_byte = value.len >= 2 and value[0] == 0x00;
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);
129141}
130142
131143test int {
......@@ -135,7 +147,26 @@ test int {
135147
136148 const big = [_]u8{ 0xef, 0xff };
137149 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 }));
139170}
140171
141172test Decoder {
lib/std/crypto/codecs/asn1/der/Encoder.zig+101-38
......@@ -24,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void {
2424fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
2525 const T = @TypeOf(val);
2626 if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self);
27 const outer_field_tag = self.field_tag;
2728 const start = self.buffer.data.len;
2829 const merged_tag = self.mergedTag(tag_);
2930
......@@ -42,8 +43,9 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
4243 const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: {
4344 break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val));
4445 } 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) {
4749 const start2 = self.buffer.data.len;
4850 self.field_tag = field_tag;
4951 // will merge with self.field_tag.
......@@ -58,6 +60,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
5860 }
5961 }
6062 }
63 self.field_tag = outer_field_tag;
6164 },
6265 .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}),
6366 .int => try self.int(T, val),
......@@ -68,7 +71,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
6871 try self.int(e.tag_type, @intFromEnum(val));
6972 }
7073 },
71 .optional => if (val) |v| return try self.anyTag(tag_, v),
74 .optional => if (val) |v| return try self.anyTag(tag_, v) else return,
7275 .null => {},
7376 else => @compileError("cannot encode type " ++ @typeName(T)),
7477 }
......@@ -80,7 +83,8 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void {
8083/// Encode a tag.
8184pub fn tag(self: *Encoder, tag_: Tag) !void {
8285 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));
8488}
8589
8690fn mergedTag(self: *Encoder, tag_: Tag) Tag {
......@@ -96,19 +100,14 @@ fn mergedTag(self: *Encoder, tag_: Tag) Tag {
96100
97101/// Encode a length.
98102pub fn length(self: *Encoder, len: usize) !void {
99 const writer_ = self.writer();
100 if (len < 128) {
101 try writer_.writeInt(u8, @intCast(len), .big);
102 return;
103 }
104 inline for ([_]type{ u8, u16, u32 }) |T| {
105 if (len < std.math.maxInt(T)) {
106 try writer_.writeInt(T, @intCast(len), .big);
107 try writer_.writeInt(u8, @sizeOf(T) | 0x80, .big);
108 return;
109 }
110 }
111 return error.InvalidLength;
103 if (len < 128) return self.buffer.prependSlice(&.{@intCast(len)});
104 const len32 = std.math.cast(u32, len) orelse return error.InvalidLength;
105 var buf: [@sizeOf(u32) + 1]u8 = undefined;
106 std.mem.writeInt(u32, buf[1..], len32, .big);
107 var first: usize = 1;
108 while (buf[first] == 0) first += 1;
109 buf[first - 1] = @intCast((buf.len - first) | 0x80);
110 return self.buffer.prependSlice(buf[first - 1 ..]);
112111}
113112
114113/// Encode a tag and length-prefixed bytes.
......@@ -118,28 +117,23 @@ pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void {
118117 try self.tag(tag_);
119118}
120119
121/// Warning: This writer writes backwards. `fn print` will NOT work as expected.
122pub fn writer(self: *Encoder) ArrayListReverse.Writer {
123 return self.buffer.writer();
120/// Write raw bytes. The encoder builds its output back-to-front, so chained
121/// calls should be made in reverse of the desired on-wire order.
122pub fn prependBytes(self: *Encoder, bytes: []const u8) !void {
123 return self.buffer.prependSlice(bytes);
124124}
125125
126126fn int(self: *Encoder, comptime T: type, value: T) !void {
127 const big = std.mem.nativeTo(T, value, .big);
128 const big_bytes = std.mem.asBytes(&big);
129
130 const bits_needed = @bitSizeOf(T) - @clz(value);
131 const needs_padding: u1 = if (value == 0)
132 1
133 else if (bits_needed > 8) brk: {
134 const RightShift = @Int(.unsigned, @bitSizeOf(@TypeOf(bits_needed)) - 1);
135 const right_shift: RightShift = @intCast(bits_needed - 9);
136 break :brk if (value >> right_shift == 0x1ff) 1 else 0;
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);
127 const info = @typeInfo(T).int;
128 const Unsigned = @Int(.unsigned, info.bits);
129 const pad: u8 = if (info.signedness == .signed and value < 0) 0xff else 0;
130 var buf: [@sizeOf(Unsigned) + 1]u8 = undefined;
131 buf[0] = pad;
132 std.mem.writeInt(Unsigned, buf[1..], @bitCast(value), .big);
133
134 var first: usize = 0;
135 while (first + 1 < buf.len and buf[first] == pad and (buf[first + 1] ^ pad) & 0x80 == 0) first += 1;
136 try self.buffer.prependSlice(buf[first..]);
143137}
144138
145139test int {
......@@ -148,15 +142,84 @@ test int {
148142 defer encoder.deinit();
149143
150144 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
153147 encoder.buffer.clearAndFree();
154148 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
157151 encoder.buffer.clearAndFree();
158152 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);
160223}
161224
162225const std = @import("std");