1//! Distinguised Encoding Rules as defined in X.690 and X.691.
2//!
3//! Subset of Basic Encoding Rules (BER) which eliminates flexibility in
4//! an effort to acheive normality. Used in PKI.
5const std = @import("std");
6const asn1 = @import("../asn1.zig");
7
8pub const Decoder = @import("der/Decoder.zig");
9pub const Encoder = @import("der/Encoder.zig");
10
11pub fn decode(comptime T: type, encoded: []const u8) !T {
12 var decoder = Decoder{ .bytes = encoded };
13 const res = try decoder.any(T);
14 std.debug.assert(decoder.index == encoded.len);
15 return res;
16}
17
18/// Caller owns returned memory.
19pub fn encode(allocator: std.mem.Allocator, value: anytype) ![]u8 {
20 var encoder = Encoder.init(allocator);
21 defer encoder.deinit();
22 try encoder.any(value);
23 return try encoder.buffer.toOwnedSlice();
24}
25
26test encode {
27 // https://lapo.it/asn1js/#MAgGAyoDBAIBBA
28 const Value = struct { a: asn1.Oid, b: i32 };
29 const test_case = .{
30 .value = Value{ .a = asn1.Oid.fromDotComptime("1.2.3.4"), .b = 4 },
31 .encoded = &[_]u8{ 0x30, 0x08, 0x06, 0x03, 0x2A, 0x03, 0x04, 0x02, 0x01, 0x04 },
32 };
33 const allocator = std.testing.allocator;
34 const actual = try encode(allocator, test_case.value);
35 defer allocator.free(actual);
36
37 try std.testing.expectEqualSlices(u8, test_case.encoded, actual);
38}
39
40test decode {
41 // https://lapo.it/asn1js/#MAgGAyoDBAIBBA
42 const Value = struct { a: asn1.Oid, b: i32 };
43 const test_case = .{
44 .value = Value{ .a = asn1.Oid.fromDotComptime("1.2.3.4"), .b = 4 },
45 .encoded = &[_]u8{ 0x30, 0x08, 0x06, 0x03, 0x2A, 0x03, 0x04, 0x02, 0x01, 0x04 },
46 };
47 const decoded = try decode(Value, test_case.encoded);
48
49 try std.testing.expectEqualDeep(test_case.value, decoded);
50}
51
52test "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
69test "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
78test "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
94test {
95 _ = Decoder;
96 _ = Encoder;
97}