[IOT-2097][IOT-2098] Cleanup of base64 code 87/19187/13
authorGeorge Nash <george.nash@intel.com>
Fri, 21 Apr 2017 05:48:41 +0000 (22:48 -0700)
committerRick Bell <richard.s.bell@intel.com>
Mon, 19 Jun 2017 18:23:30 +0000 (18:23 +0000)
[IOT-2097] According to RFC4648 Base64 can accept empty data.
Fixed error for input containing no data for b64Encode and
b64Decode.

[IOT-2098] Add error checking to b64Decode. The original code
would decode any string even if it contained characters that are
invalid for base 64 encoding. Code now checks that all
characters found in the encoded string are [A-Z][a-z][0-9]+/=

The padding character '=' is checked to only be at the end of
the encoded string.

Unit tests improved. They now include test vectors from RFC4648
Unit tests now cover failure cases. Not just pass cases.

Improved documentation
 - Add file documentation for base64.h file
 - Add reference to RFC4648.
 - Documented all B64Result enum values
 - Add [in],[out] markup for b64Encode function
 - Add code example for b64Encode function
 - Add [in],[out] markup for b64Decode function
 - Add code example for b64Decode function

Bug: https://jira.iotivity.org/browse/IOT-2097
Bug: https://jira.iotivity.org/browse/IOT-2098

Change-Id: I3d6167c85a766ae8200339f00ef5c978f0ec55b7
Signed-off-by: George Nash <george.nash@intel.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/19187
Tested-by: jenkins-iotivity <jenkins@iotivity.org>
Reviewed-by: Phil Coval <philippe.coval@osg.samsung.com>
Reviewed-by: Pawel Winogrodzki <pawelwi@microsoft.com>
Reviewed-by: Uze Choi <uzchoi@samsung.com>
Reviewed-by: Rick Bell <richard.s.bell@intel.com>
resource/csdk/security/include/base64.h
resource/csdk/security/src/base64.c
resource/csdk/security/unittest/base64tests.cpp

