solidity/test/libsolidity/semanticTests/externalContracts/_base64/base64_inline_asm.sol
Paarth Madan 17fa85a2fb Add Base64 test cases with and without inline assembly
Adds two implementations of Base64 encoding as specified in RFC4648.
Implementation (1) uses inline assembly, while Implementation (2) is
written purely in Solidity.

Assertions are added to replicate the test vectors specified in the RFC
for Base64 to ensure both implementations to specification.
2022-03-05 19:51:46 -05:00

97 lines
3.4 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Provides a set of functions to operate with Base64 strings.
*/
library InlineAsmBase64 {
/**
* @dev Base64 Encoding/Decoding Table
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
/**
* Inspired by OpenZepplin Base64 implementation
* https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884/commits/157c32b65a15cb0b58257543643cafa1cebf883a
*/
if (data.length == 0) return "";
// Loads the table into memory
string memory table = _TABLE;
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
// and split into 4 numbers of 6 bits.
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
// - `data.length + 2` -> Round up
// - `/ 3` -> Number of 3-bytes chunks
// - `4 *` -> 4 characters for each chunk
uint256 encodedLen = 4 * ((data.length + 2) / 3);
// Add some extra buffer at the end required for the writing
string memory result = new string(encodedLen);
assembly {
// Store the actual result length in memory
mstore(result, encodedLen)
// Prepare the lookup table
let tablePtr := add(table, 1)
// Prepare input pointer
let dataPtr := data
let endPtr := add(dataPtr, mload(data))
// Prepare result pointer, jump over length
let resultPtr := add(result, 32)
// Run over the input, 3 bytes at a time
for {
} lt(dataPtr, endPtr) {
} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
// To write each character, shift the 3 bytes (24 bits) chunk 4
// times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F to extract the 6-bit group.
// Add the 6-bit group with the table ptr to index into the
// table and acquire the character to write. Finally, write
// the character to the result pointer.
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
}
// When data `bytes` is not exactly 3 bytes long
// it is padded with `=` characters at the end
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
return result;
}
}