blob bfcdabae (23185B) - Raw
1 const std = @import("std.zig"); 2 const assert = std.debug.assert; 3 const builtin = @import("builtin"); 4 const testing = std.testing; 5 const mem = std.mem; 6 7 pub const Error = error{ 8 InvalidCharacter, 9 InvalidPadding, 10 NoSpaceLeft, 11 }; 12 13 const decoderWithIgnoreProto = *const fn (ignore: []const u8) Base64DecoderWithIgnore; 14 15 /// Base64 codecs 16 pub const Codecs = struct { 17 alphabet_chars: [64]u8, 18 pad_char: ?u8, 19 decoderWithIgnore: decoderWithIgnoreProto, 20 Encoder: Base64Encoder, 21 Decoder: Base64Decoder, 22 }; 23 24 pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".*; 25 fn standardBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { 26 return Base64DecoderWithIgnore.init(standard_alphabet_chars, '=', ignore); 27 } 28 29 /// Standard Base64 codecs, with padding 30 pub const standard = Codecs{ 31 .alphabet_chars = standard_alphabet_chars, 32 .pad_char = '=', 33 .decoderWithIgnore = standardBase64DecoderWithIgnore, 34 .Encoder = Base64Encoder.init(standard_alphabet_chars, '='), 35 .Decoder = Base64Decoder.init(standard_alphabet_chars, '='), 36 }; 37 38 /// Standard Base64 codecs, without padding 39 pub const standard_no_pad = Codecs{ 40 .alphabet_chars = standard_alphabet_chars, 41 .pad_char = null, 42 .decoderWithIgnore = standardBase64DecoderWithIgnore, 43 .Encoder = Base64Encoder.init(standard_alphabet_chars, null), 44 .Decoder = Base64Decoder.init(standard_alphabet_chars, null), 45 }; 46 47 pub const url_safe_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; 48 fn urlSafeBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { 49 return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, null, ignore); 50 } 51 52 /// URL-safe Base64 codecs, with padding 53 pub const url_safe = Codecs{ 54 .alphabet_chars = url_safe_alphabet_chars, 55 .pad_char = '=', 56 .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, 57 .Encoder = Base64Encoder.init(url_safe_alphabet_chars, '='), 58 .Decoder = Base64Decoder.init(url_safe_alphabet_chars, '='), 59 }; 60 61 /// URL-safe Base64 codecs, without padding 62 pub const url_safe_no_pad = Codecs{ 63 .alphabet_chars = url_safe_alphabet_chars, 64 .pad_char = null, 65 .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, 66 .Encoder = Base64Encoder.init(url_safe_alphabet_chars, null), 67 .Decoder = Base64Decoder.init(url_safe_alphabet_chars, null), 68 }; 69 70 pub const Base64Encoder = struct { 71 alphabet_chars: [64]u8, 72 pad_char: ?u8, 73 74 /// A bunch of assertions, then simply pass the data right through. 75 pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Encoder { 76 assert(alphabet_chars.len == 64); 77 var char_in_alphabet = [_]bool{false} ** 256; 78 for (alphabet_chars) |c| { 79 assert(!char_in_alphabet[c]); 80 assert(pad_char == null or c != pad_char.?); 81 char_in_alphabet[c] = true; 82 } 83 return Base64Encoder{ 84 .alphabet_chars = alphabet_chars, 85 .pad_char = pad_char, 86 }; 87 } 88 89 /// Compute the encoded length 90 pub fn calcSize(encoder: *const Base64Encoder, source_len: usize) usize { 91 if (encoder.pad_char != null) { 92 return @divTrunc(source_len + 2, 3) * 4; 93 } else { 94 const leftover = source_len % 3; 95 return @divTrunc(source_len, 3) * 4 + @divTrunc(leftover * 4 + 2, 3); 96 } 97 } 98 99 /// dest.len must at least be what you get from ::calcSize. 100 pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 { 101 const out_len = encoder.calcSize(source.len); 102 assert(dest.len >= out_len); 103 104 var idx: usize = 0; 105 var out_idx: usize = 0; 106 while (idx + 15 < source.len) : (idx += 12) { 107 const bits = std.mem.readIntBig(u128, source[idx..][0..16]); 108 inline for (0..16) |i| { 109 dest[out_idx + i] = encoder.alphabet_chars[@truncate((bits >> (122 - i * 6)) & 0x3f)]; 110 } 111 out_idx += 16; 112 } 113 while (idx + 3 < source.len) : (idx += 3) { 114 const bits = std.mem.readIntBig(u32, source[idx..][0..4]); 115 dest[out_idx] = encoder.alphabet_chars[(bits >> 26) & 0x3f]; 116 dest[out_idx + 1] = encoder.alphabet_chars[(bits >> 20) & 0x3f]; 117 dest[out_idx + 2] = encoder.alphabet_chars[(bits >> 14) & 0x3f]; 118 dest[out_idx + 3] = encoder.alphabet_chars[(bits >> 8) & 0x3f]; 119 out_idx += 4; 120 } 121 if (idx + 2 < source.len) { 122 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; 123 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; 124 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2 | (source[idx + 2] >> 6)]; 125 dest[out_idx + 3] = encoder.alphabet_chars[source[idx + 2] & 0x3f]; 126 out_idx += 4; 127 } else if (idx + 1 < source.len) { 128 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; 129 dest[out_idx + 1] = encoder.alphabet_chars[((source[idx] & 0x3) << 4) | (source[idx + 1] >> 4)]; 130 dest[out_idx + 2] = encoder.alphabet_chars[(source[idx + 1] & 0xf) << 2]; 131 out_idx += 3; 132 } else if (idx < source.len) { 133 dest[out_idx] = encoder.alphabet_chars[source[idx] >> 2]; 134 dest[out_idx + 1] = encoder.alphabet_chars[(source[idx] & 0x3) << 4]; 135 out_idx += 2; 136 } 137 if (encoder.pad_char) |pad_char| { 138 for (dest[out_idx..out_len]) |*pad| { 139 pad.* = pad_char; 140 } 141 } 142 return dest[0..out_len]; 143 } 144 }; 145 146 pub const Base64Decoder = struct { 147 const invalid_char: u8 = 0xff; 148 const invalid_char_tst: u32 = 0xff000000; 149 150 /// e.g. 'A' => 0. 151 /// `invalid_char` for any value not in the 64 alphabet chars. 152 char_to_index: [256]u8, 153 fast_char_to_index: [4][256]u32, 154 pad_char: ?u8, 155 156 pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { 157 var result = Base64Decoder{ 158 .char_to_index = [_]u8{invalid_char} ** 256, 159 .fast_char_to_index = .{[_]u32{invalid_char_tst} ** 256} ** 4, 160 .pad_char = pad_char, 161 }; 162 163 var char_in_alphabet = [_]bool{false} ** 256; 164 for (alphabet_chars, 0..) |c, i| { 165 assert(!char_in_alphabet[c]); 166 assert(pad_char == null or c != pad_char.?); 167 168 const ci = @as(u32, @intCast(i)); 169 result.fast_char_to_index[0][c] = ci << 2; 170 result.fast_char_to_index[1][c] = (ci >> 4) | ((ci & 0x0f) << 12); 171 result.fast_char_to_index[2][c] = ((ci & 0x3) << 22) | ((ci & 0x3c) << 6); 172 result.fast_char_to_index[3][c] = ci << 16; 173 174 result.char_to_index[c] = @as(u8, @intCast(i)); 175 char_in_alphabet[c] = true; 176 } 177 return result; 178 } 179 180 /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding. 181 /// `InvalidPadding` is returned if the input length is not valid. 182 pub fn calcSizeUpperBound(decoder: *const Base64Decoder, source_len: usize) Error!usize { 183 var result = source_len / 4 * 3; 184 const leftover = source_len % 4; 185 if (decoder.pad_char != null) { 186 if (leftover % 4 != 0) return error.InvalidPadding; 187 } else { 188 if (leftover % 4 == 1) return error.InvalidPadding; 189 result += leftover * 3 / 4; 190 } 191 return result; 192 } 193 194 /// Return the exact decoded size for a slice. 195 /// `InvalidPadding` is returned if the input length is not valid. 196 pub fn calcSizeForSlice(decoder: *const Base64Decoder, source: []const u8) Error!usize { 197 const source_len = source.len; 198 var result = try decoder.calcSizeUpperBound(source_len); 199 if (decoder.pad_char) |pad_char| { 200 if (source_len >= 1 and source[source_len - 1] == pad_char) result -= 1; 201 if (source_len >= 2 and source[source_len - 2] == pad_char) result -= 1; 202 } 203 return result; 204 } 205 206 /// dest.len must be what you get from ::calcSize. 207 /// Invalid characters result in `error.InvalidCharacter`. 208 /// Invalid padding results in `error.InvalidPadding`. 209 pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { 210 if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; 211 var dest_idx: usize = 0; 212 var fast_src_idx: usize = 0; 213 var acc: u12 = 0; 214 var acc_len: u4 = 0; 215 var leftover_idx: ?usize = null; 216 while (fast_src_idx + 16 < source.len and dest_idx + 15 < dest.len) : ({ 217 fast_src_idx += 16; 218 dest_idx += 12; 219 }) { 220 var bits: u128 = 0; 221 inline for (0..4) |i| { 222 var new_bits: u128 = decoder.fast_char_to_index[0][source[fast_src_idx + i * 4]]; 223 new_bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1 + i * 4]]; 224 new_bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2 + i * 4]]; 225 new_bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3 + i * 4]]; 226 if ((new_bits & invalid_char_tst) != 0) return error.InvalidCharacter; 227 bits |= (new_bits << (24 * i)); 228 } 229 std.mem.writeIntLittle(u128, dest[dest_idx..][0..16], bits); 230 } 231 while (fast_src_idx + 4 < source.len and dest_idx + 3 < dest.len) : ({ 232 fast_src_idx += 4; 233 dest_idx += 3; 234 }) { 235 var bits = decoder.fast_char_to_index[0][source[fast_src_idx]]; 236 bits |= decoder.fast_char_to_index[1][source[fast_src_idx + 1]]; 237 bits |= decoder.fast_char_to_index[2][source[fast_src_idx + 2]]; 238 bits |= decoder.fast_char_to_index[3][source[fast_src_idx + 3]]; 239 if ((bits & invalid_char_tst) != 0) return error.InvalidCharacter; 240 std.mem.writeIntLittle(u32, dest[dest_idx..][0..4], bits); 241 } 242 var remaining = source[fast_src_idx..]; 243 for (remaining, fast_src_idx..) |c, src_idx| { 244 const d = decoder.char_to_index[c]; 245 if (d == invalid_char) { 246 if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; 247 leftover_idx = src_idx; 248 break; 249 } 250 acc = (acc << 6) + d; 251 acc_len += 6; 252 if (acc_len >= 8) { 253 acc_len -= 8; 254 dest[dest_idx] = @as(u8, @truncate(acc >> acc_len)); 255 dest_idx += 1; 256 } 257 } 258 if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { 259 return error.InvalidPadding; 260 } 261 if (leftover_idx == null) return; 262 var leftover = source[leftover_idx.?..]; 263 if (decoder.pad_char) |pad_char| { 264 const padding_len = acc_len / 2; 265 var padding_chars: usize = 0; 266 for (leftover) |c| { 267 if (c != pad_char) { 268 return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; 269 } 270 padding_chars += 1; 271 } 272 if (padding_chars != padding_len) return error.InvalidPadding; 273 } 274 } 275 }; 276 277 pub const Base64DecoderWithIgnore = struct { 278 decoder: Base64Decoder, 279 char_is_ignored: [256]bool, 280 281 pub fn init(alphabet_chars: [64]u8, pad_char: ?u8, ignore_chars: []const u8) Base64DecoderWithIgnore { 282 var result = Base64DecoderWithIgnore{ 283 .decoder = Base64Decoder.init(alphabet_chars, pad_char), 284 .char_is_ignored = [_]bool{false} ** 256, 285 }; 286 for (ignore_chars) |c| { 287 assert(result.decoder.char_to_index[c] == Base64Decoder.invalid_char); 288 assert(!result.char_is_ignored[c]); 289 assert(result.decoder.pad_char != c); 290 result.char_is_ignored[c] = true; 291 } 292 return result; 293 } 294 295 /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding. 296 /// `InvalidPadding` is returned if the input length is not valid. 297 pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) Error!usize { 298 var result = source_len / 4 * 3; 299 if (decoder_with_ignore.decoder.pad_char == null) { 300 const leftover = source_len % 4; 301 result += leftover * 3 / 4; 302 } 303 return result; 304 } 305 306 /// Invalid characters that are not ignored result in error.InvalidCharacter. 307 /// Invalid padding results in error.InvalidPadding. 308 /// Decoding more data than can fit in dest results in error.NoSpaceLeft. See also ::calcSizeUpperBound. 309 /// Returns the number of bytes written to dest. 310 pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8) Error!usize { 311 const decoder = &decoder_with_ignore.decoder; 312 var acc: u12 = 0; 313 var acc_len: u4 = 0; 314 var dest_idx: usize = 0; 315 var leftover_idx: ?usize = null; 316 for (source, 0..) |c, src_idx| { 317 if (decoder_with_ignore.char_is_ignored[c]) continue; 318 const d = decoder.char_to_index[c]; 319 if (d == Base64Decoder.invalid_char) { 320 if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; 321 leftover_idx = src_idx; 322 break; 323 } 324 acc = (acc << 6) + d; 325 acc_len += 6; 326 if (acc_len >= 8) { 327 if (dest_idx == dest.len) return error.NoSpaceLeft; 328 acc_len -= 8; 329 dest[dest_idx] = @as(u8, @truncate(acc >> acc_len)); 330 dest_idx += 1; 331 } 332 } 333 if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { 334 return error.InvalidPadding; 335 } 336 const padding_len = acc_len / 2; 337 if (leftover_idx == null) { 338 if (decoder.pad_char != null and padding_len != 0) return error.InvalidPadding; 339 return dest_idx; 340 } 341 var leftover = source[leftover_idx.?..]; 342 if (decoder.pad_char) |pad_char| { 343 var padding_chars: usize = 0; 344 for (leftover) |c| { 345 if (decoder_with_ignore.char_is_ignored[c]) continue; 346 if (c != pad_char) { 347 return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; 348 } 349 padding_chars += 1; 350 } 351 if (padding_chars != padding_len) return error.InvalidPadding; 352 } 353 return dest_idx; 354 } 355 }; 356 357 test "base64" { 358 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; 359 360 @setEvalBranchQuota(8000); 361 try testBase64(); 362 try comptime testAllApis(standard, "comptime", "Y29tcHRpbWU="); 363 } 364 365 test "base64 padding dest overflow" { 366 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; 367 368 const input = "foo"; 369 370 var expect: [128]u8 = undefined; 371 @memset(&expect, 0); 372 _ = url_safe.Encoder.encode(expect[0..url_safe.Encoder.calcSize(input.len)], input); 373 374 var got: [128]u8 = undefined; 375 @memset(&got, 0); 376 _ = url_safe.Encoder.encode(&got, input); 377 378 try std.testing.expectEqualSlices(u8, &expect, &got); 379 } 380 381 test "base64 url_safe_no_pad" { 382 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; 383 384 @setEvalBranchQuota(8000); 385 try testBase64UrlSafeNoPad(); 386 try comptime testAllApis(url_safe_no_pad, "comptime", "Y29tcHRpbWU"); 387 } 388 389 fn testBase64() !void { 390 const codecs = standard; 391 392 try testAllApis(codecs, "", ""); 393 try testAllApis(codecs, "f", "Zg=="); 394 try testAllApis(codecs, "fo", "Zm8="); 395 try testAllApis(codecs, "foo", "Zm9v"); 396 try testAllApis(codecs, "foob", "Zm9vYg=="); 397 try testAllApis(codecs, "fooba", "Zm9vYmE="); 398 try testAllApis(codecs, "foobar", "Zm9vYmFy"); 399 try testAllApis(codecs, "foobarfoobarfoo", "Zm9vYmFyZm9vYmFyZm9v"); 400 try testAllApis(codecs, "foobarfoobarfoob", "Zm9vYmFyZm9vYmFyZm9vYg=="); 401 try testAllApis(codecs, "foobarfoobarfooba", "Zm9vYmFyZm9vYmFyZm9vYmE="); 402 try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy"); 403 404 try testDecodeIgnoreSpace(codecs, "", " "); 405 try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); 406 try testDecodeIgnoreSpace(codecs, "fo", " Zm8="); 407 try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); 408 try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg = = "); 409 try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE="); 410 try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); 411 412 // test getting some api errors 413 try testError(codecs, "A", error.InvalidPadding); 414 try testError(codecs, "AA", error.InvalidPadding); 415 try testError(codecs, "AAA", error.InvalidPadding); 416 try testError(codecs, "A..A", error.InvalidCharacter); 417 try testError(codecs, "AA=A", error.InvalidPadding); 418 try testError(codecs, "AA/=", error.InvalidPadding); 419 try testError(codecs, "A/==", error.InvalidPadding); 420 try testError(codecs, "A===", error.InvalidPadding); 421 try testError(codecs, "====", error.InvalidPadding); 422 try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter); 423 try testError(codecs, "Zm9vYmFyZm9vYmFyAA=A", error.InvalidPadding); 424 try testError(codecs, "Zm9vYmFyZm9vYmFyAA/=", error.InvalidPadding); 425 try testError(codecs, "Zm9vYmFyZm9vYmFyA/==", error.InvalidPadding); 426 try testError(codecs, "Zm9vYmFyZm9vYmFyA===", error.InvalidPadding); 427 try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter); 428 try testError(codecs, "Zm9vYmFyZm9vAA=A", error.InvalidPadding); 429 try testError(codecs, "Zm9vYmFyZm9vAA/=", error.InvalidPadding); 430 try testError(codecs, "Zm9vYmFyZm9vA/==", error.InvalidPadding); 431 try testError(codecs, "Zm9vYmFyZm9vA===", error.InvalidPadding); 432 433 try testNoSpaceLeftError(codecs, "AA=="); 434 try testNoSpaceLeftError(codecs, "AAA="); 435 try testNoSpaceLeftError(codecs, "AAAA"); 436 try testNoSpaceLeftError(codecs, "AAAAAA=="); 437 438 try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA"); 439 } 440 441 fn testBase64UrlSafeNoPad() !void { 442 const codecs = url_safe_no_pad; 443 444 try testAllApis(codecs, "", ""); 445 try testAllApis(codecs, "f", "Zg"); 446 try testAllApis(codecs, "fo", "Zm8"); 447 try testAllApis(codecs, "foo", "Zm9v"); 448 try testAllApis(codecs, "foob", "Zm9vYg"); 449 try testAllApis(codecs, "fooba", "Zm9vYmE"); 450 try testAllApis(codecs, "foobar", "Zm9vYmFy"); 451 try testAllApis(codecs, "foobarfoobarfoobar", "Zm9vYmFyZm9vYmFyZm9vYmFy"); 452 453 try testDecodeIgnoreSpace(codecs, "", " "); 454 try testDecodeIgnoreSpace(codecs, "f", "Z g "); 455 try testDecodeIgnoreSpace(codecs, "fo", " Zm8"); 456 try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); 457 try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg "); 458 try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE"); 459 try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); 460 461 // test getting some api errors 462 try testError(codecs, "A", error.InvalidPadding); 463 try testError(codecs, "AAA=", error.InvalidCharacter); 464 try testError(codecs, "A..A", error.InvalidCharacter); 465 try testError(codecs, "AA=A", error.InvalidCharacter); 466 try testError(codecs, "AA/=", error.InvalidCharacter); 467 try testError(codecs, "A/==", error.InvalidCharacter); 468 try testError(codecs, "A===", error.InvalidCharacter); 469 try testError(codecs, "====", error.InvalidCharacter); 470 try testError(codecs, "Zm9vYmFyZm9vYmFyA..A", error.InvalidCharacter); 471 try testError(codecs, "A..AZm9vYmFyZm9vYmFy", error.InvalidCharacter); 472 473 try testNoSpaceLeftError(codecs, "AA"); 474 try testNoSpaceLeftError(codecs, "AAA"); 475 try testNoSpaceLeftError(codecs, "AAAA"); 476 try testNoSpaceLeftError(codecs, "AAAAAA"); 477 478 try testFourBytesDestNoSpaceLeftError(codecs, "AAAAAAAAAAAAAAAA"); 479 } 480 481 fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { 482 // Base64Encoder 483 { 484 var buffer: [0x100]u8 = undefined; 485 const encoded = codecs.Encoder.encode(&buffer, expected_decoded); 486 try testing.expectEqualSlices(u8, expected_encoded, encoded); 487 } 488 489 // Base64Decoder 490 { 491 var buffer: [0x100]u8 = undefined; 492 var decoded = buffer[0..try codecs.Decoder.calcSizeForSlice(expected_encoded)]; 493 try codecs.Decoder.decode(decoded, expected_encoded); 494 try testing.expectEqualSlices(u8, expected_decoded, decoded); 495 } 496 497 // Base64DecoderWithIgnore 498 { 499 const decoder_ignore_nothing = codecs.decoderWithIgnore(""); 500 var buffer: [0x100]u8 = undefined; 501 var decoded = buffer[0..try decoder_ignore_nothing.calcSizeUpperBound(expected_encoded.len)]; 502 var written = try decoder_ignore_nothing.decode(decoded, expected_encoded); 503 try testing.expect(written <= decoded.len); 504 try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); 505 } 506 } 507 508 fn testDecodeIgnoreSpace(codecs: Codecs, expected_decoded: []const u8, encoded: []const u8) !void { 509 const decoder_ignore_space = codecs.decoderWithIgnore(" "); 510 var buffer: [0x100]u8 = undefined; 511 var decoded = buffer[0..try decoder_ignore_space.calcSizeUpperBound(encoded.len)]; 512 var written = try decoder_ignore_space.decode(decoded, encoded); 513 try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); 514 } 515 516 fn testError(codecs: Codecs, encoded: []const u8, expected_err: anyerror) !void { 517 const decoder_ignore_space = codecs.decoderWithIgnore(" "); 518 var buffer: [0x100]u8 = undefined; 519 if (codecs.Decoder.calcSizeForSlice(encoded)) |decoded_size| { 520 var decoded = buffer[0..decoded_size]; 521 if (codecs.Decoder.decode(decoded, encoded)) |_| { 522 return error.ExpectedError; 523 } else |err| if (err != expected_err) return err; 524 } else |err| if (err != expected_err) return err; 525 526 if (decoder_ignore_space.decode(buffer[0..], encoded)) |_| { 527 return error.ExpectedError; 528 } else |err| if (err != expected_err) return err; 529 } 530 531 fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { 532 const decoder_ignore_space = codecs.decoderWithIgnore(" "); 533 var buffer: [0x100]u8 = undefined; 534 var decoded = buffer[0 .. (try codecs.Decoder.calcSizeForSlice(encoded)) - 1]; 535 if (decoder_ignore_space.decode(decoded, encoded)) |_| { 536 return error.ExpectedError; 537 } else |err| if (err != error.NoSpaceLeft) return err; 538 } 539 540 fn testFourBytesDestNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { 541 const decoder_ignore_space = codecs.decoderWithIgnore(" "); 542 var buffer: [0x100]u8 = undefined; 543 var decoded = buffer[0..4]; 544 if (decoder_ignore_space.decode(decoded, encoded)) |_| { 545 return error.ExpectedError; 546 } else |err| if (err != error.NoSpaceLeft) return err; 547 }