425 lines
16 KiB
C++
425 lines
16 KiB
C++
/*
|
|
* Base64Decode.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <string_view>
|
|
#include <fmt/format.h>
|
|
#include "fdbrpc/Base64Encode.h"
|
|
#include "fdbrpc/Base64Decode.h"
|
|
#include "flow/flow.h"
|
|
#include "flow/Arena.h"
|
|
#include "flow/Error.h"
|
|
#include "flow/UnitTest.h"
|
|
|
|
namespace {
|
|
|
|
constexpr uint8_t _X = 0xff;
|
|
|
|
template <bool UrlDecode>
|
|
inline uint8_t decodeValue(uint8_t valueIn) noexcept {
|
|
if constexpr (UrlDecode) {
|
|
// clang-format off
|
|
// Decodes base64url-encoding's 64 legal ASCII character byte: i.e. alphanumeric, '-', and '_'
|
|
// into 6-bit fragment, a sequence containing four of which would form 3 original bytes.
|
|
constexpr const uint8_t decoding[] = { // 20x13
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, 62, _X, _X, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, _X, _X,
|
|
_X, _X, _X, _X, _X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, _X, _X, _X, _X, 63, _X, 26, 27, 28,
|
|
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
|
49, 50, 51, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X };
|
|
// clang-format on
|
|
static_assert(sizeof(decoding) / sizeof(decoding[0]) == 256);
|
|
return decoding[valueIn];
|
|
} else {
|
|
// clang-format off
|
|
// same as url-encoded base64, except that this encoding assumes '+' instead of '-',
|
|
// and '/' instead of '_'.
|
|
constexpr const uint8_t decoding[] = { // 20x13
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, 62, _X, _X, _X, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, _X, _X,
|
|
_X, _X, _X, _X, _X, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, _X, _X, _X, _X, _X, _X, 26, 27, 28,
|
|
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
|
49, 50, 51, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X,
|
|
_X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X, _X };
|
|
// clang-format on
|
|
static_assert(sizeof(decoding) / sizeof(decoding[0]) == 256);
|
|
return decoding[valueIn];
|
|
}
|
|
}
|
|
|
|
template <bool UrlDecode>
|
|
int doDecode(const uint8_t* __restrict codeIn, const int lengthIn, uint8_t* __restrict plaintextOut) noexcept {
|
|
const uint8_t* codechar = codeIn;
|
|
const uint8_t* const codeEnd = codeIn + lengthIn;
|
|
uint8_t* plainchar = plaintextOut;
|
|
uint8_t fragment = 0;
|
|
|
|
while (1) {
|
|
// code 1 of 4
|
|
if (codechar == codeEnd) {
|
|
return plainchar - plaintextOut;
|
|
}
|
|
fragment = decodeValue<UrlDecode>(*codechar++);
|
|
if (fragment == _X)
|
|
return -1;
|
|
*plainchar = (fragment & 0x03f) << 2;
|
|
if (codechar == codeEnd) {
|
|
return -1; // requires at least 2 chars to decode 1 plain byte
|
|
}
|
|
// code 2 of 4
|
|
fragment = decodeValue<UrlDecode>(*codechar++);
|
|
if (fragment == _X)
|
|
return -1;
|
|
*plainchar++ |= (fragment & 0x030) >> 4;
|
|
if (codechar == codeEnd) {
|
|
return plainchar - plaintextOut;
|
|
}
|
|
*plainchar = (fragment & 0x00f) << 4;
|
|
// code 3 of 4
|
|
fragment = decodeValue<UrlDecode>(*codechar++);
|
|
if (fragment == _X)
|
|
return -1;
|
|
*plainchar++ |= (fragment >> 2);
|
|
if (codechar == codeEnd) {
|
|
return plainchar - plaintextOut;
|
|
}
|
|
*plainchar = (fragment & 0x003) << 6;
|
|
// code 4 of 4
|
|
fragment = decodeValue<UrlDecode>(*codechar++);
|
|
if (fragment == _X)
|
|
return -1;
|
|
*plainchar++ |= (fragment & 0x03f);
|
|
}
|
|
/* control should not reach here */
|
|
return plainchar - plaintextOut;
|
|
}
|
|
|
|
// assumes codeLength after padding stripped
|
|
int getDecodedLength(int codeLength) noexcept {
|
|
const auto r = (codeLength % 4);
|
|
if (r == 1)
|
|
return -1;
|
|
else if (r == 0)
|
|
return (codeLength / 4) * 3;
|
|
else
|
|
return (codeLength / 4) * 3 + (r - 1);
|
|
}
|
|
|
|
template <bool UrlDecode>
|
|
Optional<StringRef> decodeStringRef(Arena& arena, StringRef codeText) {
|
|
if constexpr (!UrlDecode) {
|
|
// check length alignment and strip padding, if any
|
|
if (codeText.size() % 4)
|
|
return {};
|
|
if (!codeText.empty() && codeText.back() == '=')
|
|
codeText.popBack();
|
|
if (!codeText.empty() && codeText.back() == '=')
|
|
codeText.popBack();
|
|
}
|
|
auto decodedLen = getDecodedLength(codeText.size());
|
|
if (decodedLen <= 0) {
|
|
if (decodedLen == 0)
|
|
return StringRef{};
|
|
return {};
|
|
}
|
|
auto out = new (arena) uint8_t[decodedLen];
|
|
auto actualLen = doDecode<UrlDecode>(codeText.begin(), codeText.size(), out);
|
|
if (actualLen == -1) {
|
|
return {};
|
|
}
|
|
ASSERT_EQ(decodedLen, actualLen);
|
|
return StringRef(out, decodedLen);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace base64 {
|
|
|
|
int decodedLength(int codeLength) noexcept {
|
|
// Non-urlencoded base64 cannot predict the decoded length on encoded length alone due to padding,
|
|
// which makes this a conservative estimate, not an exact one.
|
|
return (codeLength / 4) * 3;
|
|
}
|
|
|
|
int decode(const uint8_t* __restrict codeIn, const int lengthIn, uint8_t* __restrict plaintextOut) noexcept {
|
|
if (lengthIn % 4) {
|
|
return -1;
|
|
} else {
|
|
int actualLen = lengthIn;
|
|
if (actualLen > 0 && codeIn[actualLen - 1] == '=')
|
|
actualLen--;
|
|
if (actualLen > 0 && codeIn[actualLen - 1] == '=')
|
|
actualLen--;
|
|
return doDecode<false>(codeIn, actualLen, plaintextOut);
|
|
}
|
|
}
|
|
|
|
Optional<StringRef> decode(Arena& arena, StringRef codeText) {
|
|
return decodeStringRef<false>(arena, codeText);
|
|
}
|
|
|
|
namespace url {
|
|
|
|
int decodedLength(int codeLength) noexcept {
|
|
return getDecodedLength(codeLength);
|
|
}
|
|
|
|
int decode(const uint8_t* __restrict codeIn, const int lengthIn, uint8_t* __restrict plaintextOut) noexcept {
|
|
return doDecode<true>(codeIn, lengthIn, plaintextOut);
|
|
}
|
|
|
|
Optional<StringRef> decode(Arena& arena, StringRef codeText) {
|
|
return decodeStringRef<true>(arena, codeText);
|
|
}
|
|
|
|
} // namespace url
|
|
|
|
} // namespace base64
|
|
|
|
// transform the input on-the-fly to build testcase for non-urlencoded cases
|
|
StringRef urlEncodedTestData[][2] = {
|
|
{ ""_sr, ""_sr },
|
|
{ "f"_sr, "Zg"_sr },
|
|
{ "fo"_sr, "Zm8"_sr },
|
|
{ "foo"_sr, "Zm9v"_sr },
|
|
{ "foob"_sr, "Zm9vYg"_sr },
|
|
{ "fooba"_sr, "Zm9vYmE"_sr },
|
|
{ "foobar"_sr, "Zm9vYmFy"_sr },
|
|
{ "Q\xc2\x93\x86\x04H\xfd\"r\x9c\xf7\xafW\xd1\x87^"_sr, "UcKThgRI_SJynPevV9GHXg"_sr },
|
|
{ "\x93"_sr, "kw"_sr },
|
|
{ "\xfcpF\xd2\x15\x03\x8a\xcb\\#!\xa1\x95\x18\x13\xfcpoN\rh=\xa5\x05\xe5\x00\xf8<\xc3\x8b'C\x90\xfc\xa0x\x13"
|
|
"8q\r\xd4\xca\xc9Yjv"_sr,
|
|
"_HBG0hUDistcIyGhlRgT_HBvTg1oPaUF5QD4PMOLJ0OQ_KB4EzhxDdTKyVlqdg"_sr },
|
|
{ "\xdd\xbb\x91>@\x9d\x88\x01Qb\x97[\xc3Q\xf6Q\\LF\xe2}\xfb\xf0\xe8\x98\xba\x8c\xc7\xc9\x0e$\xe4q\xcf;\xe4"
|
|
"e\x02"
|
|
"DA\xa9\x9a\xf0r\xc9\xf0\xd2-\x98"_sr,
|
|
"3buRPkCdiAFRYpdbw1H2UVxMRuJ9-_DomLqMx8kOJORxzzvkZQJEQama8HLJ8NItmA"_sr },
|
|
{ "2\x7f\x98\xfe\xb4\x05\x18.%.\xd0\x14\xea\x8e+\xa5\xc5\xbd"
|
|
"F-Lm\x04\x1aQ\xde\x1e\x9c\x12\xe6\x81{\x9dj\xe8\x9cP\xf4\xf7\x8a<\x12"_sr,
|
|
"Mn-Y_rQFGC4lLtAU6o4rpcW9Ri1MbQQaUd4enBLmgXudauicUPT3ijwS"_sr },
|
|
{ "\xa3"
|
|
"b\xc9\x13|\x94\xab)}\xf4N\xbc\xb2\xc5$\x15\xed\xb0\x98\xa4v\x8b\x91\xe4M\xb8\xde!\x94"_sr,
|
|
"o2LJE3yUqyl99E68ssUkFe2wmKR2i5HkTbjeIZQ"_sr },
|
|
{ "\xa0\xe9\xb4=4\xba\xbd\xbd\xaa\xfd\x96\xcb\x03\xd3\xb7\xc9\xb7i7\x18$^\xba\xe5\xb3\x8a\xf4O\xdb"_sr,
|
|
"oOm0PTS6vb2q_ZbLA9O3ybdpNxgkXrrls4r0T9s"_sr },
|
|
{ "\x10\xf4zscNoE\xfd\xc5\x8d\x16\x82t|y\n\xcf\xe8\x98\xf8)\xcd\xefm\xe2\xe1%\x17\x05T9;Zb\x05\x02\xc7"
|
|
"B\x8c\xc5\xc5\x95"
|
|
"8\xf2"_sr,
|
|
"EPR6c2NOb0X9xY0WgnR8eQrP6Jj4Kc3vbeLhJRcFVDk7WmIFAsdCjMXFlTjy"_sr },
|
|
{ "NG\"hA\xff\\\xf5lD\xf7\x08"
|
|
"7\xb9\t\x07\xa5\xb9\xac\x0b\x9fT+\xfa"_sr,
|
|
"TkciaEH_XPVsRPcIN7kJB6W5rAufVCv6"_sr },
|
|
{ "\xbd\xf5\xb0\x8eh1\x1b\xd1\x13q\x88z0*b\x9cNg\x88\x88MBD\x17\xec\xb0yc3\xbb"_sr,
|
|
"vfWwjmgxG9ETcYh6MCpinE5niIhNQkQX7LB5YzO7"_sr },
|
|
{ "&\xe0> \xfc\xea\x8e\xc1[I\xec\xe8\x03\x15\tc\x9b\x0f"
|
|
"d\x13"
|
|
"d\xab\xa5\x16\xa2p\x91\xd5\x11\xf5X\xa7\xbd\xe1\xa1"
|
|
"B\x8e\xe8\xddn2\xbf\x97"_sr,
|
|
"JuA-IPzqjsFbSezoAxUJY5sPZBNkq6UWonCR1RH1WKe94aFCjujdbjK_lw"_sr },
|
|
{ "|W,\xa5\xce\x83\xb0\xec\x87\x86\xd0<O\x94\x97"
|
|
"0F"_sr,
|
|
"fFcspc6DsOyHhtA8T5SXMEY"_sr },
|
|
{ "\x06\xa7\xf1"_sr, "Bqfx"_sr },
|
|
{ "u\xc4"
|
|
"7\x8e&\xa7\x90v"_sr,
|
|
"dcQ3jiankHY"_sr },
|
|
{ "X\xfe\xcd\x1f\xc8\x8f\xe3\xca"
|
|
"6\x96\x8c\x87\xcd\xbaJ!\xabq\x8c\x97)#\xfb\xda\xb8\xa9\xe9"
|
|
"a\x0c\xe2\x10\xe9\xe7\x16\x96\xb5"_sr,
|
|
"WP7NH8iP48o2loyHzbpKIatxjJcpI_vauKnpYQziEOnnFpa1"_sr },
|
|
{ "\xf1\n@\x18\xc3"
|
|
"F\xc4\xf8\x1c\xa9\xa9\xdb\x15\xcb\xd0V\xe4P\x8b\x8b\xaf\xf2\xfc\xb7\x1d\xa6p\n\xa3\x13,.\x12#"_sr,
|
|
"8QpAGMNGxPgcqanbFcvQVuRQi4uv8vy3HaZwCqMTLC4SIw"_sr },
|
|
{ "h\x9e\xe0X_\x1c\x04\xe3\xaf\xac\xe3\x18JK/\xe7\xbc"
|
|
"D\xf5"
|
|
"B\xfbK(Q\x8c\n\xca\xfc^\xd9\xdb\xb4\xea\xae\xb8"_sr,
|
|
"aJ7gWF8cBOOvrOMYSksv57xE9UL7SyhRjArK_F7Z27Tqrrg"_sr },
|
|
{ "\x9aX\xb2Q\xf0\x85R`\xc1\xbb\x95\xe7\x10\xe3\xd0x\n"_sr, "mliyUfCFUmDBu5XnEOPQeAo"_sr },
|
|
{ "\xa7\x1f"
|
|
"0\x90P\xab\x1fwJ\x86\x82Tj\xd0Ob\xa4\xac+\xc9"
|
|
"4;\x86\\\x8b\xb6"_sr,
|
|
"px8wkFCrH3dKhoJUatBPYqSsK8k0O4Zci7Y"_sr },
|
|
{ "\x0e"
|
|
"C\x87u-\x12\x8a\xe4\x11"
|
|
"F\xb6"
|
|
"a5\xcds\x1ez?J4\x02?@g\xaa-\xc4\xe0\x80\xe1!\xc0\x1d\x16\x1e"
|
|
"Em\xa8"
|
|
"a\xc8\x9d<\xd1"_sr,
|
|
"DkOHdS0SiuQRRrZhNc1zHno_SjQCP0Bnqi3E4IDhIcAdFh5FbahhyJ080Q"_sr },
|
|
{ "\xb7\xf6\xb6\\\xd3&\xf5"
|
|
"F\xb1\x86\xd8n%\xb5"
|
|
"a\x1d^Iv\xbe\x9bO\xcb\xca\xd2"_sr,
|
|
"t_a2XNMm9UaxhthuJbVhHV5Jdr6bT8vK0g"_sr },
|
|
{ "\xc0+\xea\xb9\x01\xf2\xe8\x8e\xf2\x0ft\x8b\xa0\xa4\x0cq=\xa6"
|
|
"c\xf1v\x9b\x81\xc7*\xd0\xe8Z\x04\"\t\xe8>\xd5w\xddQI\xc9\xba\x8f"_sr,
|
|
"wCvquQHy6I7yD3SLoKQMcT2mY_F2m4HHKtDoWgQiCeg-1XfdUUnJuo8"_sr },
|
|
{ "\xac"
|
|
"5\x8aH\xc9q\xad\xbe\x1f\x80\xed\xe1"_sr,
|
|
"rDWKSMlxrb4fgO3h"_sr },
|
|
{ "0}\x82\x95\xbb'\xf2\xdf"
|
|
"dR\x8f\xc2\xac\xb3\xc7\x9f\xc0\xf0"
|
|
"C3L\xbe"
|
|
"E\xe5\xf1\xc4% \xec\xe9"_sr,
|
|
"MH2Clbsn8t9kUo_CrLPHn8DwQzNMvkXl8cQlIOzp"_sr },
|
|
{ "\x8b\xab\xd7\xf9\xa5\xd8H\x1d"_sr, "i6vX-aXYSB0"_sr },
|
|
{ "i!\xdc\xb7~9\x7f\xad\xa0\x9d\x1e\xcc\xedTj\xe3\xe2\x88Q\x1e\xaa\xf9\xc3\xc5\xc5\xcdq\x9e\x07~\x9e\xcb\xf3\xd3\xb2\xec\xe0[m+\x0c\x9c"_sr,
|
|
"aSHct345f62gnR7M7VRq4-KIUR6q-cPFxc1xngd-nsvz07Ls4FttKwyc"_sr },
|
|
{ "\x01-{.s\xa5qF4\x1f"
|
|
"a\x11\xe4\x1eN"_sr,
|
|
"AS17LnOlcUY0H2ER5B5O"_sr },
|
|
{ "\x1c\xab\xce"
|
|
"e\xfc\xa7"
|
|
"are\x1f\x9a\xb4\xcdr\xe2v95\x88"_sr,
|
|
"HKvOZfynYXJlH5q0zXLidjk1iA"_sr },
|
|
{ "\xde\x0e\x16V\x12\x0f\xa4\xaf"
|
|
"2\xe7k3\xe8\"\x0b\xcb\x80\xa5\x96,\xba\xef\x1c\xe3\xd8\x16"
|
|
"C1\xccI\x8a]W\xf0\xbf\xaf\x19"
|
|
"4\xf9\r*<^?8C\x81\xd3(\xc6"_sr,
|
|
"3g4WVhIPpK8y52sz6CILy4Clliy67xzj2BZDMcxJil1X8L-vGTT5DSo8Xj84Q4HTKMY"_sr },
|
|
{ "\xe1\xe2\x8a"
|
|
"8v\x1f\xe0|\xccIJ\t"_sr,
|
|
"4eKKOHYf4HzMSUoJ"_sr },
|
|
};
|
|
|
|
static Void runTest(std::function<StringRef(Arena&, StringRef)> conversionFn,
|
|
StringRef (&stringEncodeFn)(Arena&, StringRef),
|
|
Optional<StringRef> (&stringDecodeFn)(Arena&, StringRef),
|
|
std::function<void(StringRef, int)> encodeOutputCheckFn) {
|
|
const int testSetLen = sizeof(urlEncodedTestData) / sizeof(urlEncodedTestData[0]);
|
|
for (auto i = 0; i < testSetLen; i++) {
|
|
auto tmpArena = Arena();
|
|
auto [decodeOutputExpected, encodeOutputExpected] = urlEncodedTestData[i];
|
|
// optionally convert base64Url-encoded test input to regular base64
|
|
encodeOutputExpected = conversionFn(tmpArena, encodeOutputExpected);
|
|
auto encodeOutput = stringEncodeFn(tmpArena, decodeOutputExpected);
|
|
if (encodeOutput != encodeOutputExpected) {
|
|
fmt::print("Test case {} (encode): expected '{}' got '{}'\n",
|
|
i + 1,
|
|
encodeOutputExpected.toHexString(),
|
|
encodeOutput.toHexString());
|
|
ASSERT(false);
|
|
}
|
|
auto decodeOutput = stringDecodeFn(tmpArena, encodeOutputExpected);
|
|
ASSERT(decodeOutput.present());
|
|
if (decodeOutput.get() != decodeOutputExpected) {
|
|
fmt::print("Test case {} (decode): expected '{}' got '{}'\n",
|
|
i + 1,
|
|
decodeOutputExpected.toHexString(),
|
|
decodeOutput.get().toHexString());
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
auto& rng = *deterministicRandom();
|
|
for (auto i = 0; i < 100; i++) {
|
|
auto tmpArena = Arena();
|
|
auto inputLen = rng.randomInt(1, 300);
|
|
auto inputBuf = new (tmpArena) uint8_t[inputLen];
|
|
for (auto i = 0; i < inputLen; i++)
|
|
inputBuf[i] = rng.randomInt(0, 256);
|
|
auto input = StringRef(inputBuf, inputLen);
|
|
auto output = stringEncodeFn(tmpArena, input);
|
|
encodeOutputCheckFn(output, i);
|
|
auto decodeOutput = stringDecodeFn(tmpArena, output);
|
|
ASSERT(decodeOutput.present());
|
|
if (input != decodeOutput.get()) {
|
|
fmt::print("Dynamic case {} (decode) failed, expected '{}', got '{}'\n",
|
|
i + 1,
|
|
input.toHexString(),
|
|
decodeOutput.get().toHexString());
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
TEST_CASE("/fdbrpc/Base64UrlEncode") {
|
|
return runTest([](Arena&, StringRef input) { return input; }, // no op (input already url-encoded)
|
|
base64::url::encode,
|
|
base64::url::decode,
|
|
[](StringRef encodeOutput, int n) {
|
|
ASSERT_NE(encodeOutput.size() % 4, 1);
|
|
// verify that output contains only base64url-legal characters
|
|
for (auto i = 0; i < encodeOutput.size(); ++i) {
|
|
auto const value = decodeValue<true /*urlencoded*/>(encodeOutput[i]);
|
|
if (value == _X) {
|
|
fmt::print(
|
|
"Random-generated case {} has illegal encoded output char: {}th byte, value {}\n",
|
|
n + 1,
|
|
i + 1,
|
|
static_cast<int>(value));
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static StringRef transformBase64UrlToBase64(Arena& arena, StringRef input) {
|
|
if (input.empty())
|
|
return StringRef();
|
|
const int len = ((input.size() + 3) / 4) * 4; // ceil_align(input.size(), 4)
|
|
auto output = new (arena) uint8_t[len];
|
|
for (auto i = 0; i < input.size(); i++) {
|
|
if (input[i] == '-') {
|
|
output[i] = '+';
|
|
} else if (input[i] == '_') {
|
|
output[i] = '/';
|
|
} else {
|
|
output[i] = input[i];
|
|
}
|
|
}
|
|
for (auto i = input.size(); i < len; i++)
|
|
output[i] = '=';
|
|
return StringRef(output, len);
|
|
}
|
|
|
|
TEST_CASE("/fdbrpc/Base64Encode") {
|
|
return runTest(transformBase64UrlToBase64, base64::encode, base64::decode, [](StringRef encodeOutput, int n) {
|
|
ASSERT_EQ(encodeOutput.size() % 4, 0);
|
|
if (!encodeOutput.empty() && encodeOutput.back() == '=')
|
|
encodeOutput.popBack();
|
|
if (!encodeOutput.empty() && encodeOutput.back() == '=')
|
|
encodeOutput.popBack();
|
|
for (auto i = 0; i < encodeOutput.size(); ++i) {
|
|
auto const value = decodeValue<false /*urlencoded*/>(encodeOutput[i]);
|
|
if (value == _X) {
|
|
fmt::print("Random-generated case {} has illegal encoded output char: {}th byte, value {}\n",
|
|
n + 1,
|
|
i + 1,
|
|
static_cast<int>(value));
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
});
|
|
}
|