| author | |
| committer | |
| log | d4a295a9de6c2ac0cb76fa0fcfd5162cbb7031d5 |
| tree | f7601d2941b0aed30fb61730860980fee198febf |
| parent | d534cfa787cfa077b24e949b19749bd5c6e89a80 |
| parent | ff3fcb0290a1ce3f938d8cdaae459157af7d3e37 |
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 | 1 | pub const asn1 = @import("codecs/asn1.zig"); |
| 2 | 2 | pub const base64 = @import("codecs/base64_hex_ct.zig").base64; |
| 3 | 3 | pub const hex = @import("codecs/base64_hex_ct.zig").hex; |
| 4 | ||
| 5 | test { | |
| 6 | _ = asn1; | |
| 7 | _ = base64; | |
| 8 | _ = hex; | |
| 9 | } |
lib/std/crypto/codecs/asn1.zig+96-37| ... | ... | @@ -71,16 +71,18 @@ pub const Tag = struct { |
| 71 | 71 | |
| 72 | 72 | pub fn decode(reader: *std.Io.Reader) !Tag { |
| 73 | 73 | 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; | |
| 84 | 86 | } |
| 85 | 87 | |
| 86 | 88 | return Tag{ |
| ... | ... | @@ -90,40 +92,51 @@ pub const Tag = struct { |
| 90 | 92 | }; |
| 91 | 93 | } |
| 92 | 94 | |
| 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 = .{ | |
| 95 | 98 | .number = undefined, |
| 96 | 99 | .constructed = self.constructed, |
| 97 | 100 | .class = self.class, |
| 98 | 101 | }; |
| 99 | 102 | |
| 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 | } | |
| 102 | 108 | |
| 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; | |
| 122 | 123 | } |
| 124 | return buf[0 .. 1 + len]; | |
| 125 | } | |
| 123 | 126 | |
| 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 | } |
| 126 | 131 | |
| 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 | 140 | const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class }; |
| 128 | 141 | const NextTag = packed struct(u8) { number: u7, continues: bool }; |
| 129 | 142 | |
| ... | ... | @@ -165,6 +178,42 @@ test Tag { |
| 165 | 178 | try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t); |
| 166 | 179 | } |
| 167 | 180 | |
| 181 | test "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 | ||
| 199 | test "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 | ||
| 210 | test "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 | 217 | /// A decoded view. |
| 169 | 218 | pub const Element = struct { |
| 170 | 219 | tag: Tag, |
| ... | ... | @@ -183,13 +232,14 @@ pub const Element = struct { |
| 183 | 232 | } |
| 184 | 233 | }; |
| 185 | 234 | |
| 186 | pub const DecodeError = error{EndOfStream}; | |
| 235 | pub const DecodeError = error{ EndOfStream, InvalidEncoding }; | |
| 187 | 236 | |
| 188 | 237 | /// Safely decode a DER/BER/CER element at `index`: |
| 189 | 238 | /// - Ensures length uses shortest form |
| 190 | 239 | /// - Ensures length is within `bytes` |
| 191 | 240 | /// - Ensures length is less than `std.math.maxInt(Index)` |
| 192 | 241 | pub fn decode(bytes: []const u8, index: Index) DecodeError!Element { |
| 242 | if (index > bytes.len) return error.EndOfStream; | |
| 193 | 243 | var reader: std.Io.Reader = .fixed(bytes[index..]); |
| 194 | 244 | |
| 195 | 245 | const tag = Tag.decode(&reader) catch |err| switch (err) { |
| ... | ... | @@ -327,13 +377,22 @@ pub const BitString = struct { |
| 327 | 377 | } |
| 328 | 378 | |
| 329 | 379 | 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}); | |
| 332 | 382 | try encoder.length(self.bytes.len + 1); |
| 333 | 383 | try encoder.tag(asn1_tag); |
| 334 | 384 | } |
| 335 | 385 | }; |
| 336 | 386 | |
| 387 | test 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 | ||
| 337 | 396 | pub fn Opaque(comptime tag: Tag) type { |
| 338 | 397 | return struct { |
| 339 | 398 | bytes: []const u8, |
lib/std/crypto/codecs/asn1/Oid.zig+2-2| ... | ... | @@ -47,7 +47,7 @@ test fromDot { |
| 47 | 47 | } |
| 48 | 48 | } |
| 49 | 49 | |
| 50 | pub fn toDot(self: Oid, writer: anytype) @TypeOf(writer).Error!void { | |
| 50 | pub fn toDot(self: Oid, writer: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 51 | 51 | const encoded = self.encoded; |
| 52 | 52 | const first = @divTrunc(encoded[0], 40); |
| 53 | 53 | const second = encoded[0] - first * 40; |
| ... | ... | @@ -81,7 +81,7 @@ test toDot { |
| 81 | 81 | for (test_cases) |t| { |
| 82 | 82 | var stream: std.Io.Writer = .fixed(&buf); |
| 83 | 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 | } |
| 87 | 87 |
lib/std/crypto/codecs/asn1/der.zig+42| ... | ... | @@ -49,6 +49,48 @@ test decode { |
| 49 | 49 | try std.testing.expectEqualDeep(test_case.value, decoded); |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | test "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 | ||
| 69 | test "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 | ||
| 78 | test "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 | ||
| 52 | 94 | test { |
| 53 | 95 | _ = Decoder; |
| 54 | 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 | 111 | } |
| 112 | 112 | |
| 113 | 113 | fn 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; | |
| 123 | 121 | } |
| 124 | 122 | |
| 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 | } | |
| 127 | 137 | |
| 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 | } |
| 130 | 142 | |
| 131 | 143 | test int { |
| ... | ... | @@ -135,7 +147,26 @@ test int { |
| 135 | 147 | |
| 136 | 148 | const big = [_]u8{ 0xef, 0xff }; |
| 137 | 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 | } |
| 140 | 171 | |
| 141 | 172 | test Decoder { |
lib/std/crypto/codecs/asn1/der/Encoder.zig+101-38| ... | ... | @@ -24,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void { |
| 24 | 24 | fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { |
| 25 | 25 | const T = @TypeOf(val); |
| 26 | 26 | if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self); |
| 27 | const outer_field_tag = self.field_tag; | |
| 27 | 28 | const start = self.buffer.data.len; |
| 28 | 29 | const merged_tag = self.mergedTag(tag_); |
| 29 | 30 | |
| ... | ... | @@ -42,8 +43,9 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { |
| 42 | 43 | const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: { |
| 43 | 44 | break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val)); |
| 44 | 45 | } else false; |
| 46 | const is_null_optional = if (@typeInfo(f_type) == .optional) field_val == null else false; | |
| 45 | 47 | |
| 46 | if (!is_default) { | |
| 48 | if (!is_default and !is_null_optional) { | |
| 47 | 49 | const start2 = self.buffer.data.len; |
| 48 | 50 | self.field_tag = field_tag; |
| 49 | 51 | // will merge with self.field_tag. |
| ... | ... | @@ -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 | 65 | .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}), |
| 63 | 66 | .int => try self.int(T, val), |
| ... | ... | @@ -68,7 +71,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { |
| 68 | 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 | 75 | .null => {}, |
| 73 | 76 | else => @compileError("cannot encode type " ++ @typeName(T)), |
| 74 | 77 | } |
| ... | ... | @@ -80,7 +83,8 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { |
| 80 | 83 | /// Encode a tag. |
| 81 | 84 | pub fn tag(self: *Encoder, tag_: Tag) !void { |
| 82 | 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 | } |
| 85 | 89 | |
| 86 | 90 | fn mergedTag(self: *Encoder, tag_: Tag) Tag { |
| ... | ... | @@ -96,19 +100,14 @@ fn mergedTag(self: *Encoder, tag_: Tag) Tag { |
| 96 | 100 | |
| 97 | 101 | /// Encode a length. |
| 98 | 102 | pub 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 ..]); | |
| 112 | 111 | } |
| 113 | 112 | |
| 114 | 113 | /// Encode a tag and length-prefixed bytes. |
| ... | ... | @@ -118,28 +117,23 @@ pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void { |
| 118 | 117 | try self.tag(tag_); |
| 119 | 118 | } |
| 120 | 119 | |
| 121 | /// Warning: This writer writes backwards. `fn print` will NOT work as expected. | |
| 122 | pub 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. | |
| 122 | pub fn prependBytes(self: *Encoder, bytes: []const u8) !void { | |
| 123 | return self.buffer.prependSlice(bytes); | |
| 124 | 124 | } |
| 125 | 125 | |
| 126 | 126 | fn 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..]); | |
| 143 | 137 | } |
| 144 | 138 | |
| 145 | 139 | test int { |
| ... | ... | @@ -148,15 +142,84 @@ test int { |
| 148 | 142 | defer encoder.deinit(); |
| 149 | 143 | |
| 150 | 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); | |
| 152 | 146 | |
| 153 | 147 | encoder.buffer.clearAndFree(); |
| 154 | 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); | |
| 156 | 150 | |
| 157 | 151 | encoder.buffer.clearAndFree(); |
| 158 | 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 | ||
| 196 | test 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 | } |
| 161 | 224 | |
| 162 | 225 | const std = @import("std"); |