index d2cea9b..9685b72 100644 (file)
 extern "C" {
 #endif
 
+/**
+ * @file
+ * An implementation of Base 64 encoding. As defined in RFC4648 section 4.
+ *
+ * This enables encoding/decoding binary data into a 65-character subset
+ * of US-ASCII (64 characters plus a padding character).
+ *
+ * @note Only Base 64 encoding is implemented. Other encoding types
+ * defined in RFC4648 are not implemented. Base 64 encoding with URL
+ * and filename safe alphabet, Bass 32, and Base 16 Encoding are not
+ * implemented.
+ */
+
 /**
  * Macro to calculate the size of 'output' buffer required for
  * a 'input' buffer of length x during Base64 encoding operation.
@@ -45,41 +58,67 @@ extern "C" {
  */
 typedef enum
 {
+    /// The encode, decode operation was successful
     B64_OK = 0,
+    /// Invalid input parameter.
     B64_INVALID_PARAM,
+    /// The output buffer is not large enough for the encoded or decoded data.
     B64_OUTPUT_BUFFER_TOO_SMALL,
+    /// Indicates error encoding or decoding.
     B64_ERROR
 } B64Result;
 
 /**
  * Encode the plain message in base64.
  *
- * @param in is the plain message to be converted.
- * @param inLen is the byte length of plain message.
- * @param outBuf is the output buffer containing Base64 encoded message.
- * @note outBuf adds a NULL to the string configuration.
- * @param outBufSize is the size of output buffer.
- * @param outLen is the byte length of encoded message.
+ * Example usage
+ *
+ *     const uint8_t* input = { 0x00, 0x11, 0x22, 0x33 };
+ *     size_t b64Size = 0;
+ *     size_t b64BufSize = B64ENCODE_OUT_SAFESIZE(sizeof(input) / sizeof(uint8_t*));
+ *     char* b64Buf = (char*)OICCalloc(1, b64BufSize);
+ *     if(B64_OK != b64Encode(input, sizeof(input) / sizeof(uint8_t*),
+ *                            b64Buf, b64BufSize, &b64Size))
+ *     {
+ *         printf("Failure Base 64 encoding input\n");
+ *     }
+ *
+ * @param[in]  in is the plain message to be converted.
+ * @param[in]  inLen is the byte length of plain message.
+ * @param[out] outBuf is the output buffer containing Base64 encoded message.
+ * @param[in]  outBufSize is the size of output buffer.
+ * @param[out] outLen is the byte length of encoded message.
+ * @note `outBuf` adds a NULL to the string configuration.
  *
  * @return ::B64_OK for Success, otherwise some error value.
  */
-B64Result b64Encode(const uint8_tin, const size_t inLen,
-               char* outBuf, const size_t outBufSize, size_t* outLen);
+B64Result b64Encode(const uint8_t *in, const size_t inLen,
+                    char *outBuf, const size_t outBufSize, size_t *outLen);
 
 /**
  * Decode the encoded message in base64.
  *
- * @param in is the Base64 encoded message to be converted.
- * @param inLen is the byte length of the encoded message.
- * @param outBuf is the output buffer containing decoded message.
- * @note outBuf adds a NULL to the string configuration.
- * @param outBufSize is the size of output buffer.
- * @param outLen is the byte length of decoded message.
+ * Example usage
+ *
+ *     const char* b64EncodedData = "Zm9vYmFy";
+ *     size_t outLen;
+ *     size_t outBufLen = B64DECODE_OUT_SAFESIZE(strlen(b64EncodedData));
+ *     uint8_t* outBuf = (uint8_t*)OICCalloc(1, outBufSize);
+ *     if (B64_OK != b64Decode(b64EncodedData, strlen(b64EncodedData), outBuf, outBufLen, &outLen));
+ *     {
+ *          printf("Failure decoding Base 64 encoded data.\n");
+ *     }
+ *
+ * @param[in]  in is the Base64 encoded message to be converted.
+ * @param[in]  inLen is the byte length of the encoded message.
+ * @param[out] outBuf is the output buffer containing decoded message.
+ * @param[in]  outBufSize is the size of output buffer.
+ * @param[out] outLen is the byte length of decoded message.
  *
  * @return ::B64_OK for Success, otherwise some error value.
  */
-B64Result b64Decode(const charin, const size_t inLen,
-               uint8_t* outBuf, size_t outBufSize, size_t* outLen);
+B64Result b64Decode(const char *in, const size_t inLen,
+                    uint8_t *outBuf, size_t outBufSize, size_t *outLen);
 
 #ifdef __cplusplus
 }
index 8cd202a..dc2ea12 100644 (file)
@@ -80,7 +80,7 @@ static B64Result b64EncodeBlk(const uint8_t* in, char* out, size_t len)
 B64Result b64Encode(const uint8_t* in, const size_t inLen,
                char* outBuf, const size_t outBufSize, size_t* outLen)
 {
-    if (NULL == in || 0 == inLen || NULL ==  outBuf || NULL == outLen )
+    if (NULL == in || NULL == outBuf || NULL == outLen )
     {
         return B64_INVALID_PARAM;
     }
@@ -153,6 +153,54 @@ static uint32_t b64GetVal(char c)
     return 0;
 }
 
