| ... | ... | @@ -71,16 +71,18 @@ pub const Tag = struct { |
| 71 | 71 | |
| 72 | 72 | pub fn decode(reader: *std.Io.Reader) !Tag { |
| 73 | 73 | const tag1: FirstTag = @bitCast(try reader.takeByte()); |
| 74 | | var number: u14 = tag1.number; |
| 75 | | |
| 76 | | if (tag1.number == 31) { |
| 77 | | const tag2: NextTag = @bitCast(try reader.takeByte()); |
| 78 | | number = tag2.number; |
| 79 | | if (tag2.continues) { |
| 80 | | const tag3: NextTag = @bitCast(try reader.takeByte()); |
| 81 | | number = (number << 7) + tag3.number; |
| 82 | | if (tag3.continues) return error.EndOfStream; |
| 83 | | } |
| 74 | var number: std.meta.Tag(Tag.Number) = tag1.number; |
| 75 | |
| 76 | if (tag1.number == high_tag_marker) { |
| 77 | number = 0; |
| 78 | for (0..max_continuations) |i| { |
| 79 | const next: NextTag = @bitCast(try reader.takeByte()); |
| 80 | if (i == 0 and next.number == 0) return error.InvalidEncoding; |
| 81 | number = std.math.shlExact(@TypeOf(number), number, 7) catch return error.InvalidEncoding; |
| 82 | number |= next.number; |
| 83 | if (!next.continues) break; |
| 84 | } else return error.InvalidEncoding; |
| 85 | if (number < high_tag_marker) return error.InvalidEncoding; |
| 84 | 86 | } |
| 85 | 87 | |
| 86 | 88 | return Tag{ |
| ... | ... | @@ -90,40 +92,51 @@ pub const Tag = struct { |
| 90 | 92 | }; |
| 91 | 93 | } |
| 92 | 94 | |
| 93 | | pub fn encode(self: Tag, writer: *std.Io.Writer) @TypeOf(writer).Error!void { |
| 94 | | var tag1 = FirstTag{ |
| 95 | pub fn encodeToSlice(self: Tag, buf: *[max_encoded_len]u8) []const u8 { |
| 96 | const n = @intFromEnum(self.number); |
| 97 | var tag1: FirstTag = .{ |
| 95 | 98 | .number = undefined, |
| 96 | 99 | .constructed = self.constructed, |
| 97 | 100 | .class = self.class, |
| 98 | 101 | }; |
| 99 | 102 | |
| 100 | | var buffer: [3]u8 = undefined; |
| 101 | | var writer2: std.Io.Writer = .init(&buffer); |
| 103 | if (n < high_tag_marker) { |
| 104 | tag1.number = @intCast(n); |
| 105 | buf[0] = @bitCast(tag1); |
| 106 | return buf[0..1]; |
| 107 | } |
| 102 | 108 | |
| 103 | | switch (@intFromEnum(self.number)) { |
| 104 | | 0...std.math.maxInt(u5) => |n| { |
| 105 | | tag1.number = @intCast(n); |
| 106 | | writer2.writeByte(@bitCast(tag1)) catch unreachable; |
| 107 | | }, |
| 108 | | std.math.maxInt(u5) + 1...std.math.maxInt(u7) => |n| { |
| 109 | | tag1.number = 15; |
| 110 | | const tag2 = NextTag{ .number = @intCast(n), .continues = false }; |
| 111 | | writer2.writeByte(@bitCast(tag1)) catch unreachable; |
| 112 | | writer2.writeByte(@bitCast(tag2)) catch unreachable; |
| 113 | | }, |
| 114 | | else => |n| { |
| 115 | | tag1.number = 15; |
| 116 | | const tag2 = NextTag{ .number = @intCast(n >> 7), .continues = true }; |
| 117 | | const tag3 = NextTag{ .number = @truncate(n), .continues = false }; |
| 118 | | writer2.writeByte(@bitCast(tag1)) catch unreachable; |
| 119 | | writer2.writeByte(@bitCast(tag2)) catch unreachable; |
| 120 | | writer2.writeByte(@bitCast(tag3)) catch unreachable; |
| 121 | | }, |
| 109 | tag1.number = high_tag_marker; |
| 110 | buf[0] = @bitCast(tag1); |
| 111 | |
| 112 | const bits_used = @bitSizeOf(@TypeOf(n)) - @clz(n); |
| 113 | const len = std.math.divCeil(usize, bits_used, 7) catch unreachable; |
| 114 | |
| 115 | var remaining = n; |
| 116 | var i = len; |
| 117 | while (i > 0) : (i -= 1) { |
| 118 | buf[i] = @bitCast(NextTag{ |
| 119 | .number = @truncate(remaining), |
| 120 | .continues = i != len, |
| 121 | }); |
| 122 | remaining >>= 7; |
| 122 | 123 | } |
| 124 | return buf[0 .. 1 + len]; |
| 125 | } |
| 123 | 126 | |
| 124 | | _ = try writer.write(writer2.buffered()); |
| 127 | pub fn encode(self: Tag, writer: *std.Io.Writer) std.Io.Writer.Error!void { |
| 128 | var buf: [max_encoded_len]u8 = undefined; |
| 129 | try writer.writeAll(self.encodeToSlice(&buf)); |
| 125 | 130 | } |
| 126 | 131 | |
| 132 | pub const max_encoded_len = 1 + (std.math.divCeil( |
| 133 | comptime_int, |
| 134 | @bitSizeOf(std.meta.Tag(Tag.Number)), |
| 135 | 7, |
| 136 | ) catch unreachable); |
| 137 | const max_continuations = max_encoded_len - 1; |
| 138 | const high_tag_marker = std.math.maxInt(u5); |
| 139 | |
| 127 | 140 | const FirstTag = packed struct(u8) { number: u5, constructed: bool, class: Tag.Class }; |
| 128 | 141 | const NextTag = packed struct(u8) { number: u7, continues: bool }; |
| 129 | 142 | |
| ... | ... | @@ -165,6 +178,24 @@ test Tag { |
| 165 | 178 | try std.testing.expectEqual(Tag.init(@enumFromInt(3), true, .context_specific), t); |
| 166 | 179 | } |
| 167 | 180 | |
| 181 | test "Tag.encode/decode round trip" { |
| 182 | for ([_]u16{ 0, 30, 31, 32, 127, 128, 16383, 16384, 65535 }) |n| { |
| 183 | const tag = Tag.init(@enumFromInt(n), false, .universal); |
| 184 | var buf: [Tag.max_encoded_len]u8 = undefined; |
| 185 | const encoded = tag.encodeToSlice(&buf); |
| 186 | var reader: std.Io.Reader = .fixed(encoded); |
| 187 | try std.testing.expectEqual(tag, try Tag.decode(&reader)); |
| 188 | try std.testing.expectEqual(encoded.len, reader.seek); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | test "Tag.decode rejects non-minimal high-tag form" { |
| 193 | for ([_][]const u8{ &.{ 0x1f, 0x1e }, &.{ 0x1f, 0x80, 0x01 } }) |bytes| { |
| 194 | var reader: std.Io.Reader = .fixed(bytes); |
| 195 | try std.testing.expectError(error.InvalidEncoding, Tag.decode(&reader)); |
| 196 | } |
| 197 | } |
| 198 | |
| 168 | 199 | /// A decoded view. |
| 169 | 200 | pub const Element = struct { |
| 170 | 201 | tag: Tag, |
| ... | ... | @@ -183,13 +214,14 @@ pub const Element = struct { |
| 183 | 214 | } |
| 184 | 215 | }; |
| 185 | 216 | |
| 186 | | pub const DecodeError = error{EndOfStream}; |
| 217 | pub const DecodeError = error{ EndOfStream, InvalidEncoding }; |
| 187 | 218 | |
| 188 | 219 | /// Safely decode a DER/BER/CER element at `index`: |
| 189 | 220 | /// - Ensures length uses shortest form |
| 190 | 221 | /// - Ensures length is within `bytes` |
| 191 | 222 | /// - Ensures length is less than `std.math.maxInt(Index)` |
| 192 | 223 | pub fn decode(bytes: []const u8, index: Index) DecodeError!Element { |
| 224 | if (index > bytes.len) return error.EndOfStream; |
| 193 | 225 | var reader: std.Io.Reader = .fixed(bytes[index..]); |
| 194 | 226 | |
| 195 | 227 | const tag = Tag.decode(&reader) catch |err| switch (err) { |