Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions sdk/core/azure-core/test/ut/base64_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,225 @@ TEST(Base64, InvalidDecode)

// cspell::enable
}

Comment thread
RickWinter marked this conversation as resolved.
// Base64Url Tests
TEST(Base64Url, BasicEncode)
{
// Test empty input
std::vector<uint8_t> emptyData;
std::string result = _internal::Base64Url::Base64UrlEncode(emptyData);
EXPECT_EQ(result, "");

// Test single byte (padding should be removed)
std::vector<uint8_t> oneByte = {0x01};
result = _internal::Base64Url::Base64UrlEncode(oneByte);
EXPECT_EQ(result, "AQ"); // Base64 would be "AQ==", Base64Url removes padding

// Test two bytes (padding should be removed)
std::vector<uint8_t> twoBytes = {0x01, 0x02};
result = _internal::Base64Url::Base64UrlEncode(twoBytes);
EXPECT_EQ(result, "AQI"); // Base64 would be "AQI=", Base64Url removes padding

// Test three bytes (no padding needed)
std::vector<uint8_t> threeBytes = {0x01, 0x02, 0x03};
result = _internal::Base64Url::Base64UrlEncode(threeBytes);
EXPECT_EQ(result, "AQID"); // cspell:disable-line

// Test data with + and / in Base64 encoding (should be replaced with - and _)
// Base64 for these bytes would contain '+' and '/', which should be replaced in Base64Url
std::vector<uint8_t> specialChars = {0xFB, 0xEF};
result = _internal::Base64Url::Base64UrlEncode(specialChars);
// Verify it doesn't contain + or /
EXPECT_EQ(result.find('+'), std::string::npos);
EXPECT_EQ(result.find('/'), std::string::npos);

// Test data that generates + in Base64
std::vector<uint8_t> dataWithPlus = {0xFB}; // This should generate '+' in standard Base64
result = _internal::Base64Url::Base64UrlEncode(dataWithPlus);
EXPECT_EQ(result.find('+'), std::string::npos);
EXPECT_NE(result.find('-'), std::string::npos); // Should have '-' instead

// Test data that generates / in Base64
std::vector<uint8_t> dataWithSlash = {0xFF}; // This should generate '/' in standard Base64
result = _internal::Base64Url::Base64UrlEncode(dataWithSlash);
EXPECT_EQ(result.find('/'), std::string::npos);
EXPECT_NE(result.find('_'), std::string::npos); // Should have '_' instead
}

TEST(Base64Url, BasicDecode)
{
// Test empty input
std::vector<uint8_t> result = _internal::Base64Url::Base64UrlDecode("");
EXPECT_TRUE(result.empty());

// Test single byte (no padding in input)
result = _internal::Base64Url::Base64UrlDecode("AQ");
EXPECT_EQ(result.size(), 1u);
EXPECT_EQ(result[0], 0x01);

// Test two bytes (no padding in input)
result = _internal::Base64Url::Base64UrlDecode("AQI");
EXPECT_EQ(result.size(), 2u);
EXPECT_EQ(result[0], 0x01);
EXPECT_EQ(result[1], 0x02);

// Test three bytes
result = _internal::Base64Url::Base64UrlDecode("AQID"); // cspell:disable-line
EXPECT_EQ(result.size(), 3u);
EXPECT_EQ(result[0], 0x01);
EXPECT_EQ(result[1], 0x02);
EXPECT_EQ(result[2], 0x03);

// Test with URL-safe characters (- and _)
std::vector<uint8_t> encoded = {0xFB, 0xEF};
std::string base64UrlEncoded = _internal::Base64Url::Base64UrlEncode(encoded);
result = _internal::Base64Url::Base64UrlDecode(base64UrlEncoded);
EXPECT_EQ(result, encoded);
}

TEST(Base64Url, RoundtripEncodeDecode)
{
// Test various lengths to verify padding handling
for (size_t len : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000})
{
std::vector<uint8_t> data;
data.resize(len);
RandomBuffer(data.data(), data.size());

std::string encoded = _internal::Base64Url::Base64UrlEncode(data);
std::vector<uint8_t> decoded = _internal::Base64Url::Base64UrlDecode(encoded);

EXPECT_EQ(decoded, data) << "Roundtrip failed for length " << len;

// Verify no padding characters in encoded output
EXPECT_EQ(encoded.find('='), std::string::npos)
<< "Base64Url encoded string should not contain padding";

// Verify no standard Base64 special chars (+ and /)
EXPECT_EQ(encoded.find('+'), std::string::npos)
<< "Base64Url encoded string should not contain '+'";
EXPECT_EQ(encoded.find('/'), std::string::npos)
<< "Base64Url encoded string should not contain '/'";
}
}

