| ... | @@ -102,14 +102,28 @@ pub const Base64Encoder = struct { | ... | @@ -102,14 +102,28 @@ pub const Base64Encoder = struct { |
| 102 | | 102 | |
| 103 | var idx: usize = 0; | 103 | var idx: usize = 0; |
| 104 | var out_idx: usize = 0; | 104 | var out_idx: usize = 0; |
| 105 | while (idx + 2 < source.len) : (idx += 3) { | 105 | while (idx + 15 < source.len) : (idx += 12) { |
| | 106 | const bits = std.mem.readIntBig(u128, source[idx..][0..16]); |
| | 107 | inline for (0..16) |i| { |
| | 108 | dest[out_idx + i] = encoder.alphabet_chars[@truncate((bits >> (122 - i * 6)) & 0x3f)]; |
| | 109 | } |
| | 110 | out_idx += 16; |
| | 111 | } |
| | 112 | while (idx + 3 < source.len) : (idx += 3) { |
| | 113 | const bits = std.mem.readIntBig(u32, source[idx..][0..4]); |
| | 114 | dest[out_idx] = encoder.alphabet_chars[(bits >> 26) & 0x3f]; |
| | 115 | dest[out_idx + 1] = encoder.alphabet_chars[(bits >> 20) & 0x3f]; |
| | 116 | dest[out_idx + 2] = encoder.alphabet_chars[(bits >> 14) & 0x3f]; |
| | 117 | dest[out_idx + 3] = encoder.alphabet_chars[(bits >> 8) & 0x3f]; |
| | 118 | out_idx += 4; |
| | 119 | } |
| | 120 | if (idx + 2 < source.len) { |
| 106 | dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; | 121 | dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; |
| 107 | dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; | 122 | dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; |
| 108 | dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2 | (source[idx + 2] >> 6)]; | 123 | dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2 | (source[idx + 2] >> 6)]; |
| 109 | dest[out_idx + 3] = encoder.alphabet_chars[source[idx + 2] & 0x3f]; | 124 | dest[out_idx + 3] = encoder.alphabet_chars[source[idx + 2] & 0x3f]; |
| 110 | out_idx += 4; | 125 | out_idx += 4; |
| 111 | } | 126 | } else if (idx + 1 < source.len) { |
| 112 | if (idx + 1 < source.len) { | | |
| 113 | dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; | 127 | dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; |
| 114 | dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; | 128 | dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; |
| 115 | dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2]; | 129 | dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2]; |
| ... | @@ -130,15 +144,18 @@ pub const Base64Encoder = struct { | ... | @@ -130,15 +144,18 @@ pub const Base64Encoder = struct { |
| 130 | | 144 | |
| 131 | pub const Base64Decoder = struct { | 145 | pub const Base64Decoder = struct { |
| 132 | const invalid_char: u8 = 0xff; | 146 | const invalid_char: u8 = 0xff; |
| | 147 | const invalid_char_tst: u32 = 0xff000000; |
| 133 | | 148 | |
| 134 | /// e.g. 'A' => 0. | 149 | /// e.g. 'A' => 0. |
| 135 | /// `invalid_char` for any value not in the 64 alphabet chars. | 150 | /// `invalid_char` for any value not in the 64 alphabet chars. |
| 136 | char_to_index: [256]u8, | 151 | char_to_index: [256]u8, |
| | 152 | fast_char_to_index: [4][256]u32, |
| 137 | pad_char: ?u8, | 153 | pad_char: ?u8, |
| 138 | | 154 | |
| 139 | pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { | 155 | pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { |
| 140 | var result = Base64Decoder{ | 156 | var result = Base64Decoder{ |
| 141 | .char_to_index = [_]u8{invalid_char} ** 256, | 157 | .char_to_index = [_]u8{invalid_char} ** 256, |
| | 158 | .fast_char_to_index = .{[_]u32{invalid_char_tst} ** 256} ** 4, |
| 142 | .pad_char = pad_char, | 159 | .pad_char = pad_char, |
| 143 | }; | 160 | }; |
| 144 | | 161 | |
| ... | @@ -147,6 +164,12 @@ pub const Base64Decoder = struct { | ... | @@ -147,6 +164,12 @@ pub const Base64Decoder = struct { |
| 147 | assert(!char_in_alphabet[c]); | 164 | assert(!char_in_alphabet[c]); |
| 148 | assert(pad_char == null or c != pad_char.?); | 165 | assert(pad_char == null or c != pad_char.?); |
| 149 | | 166 | |
| | 167 | const ci = @as(u32, @intCast(i)); |
| | 168 | result.fast_char_to_index[0][c] = ci << 2; |
| | 169 | result.fast_char_to_index[1][c] = (ci >> 4) | ((ci & 0x0f) << 12); |
| | 170 | result.fast_char_to_index[2][c] = ((ci & 0x3) << 22) | ((ci & 0x3c) << 6); |
| | 171 | result.fast_char_to_index[3][c] = ci << 16; |
| | 172 | |
| 150 | result.char_to_index[c] = @as(u8, @intCast(i)); | 173 | result.char_to_index[c] = @as(u8, @intCast(i)); |
| 151 | char_in_alphabet[c] = true; | 174 | char_in_alphabet[c] = true; |
| 152 | } | 175 | } |
| ... | @@ -184,11 +207,39 @@ pub const Base64Decoder = struct { | ... | @@ -184,11 +207,39 @@ pub const Base64Decoder = struct { |
| 184 | /// invalid padding results in error.InvalidPadding. | 207 | /// invalid padding results in error.InvalidPadding. |
| 185 | pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { | 208 | pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { |
| 186 | if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; | 209 | if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; |
| | 210 | var dest_idx: usize = 0; |
| | 211 | var fast_src_idx: usize = 0; |
| 187 | var acc: u12 = 0; | 212 | var acc: u12 = 0; |
| 188 | var acc_len: u4 = 0; | 213 | var acc_len: u4 = 0; |
| 189 | var dest_idx: usize = 0; | | |
| 190 | var leftover_idx: ?usize = null; | 214 | var leftover_idx: ?usize = null; |
| 191 | for (source, 0..) |c, src_idx| { | 215 | while (fast_src_idx + 16 < source.len and dest_idx + 15 < dest.len) : ({ |
| | 216 | fast_src_idx += 16; |
| | 217 | dest_idx += 12; |
| | 218 | }) { |
| | 219 | var bits: u128 = 0; |
| | 220 | inline for (0..4) |i| { |
| | 221 | var new_bits: u128 = decoder.fast_char_to_index[0][source[fast_src_idx + i * 4]]; |
| | 222 | new_bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1 + i * 4]]; |
| | 223 | new_bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2 + i * 4]]; |
| | 224 | new_bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3 + i * 4]]; |
| | 225 | if ((new_bits & invalid_char_tst) != 0) return error.InvalidCharacter; |
| | 226 | bits |= (new_bits << (24 * i)); |
| | 227 | } |
| | 228 | std.mem.writeIntLittle(u128, dest[dest_idx..][0..16], bits); |
| | 229 | } |
| | 230 | while (fast_src_idx + 4 < source.len and dest_idx + 3 < dest.len) : ({ |
| | 231 | fast_src_idx += 4; |
| | 232 | dest_idx += 3; |
| | 233 | }) { |
| | 234 | var bits = decoder.fast_char_to_index[0][source[fast_src_idx]]; |
| | 235 | bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1]]; |
| | 236 | bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2]]; |
| | 237 | bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3]]; |
| | 238 | if ((bits & invalid_char_tst) != 0) return error.InvalidCharacter; |
| | 239 | std.mem.writeIntLittle(u32, dest[dest_idx..][0..4], bits); |
| | 240 | } |
| | 241 | var remaining = source[fast_src_idx..]; |
| | 242 | for (remaining, fast_src_idx..) |c, src_idx| { |
| 192 | const d = decoder.char_to_index[c]; | 243 | const d = decoder.char_to_index[c]; |
| 193 | if (d == invalid_char) { | 244 | if (d == invalid_char) { |
| 194 | if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; | 245 | if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; |
| ... | @@ -338,6 +389,10 @@ fn testBase64() !void { | ... | @@ -338,6 +389,10 @@ fn testBase64() !void { |
| 338 | try testAllApis(codecs, "foob", "Zm9vYg=="); | 389 | try testAllApis(codecs, "foob", "Zm9vYg=="); |
| 339 | try testAllApis(codecs, "fooba", "Zm9vYmE="); | 390 | try testAllApis(codecs, "fooba", "Zm9vYmE="); |
| 340 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); | 391 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); |
| | 392 | try testAllApis(codecs, "foobarfoobarfoo", "Zm9vYmFyZm9vYmFyZm9v"); |
| | 393 | try testAllApis(codecs, "foobarfoobarfoob", "Zm9vYmFyZm9vYmFyZm9vYg=="); |
| | 394 | try testAllApis(codecs, "foobarfoobarfooba", "Zm9vYmFyZm9vYmFyZm9vYmE="); |
| | 395 | try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy"); |
| 341 | | 396 | |
| 342 | try testDecodeIgnoreSpace(codecs, "", " "); | 397 | try testDecodeIgnoreSpace(codecs, "", " "); |
| 343 | try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); | 398 | try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); |
| ... | @@ -357,11 +412,23 @@ fn testBase64() !void { | ... | @@ -357,11 +412,23 @@ fn testBase64() !void { |
| 357 | try testError(codecs, "A/==", error.InvalidPadding); | 412 | try testError(codecs, "A/==", error.InvalidPadding); |
| 358 | try testError(codecs, "A===", error.InvalidPadding); | 413 | try testError(codecs, "A===", error.InvalidPadding); |
| 359 | try testError(codecs, "====", error.InvalidPadding); | 414 | try testError(codecs, "====", error.InvalidPadding); |
| | 415 | try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter); |
| | 416 | try testError(codecs, "Zm9vYmFyZm9vYmFyAA=A", error.InvalidPadding); |
| | 417 | try testError(codecs, "Zm9vYmFyZm9vYmFyAA/=", error.InvalidPadding); |
| | 418 | try testError(codecs, "Zm9vYmFyZm9vYmFyA/==", error.InvalidPadding); |
| | 419 | try testError(codecs, "Zm9vYmFyZm9vYmFyA===", error.InvalidPadding); |
| | 420 | try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter); |
| | 421 | try testError(codecs, "Zm9vYmFyZm9vAA=A", error.InvalidPadding); |
| | 422 | try testError(codecs, "Zm9vYmFyZm9vAA/=", error.InvalidPadding); |
| | 423 | try testError(codecs, "Zm9vYmFyZm9vA/==", error.InvalidPadding); |
| | 424 | try testError(codecs, "Zm9vYmFyZm9vA===", error.InvalidPadding); |
| 360 | | 425 | |
| 361 | try testNoSpaceLeftError(codecs, "AA=="); | 426 | try testNoSpaceLeftError(codecs, "AA=="); |
| 362 | try testNoSpaceLeftError(codecs, "AAA="); | 427 | try testNoSpaceLeftError(codecs, "AAA="); |
| 363 | try testNoSpaceLeftError(codecs, "AAAA"); | 428 | try testNoSpaceLeftError(codecs, "AAAA"); |
| 364 | try testNoSpaceLeftError(codecs, "AAAAAA=="); | 429 | try testNoSpaceLeftError(codecs, "AAAAAA=="); |
| | 430 | |
| | 431 | try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA"); |
| 365 | } | 432 | } |
| 366 | | 433 | |
| 367 | fn testBase64UrlSafeNoPad() !void { | 434 | fn testBase64UrlSafeNoPad() !void { |
| ... | @@ -374,6 +441,7 @@ fn testBase64UrlSafeNoPad() !void { | ... | @@ -374,6 +441,7 @@ fn testBase64UrlSafeNoPad() !void { |
| 374 | try testAllApis(codecs, "foob", "Zm9vYg"); | 441 | try testAllApis(codecs, "foob", "Zm9vYg"); |
| 375 | try testAllApis(codecs, "fooba", "Zm9vYmE"); | 442 | try testAllApis(codecs, "fooba", "Zm9vYmE"); |
| 376 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); | 443 | try testAllApis(codecs, "foobar", "Zm9vYmFy"); |
| | 444 | try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy"); |
| 377 | | 445 | |
| 378 | try testDecodeIgnoreSpace(codecs, "", " "); | 446 | try testDecodeIgnoreSpace(codecs, "", " "); |
| 379 | try testDecodeIgnoreSpace(codecs, "f", "Z g "); | 447 | try testDecodeIgnoreSpace(codecs, "f", "Z g "); |
| ... | @@ -392,11 +460,15 @@ fn testBase64UrlSafeNoPad() !void { | ... | @@ -392,11 +460,15 @@ fn testBase64UrlSafeNoPad() !void { |
| 392 | try testError(codecs, "A/==", error.InvalidCharacter); | 460 | try testError(codecs, "A/==", error.InvalidCharacter); |
| 393 | try testError(codecs, "A===", error.InvalidCharacter); | 461 | try testError(codecs, "A===", error.InvalidCharacter); |
| 394 | try testError(codecs, "====", error.InvalidCharacter); | 462 | try testError(codecs, "====", error.InvalidCharacter); |
| | 463 | try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter); |
| | 464 | try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter); |
| 395 | | 465 | |
| 396 | try testNoSpaceLeftError(codecs, "AA"); | 466 | try testNoSpaceLeftError(codecs, "AA"); |
| 397 | try testNoSpaceLeftError(codecs, "AAA"); | 467 | try testNoSpaceLeftError(codecs, "AAA"); |
| 398 | try testNoSpaceLeftError(codecs, "AAAA"); | 468 | try testNoSpaceLeftError(codecs, "AAAA"); |
| 399 | try testNoSpaceLeftError(codecs, "AAAAAA"); | 469 | try testNoSpaceLeftError(codecs, "AAAAAA"); |
| | 470 | |
| | 471 | try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA"); |
| 400 | } | 472 | } |
| 401 | | 473 | |
| 402 | fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { | 474 | fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { |
| ... | @@ -457,3 +529,12 @@ fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { | ... | @@ -457,3 +529,12 @@ fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { |
| 457 | return error.ExpectedError; | 529 | return error.ExpectedError; |
| 458 | } else |err| if (err != error.NoSpaceLeft) return err; | 530 | } else |err| if (err != error.NoSpaceLeft) return err; |
| 459 | } | 531 | } |
| | 532 | |
| | 533 | fn testFourBytesDestNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { |
| | 534 | const decoder_ignore_space = codecs.decoderWithIgnore(" "); |
| | 535 | var buffer: [0x100]u8 = undefined; |
| | 536 | var decoded = buffer[0..4]; |
| | 537 | if (decoder_ignore_space.decode(decoded, encoded)) |_| { |
| | 538 | return error.ExpectedError; |
| | 539 | } else |err| if (err != error.NoSpaceLeft) return err; |
| | 540 | } |