master
1//! Globally unique hierarchical identifier made of a sequence of integers.
2//!
3//! Commonly used to identify standards, algorithms, certificate extensions,
4//! organizations, or policy documents.
5encoded: []const u8,
6
7pub const InitError = std.fmt.ParseIntError || error{MissingPrefix} || std.Io.Writer.Error;
8
9pub fn fromDot(dot_notation: []const u8, out: []u8) InitError!Oid {
10 var split = std.mem.splitScalar(u8, dot_notation, '.');
11 const first_str = split.next() orelse return error.MissingPrefix;
12 const second_str = split.next() orelse return error.MissingPrefix;
13
14 const first = try std.fmt.parseInt(u8, first_str, 10);
15 const second = try std.fmt.parseInt(u8, second_str, 10);
16
17 var writer: std.Io.Writer = .fixed(out);
18
19 try writer.writeByte(first * 40 + second);
20
21 var i: usize = 1;
22 while (split.next()) |s| {
23 var parsed = try std.fmt.parseUnsigned(Arc, s, 10);
24 const n_bytes = if (parsed == 0) 0 else std.math.log(Arc, encoding_base, parsed);
25
26 for (0..n_bytes) |j| {
27 const place = std.math.pow(Arc, encoding_base, n_bytes - @as(Arc, @intCast(j)));
28 const digit: u8 = @intCast(@divFloor(parsed, place));
29
30 try writer.writeByte(digit | 0x80);
31 parsed -= digit * place;
32
33 i += 1;
34 }
35 try writer.writeByte(@intCast(parsed));
36 i += 1;
37 }
38
39 return .{ .encoded = writer.buffered() };
40}
41
42test fromDot {
43 var buf: [256]u8 = undefined;
44 for (test_cases) |t| {
45 const actual = try fromDot(t.dot_notation, &buf);
46 try std.testing.expectEqualSlices(u8, t.encoded, actual.encoded);
47 }
48}
49
50pub fn toDot(self: Oid, writer: anytype) @TypeOf(writer).Error!void {
51 const encoded = self.encoded;
52 const first = @divTrunc(encoded[0], 40);
53 const second = encoded[0] - first * 40;
54 try writer.print("{d}.{d}", .{ first, second });
55
56 var i: usize = 1;
57 while (i != encoded.len) {
58 const n_bytes: usize = brk: {
59 var res: usize = 1;
60 var j: usize = i;
61 while (encoded[j] & 0x80 != 0) {
62 res += 1;
63 j += 1;
64 }
65 break :brk res;
66 };
67
68 var n: usize = 0;
69 for (0..n_bytes) |j| {
70 const place = std.math.pow(usize, encoding_base, n_bytes - j - 1);
71 n += place * (encoded[i] & 0b01111111);
72 i += 1;
73 }
74 try writer.print(".{d}", .{n});
75 }
76}
77
78test toDot {
79 var buf: [256]u8 = undefined;
80
81 for (test_cases) |t| {
82 var stream: std.Io.Writer = .fixed(&buf);
83 try toDot(Oid{ .encoded = t.encoded }, &stream);
84 try std.testing.expectEqualStrings(t.dot_notation, stream.written());
85 }
86}
87
88const TestCase = struct {
89 encoded: []const u8,
90 dot_notation: []const u8,
91
92 pub fn init(comptime hex: []const u8, dot_notation: []const u8) TestCase {
93 return .{ .encoded = &hexToBytes(hex), .dot_notation = dot_notation };
94 }
95};
96
97const test_cases = [_]TestCase{
98 // https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier
99 TestCase.init("2b0601040182371514", "1.3.6.1.4.1.311.21.20"),
100 // https://luca.ntop.org/Teaching/Appunti/asn1.html
101 TestCase.init("2a864886f70d", "1.2.840.113549"),
102 // https://www.sysadmins.lv/blog-en/how-to-encode-object-identifier-to-an-asn1-der-encoded-string.aspx
103 TestCase.init("2a868d20", "1.2.100000"),
104 TestCase.init("2a864886f70d01010b", "1.2.840.113549.1.1.11"),
105 TestCase.init("2b6570", "1.3.101.112"),
106};
107
108pub const asn1_tag = asn1.Tag.init(.oid, false, .universal);
109
110pub fn decodeDer(decoder: *der.Decoder) !Oid {
111 const ele = try decoder.element(asn1_tag.toExpected());
112 return Oid{ .encoded = decoder.view(ele) };
113}
114
115pub fn encodeDer(self: Oid, encoder: *der.Encoder) !void {
116 try encoder.tagBytes(asn1_tag, self.encoded);
117}
118
119fn encodedLen(dot_notation: []const u8) usize {
120 var buf: [256]u8 = undefined;
121 const oid = fromDot(dot_notation, &buf) catch unreachable;
122 return oid.encoded.len;
123}
124
125/// Returns encoded bytes of OID.
126fn encodeComptime(comptime dot_notation: []const u8) [encodedLen(dot_notation)]u8 {
127 @setEvalBranchQuota(4000);
128 comptime var buf: [256]u8 = undefined;
129 const oid = comptime fromDot(dot_notation, &buf) catch unreachable;
130 return oid.encoded[0..oid.encoded.len].*;
131}
132
133test encodeComptime {
134 try std.testing.expectEqual(
135 hexToBytes("2b0601040182371514"),
136 comptime encodeComptime("1.3.6.1.4.1.311.21.20"),
137 );
138}
139
140pub fn fromDotComptime(comptime dot_notation: []const u8) Oid {
141 const tmp = comptime encodeComptime(dot_notation);
142 return Oid{ .encoded = &tmp };
143}
144
145/// Maps of:
146/// - Oid -> enum
147/// - Enum -> oid
148pub fn StaticMap(comptime Enum: type) type {
149 const enum_info = @typeInfo(Enum).@"enum";
150 const EnumToOid = std.EnumArray(Enum, []const u8);
151 const ReturnType = struct {
152 oid_to_enum: std.StaticStringMap(Enum),
153 enum_to_oid: EnumToOid,
154
155 pub fn oidToEnum(self: @This(), encoded: []const u8) ?Enum {
156 return self.oid_to_enum.get(encoded);
157 }
158
159 pub fn enumToOid(self: @This(), value: Enum) Oid {
160 const bytes = self.enum_to_oid.get(value);
161 return .{ .encoded = bytes };
162 }
163 };
164
165 return struct {
166 pub fn initComptime(comptime key_pairs: anytype) ReturnType {
167 const struct_info = @typeInfo(@TypeOf(key_pairs)).@"struct";
168 const error_msg = "Each field of '" ++ @typeName(Enum) ++ "' must map to exactly one OID";
169 if (!enum_info.is_exhaustive or enum_info.fields.len != struct_info.fields.len) {
170 @compileError(error_msg);
171 }
172
173 comptime var enum_to_oid = EnumToOid.initUndefined();
174
175 const KeyPair = struct { []const u8, Enum };
176 comptime var static_key_pairs: [enum_info.fields.len]KeyPair = undefined;
177
178 comptime for (enum_info.fields, 0..) |f, i| {
179 if (!@hasField(@TypeOf(key_pairs), f.name)) {
180 @compileError("Field '" ++ f.name ++ "' missing Oid.StaticMap entry");
181 }
182 const encoded = &encodeComptime(@field(key_pairs, f.name));
183 const tag: Enum = @enumFromInt(f.value);
184 static_key_pairs[i] = .{ encoded, tag };
185 enum_to_oid.set(tag, encoded);
186 };
187
188 const oid_to_enum = std.StaticStringMap(Enum).initComptime(static_key_pairs);
189 if (oid_to_enum.values().len != enum_info.fields.len) @compileError(error_msg);
190
191 return ReturnType{ .oid_to_enum = oid_to_enum, .enum_to_oid = enum_to_oid };
192 }
193 };
194}
195
196/// Strictly for testing.
197fn hexToBytes(comptime hex: []const u8) [hex.len / 2]u8 {
198 var res: [hex.len / 2]u8 = undefined;
199 _ = std.fmt.hexToBytes(&res, hex) catch unreachable;
200 return res;
201}
202
203const std = @import("std");
204const Oid = @This();
205const Arc = u32;
206const encoding_base = 128;
207const Allocator = std.mem.Allocator;
208const der = @import("der.zig");
209const asn1 = @import("../asn1.zig");