| 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,3 +1,9 @@ |
| 1 | pub const asn1 = @import("codecs/asn1.zig"); | 1 | pub const asn1 = @import("codecs/asn1.zig"); |
| 2 | pub const base64 = @import("codecs/base64_hex_ct.zig").base64; | 2 | pub const base64 = @import("codecs/base64_hex_ct.zig").base64; |
| 3 | pub const hex = @import("codecs/base64_hex_ct.zig").hex; | 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,16 +71,18 @@ pub const Tag = struct { |
| 71 | 71 | ||
| 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; |
| 75 | 75 | ||
| 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 | } |
| 85 | 87 | ||
| 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 | } |
| 92 | 94 | ||
| 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 | }; |
| 99 | 102 | ||
| 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 | } | ||
| 102 | 108 | ||
| 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 | } | ||
| 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 | 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 }; |
| 129 | 142 | ||
| ... | @@ -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 | } |
| 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 | /// A decoded view. | 217 | /// A decoded view. |
| 169 | pub const Element = struct { | 218 | pub 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 | }; |
| 185 | 234 | ||
| 186 | pub const DecodeError = error{EndOfStream}; | 235 | pub const DecodeError = error{ EndOfStream, InvalidEncoding }; |
| 187 | 236 | ||
| 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 form | 238 | /// - 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..]); |
| 194 | 244 | ||
| 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 | } |
| 328 | 378 | ||
| 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 | }; |
| 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 | pub fn Opaque(comptime tag: Tag) type { | 396 | pub 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 | } |
| 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 | 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 | } |
| 87 | 87 |
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 | } |
| 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 | test { | 94 | test { |
| 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 | } |
| 112 | 112 | ||
| 113 | fn int(comptime T: type, value: []const u8) error{ NonCanonical, LargeValue }!T { | 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"); | 114 | const info = @typeInfo(T).int; |
| 115 | 115 | 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 | } |
| 124 | 122 | ||
| 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 | } | ||
| 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 | test int { | 143 | test int { |
| ... | @@ -135,7 +147,26 @@ test int { | ... | @@ -135,7 +147,26 @@ test int { |
| 135 | 147 | ||
| 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 | } |
| 140 | 171 | ||
| 141 | test Decoder { | 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,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void { |
| 24 | fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { | 24 | fn 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_); |
| 29 | 30 | ||
| ... | @@ -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; | ||
| 45 | 47 | ||
| 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. |
| 81 | pub fn tag(self: *Encoder, tag_: Tag) !void { | 84 | pub 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 | } |
| 85 | 89 | ||
| 86 | fn mergedTag(self: *Encoder, tag_: Tag) Tag { | 90 | fn 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 { |
| 96 | 100 | ||
| 97 | /// Encode a length. | 101 | /// Encode a length. |
| 98 | pub fn length(self: *Encoder, len: usize) !void { | 102 | pub 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 | } |
| 113 | 112 | ||
| 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 | } |
| 120 | 119 | ||
| 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 |
| 122 | pub fn writer(self: *Encoder) ArrayListReverse.Writer { | 121 | /// calls should be made in reverse of the desired on-wire order. |
| 123 | return self.buffer.writer(); | 122 | pub fn prependBytes(self: *Encoder, bytes: []const u8) !void { |
| 123 | return self.buffer.prependSlice(bytes); | ||
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | fn int(self: *Encoder, comptime T: type, value: T) !void { | 126 | fn 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); |
| 129 | 129 | 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 | 1 | 132 | 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 | } |
| 144 | 138 | ||
| 145 | test int { | 139 | test int { |
| ... | @@ -148,15 +142,84 @@ test int { | ... | @@ -148,15 +142,84 @@ test int { |
| 148 | defer encoder.deinit(); | 142 | defer encoder.deinit(); |
| 149 | 143 | ||
| 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); |
| 152 | 146 | ||
| 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); |
| 156 | 150 | ||
| 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 | |||
| 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 | const std = @import("std"); | 225 | const std = @import("std"); |