+/**
+ * Check if the input string is a valid Base64 encoded string.
+ * The string must have groups of 4 characters and only contain
+ * the US-ASCII characters `[A-Z][a-z][0-9]+/` and (pad) `=`
+ *
+ * @param[in] in is the Base64 encoded message to validate.
+ * @param[in] inLen is the byte length of the encoded message.
+ *
+ * @return
+ *    - B64_OK    if it is valid Base64 encoding string.
+ *    - B64_ERROR if string is not a valid Base64 encoded string.
+ */
+static B64Result isValidB64EncodedString(const char* in, const size_t inLen)
+{
+    // All valid Base64 encoded strings will be multiples of 4
+    if (0 != (inLen % 4))
+    {
+        return B64_ERROR;
+    }
+    for (size_t i = 0; i < inLen; ++i)
+    {
+        // "[A-Z][a-z][0-9]+/=" are the only valid
+        // letters in a Base64 encoded string
+        if ((('A' > in[i]) || ('Z' < in[i])) &&
+            (('a' > in[i]) || ('z' < in[i])) &&
+            (('0' > in[i]) || ('9' < in[i])) &&
+            ('+' != in[i]) &&
+            ('/' != in[i]) &&
+            ('=' != in[i]))
+        {
+            return B64_ERROR;
+        }
+        // Padding character "=" can only show up as last 2 characters
+        if ('=' == in[i])
+        {
+            if (i < inLen - 2)
+            {
+                return B64_ERROR;
+            }
+            if (i == inLen - 2 && '=' != in[i+1])
+            {
+                return B64_ERROR;
+            }
+        }
+    }
+    return B64_OK;
+}
+
 /**
  * Base64 block decode function.
  *
@@ -188,22 +236,30 @@ static B64Result b64DecodeBlk(const char* in, uint8_t* out)
 B64Result b64Decode(const char* in, const size_t inLen,
                uint8_t* outBuf, size_t outBufSize, size_t* outLen)
 {
-    if (NULL == in || 0 == inLen || 0 != (inLen & 0x03) || NULL == outBuf || NULL == outLen)
+    if (NULL == in || NULL == outBuf || NULL == outLen)
     {
         return B64_INVALID_PARAM;
     }
 
-    *outLen = (inLen / 4) * 3;
-    size_t minBufSize = (inLen / 4) * 3;
-    if ('=' == in[inLen - 1])
+    if (B64_OK != isValidB64EncodedString(in, inLen))
     {
-        minBufSize--;
-        (*outLen)--;
+        return B64_INVALID_PARAM;
     }
-    if ('=' == in[inLen - 2])
+
+    *outLen = (inLen / 4) * 3;
+    size_t minBufSize = (inLen / 4) * 3;
+    if (inLen != 0)
     {
-        minBufSize--;
-        (*outLen)--;
+        if ('=' == in[inLen - 1])
+        {
+            minBufSize--;
+            (*outLen)--;
+        }
+        if ('=' == in[inLen - 2])
+        {
+            minBufSize--;
+            (*outLen)--;
+        }
     }
     if (outBufSize < minBufSize)
     {
@@ -212,7 +268,7 @@ B64Result b64Decode(const char* in, const size_t inLen,
 
     for (size_t i = 0; i < inLen / 4; i++)
     {
-        if(B64_OK != b64DecodeBlk(in + i * 4, outBuf + i * 3))
+        if (B64_OK != b64DecodeBlk(in + i * 4, outBuf + i * 3))
         {
             return B64_INVALID_PARAM;
         }
@@ -220,4 +276,3 @@ B64Result b64Decode(const char* in, const size_t inLen,
 
     return B64_OK;
 }
-
index 1ae5287..ec15547 100644 (file)
@@ -1,41 +1,43 @@
- /******************************************************************
 *
 * Copyright 2015 Samsung Electronics All Rights Reserved.
 *
 *
 *
 * 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.
 *
 ******************************************************************/
+/******************************************************************
+ *
+ * Copyright 2015 Samsung Electronics All Rights Reserved.
+ *
+ *
+ *
+ * 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 <gtest/gtest.h>
 #include "base64.h"
+#include "oic_malloc.h"
 #include <stdlib.h>
 #include <stdint.h>
 
 // Tests for base64 encode function
-TEST(B64EncodeTest, ValidInputForEncoding)
+TEST(B64Test, ValidInputForEncoding)
 {
     char buf[128];
     size_t outputLength = 0;
     size_t i = 0;
     B64Result res = B64_OK;
 
-    const charinput = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
+    const char *input = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
 
     /**< expected output is generated from
              "http://www.convertstring.com/EncodeDecode/Base64Encode" */
