zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit af1f91cadf0a2b04ea099334fe2008e526d4080b (tree)
parent 87e0dc7dea6153d7b34c3b8d626316ca7d5ce787
Author: Frank Denis <github@pureftpd.org>
Date:   Sun,  3 May 2026 17:29:44 +0200

std.crypto.codecs.asn1.der.Encoder: rewrite with prependBytes

Drop the writer wrapper since ArrayListReverse no longer exposes one,
and use prependBytes consistently.

Also rewrite length encoding so the high-bit length-of-length byte is
placed last as expected by X.690.

And always derive the leading-zero pad from the sign bit of the
first encoded byte.

Diffstat:
Mlib/std/crypto/codecs/asn1.zig | 4++--
Mlib/std/crypto/codecs/asn1/der/Encoder.zig | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
2 files changed, 103 insertions(+), 40 deletions(-)

diff --git a/lib/std/crypto/codecs/asn1.zig b/lib/std/crypto/codecs/asn1.zig @@ -327,8 +327,8 @@ pub const BitString = struct { } pub fn encodeDer(self: BitString, encoder: *der.Encoder) !void { - try encoder.writer().writeAll(self.bytes); - try encoder.writer().writeByte(self.right_padding); + try encoder.prependBytes(self.bytes); + try encoder.prependBytes(&.{self.right_padding}); try encoder.length(self.bytes.len + 1); try encoder.tag(asn1_tag); } diff --git a/lib/std/crypto/codecs/asn1/der/Encoder.zig b/lib/std/crypto/codecs/asn1/der/Encoder.zig @@ -24,6 +24,7 @@ pub fn any(self: *Encoder, val: anytype) !void { fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { const T = @TypeOf(val); if (std.meta.hasFn(T, "encodeDer")) return try val.encodeDer(self); + const outer_field_tag = self.field_tag; const start = self.buffer.data.len; const merged_tag = self.mergedTag(tag_); @@ -42,8 +43,9 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { const is_default = if (f_attrs.@"comptime") false else if (f_attrs.defaultValue(f_type)) |default_val| brk: { break :brk std.mem.eql(u8, std.mem.asBytes(&default_val), std.mem.asBytes(&field_val)); } else false; + const is_null_optional = if (@typeInfo(f.type) == .optional) field_val == null else false; - if (!is_default) { + if (!is_default and !is_null_optional) { const start2 = self.buffer.data.len; self.field_tag = field_tag; // will merge with self.field_tag. @@ -58,6 +60,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { } } } + self.field_tag = outer_field_tag; }, .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}), .int => try self.int(T, val), @@ -68,7 +71,7 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { try self.int(e.tag_type, @intFromEnum(val)); } }, - .optional => if (val) |v| return try self.anyTag(tag_, v), + .optional => if (val) |v| return try self.anyTag(tag_, v) else return, .null => {}, else => @compileError("cannot encode type " ++ @typeName(T)), } @@ -80,7 +83,8 @@ fn anyTag(self: *Encoder, tag_: Tag, val: anytype) !void { /// Encode a tag. pub fn tag(self: *Encoder, tag_: Tag) !void { const t = self.mergedTag(tag_); - try t.encode(self.writer()); + var buf: [Tag.max_encoded_len]u8 = undefined; + try self.buffer.prependSlice(t.encodeToSlice(&buf)); } fn mergedTag(self: *Encoder, tag_: Tag) Tag { @@ -96,19 +100,14 @@ fn mergedTag(self: *Encoder, tag_: Tag) Tag { /// Encode a length. pub fn length(self: *Encoder, len: usize) !void { - const writer_ = self.writer(); - if (len < 128) { - try writer_.writeInt(u8, @intCast(len), .big); - return; - } - inline for ([_]type{ u8, u16, u32 }) |T| { - if (len < std.math.maxInt(T)) { - try writer_.writeInt(T, @intCast(len), .big); - try writer_.writeInt(u8, @sizeOf(T) | 0x80, .big); - return; - } - } - return error.InvalidLength; + if (len < 128) return self.buffer.prependSlice(&.{@intCast(len)}); + const len32 = std.math.cast(u32, len) orelse return error.InvalidLength; + var buf: [@sizeOf(u32) + 1]u8 = undefined; + std.mem.writeInt(u32, buf[1..], len32, .big); + var first: usize = 1; + while (buf[first] == 0) first += 1; + buf[first - 1] = @intCast((buf.len - first) | 0x80); + return self.buffer.prependSlice(buf[first - 1 ..]); } /// Encode a tag and length-prefixed bytes. @@ -118,28 +117,23 @@ pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void { try self.tag(tag_); } -/// Warning: This writer writes backwards. `fn print` will NOT work as expected. -pub fn writer(self: *Encoder) ArrayListReverse.Writer { - return self.buffer.writer(); +/// Write raw bytes. The encoder builds its output back-to-front, so chained +/// calls should be made in reverse of the desired on-wire order. +pub fn prependBytes(self: *Encoder, bytes: []const u8) !void { + return self.buffer.prependSlice(bytes); } fn int(self: *Encoder, comptime T: type, value: T) !void { - const big = std.mem.nativeTo(T, value, .big); - const big_bytes = std.mem.asBytes(&big); - - const bits_needed = @bitSizeOf(T) - @clz(value); - const needs_padding: u1 = if (value == 0) - 1 - else if (bits_needed > 8) brk: { - const RightShift = @Int(.unsigned, @bitSizeOf(@TypeOf(bits_needed)) - 1); - const right_shift: RightShift = @intCast(bits_needed - 9); - break :brk if (value >> right_shift == 0x1ff) 1 else 0; - } else 0; - const bytes_needed = try std.math.divCeil(usize, bits_needed, 8) + needs_padding; - - const writer_ = self.writer(); - for (0..bytes_needed - needs_padding) |i| try writer_.writeByte(big_bytes[big_bytes.len - i - 1]); - if (needs_padding == 1) try writer_.writeByte(0); + const info = @typeInfo(T).int; + const Unsigned = @Int(.unsigned, info.bits); + const pad: u8 = if (info.signedness == .signed and value < 0) 0xff else 0; + var buf: [@sizeOf(Unsigned) + 1]u8 = undefined; + buf[0] = pad; + std.mem.writeInt(Unsigned, buf[1..], @bitCast(value), .big); + + var first: usize = 0; + while (first + 1 < buf.len and buf[first] == pad and (buf[first + 1] ^ pad) & 0x80 == 0) first += 1; + try self.buffer.prependSlice(buf[first..]); } test int { @@ -148,15 +142,84 @@ test int { defer encoder.deinit(); try encoder.int(u8, 0); - try std.testing.expectEqualSlices(u8, &[_]u8{0}, encoder.buffer.data); + try std.testing.expectEqualSlices(u8, &.{0}, encoder.buffer.data); encoder.buffer.clearAndFree(); try encoder.int(u16, 0x00ff); - try std.testing.expectEqualSlices(u8, &[_]u8{0xff}, encoder.buffer.data); + try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data); encoder.buffer.clearAndFree(); try encoder.int(u32, 0xffff); - try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0xff, 0xff }, encoder.buffer.data); + try std.testing.expectEqualSlices(u8, &.{ 0, 0xff, 0xff }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u32, 0x01020304); + try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x02, 0x03, 0x04 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u8, 127); + try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u16, 128); + try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u16, 256); + try std.testing.expectEqualSlices(u8, &.{ 0x01, 0x00 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u8, 128); + try std.testing.expectEqualSlices(u8, &.{ 0, 0x80 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u8, 255); + try std.testing.expectEqualSlices(u8, &.{ 0, 0xff }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(u16, 0x8000); + try std.testing.expectEqualSlices(u8, &.{ 0, 0x80, 0 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(i8, -1); + try std.testing.expectEqualSlices(u8, &.{0xff}, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(i8, -128); + try std.testing.expectEqualSlices(u8, &.{0x80}, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.int(i16, -129); + try std.testing.expectEqualSlices(u8, &.{ 0xff, 0x7f }, encoder.buffer.data); +} + +test length { + const allocator = std.testing.allocator; + var encoder = Encoder.init(allocator); + defer encoder.deinit(); + + try encoder.length(127); + try std.testing.expectEqualSlices(u8, &.{0x7f}, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.length(128); + try std.testing.expectEqualSlices(u8, &.{ 0x81, 0x80 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.length(255); + try std.testing.expectEqualSlices(u8, &.{ 0x81, 0xff }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.length(256); + try std.testing.expectEqualSlices(u8, &.{ 0x82, 0x01, 0x00 }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.length(65535); + try std.testing.expectEqualSlices(u8, &.{ 0x82, 0xff, 0xff }, encoder.buffer.data); + + encoder.buffer.clearAndFree(); + try encoder.length(65536); + try std.testing.expectEqualSlices(u8, &.{ 0x83, 0x01, 0x00, 0x00 }, encoder.buffer.data); } const std = @import("std");