struct Encoder [src]

Alias for std.crypto.asn1.der.Encoder

A buffered DER encoder. Prefers calling container's fn encodeDer(self: @This(), encoder: *der.Encoder). That function should encode values, lengths, then tags.

Fields

buffer: ArrayListReverse
field_tag: ?FieldTag = nullThe field tag set by a parent container. This is needed because we might visit an implicitly tagged container with a fn encodeDer.

Members

Source

//! A buffered DER encoder. //! //! Prefers calling container's `fn encodeDer(self: @This(), encoder: *der.Encoder)`. //! That function should encode values, lengths, then tags. buffer: ArrayListReverse, /// The field tag set by a parent container. /// This is needed because we might visit an implicitly tagged container with a `fn encodeDer`. field_tag: ?FieldTag = null, pub fn init(allocator: std.mem.Allocator) Encoder { return Encoder{ .buffer = ArrayListReverse.init(allocator) }; } pub fn deinit(self: *Encoder) void { self.buffer.deinit(); } /// Encode any value. pub fn any(self: *Encoder, val: anytype) !void { const T = @TypeOf(val); try self.anyTag(Tag.fromZig(T), val); } 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 start = self.buffer.data.len; const merged_tag = self.mergedTag(tag_); switch (@typeInfo(T)) { .@"struct" => |info| { inline for (0..info.fields.len) |i| { const f = info.fields[info.fields.len - i - 1]; const field_val = @field(val, f.name); const field_tag = FieldTag.fromContainer(T, f.name); // > The encoding of a set value or sequence value shall not include an encoding for any // > component value which is equal to its default value. const is_default = if (f.is_comptime) false else if (f.default_value_ptr) |v| brk: { const default_val: *const f.type = @alignCast(@ptrCast(v)); break :brk std.mem.eql(u8, std.mem.asBytes(default_val), std.mem.asBytes(&field_val)); } else false; if (!is_default) { const start2 = self.buffer.data.len; self.field_tag = field_tag; // will merge with self.field_tag. // may mutate self.field_tag. try self.anyTag(Tag.fromZig(f.type), field_val); if (field_tag) |ft| { if (ft.explicit) { try self.length(self.buffer.data.len - start2); try self.tag(ft.toTag()); self.field_tag = null; } } } } }, .bool => try self.buffer.prependSlice(&[_]u8{if (val) 0xff else 0}), .int => try self.int(T, val), .@"enum" => |e| { if (@hasDecl(T, "oids")) { return self.any(T.oids.enumToOid(val)); } else { try self.int(e.tag_type, @intFromEnum(val)); } }, .optional => if (val) |v| return try self.anyTag(tag_, v), .null => {}, else => @compileError("cannot encode type " ++ @typeName(T)), } try self.length(self.buffer.data.len - start); try self.tag(merged_tag); } /// Encode a tag. pub fn tag(self: *Encoder, tag_: Tag) !void { const t = self.mergedTag(tag_); try t.encode(self.writer()); } fn mergedTag(self: *Encoder, tag_: Tag) Tag { var res = tag_; if (self.field_tag) |ft| { if (!ft.explicit) { res.number = @enumFromInt(ft.number); res.class = ft.class; } } return res; } /// 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; } /// Encode a tag and length-prefixed bytes. pub fn tagBytes(self: *Encoder, tag_: Tag, bytes: []const u8) !void { try self.buffer.prependSlice(bytes); try self.length(bytes.len); 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(); } 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 = std.meta.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); } test int { const allocator = std.testing.allocator; var encoder = Encoder.init(allocator); defer encoder.deinit(); try encoder.int(u8, 0); try std.testing.expectEqualSlices(u8, &[_]u8{0}, encoder.buffer.data); encoder.buffer.clearAndFree(); try encoder.int(u16, 0x00ff); try std.testing.expectEqualSlices(u8, &[_]u8{0xff}, encoder.buffer.data); encoder.buffer.clearAndFree(); try encoder.int(u32, 0xffff); try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0xff, 0xff }, encoder.buffer.data); } const std = @import("std"); const Oid = @import("../Oid.zig"); const asn1 = @import("../../asn1.zig"); const ArrayListReverse = @import("./ArrayListReverse.zig"); const Tag = asn1.Tag; const FieldTag = asn1.FieldTag; const Encoder = @This();