-    const char* expectedOutput[53] = {
+    const char *expectedOutput[53] =
+    {
         "SQ==",
         "SW8=",
         "SW9U",
@@ -97,7 +99,7 @@ TEST(B64EncodeTest, ValidInputForEncoding)
 
         size_t expectedLength = strlen(expectedOutput[i]);
 
-        res = b64Encode((uint8_t*)input, i + 1, buf, 128, &outputLength);
+        res = b64Encode((const uint8_t *)input, i + 1, buf, 128, &outputLength);
 
         EXPECT_EQ(B64_OK, res);
         EXPECT_EQ(expectedLength, outputLength);
@@ -106,14 +108,15 @@ TEST(B64EncodeTest, ValidInputForEncoding)
 }
 
 // Tests for base64 decode function
-TEST(B64DeodeTest, ValidInputForDecoding)
+TEST(B64Test, ValidInputForDecoding)
 {
     uint8_t buf[128];
     size_t outputLength = 0;
     size_t i = 0;
     B64Result res = B64_OK;
 
-    const char* input[53] = {
+    const char *input[53] =
+    {
         "SQ==",
         "SW8=",
         "SW9U",
@@ -168,9 +171,9 @@ TEST(B64DeodeTest, ValidInputForDecoding)
         "SW9UaXZpdHkgYmFzZTY0fiFAIyQlXiYqKCktPTAxMjM0NTY3ODk8Pj87OidbXXt9LC4iXA==",
         "SW9UaXZpdHkgYmFzZTY0fiFAIyQlXiYqKCktPTAxMjM0NTY3ODk8Pj87OidbXXt9LC4iXHw="
     };
-    const charexpectedOutput = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
+    const char *expectedOutput = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
 
-    for (i = 0; i < (sizeof(input) / sizeof(char*)); i++)
+    for (i = 0; i < (sizeof(input) / sizeof(char *)); i++)
     {
         memset(buf, 0, sizeof(buf));
 
@@ -183,13 +186,14 @@ TEST(B64DeodeTest, ValidInputForDecoding)
 }
 
 // Tests for base64 decode function
-TEST(B64DeodeTest, InvalidInputForDecoding)
+TEST(B64Test, InvalidInputForDecoding)
 {
     uint8_t buf[128] = {0,};
     size_t outputLength = 0;
     size_t i = 0;
 
-    const char* input[53] = {
+    const char *input[53] =
+    {
         "SQ=",
         "Sw8=",
         "SW1U",
@@ -244,9 +248,9 @@ TEST(B64DeodeTest, InvalidInputForDecoding)
         "SW9UaXZpdHkgYmFzZTY0fiFAIyQlXiYqKCktPTAxMjM0NTY3ODk8Pj87OidbXXt9lc4iXA==",
         "SW9UaXZpdHkgYmFzZTY0fiFqKCktPTAxMjM0NTY3ODk8Pj87OidbXXt9LC4ixHw="
     };
-    const charexpectedOutput = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
+    const char *expectedOutput = "IoTivity base64~!@#$%^&*()-=0123456789<>?;:'[]{},.\"\\|";
 
-    for (i = 0; i < (sizeof(input) / sizeof(char*)); i++)
+    for (i = 0; i < (sizeof(input) / sizeof(char *)); i++)
     {
         memset(buf, 0, sizeof(buf));
 
@@ -257,3 +261,282 @@ TEST(B64DeodeTest, InvalidInputForDecoding)
     }
 }
 
+/*
+ * Expected input and output comes from section 10 of RFC4648
+ */
+TEST(B64Test, RFC4648_EncodeTestVectors)
+{
+    char buf[128];
+    size_t outputLength = 0;
+    B64Result b64Result;
+    const char *input[] =
+    {
+        "",
+        "f",
+        "fo",
+        "foo",
+        "foob",
+        "fooba",
+        "foobar"
+    };
+
+    const char *output[] =
+    {
+        "",
+        "Zg==",
+        "Zm8=",
+        "Zm9v",
+        "Zm9vYg==",
+        "Zm9vYmE=",
+        "Zm9vYmFy"
+    };
+
+    const size_t expectedOutputLenth[] =
+    {
+        0,
+        4,
+        4,
+        4,
+        8,
+        8,
+        8
+    };
+
+    size_t bufSize = (sizeof(buf)/sizeof(buf[0]));
+    size_t inputArraySize = (sizeof(input) / sizeof(input[0]));
+    size_t outputArraySize = (sizeof(output) / sizeof(output[0]));
+    size_t expectedOutputLenthArraySize = (sizeof(expectedOutputLenth) / sizeof(
+            expectedOutputLenth[0]));
+
+    ASSERT_EQ(inputArraySize, outputArraySize)
+            << "Input test data and output test data missmatch.";
+    ASSERT_EQ(inputArraySize, expectedOutputLenthArraySize)
+            << "Input test data and output test data missmatch.";
+    for (size_t i = 0; i < inputArraySize; ++i)
+    {
+        b64Result = b64Encode((const uint8_t *)input[i], strlen(input[i]), buf, bufSize, &outputLength);
+        EXPECT_EQ(B64_OK, b64Result)
+                << "Failed to Base64 encode \"" << input[i] << "\" to \"" << output[i] << "\"";
+        EXPECT_EQ(0, outputLength % 4)
+                << "The return size for all b64Encode operations should be a multiple of 4.";
+        EXPECT_STREQ(output[i], buf)
+                << "Failed to Base64 encode \"" << input[i] << "\" to \"" << output[i] << "\"";
+        EXPECT_EQ(expectedOutputLenth[i], outputLength);
+    }
+}
+
+
+TEST(B64Test, RFC4648_DecodeTestVectors)
+{
+    uint8_t buf[128] = {0,};
+    size_t outputLength = 0;
+    B64Result b64Result;
+
+    const char *input[] =
+    {
+        "",
+        "Zg==",
+        "Zm8=",
+        "Zm9v",
+        "Zm9vYg==",
+        "Zm9vYmE=",
+        "Zm9vYmFy"
+    };
+
+    const char *output[] =
+    {
+        "",
+        "f",
+        "fo",
+        "foo",
+        "foob",
+        "fooba",
+        "foobar"
+    };
+
+    const size_t expectedOutputLenth[] =
+    {
+        0,
+        1,
+        2,
+        3,
+        4,
+        5,
+        6
+    };
+
+    size_t bufSize = (sizeof(buf)/sizeof(buf[0]));
+    size_t inputArraySize = (sizeof(input) / sizeof(input[0]));
+    size_t outputArraySize = (sizeof(output) / sizeof(output[0]));
+    size_t expectedOutputLenthArraySize = (sizeof(expectedOutputLenth) / sizeof(
+            expectedOutputLenth[0]));
+
+    ASSERT_EQ(inputArraySize, outputArraySize)
+            << "Input test data and output test data missmatch.";
+    ASSERT_EQ(inputArraySize, expectedOutputLenthArraySize)
+            << "Input test data and output test data missmatch.";
+
+    for (size_t i = 0; i < inputArraySize; ++i)
+    {
+        b64Result = b64Decode(input[i], strlen(input[i]), buf, bufSize, &outputLength);
+        EXPECT_EQ(B64_OK, b64Result)
+                << "Failed to Base64 decode \"" << input[i] << "\" to \"" << output[i] << "\"";
+        EXPECT_STREQ(output[i], (char *)buf)
+                << "Failed to Base64 decode \"" << input[i] << "\" to \"" << output[i] << "\"";
+        EXPECT_EQ(expectedOutputLenth[i], outputLength);
+    }
+}
+
+TEST(B64Test, DecodeInputMissingPadding)
+{
+    uint8_t buf[128] = {0,};
+    size_t outputLength = 0;
+    B64Result b64Result;
+
+    const char *input[] =
+    {
+        "Zg",
+        "Zg="
+    };
+
+    size_t bufSize = (sizeof(buf)/sizeof(buf[0]));
+    size_t inputArraySize = (sizeof(input) / sizeof(input[0]));
+
+    for (size_t i = 0; i < inputArraySize; ++i)
+    {
+        b64Result = b64Decode(input[i], strlen(input[i]), buf, bufSize, &outputLength);
+        EXPECT_EQ(B64_INVALID_PARAM, b64Result)
+                << "Base64 decode for \"" << input[i] << "\" did not fail as expected.";
+    }
+}
+
+TEST(B64Test, DecodeInputInvalidCharacters)
+{
+    uint8_t buf[128] = {0,};
+    size_t outputLength = 0;
+    B64Result b64Result;
+    // Characters '-' and '_' chosen because the are part of other encoding
+    // standards, other characters chosen at random just to increase test
+    // coverage
+    const char *input[] =
+    {
+        "-a==",
+        "_a==",
+        "&a==",
+        "<>=="
+    };
+
+    size_t bufSize = (sizeof(buf)/sizeof(buf[0]));
+    size_t inputArraySize = (sizeof(input) / sizeof(input[0]));
+
+    for (size_t i = 0; i < inputArraySize; ++i)
+    {
+        b64Result = b64Decode(input[i], strlen(input[i]), buf, bufSize, &outputLength);
+        EXPECT_EQ(B64_INVALID_PARAM, b64Result)
+                << "Base64 decode for \"" << input[i] << "\" did not fail as expected.";
+    }
+}
+
+TEST(B64Test, DecodeInputInvalidPadding)
+{
+    uint8_t buf[128] = {0,};
+    size_t outputLength = 0;
+    B64Result b64Result;
+    const char *input[] =
+    {
+        "Zg==Zg==", // Invalid padding in middle of encoded string
+        "Zm8=Zm8=", // Invalid padding in middle of encoded string
+        "Z===", // Invalid padding max padding for Base64 string is two '=='
+        "====", // Invalid padding max padding for Base64 string is two '=='
+        "Zm=v" // Invalid padding no characters should follow padding
+    };
+
+    size_t bufSize = (sizeof(buf)/sizeof(buf[0]));
+    size_t inputArraySize = (sizeof(input) / sizeof(input[0]));
+
+    for (size_t i = 0; i < inputArraySize; ++i)
+    {
+        b64Result = b64Decode(input[i], strlen(input[i]), buf, bufSize, &outputLength);
+        EXPECT_EQ(B64_INVALID_PARAM, b64Result)
+                << "Base64 decode for \"" << input[i] << "\" did not fail as expected.";
+    }
+}
+
+// verify round trip encoding
+TEST(B64Test, EncodeThenDecode)
+{
+
+    const char input[] = "This is a string that will be passed into  the Base64 "
+                         "encoder.  After it is encoded the encoded result will "
+                         "be passed into the Base64 decoded and the result will "
+                         "be checked with the original input to make sure the "
+                         "round trip results are as expected.";
+
+    size_t b64Size = 0;
+    // encode the null character at the end of the string.
+    size_t b64BufSize = B64ENCODE_OUT_SAFESIZE(sizeof(input));
+    char *b64Buf = (char *)OICCalloc(1, b64BufSize);
+    ASSERT_NE(nullptr, b64Buf) << "memory allocation error.";
+    EXPECT_EQ(B64_OK, b64Encode((const uint8_t *)input, sizeof(input), b64Buf, b64BufSize, &b64Size));
+    EXPECT_EQ(0, b64Size % 4) <<
+                              "The return size for all b64Encode operations should be a multiple of 4.";
+
+    size_t outSize;
+    size_t outBufSize = B64DECODE_OUT_SAFESIZE(b64Size);
+    uint8_t *outBuf = (uint8_t *)OICCalloc(1, outBufSize);
+    if (nullptr == outBuf)
+    {
+        OICFree(b64Buf);
+    }
+    ASSERT_NE(nullptr, outBuf) << "memory allocation error.";
+    EXPECT_EQ(B64_OK, b64Decode(b64Buf, b64Size, outBuf, outBufSize, &outSize));
+    EXPECT_STREQ(input, (char *)outBuf);
+    OICFree(b64Buf);
+    OICFree(outBuf);
+}
+
+TEST(B64Test, Test_B64ENCODE_OUT_SAFESIZE_macro)
+{
+    EXPECT_EQ(1, B64ENCODE_OUT_SAFESIZE(0)) << "macro tested B64ENCODE_OUT_SAFESIZE(0)";
+    EXPECT_EQ(5, B64ENCODE_OUT_SAFESIZE(1)) << "macro tested B64ENCODE_OUT_SAFESIZE(1)";
+    EXPECT_EQ(5, B64ENCODE_OUT_SAFESIZE(2)) << "macro tested B64ENCODE_OUT_SAFESIZE(2)";
+    EXPECT_EQ(5, B64ENCODE_OUT_SAFESIZE(3)) << "macro tested B64ENCODE_OUT_SAFESIZE(3)";
+    EXPECT_EQ(9, B64ENCODE_OUT_SAFESIZE(4)) << "macro tested B64ENCODE_OUT_SAFESIZE(4)";
+    EXPECT_EQ(9, B64ENCODE_OUT_SAFESIZE(5)) << "macro tested B64ENCODE_OUT_SAFESIZE(5)";
+    EXPECT_EQ(9, B64ENCODE_OUT_SAFESIZE(6)) << "macro tested B64ENCODE_OUT_SAFESIZE(6)";
+    EXPECT_EQ(13, B64ENCODE_OUT_SAFESIZE(7)) << "macro tested B64ENCODE_OUT_SAFESIZE(7)";
+    EXPECT_EQ(13, B64ENCODE_OUT_SAFESIZE(8)) << "macro tested B64ENCODE_OUT_SAFESIZE(8)";
+    EXPECT_EQ(13, B64ENCODE_OUT_SAFESIZE(9)) << "macro tested B64ENCODE_OUT_SAFESIZE(8)";
+    EXPECT_EQ(17, B64ENCODE_OUT_SAFESIZE(10)) << "macro tested B64ENCODE_OUT_SAFESIZE(10)";
+}
+
+TEST(B64Test, Test_B64DECODE_OUT_SAFESIZE_macro)
+{
+    // Valid Base64 encode string is multiples of 4 in size.
+    EXPECT_EQ(0, B64DECODE_OUT_SAFESIZE(0)) << "macro tested B64DECODE_OUT_SAFESIZE(0)";
+    EXPECT_EQ(3, B64DECODE_OUT_SAFESIZE(4)) << "macro tested B64DECODE_OUT_SAFESIZE(4)";
+    EXPECT_EQ(6, B64DECODE_OUT_SAFESIZE(8)) << "macro tested B64DECODE_OUT_SAFESIZE(8)";
+    EXPECT_EQ(9, B64DECODE_OUT_SAFESIZE(12)) << "macro tested B64DECODE_OUT_SAFESIZE(12)";
+    EXPECT_EQ(12, B64DECODE_OUT_SAFESIZE(16)) << "macro tested B64DECODE_OUT_SAFESIZE(16)";
+    EXPECT_EQ(15, B64DECODE_OUT_SAFESIZE(20)) << "macro tested B64DECODE_OUT_SAFESIZE(20)";
+}
+
+TEST(B64Test, EncodeBufferTooSmall)
+{
+    // buffer too small the size must be 5. 4 for the first encoded byte + 1
+    // for '\0' at end of string
+    const char *input = "A";
+    char b64Buf[4];
+    size_t b64Size = 0;
+    EXPECT_EQ(B64_OUTPUT_BUFFER_TOO_SMALL, b64Encode((const uint8_t *)input, strlen(input),
+              b64Buf, sizeof(b64Buf) / sizeof(char *), &b64Size));
+}
+
+TEST(B64Test, DecodeBufferTooSmall)
+{
+    uint8_t buf[2] = {0,};
+    size_t outputLength = 0;
+    const char *input = "Zm9v"; //Decodes to "foo"
+    EXPECT_EQ(B64_OUTPUT_BUFFER_TOO_SMALL, b64Decode(input, strlen(input),
+              buf, sizeof(buf) / sizeof(uint8_t), &outputLength));
+}