TEST(Base64Url, SpecialCharacterReplacement)
{
// Create data that will produce '+' and '/' in standard Base64
// 0x3E in 6-bit encoding = 62 = '+' in Base64, should be '-' in Base64Url
// 0x3F in 6-bit encoding = 63 = '/' in Base64, should be '_' in Base64Url

// Pattern that generates both + and /
// We need bytes that when encoded produce these characters
std::vector<uint8_t> data;
for (int i = 0; i < 256; i++)
{
data.push_back(static_cast<uint8_t>(i));
}

std::string encoded = _internal::Base64Url::Base64UrlEncode(data);

// Verify the encoding uses URL-safe characters
EXPECT_EQ(encoded.find('+'), std::string::npos) << "Should not contain '+'";
EXPECT_EQ(encoded.find('/'), std::string::npos) << "Should not contain '/'";

// Verify roundtrip works
std::vector<uint8_t> decoded = _internal::Base64Url::Base64UrlDecode(encoded);
EXPECT_EQ(decoded, data);
}

TEST(Base64Url, PaddingHandling)
{
// Test that Base64Url decode correctly adds padding

// Length % 4 == 2 should add "=="
Comment thread
RickWinter marked this conversation as resolved.
std::vector<uint8_t> data1 = {0x01};
std::string encoded1 = _internal::Base64Url::Base64UrlEncode(data1);
EXPECT_EQ(encoded1.length() % 4, 2u);
EXPECT_EQ(encoded1.find('='), std::string::npos);
std::vector<uint8_t> decoded1 = _internal::Base64Url::Base64UrlDecode(encoded1);
EXPECT_EQ(decoded1, data1);

// Length % 4 == 3 should add "="
std::vector<uint8_t> data2 = {0x01, 0x02};
std::string encoded2 = _internal::Base64Url::Base64UrlEncode(data2);
EXPECT_EQ(encoded2.length() % 4, 3u);
EXPECT_EQ(encoded2.find('='), std::string::npos);
std::vector<uint8_t> decoded2 = _internal::Base64Url::Base64UrlDecode(encoded2);
EXPECT_EQ(decoded2, data2);

// Length % 4 == 0 should not add padding
std::vector<uint8_t> data3 = {0x01, 0x02, 0x03};
std::string encoded3 = _internal::Base64Url::Base64UrlEncode(data3);
EXPECT_EQ(encoded3.length() % 4, 0u);
std::vector<uint8_t> decoded3 = _internal::Base64Url::Base64UrlDecode(encoded3);
EXPECT_EQ(decoded3, data3);
}

TEST(Base64Url, InvalidDecodeInput)
{
// Test length % 4 == 1 (invalid)
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("A"), // cspell:disable-line
std::invalid_argument);
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("AAAAA"), // cspell:disable-line
std::invalid_argument);
EXPECT_THROW(
_internal::Base64Url::Base64UrlDecode("AAAAAAAAA"), // cspell:disable-line
std::invalid_argument);

// Test invalid characters (that aren't valid in Base64)
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("@@@@"), std::runtime_error);
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("A@@@"), std::runtime_error);
EXPECT_THROW(_internal::Base64Url::Base64UrlDecode("####"), std::runtime_error);
}

TEST(Base64Url, ComparisonWithStandardBase64)
{
// Create test data
std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

// Encode with standard Base64
std::string base64 = Convert::Base64Encode(data);

// Encode with Base64Url
std::string base64Url = _internal::Base64Url::Base64UrlEncode(data);

// Base64Url should not have padding
EXPECT_NE(base64.find('='), std::string::npos); // Standard Base64 should have padding
EXPECT_EQ(base64Url.find('='), std::string::npos); // Base64Url should not

// Both should decode to the same data
EXPECT_EQ(Convert::Base64Decode(base64), data);
EXPECT_EQ(_internal::Base64Url::Base64UrlDecode(base64Url), data);
}

TEST(Base64Url, KnownVectors)
{
// Test with known Base64 -> Base64Url conversions
// Standard Base64: "hello" -> "aGVsbG8="
// Base64Url should be: "aGVsbG8" (no padding)
std::vector<uint8_t> hello = {'h', 'e', 'l', 'l', 'o'};
std::string encoded = _internal::Base64Url::Base64UrlEncode(hello);
EXPECT_EQ(encoded, "aGVsbG8"); // cspell:disable-line

// Decode and verify
std::vector<uint8_t> decoded
= _internal::Base64Url::Base64UrlDecode("aGVsbG8"); // cspell:disable-line
EXPECT_EQ(decoded, hello);

// Test case with special character replacement needs
// Using bytes that create both + and / in standard Base64
// Standard Base64 for {0xFB, 0xFF, 0xFE} is "+//+", which includes both + and /
std::vector<uint8_t> specialData = {0xFB, 0xFF, 0xFE};
std::string specialEncoded = _internal::Base64Url::Base64UrlEncode(specialData);

// Verify URL-safe
EXPECT_EQ(specialEncoded.find('+'), std::string::npos);
EXPECT_EQ(specialEncoded.find('/'), std::string::npos);

// Verify roundtrip
std::vector<uint8_t> specialDecoded = _internal::Base64Url::Base64UrlDecode(specialEncoded);
EXPECT_EQ(specialDecoded, specialData);
}