/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * 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.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * 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 OWNER 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 <opencm.h>

const char b64chars[] = 
  "ABCDEFGHIJKLMNOP"
  "QRSTUVWXYZabcdef"
  "ghijklmnopqrstuv"
  "wxyz0123456789+-";	/* not slash! */

static unsigned char 
b64_lookup(unsigned char c)
{
  if (isupper(c))
    return c - 'A';
  else if (islower(c))
    return c - 'a' + 26;
  else if (isdigit(c))
    return c - '0' + 52;
  else if (c == '+')
    return 62;
  else if (c == '-')
    return 63;
  else
    THROW(ExMalformed, format("Invalid base64 character %c", c));
}

void *
b64_decode(const char *s, uint32_t *inlen)
{
  uint32_t len = *inlen;
  uint32_t outlen = (len * 6) / 8;
  unsigned char *outbuf = GC_MALLOC_ATOMIC(outlen);
  char *pBuf = outbuf;

  while (len) {
    uint32_t value = 0;

    if (len >= 4) {
      value = (b64_lookup(s[3]) << 18 |
	       b64_lookup(s[2]) << 12 |
	       b64_lookup(s[1]) << 6 |
	       b64_lookup(s[0]) << 0);
      s += 4;
    }
    else if (len == 3) {
      value = (b64_lookup(s[2]) << 12 |
	       b64_lookup(s[1]) << 6 |
	       b64_lookup(s[0]) << 0);
      s += 3;
    }
    else if (len == 2) {
      value = (b64_lookup(s[1]) << 6 |
	       b64_lookup(s[0]) << 0);
      s += 2;
    }

    /* Len is *always* > 1 */

    if (len == 1)
      THROW(ExBadValue, "b64 string truncated");

    if (len >= 4) {
      *pBuf++ = value & 0xffu;
      *pBuf++ = (value >> 8) & 0xffu;
      *pBuf++ = (value >> 16) & 0xffu;
    }
    else if (len == 3) {
      *pBuf++ = value & 0xffu;
      *pBuf++ = (value >> 8) & 0xffu;
    }
    else if (len == 2) {
      *pBuf++ = value & 0xffu;
    }

    len -= min(len, 4);
  }

  *inlen = outlen;
  return outbuf;
}

char *
b64_encode(const void *vp, uint32_t len)
{
  unsigned const char *cp = vp;
  uint32_t outlen = (len*8 + 5) / 6;
  char *outbuf = GC_MALLOC_ATOMIC(outlen + 1);
  char *pBuf = outbuf;

  while (len) {
    uint32_t take = min(len, 3);
    uint32_t value = 0;
    uint32_t outlen = (take * 8 + 5) / 6;
    uint32_t i;

    if (take == 3) {
      value = cp[2];
      value <<= 8;
    }
    if (take >= 2) {
      value |= cp[1];
      value <<= 8;
    }
    value |= cp[0];
    
    for (i = 0; i < outlen; i++) {
      *pBuf++ = b64chars[value & 0x3f];
      value >>= 6;
    }

    len -= take;
    cp += 3;
  }

  *pBuf = 0;
  return outbuf;
}

static unsigned
fromhex(char c)
{
  if (c >= '0' && c <= '9')
    return c - '0';
  else if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  else if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  else
    THROW(ExMalformed, format("Invalid hex character %c", c));
}

/* Return decoded sequence of bytes along
 * with length of that sequence in 'len'*/
void *
hex_decode(const char *s, uint32_t *len)
{
  unsigned char *b;
  uint32_t u, w;
  uint32_t z = strlen(s);

  /* An odd sized hex string can be perfectly legal in some contexts, but probably
     not in anything related to OpenCM.
  */
  if(z % 2 == 1)
    THROW(ExMalformed, "Hexadecimal strings must be even length");

  *len = z / 2;
  b = GC_MALLOC_ATOMIC(*len);

  w = 0;
  for (u = 0; u < z; u=u+2) {
    char c_upper = s[u];
    char c_lower = s[u+1];

    b[w] = fromhex(c_upper) << 4;
    b[w] |= fromhex(c_lower);
    w++;
  }

  return b;
}

/* Return encoded string that represents
 * 'len' bytes beginning at buf: */
char *
hex_encode(const void *vp, uint32_t len)
{
  static char *hex_digits = "0123456789abcdef";
  uint32_t u, w;
  const unsigned char *buf = vp;
  char *b = GC_MALLOC_ATOMIC(2*len+1);

  w = 0;
  for (u = 0; u < len; u++) {
    unsigned char upper = (buf[u] >> 4) & 0xfu;
    unsigned char lower = buf[u] & 0xfu;
    b[w++] = hex_digits[upper];
    b[w++] = hex_digits[lower];
  }
  b[2*len] = 0;
  return b;
}
