/*
 *
 *  base64 encoding/decoding per RFC 4648
 *
 *   Copyright (C) 2013, 2016  Milan Kupcevic
 *   All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "basez.h"

static const unsigned char
code64[]   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const unsigned char
code64url[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

static const unsigned char c64d[] =
{
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 00 - 07 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 08 - 0f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 10 - 17 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 18 - 1f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 20 - 27 */
  0xff, 0xff, 0xff, 0x3e,    0xff, 0xff, 0xff, 0x3f, /* 28 - 2f */
  0x34, 0x35, 0x36, 0x37,    0x38, 0x39, 0x3a, 0x3b, /* 30 - 37 */
  0x3c, 0x3d, 0xff, 0xff,    0xff, 0xfe, 0xff, 0xff, /* 38 - 3f */
  0xff, 0x00, 0x01, 0x02,    0x03, 0x04, 0x05, 0x06, /* 40 - 47 */
  0x07, 0x08, 0x09, 0x0a,    0x0b, 0x0c, 0x0d, 0x0e, /* 48 - 4f */
  0x0f, 0x10, 0x11, 0x12,    0x13, 0x14, 0x15, 0x16, /* 50 - 57 */
  0x17, 0x18, 0x19, 0xff,    0xff, 0xff, 0xff, 0xff, /* 58 - 5f */
  0xff, 0x1a, 0x1b, 0x1c,    0x1d, 0x1e, 0x1f, 0x20, /* 60 - 67 */
  0x21, 0x22, 0x23, 0x24,    0x25, 0x26, 0x27, 0x28, /* 68 - 6f */
  0x29, 0x2a, 0x2b, 0x2c,    0x2d, 0x2e, 0x2f, 0x30, /* 70 - 77 */
  0x31, 0x32, 0x33, 0xff,    0xff, 0xff, 0xff, 0xff, /* 78 - 7f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 80 - 87 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 88 - 8f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 90 - 97 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 98 - 9f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* a0 - a7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* a8 - af */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* b0 - b7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* b8 - bf */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* c0 - c7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* c8 - cf */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* d0 - d7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* d8 - df */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* e0 - e7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* e8 - ef */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* f0 - f7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* f8 - ff */
};

static const unsigned char c64urld[] =
{
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 00 - 07 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 08 - 0f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 10 - 17 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 18 - 1f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 20 - 27 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0x3e, 0xff, 0xff, /* 28 - 2f */
  0x34, 0x35, 0x36, 0x37,    0x38, 0x39, 0x3a, 0x3b, /* 30 - 37 */
  0x3c, 0x3d, 0xff, 0xff,    0xff, 0xfe, 0xff, 0xff, /* 38 - 3f */
  0xff, 0x00, 0x01, 0x02,    0x03, 0x04, 0x05, 0x06, /* 40 - 47 */
  0x07, 0x08, 0x09, 0x0a,    0x0b, 0x0c, 0x0d, 0x0e, /* 48 - 4f */
  0x0f, 0x10, 0x11, 0x12,    0x13, 0x14, 0x15, 0x16, /* 50 - 57 */
  0x17, 0x18, 0x19, 0xff,    0xff, 0xff, 0xff, 0x3f, /* 58 - 5f */
  0xff, 0x1a, 0x1b, 0x1c,    0x1d, 0x1e, 0x1f, 0x20, /* 60 - 67 */
  0x21, 0x22, 0x23, 0x24,    0x25, 0x26, 0x27, 0x28, /* 68 - 6f */
  0x29, 0x2a, 0x2b, 0x2c,    0x2d, 0x2e, 0x2f, 0x30, /* 70 - 77 */
  0x31, 0x32, 0x33, 0xff,    0xff, 0xff, 0xff, 0xff, /* 78 - 7f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 80 - 87 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 88 - 8f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 90 - 97 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* 98 - 9f */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* a0 - a7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* a8 - af */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* b0 - b7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* b8 - bf */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* c0 - c7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* c8 - cf */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* d0 - d7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* d8 - df */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* e0 - e7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* e8 - ef */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* f0 - f7 */
  0xff, 0xff, 0xff, 0xff,    0xff, 0xff, 0xff, 0xff, /* f8 - ff */
};

static
void
enc_b64(
  const unsigned char *bin,
  const int benc,
  unsigned char *bout,
  const int code)
{
  const unsigned char *c;

  switch(code)
  {
    case 1:
      c = code64url;
      break;
    default:
      c = code64;
  }
 
  switch(benc)
  {
    case 3:
      bout[3] = c[bin[2] & 0x3f];
      bout[2] = c[(bin[1]<<2 & 0x3f) | bin[2]>>6];
    case 2:
      bout[1] = c[(bin[0]<<4 & 0x3f) | bin[1]>>4];
    case 1:
      bout[0] = c[bin[0]>>2];
  }

  switch(benc)
  {
    case 1:
      bout[1] = c[bin[0]<<4 & 0x3f];
      bout[2] = '=';
      bout[3] = '=';
      break;
    case 2:
      bout[2] = c[bin[1]<<2 & 0x3f];
      bout[3] = '=';
      break;
  }
}

static
int
dec_b64(
  const unsigned char *bin,
  unsigned char *bout,
  const int code)
{
  const unsigned char* c;

  switch(code)
  {
    case 1:
      c = c64urld;
      break;
    default:
      c = c64d;
  }
 
  switch(c[bin[0]])
  {
    case 0xfe:
    case 0xff:
      return 0;
    default:
      bout[0] = c[bin[0]] << 2;
  }

  switch(c[bin[1]])
  {
    case 0xfe:
    case 0xff:
      return 0;
    default:
      bout[0] = bout[0] | c[bin[1]] >> 4;
  }

  switch(c[bin[2]])
  {
    case 0xfe:
      if(bin[3] == '=')
        return 1;
    case 0xff:
      return 0;
    default:
      bout[1] = (c[bin[1]] << 4) | c[bin[2]] >> 2;
  }

  switch(c[bin[3]])
  {
    case 0xfe:
      return 2;
    case 0xff:
      return 0;
    default:
      bout[2] = (c[bin[2]] << 6) | c[bin[3]];
  }
  return 3;
}

void
encode_base64(
  const unsigned char *bin,
  const int benc,
  unsigned char *bout)
{
  enc_b64(bin, benc, bout, 0);
}

int
decode_base64(
  const unsigned char *bin,
  unsigned char *bout)
{
  return dec_b64(bin, bout, 0);
}

void
encode_base64url(
  const unsigned char *bin,
  const int benc,
  unsigned char *bout)
{
  enc_b64(bin, benc, bout, 1);
}

int
decode_base64url(
  const unsigned char *bin,
  unsigned char *bout)
{
  return dec_b64(bin, bout, 1);
}
