diff --git a/doc/xml/release.xml b/doc/xml/release.xml index a7c2061a3..3f0e549d2 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -63,6 +63,10 @@

Add C memory contexts.

+ + +

Add base64 encode/decode.

+
diff --git a/libc/LibC.xs b/libc/LibC.xs index 0ff4e6d95..ac0bb7fad 100644 --- a/libc/LibC.xs +++ b/libc/LibC.xs @@ -25,6 +25,7 @@ C includes These includes are from the src directory. There is no Perl-specific code in them. ***********************************************************************************************************************************/ +#include "common/encode.h" #include "common/error.h" #include "config/config.h" #include "config/configRule.h" @@ -35,6 +36,13 @@ Helper macros ***********************************************************************************************************************************/ #include "LibC.h" +/*********************************************************************************************************************************** +XSH includes + +These includes define data structures that are required for the C to Perl interface but are not part of the regular C source. +***********************************************************************************************************************************/ +#include "xs/common/encode.xsh" + /*********************************************************************************************************************************** Constant include @@ -58,6 +66,7 @@ INCLUDE: const-xs.inc # # These modules should map 1-1 with C modules in src directory. # ---------------------------------------------------------------------------------------------------------------------------------- +INCLUDE: xs/common/encode.xs INCLUDE: xs/config/config.xs INCLUDE: xs/config/configRule.xs INCLUDE: xs/postgres/pageChecksum.xs diff --git a/libc/Makefile.PL b/libc/Makefile.PL index 7f2fb352b..29f1505ad 100644 --- a/libc/Makefile.PL +++ b/libc/Makefile.PL @@ -101,6 +101,17 @@ my $rhExport = libCVersion )], }, + + 'encode' => + { + &BLD_EXPORTTYPE_CONSTANT => [qw( + ENCODE_TYPE_BASE64 + )], + + &BLD_EXPORTTYPE_SUB => [qw( + encodeToStr decodeToBin + )], + }, }; #################################################################################################################################### diff --git a/libc/xs/common/encode.xs b/libc/xs/common/encode.xs new file mode 100644 index 000000000..d41c69da6 --- /dev/null +++ b/libc/xs/common/encode.xs @@ -0,0 +1,48 @@ +# ---------------------------------------------------------------------------------------------------------------------------------- +# Binary to String Encode/Decode Perl Exports +# ---------------------------------------------------------------------------------------------------------------------------------- + +MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC + +#################################################################################################################################### +SV * +encodeToStr(encodeType, source) + int encodeType + SV *source +CODE: + RETVAL = NULL; + + STRLEN sourceSize; + unsigned char *sourcePtr = (unsigned char *)SvPV(source, sourceSize); + + ERROR_XS_BEGIN() + { + RETVAL = newSV(encodeToStrSize(encodeType, sourceSize)); + SvPOK_only(RETVAL); + + encodeToStr(encodeType, sourcePtr, sourceSize, (char *)SvPV_nolen(RETVAL)); + SvCUR_set(RETVAL, encodeToStrSize(encodeType, sourceSize)); + } + ERROR_XS_END(); +OUTPUT: + RETVAL + +#################################################################################################################################### +SV * +decodeToBin(encodeType, source) + int encodeType + const char *source +CODE: + RETVAL = NULL; + + ERROR_XS_BEGIN() + { + RETVAL = newSV(decodeToBinSize(encodeType, source)); + SvPOK_only(RETVAL); + + decodeToBin(encodeType, source, (unsigned char *)SvPV_nolen(RETVAL)); + SvCUR_set(RETVAL, decodeToBinSize(encodeType, source)); + } + ERROR_XS_END(); +OUTPUT: + RETVAL diff --git a/libc/xs/common/encode.xsh b/libc/xs/common/encode.xsh new file mode 100644 index 000000000..65f60aabc --- /dev/null +++ b/libc/xs/common/encode.xsh @@ -0,0 +1,7 @@ +/*********************************************************************************************************************************** +Binary to String Encode/Decode XS Header +***********************************************************************************************************************************/ +#include "../src/common/encode.h" + +// Encode types +#define ENCODE_TYPE_BASE64 ((int)encodeBase64) diff --git a/src/common/encode.c b/src/common/encode.c new file mode 100644 index 000000000..1933fb524 --- /dev/null +++ b/src/common/encode.c @@ -0,0 +1,102 @@ +/*********************************************************************************************************************************** +Binary to String Encode/Decode +***********************************************************************************************************************************/ +#include + +#include "common/encode.h" +#include "common/encode/base64.h" +#include "common/error.h" + +/*********************************************************************************************************************************** +Macro to handle invalid encode type errors +***********************************************************************************************************************************/ +#define ENCODE_TYPE_INVALID_ERROR(encodeType) \ + ERROR_THROW(AssertError, "invalid encode type %d", encodeType); + +/*********************************************************************************************************************************** +Encode binary data to a printable string +***********************************************************************************************************************************/ +void +encodeToStr(EncodeType encodeType, const unsigned char *source, int sourceSize, char *destination) +{ + if (encodeType == encodeBase64) + encodeToStrBase64(source, sourceSize, destination); + else + ENCODE_TYPE_INVALID_ERROR(encodeType); +} + +/*********************************************************************************************************************************** +Size of the string returned by encodeToStr() +***********************************************************************************************************************************/ +int +encodeToStrSize(EncodeType encodeType, int sourceSize) +{ + int destinationSize = 0; + + if (encodeType == encodeBase64) + destinationSize = encodeToStrSizeBase64(sourceSize); + else + ENCODE_TYPE_INVALID_ERROR(encodeType); + + return destinationSize; +} + +/*********************************************************************************************************************************** +Decode a string to binary data +***********************************************************************************************************************************/ +void +decodeToBin(EncodeType encodeType, const char *source, unsigned char *destination) +{ + if (encodeType == encodeBase64) + decodeToBinBase64(source, destination); + else + ENCODE_TYPE_INVALID_ERROR(encodeType); +} + +/*********************************************************************************************************************************** +Size of the binary data returned by decodeToBin() +***********************************************************************************************************************************/ +int +decodeToBinSize(EncodeType encodeType, const char *source) +{ + int destinationSize = 0; + + if (encodeType == encodeBase64) + destinationSize = decodeToBinSizeBase64(source); + else + ENCODE_TYPE_INVALID_ERROR(encodeType); + + return destinationSize; +} + +/*********************************************************************************************************************************** +Check that the encoded string is valid +***********************************************************************************************************************************/ +bool +decodeToBinValid(EncodeType encodeType, const char *source) +{ + bool valid = true; + + ERROR_TRY() + { + decodeToBinValidate(encodeType, source); + } + ERROR_CATCH(FormatError) + { + valid = false; + } + + return valid; +} + +/*********************************************************************************************************************************** +Validate the encoded string +***********************************************************************************************************************************/ +void +decodeToBinValidate(EncodeType encodeType, const char *source) +{ + if (encodeType == encodeBase64) + decodeToBinValidateBase64(source); + else + ENCODE_TYPE_INVALID_ERROR(encodeType); +} diff --git a/src/common/encode.h b/src/common/encode.h new file mode 100644 index 000000000..9da29bf02 --- /dev/null +++ b/src/common/encode.h @@ -0,0 +1,26 @@ +/*********************************************************************************************************************************** +Binary to String Encode/Decode + +These high-level functions are preferred to the low-level functions for each encoding type in the encode subdirectory. +***********************************************************************************************************************************/ +#ifndef ENCODE_H +#define ENCODE_H + +#include "common/type.h" + +/*********************************************************************************************************************************** +Encoding types +***********************************************************************************************************************************/ +typedef enum {encodeBase64} EncodeType; + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +void encodeToStr(EncodeType encodeType, const unsigned char *source, int sourceSize, char *destination); +int encodeToStrSize(EncodeType encodeType, int sourceSize); +void decodeToBin(EncodeType encodeType, const char *source, unsigned char *destination); +int decodeToBinSize(EncodeType encodeType, const char *source); +bool decodeToBinValid(EncodeType encodeType, const char *source); +void decodeToBinValidate(EncodeType encodeType, const char *source); + +#endif diff --git a/src/common/encode/base64.c b/src/common/encode/base64.c new file mode 100644 index 000000000..6c81f0733 --- /dev/null +++ b/src/common/encode/base64.c @@ -0,0 +1,176 @@ +/*********************************************************************************************************************************** +Base64 Binary to String Encode/Decode +***********************************************************************************************************************************/ +#include + +#include "common/encode/base64.h" +#include "common/error.h" + +/*********************************************************************************************************************************** +Encode binary data to a printable string +***********************************************************************************************************************************/ +static const char encodeBase64Lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void +encodeToStrBase64(const unsigned char *source, int sourceSize, char *destination) +{ + int destinationIdx = 0; + + // Encode the string from three bytes to four characters + for (int sourceIdx = 0; sourceIdx < sourceSize; sourceIdx += 3) + { + // First two characters are always used + destination[destinationIdx++] = encodeBase64Lookup[source[sourceIdx] >> 2]; + destination[destinationIdx++] = + encodeBase64Lookup[((source[sourceIdx] & 0x03) << 4) | ((source[sourceIdx + 1] & 0xf0) >> 4)]; + + // Last two may be coded as = if there are less than three characters + if (sourceSize - sourceIdx > 1) + { + destination[destinationIdx++] = + encodeBase64Lookup[((source[sourceIdx + 1] & 0x0f) << 2) | ((source[sourceIdx + 2] & 0xc0) >> 6)]; + } + else + destination[destinationIdx++] = 0x3d; + + if (sourceSize - sourceIdx > 2) + destination[destinationIdx++] = encodeBase64Lookup[source[sourceIdx + 2] & 0x3f]; + else + destination[destinationIdx++] = 0x3d; + } + + // Zero-terminate the string + destination[destinationIdx] = 0; +} + +/*********************************************************************************************************************************** +Size of the destination param required by encodeToStrBase64() minus space for the null terminator +***********************************************************************************************************************************/ +int +encodeToStrSizeBase64(int sourceSize) +{ + // Calculate how many groups of three are in the source + int encodeGroupTotal = sourceSize / 3; + + // Increase by one if there is a partial group + if (sourceSize % 3 != 0) + encodeGroupTotal++; + + // Four characters are needed to encode each group + return encodeGroupTotal * 4; +} + +/*********************************************************************************************************************************** +Decode a string to binary data +***********************************************************************************************************************************/ +static const int decodeBase64Lookup[256] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 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, -1, -1, -1, -1, -1, + -1, 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, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +void +decodeToBinBase64(const char *source, unsigned char *destination) +{ + // Validate encoded string + decodeToBinValidateBase64(source); + + int destinationIdx = 0; + + // Decode the binary data from four characters to three bytes + for (int sourceIdx = 0; sourceIdx < strlen(source); sourceIdx += 4) + { + // Always decode the first character + destination[destinationIdx++] = + (unsigned char)(decodeBase64Lookup[(int)source[sourceIdx]] << 2 | decodeBase64Lookup[(int)source[sourceIdx + 1]] >> 4); + + // Second character is optional + if (source[sourceIdx + 2] != 0x3d) + { + destination[destinationIdx++] = (unsigned char) + (decodeBase64Lookup[(int)source[sourceIdx + 1]] << 4 | decodeBase64Lookup[(int)source[sourceIdx + 2]] >> 2); + } + + // Third character is optional + if (source[sourceIdx + 3] != 0x3d) + { + destination[destinationIdx++] = (unsigned char) + (((decodeBase64Lookup[(int)source[sourceIdx + 2]] << 6) & 0xc0) | decodeBase64Lookup[(int)source[sourceIdx + 3]]); + } + } +} + +/*********************************************************************************************************************************** +Size of the destination param required by decodeToBinBase64() +***********************************************************************************************************************************/ +int +decodeToBinSizeBase64(const char *source) +{ + // Validate encoded string + decodeToBinValidateBase64(source); + + // Start with size calculated directly from source length + int sourceSize = strlen(source); + int destinationSize = sourceSize / 4 * 3; + + // Subtract last character if it is not present + if (source[sourceSize - 1] == 0x3d) + { + destinationSize--; + + // Subtract second to last character if it is not present + if (source[sourceSize - 2] == 0x3d) + destinationSize--; + } + + return destinationSize; +} + +/*********************************************************************************************************************************** +Validate the encoded string +***********************************************************************************************************************************/ +void +decodeToBinValidateBase64(const char *source) +{ + // Check for the correct length + int sourceSize = strlen(source); + + if (sourceSize % 4 != 0) + ERROR_THROW(FormatError, "base64 size %d is not evenly divisible by 4", sourceSize); + + // Check all characters + for (int sourceIdx = 0; sourceIdx < sourceSize; sourceIdx++) + { + // Check terminators + if (source[sourceIdx] == 0x3d) + { + // Make sure they are only in the last two positions + if (sourceIdx < sourceSize - 2) + ERROR_THROW(FormatError, "base64 '=' character may only appear in last two positions"); + + // If second to last char is = then last char must also be + if (sourceIdx == sourceSize - 2 && source[sourceSize - 1] != 0x3d) + ERROR_THROW(FormatError, "base64 last character must be '=' if second to last is"); + } + else + { + // Error on any invalid characters + if (decodeBase64Lookup[(int)source[sourceIdx]] == -1) + ERROR_THROW(FormatError, "base64 invalid character found at position %d", sourceIdx); + } + } +} diff --git a/src/common/encode/base64.h b/src/common/encode/base64.h new file mode 100644 index 000000000..4469644f8 --- /dev/null +++ b/src/common/encode/base64.h @@ -0,0 +1,20 @@ +/*********************************************************************************************************************************** +Base64 Binary to String Encode/Decode + +The high-level functions in encode.c should be used in preference to these low-level functions. +***********************************************************************************************************************************/ +#ifndef BASE64_H +#define BASE64_H + +#include "common/type.h" + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +void encodeToStrBase64(const unsigned char *source, int sourceSize, char *destination); +int encodeToStrSizeBase64(int sourceSize); +void decodeToBinBase64(const char *source, unsigned char *destination); +int decodeToBinSizeBase64(const char *source); +void decodeToBinValidateBase64(const char *source); + +#endif diff --git a/src/common/errorType.c b/src/common/errorType.c index d981c0f90..eebd86920 100644 --- a/src/common/errorType.c +++ b/src/common/errorType.c @@ -30,6 +30,7 @@ Define errors ***********************************************************************************************************************************/ ERROR_DEFINE(ERROR_CODE_MIN, AssertError, RuntimeError); +ERROR_DEFINE(ERROR_CODE_MIN + 04, FormatError, RuntimeError); ERROR_DEFINE(ERROR_CODE_MIN + 69, MemoryError, RuntimeError); ERROR_DEFINE(ERROR_CODE_MAX, RuntimeError, RuntimeError); diff --git a/src/common/errorType.h b/src/common/errorType.h index a2e94ffbf..f1b275aa9 100644 --- a/src/common/errorType.h +++ b/src/common/errorType.h @@ -16,6 +16,7 @@ typedef struct ErrorType ErrorType; // Error types ERROR_DECLARE(AssertError); +ERROR_DECLARE(FormatError); ERROR_DECLARE(MemoryError); ERROR_DECLARE(RuntimeError); diff --git a/test/lib/pgBackRestTest/Common/DefineTest.pm b/test/lib/pgBackRestTest/Common/DefineTest.pm index 39ba2f292..42f156636 100644 --- a/test/lib/pgBackRestTest/Common/DefineTest.pm +++ b/test/lib/pgBackRestTest/Common/DefineTest.pm @@ -128,6 +128,22 @@ my $oTestDef = 'common/memContext' => TESTDEF_COVERAGE_FULL, }, }, + { + &TESTDEF_NAME => 'encode', + &TESTDEF_TOTAL => 1, + &TESTDEF_C => true, + + &TESTDEF_COVERAGE => + { + 'common/encode' => TESTDEF_COVERAGE_FULL, + 'common/encode/base64' => TESTDEF_COVERAGE_FULL, + }, + }, + { + &TESTDEF_NAME => 'encode-perl', + &TESTDEF_TOTAL => 1, + &TESTDEF_CLIB => true, + }, { &TESTDEF_NAME => 'http-client', &TESTDEF_TOTAL => 2, diff --git a/test/lib/pgBackRestTest/Module/Common/CommonEncodePerlTest.pm b/test/lib/pgBackRestTest/Module/Common/CommonEncodePerlTest.pm new file mode 100644 index 000000000..7f7b407cd --- /dev/null +++ b/test/lib/pgBackRestTest/Module/Common/CommonEncodePerlTest.pm @@ -0,0 +1,62 @@ +#################################################################################################################################### +# Test Binary to String Encode/Decode +#################################################################################################################################### +package pgBackRestTest::Module::Common::CommonEncodePerlTest; +use parent 'pgBackRestTest::Common::RunTest'; + +#################################################################################################################################### +# Perl includes +#################################################################################################################################### +use strict; +use warnings FATAL => qw(all); +use Carp qw(confess); +use English '-no_match_vars'; + +use pgBackRest::Common::Exception; +use pgBackRest::Common::Ini; +use pgBackRest::Common::Log; +use pgBackRest::LibC qw(:encode); +use pgBackRest::Version; + +use pgBackRestTest::Common::ExecuteTest; +use pgBackRestTest::Common::RunTest; + +#################################################################################################################################### +# run +#################################################################################################################################### +sub run +{ + my $self = shift; + + ################################################################################################################################ + if ($self->begin("encodeToStrBase64() and decodeToBinBase64()")) + { + my $strData = 'string_to_encode'; + my $strEncodedData = 'c3RyaW5nX3RvX2VuY29kZQ=='; + + $self->testResult(sub {encodeToStr(ENCODE_TYPE_BASE64, $strData)}, $strEncodedData, 'encode string'); + $self->testResult(sub {decodeToBin(ENCODE_TYPE_BASE64, $strEncodedData)}, $strData, 'decode string'); + + #--------------------------------------------------------------------------------------------------------------------------- + my $tData = + pack('C', 1) . pack('C', 2) . pack('C', 3) . pack('C', 4) . pack('C', 5) . pack('C', 4) . pack('C', 3) . pack('C', 2); + my $tEncodedData = 'AQIDBAUEAwI='; + + $self->testResult(sub {encodeToStr(ENCODE_TYPE_BASE64, $tData)}, $tEncodedData, 'encode binary'); + $self->testResult(sub {decodeToBin(ENCODE_TYPE_BASE64, $tEncodedData)}, $tData, 'decode binary'); + + #--------------------------------------------------------------------------------------------------------------------------- + $tData .= pack('C', 1); + $tEncodedData = 'AQIDBAUEAwIB'; + + $self->testResult(sub {encodeToStr(ENCODE_TYPE_BASE64, $tData)}, $tEncodedData, 'encode binary'); + $self->testResult(sub {decodeToBin(ENCODE_TYPE_BASE64, $tEncodedData)}, $tData, 'decode binary'); + + #--------------------------------------------------------------------------------------------------------------------------- + $self->testException(sub {encodeToStr(-1, '')}, ERROR_ASSERT, 'invalid encode type -1'); + $self->testException( + sub {decodeToBin(ENCODE_TYPE_BASE64, "XX")}, ERROR_FORMAT, 'base64 size 2 is not evenly divisible by 4'); + } +} + +1; diff --git a/test/src/module/common/encodeTest.c b/test/src/module/common/encodeTest.c new file mode 100644 index 000000000..1ca8d30fb --- /dev/null +++ b/test/src/module/common/encodeTest.c @@ -0,0 +1,105 @@ +/*********************************************************************************************************************************** +Test Binary to String Encode/Decode +***********************************************************************************************************************************/ + +/*********************************************************************************************************************************** +Test Run +***********************************************************************************************************************************/ +void testRun() +{ + // ----------------------------------------------------------------------------------------------------------------------------- + if (testBegin("base64")) + { + char *source = "string_to_encode\r\n"; + unsigned char destination[256]; + + encodeToStr(encodeBase64, source, 1, destination); + TEST_RESULT_STR(destination, "c3==", "1 character encode"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, 1), strlen(destination), "check size"); + + encodeToStr(encodeBase64, source, 2, destination); + TEST_RESULT_STR(destination, "c3R=", "2 character encode"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, 2), strlen(destination), "check size"); + + encodeToStr(encodeBase64, source, 3, destination); + TEST_RESULT_STR(destination, "c3Ry", "3 character encode"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, 3), strlen(destination), "check size"); + + encodeToStr(encodeBase64, source, strlen(source) - 2, destination); + TEST_RESULT_STR(destination, "c3RyaW5nX3RvX2VuY29kZQ==", "encode full string"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, strlen(source) - 2), strlen(destination), "check size"); + + encodeToStr(encodeBase64, source, strlen(source), destination); + TEST_RESULT_STR(destination, "c3RyaW5nX3RvX2VuY29kZQ0K", "encode full string with \\r\\n"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, strlen(source)), strlen(destination), "check size"); + + encodeToStr(encodeBase64, source, strlen(source) + 1, destination); + TEST_RESULT_STR(destination, "c3RyaW5nX3RvX2VuY29kZQ0KAG==", "encode full string with \\r\\n and null"); + TEST_RESULT_INT(encodeToStrSize(encodeBase64, strlen(source) + 1), strlen(destination), "check size"); + + TEST_ERROR(encodeToStr(999, source, strlen(source), destination), AssertError, "invalid encode type 999"); + TEST_ERROR(encodeToStrSize(999, strlen(source)), AssertError, "invalid encode type 999"); + + // ------------------------------------------------------------------------------------------------------------------------- + memset(destination, 0xFF, sizeof(destination)); + char *decode = "c3RyaW5nX3RvX2VuY29kZQ0KAG=="; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_STR(destination, source, "full string with \\r\\n and null decode"); + TEST_RESULT_INT(destination[strlen(source) + 1], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), strlen(source) + 1, "check size"); + + memset(destination, 0xFF, sizeof(destination)); + decode = "c3RyaW5nX3RvX2VuY29kZQ0K"; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_INT(memcmp(destination, source, strlen(source)), 0, "full string with \\r\\n decode"); + TEST_RESULT_INT(destination[strlen(source)], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), strlen(source), "check size"); + + memset(destination, 0xFF, sizeof(destination)); + decode = "c3RyaW5nX3RvX2VuY29kZQ=="; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_INT(memcmp(destination, source, strlen(source) - 2), 0, "full string decode"); + TEST_RESULT_INT(destination[strlen(source) - 2], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), strlen(source) - 2, "check size"); + + memset(destination, 0xFF, sizeof(destination)); + decode = "c3Ry"; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_INT(memcmp(destination, source, 3), 0, "3 character decode"); + TEST_RESULT_INT(destination[3], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), 3, "check size"); + + memset(destination, 0xFF, sizeof(destination)); + decode = "c3R="; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_INT(memcmp(destination, source, 2), 0, "2 character decode"); + TEST_RESULT_INT(destination[2], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), 2, "check size"); + + memset(destination, 0xFF, sizeof(destination)); + decode = "c3=="; + decodeToBin(encodeBase64, decode, destination); + TEST_RESULT_INT(memcmp(destination, source, 1), 0, "1 character decode"); + TEST_RESULT_INT(destination[1], 0xFF, "check for overrun"); + TEST_RESULT_INT(decodeToBinSize(encodeBase64, decode), 1, "check size"); + + TEST_ERROR(decodeToBin(-1, decode, destination), AssertError, "invalid encode type -1"); + TEST_ERROR(decodeToBinSize(-1, decode), AssertError, "invalid encode type -1"); + TEST_ERROR(decodeToBin(encodeBase64, "cc$=", destination), FormatError, "base64 invalid character found at position 2"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_ERROR(decodeToBinValidate(encodeBase64, "c3"), FormatError, "base64 size 2 is not evenly divisible by 4"); + TEST_ERROR( + decodeToBinValidate(encodeBase64, "c==="), FormatError, "base64 '=' character may only appear in last two positions"); + TEST_ERROR( + decodeToBinValidate(encodeBase64, "cc=c"), FormatError, "base64 last character must be '=' if second to last is"); + + TEST_ERROR(decodeToBinValidate(9999, "cc=c"), AssertError, "invalid encode type 9999"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_BOOL(decodeToBinValid(encodeBase64, "CCCCCCCCCCC"), false, "base64 string not valid"); + TEST_RESULT_BOOL(decodeToBinValid(encodeBase64, "CCCCCCCCCCCC"), true, "base64 string valid"); + + TEST_ERROR(decodeToBinValid(-999, "CCCCCCCCCCCC"), AssertError, "invalid encode type -999"); + } +}