#include <stdio.h>
#include <stdint.h>

//Base64url encoding table
static const unsigned char base64urlEncTable[64] =
    {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'};

//Base64url decoding table
static const unsigned char base64urlDecTable[128] =
    {
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF,
        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
        0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F,
        0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
        0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

/**
  * @brief Base64url encoding algorithm
  * @param[in] input Input data to encode
  * @param[in] inputLen Length of the data to encode
  * @param[out] output NULL-terminated string encoded with Base64url algorithm
  * @param[out] outputLen Length of the encoded string (optional parameter)
  **/
void base64urlEncode(const unsigned char *input, size_t inputLen, unsigned char *output, size_t *outputLen)
{
  size_t n;
  unsigned char a;
  unsigned char b;
  unsigned char c;
  unsigned char d;
  const unsigned char *p;

  //Point to the first byte of the input data
  p = (const unsigned char *)input;

  //Divide the input stream into blocks of 3 bytes
  n = inputLen / 3;

  //A full encoding quantum is always completed at the end of a quantity
  if (inputLen == (n * 3 + 1))
  {
    //The final quantum of encoding input is exactly 8 bits
    if (input != NULL && output != NULL)
    {
      //Read input data
      a = (p[n * 3] & 0xFC) >> 2;
      b = (p[n * 3] & 0x03) << 4;

      //The final unit of encoded output will be two characters
      output[n * 4] = base64urlEncTable[a];
      output[n * 4 + 1] = base64urlEncTable[b];
      output[n * 4 + 2] = '\0';
    }

    //Length of the encoded string (excluding the terminating NULL)
    if (outputLen != NULL)
    {
      *outputLen = n * 4 + 2;
    }
  }
  else if (inputLen == (n * 3 + 2))
  {
    //The final quantum of encoding input is exactly 16 bits
    if (input != NULL && output != NULL)
    {
      //Read input data
      a = (p[n * 3] & 0xFC) >> 2;
      b = ((p[n * 3] & 0x03) << 4) | ((p[n * 3 + 1] & 0xF0) >> 4);
      c = (p[n * 3 + 1] & 0x0F) << 2;

      //The final unit of encoded output will be three characters followed
      //by one "=" padding character
      output[n * 4] = base64urlEncTable[a];
      output[n * 4 + 1] = base64urlEncTable[b];
      output[n * 4 + 2] = base64urlEncTable[c];
      output[n * 4 + 3] = '\0';
    }

    //Length of the encoded string (excluding the terminating NULL)
    if (outputLen != NULL)
    {
      *outputLen = n * 4 + 3;
    }
  }
  else
  {
    //The final quantum of encoding input is an integral multiple of 24 bits
    if (output != NULL)
    {
      //The final unit of encoded output will be an integral multiple of 4
      //characters
      output[n * 4] = '\0';
    }

    //Length of the encoded string (excluding the terminating NULL)
    if (outputLen != NULL)
    {
      *outputLen = n * 4;
    }
  }

  //If the output parameter is NULL, then the function calculates the
  //length of the resulting Base64url string without copying any data
  if (input != NULL && output != NULL)
  {
    //The input data is processed block by block
    while (n-- > 0)
    {
      //Read input data
      a = (p[n * 3] & 0xFC) >> 2;
      b = ((p[n * 3] & 0x03) << 4) | ((p[n * 3 + 1] & 0xF0) >> 4);
      c = ((p[n * 3 + 1] & 0x0F) << 2) | ((p[n * 3 + 2] & 0xC0) >> 6);
      d = p[n * 3 + 2] & 0x3F;

      //Map each 3-byte block to 4 printable characters using the Base64url
      //character set
      output[n * 4] = base64urlEncTable[a];
      output[n * 4 + 1] = base64urlEncTable[b];
      output[n * 4 + 2] = base64urlEncTable[c];
      output[n * 4 + 3] = base64urlEncTable[d];
    }
  }
}

/**
  * @brief Base64url decoding algorithm
  * @param[in] input Base64url-encoded string
  * @param[in] inputLen Length of the encoded string
  * @param[out] output Resulting decoded data
  * @param[out] outputLen Length of the decoded data
  * @return Error code
  **/

int base64urlDecode(const unsigned char *input, size_t inputLen, unsigned char *output, size_t *outputLen)
{
  uint32_t value;
  unsigned int c;
  size_t i;
  size_t n;
  unsigned char *p;
  // printf("input len= %zu\n", inputLen);
  //Check parameters
  if (input == NULL && inputLen != 0)
    return 1;
  if (outputLen == NULL)
    return 1;

  //Check the length of the input string
  if ((inputLen % 4) == 1)
    return 2;

  //Initialize status code
  int error = 0;

  //Point to the buffer where to write the decoded data
  p = (unsigned char *)output;

  //Initialize variables
  n = 0;
  value = 0;

  //Process the Base64url-encoded string
  for (i = 0; i < inputLen && !error; i++)
  {
    //Get current character
    c = (unsigned int)input[i];

    //Check the value of the current character
    if (c < 128 && base64urlDecTable[c] < 64)
    {
      //Decode the current character
      value = (value << 6) | base64urlDecTable[c];

      //Divide the input stream into blocks of 4 characters
      if ((i % 4) == 3)
      {
        //Map each 4-character block to 3 bytes
        if (p != NULL)
        {
          p[n] = (value >> 16) & 0xFF;
          p[n + 1] = (value >> 8) & 0xFF;
          p[n + 2] = value & 0xFF;
        }

        //Adjust the length of the decoded data
        n += 3;
        //Decode next block
        value = 0;
      }
    }
    else
    {
      //Implementations must reject the encoded data if it contains
      //characters outside the base alphabet
      error = 3;
    }
  }

  //Check status code
  if (!error)
  {
    //All trailing pad characters are omitted in Base64url
    if ((inputLen % 4) == 2)
    {
      //The last block contains only 1 byte
      if (p != NULL)
      {
        //Decode the last byte
        p[n] = (value >> 4) & 0xFF;
      }

      //Adjust the length of the decoded data
      n++;
    }
    else if ((inputLen % 4) == 3)
    {
      //The last block contains only 2 bytes
      if (p != NULL)
      {
        //Decode the last two bytes
        p[n] = (value >> 10) & 0xFF;
        p[n + 1] = (value >> 2) & 0xFF;
      }

      //Adjust the length of the decoded data
      n += 2;
    }
    else
    {
      //No pad characters in this case
    }
  }

  //Total number of bytes that have been written
  *outputLen = n;
  // printf("output len= %zu\n", n);

  //Return status code
  return error;
}
