pub const Tag = struct {
number: Number,
/// Whether this ASN.1 type contains other ASN.1 types.
constructed: bool,
class: Class,
/// These values apply to class == .universal.
pub const Number = enum(u16) {
// 0 is reserved by spec
boolean = 1,
integer = 2,
bitstring = 3,
octetstring = 4,
null = 5,
oid = 6,
object_descriptor = 7,
real = 9,
enumerated = 10,
embedded = 11,
string_utf8 = 12,
oid_relative = 13,
time = 14,
// 15 is reserved to mean that the tag is >= 32
sequence = 16,
/// Elements may appear in any order.
set = 17,
string_numeric = 18,
string_printable = 19,
string_teletex = 20,
string_videotex = 21,
string_ia5 = 22,
utc_time = 23,
generalized_time = 24,
string_graphic = 25,
string_visible = 26,
string_general = 27,
string_universal = 28,
string_char = 29,
string_bmp = 30,
date = 31,
time_of_day = 32,
date_time = 33,
duration = 34,
/// IRI = Internationalized Resource Identifier
oid_iri = 35,
oid_iri_relative = 36,
_,
};
pub const Class = enum(u2) {
universal,
application,
context_specific,
private,
};
pub fn init(number: Tag.Number, constructed: bool, class: Tag.Class) Tag {
return .{ .number = number, .constructed = constructed, .class = class };
}
pub fn universal(number: Tag.Number, constructed: bool) Tag {
return .{ .number = number, .constructed = constructed, .class = .universal };
}
pub fn decode(reader: *std.Io.Reader) !Tag {
const tag1: FirstTag = @bitCast(try reader.takeByte());
var number: std.meta.Tag(Tag.Number) = tag1.number;
if (tag1.number == high_tag_marker) {
number = 0;
for (0..max_continuations) |i| {
const next: NextTag = @bitCast(try reader.takeByte());
if (i == 0 and next.number == 0) return error.InvalidEncoding;
number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding;
number |= next.number;
if (!next.continues) break;
} else return error.InvalidEncoding;
if (number < high_tag_marker) return error.InvalidEncoding;
}
return Tag{
.number = @fromBackingInt(@intCast(number)),
.constructed = tag1.constructed,
.class = tag1.class,
};
}
pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 {
const n = @backingInt(self.number);
var tag1: FirstTag = .{
.number = undefined,
.constructed = self.constructed,
.class = self.class,
};
if (n < high_tag_marker) {
tag1.number = @intCast(n);
buf[0] = @bitCast(tag1);
return buf[0..1];
}
tag1.number = high_tag_marker;
buf[0] = @bitCast(tag1);
const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n);
const len = std.math.divCeil(usize, bits_used, 7) catch unreachable;
var remaining = n;
var i = len;
while (i > 0) : (i -= 1) {
buf[i] = @bitCast(NextTag{
.number = @truncate(remaining),
.continues = i != len,
});
remaining >>= 7;
}
return buf[0 .. 1 + len];
}
pub fn encode(self: Tag, writer: *std.Io.Writer) std.Io.Writer.Error!void {
var buf: [max_encoded_len]u8 = undefined;
try writer.writeAll(self.encodeToSlice(&buf));
}
pub const max_encoded_len = 1 + (std.math.divCeil(
comptime_int,
@bitSizeOf(std.meta.Tag(Tag.Number)),
7,
) catch unreachable);
const max_continuations = max_encoded_len - 1;
const high_tag_marker = std.math.maxInt(u5);
const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class };
const NextTag = packed struct(u8) { number: u7, continues: bool };
pub fn toExpected(self: Tag) ExpectedTag {
return ExpectedTag{
.number = self.number,
.constructed = self.constructed,
.class = self.class,
};
}
pub fn fromZig(comptime T: type) Tag {
switch (@typeInfo(T)) {
.@"struct", .@"enum", .@"union" => {
if (@hasDecl(T, "asn1_tag")) return T.asn1_tag;
},
else => {},
}
switch (@typeInfo(T)) {
.@"struct", .@"union" => return universal(.sequence, true),
.bool => return universal(.boolean, false),
.int => return universal(.integer, false),
.@"enum" => |e| {
if (@hasDecl(T, "oids")) return Oid.asn1_tag;
return universal(if (e.mode == .exhaustive) .enumerated else .integer, false);
},
.optional => |o| return fromZig(o.child),
.null => return universal(.null, false),
else => @compileError("cannot map Zig type to asn1_tag " ++ @typeName(T)),
}
}
}