/* ia5.c -- This code will do IA-5 encoding and decoding of strings
 * and characters.
 *
 * Created by:	Derek Atkins <warlord@MIT.EDU>
 *
 * $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/ia5.c,v $
 * $Author: warlord $
 *
 */

#include <warlord-copyright.h>

#if !defined(lint) && !defined(SABER)
static char rcsid_ia5_c[] = "$Id: ia5.c,v 1.4 93/12/11 15:15:15 warlord Exp $";
#endif

#include <stdio.h>
#include <sys/types.h>
#include <charon_prot.h>
#include <charon_err.h>

#define NUM_CHARS 256		/* There are 256 characters in the character-set */
#define NUM_IA5_CHARS 64	/* 64 characters in IA5 alphabet */

/* IA5 encoding (from RFC 1113):
**   Value Encoding  Value Encoding  Value Encoding  Value Encoding
**       0 A            17 R            34 i            51 z
**       1 B            18 S            35 j            52 0
**       2 C            19 T            36 k            53 1
**       3 D            20 U            37 l            54 2
**       4 E            21 V            38 m            55 3
**       5 F            22 W            39 n            56 4
**       6 G            23 X            40 o            57 5
**       7 H            24 Y            41 p            58 6
**       8 I            25 Z            42 q            59 7
**       9 J            26 a            43 r            60 8
**      10 K            27 b            44 s            61 9
**      11 L            28 c            45 t            62 +
**      12 M            29 d            46 u            63 /
**      13 N            30 e            47 v
**      14 O            31 f            48 w         (pad) =
**      15 P            32 g            49 x
**      16 Q            33 h            50 y           
*/

static char EncodeTable[] = {
  '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', '+', '/'
  };

static u_char DecodeTable[NUM_CHARS];
static int TableInited = 0;

#define ENCODE_HEX(a) (EncodeTable[a]) /* Convert 6-bit number to character */
#define DECODE_HEX(a) (DecodeTable[a]) /* Convert character to 6-bit number */
#define PAD_CHAR '='		/* Padding character */

/* Get the six highest bits from the first byte, and shift it to make
 * a six-bit number.  This is from the octet string c at offset o
 */
#define CHAR_ONE(c, o) ((c[o] & 0xFC) >> 2) 

/* Get the low two bits, move them over to the top two bits of a six-bit
 * number, and return that
 */
#define CHAR_TWO_OF_TWO(c, o) ((c[o] & 0x03) << 4)

/* Get the low two bits of the first byte and the high 4 bits of the second
 * byte and make that into a single six-bit number.
 */
#define CHAR_TWO(c, o) (CHAR_TWO_OF_TWO(c, o) | ((c[o+1] & 0xF0) >> 4))

/* Get the low 4 bits of the second byte and make it the top four bits
 * of a six-bit number.
 */
#define CHAR_THREE_OF_THREE(c, o) ((c[o+1] & 0x0F) << 2)

/* Get the 4 low bits of the second byte and the two high bits of the
 * third byte, and combine them into a single six-bit number.
 */
#define CHAR_THREE(c, o) (CHAR_THREE_OF_THREE(c, o) | ((c[o+2] & 0xC0) >> 6))

/* Get the low 6 bits of the third byte */
#define CHAR_FOUR(c, o) (c[o+2] & 0x3F)

/* Get the first byte two six-bit numbers, the first, and the top two
 * bits of the second.
 */
#define BYTE_ONE(c, o) ((DECODE_HEX(c[o]) << 2) | \
			((DECODE_HEX(c[o+1]) & 0x30) >> 4))

/* Get the second byte from the second and third six-bit values */
#define BYTE_TWO(c, o) (((DECODE_HEX(c[o+1]) & 0x0F) << 4) | \
			((DECODE_HEX(c[o+2]) & 0x3C) >> 2))

/* Get the third byte from the third and fourth six-bit values */
#define BYTE_THREE(c, o) (((DECODE_HEX(c[o+2]) & 0x03) << 6) | \
			  DECODE_HEX(c[o+3]))

void ia5_build_decode_table();

/* Requires:	data be valid 8-bit data of length datalen, encoded have 
 *		the length of encodedlen, which is at least 
 *		(((datalen+2)/3)*4) bytes long
 * Modifies:	encoded and encodedlen
 * Effects:	This will take data, of length datalen, and convert it
 *		from 8-bit to 6-bit using the IA5 encoding scheme
 *		explained in RFC 1113.  It will place the IA5 encoded
 *		data into encoded, and will set encodedlen to the length
 *		of this string.
 */
int
ia5_encode_data(u_char *data, u_int datalen, char *encoded, u_int *encodedlen) 
{
  int count;
  int extra_bytes;		/* 0, 1, or 2 */
  int final_length = 0;		/* For encoded length */
  
  /* Check to make sure we have enough space, just in case someone doesn't
   * understand what a requires clause is all about. ;-)
   */
  if (encodedlen != NULL)
    if (*encodedlen <= ((datalen + 2)/3)*4)
      return (CRN_ERR_DATALEN);


  /* We need to parse the data in blocks of 3 bytes, which will
   * get converted into blocks of 4 characters.  The output will
   * ALWAYS be in blocks of 4 characters, possibly using the PAD_CHAR
   * as filler, if neccessary, to make the 4 characters.
   */

  /* Iterate, Obliterate, Obfuscate, Alleviate.... */ 
  for (count = 0; count < datalen; count += 3) {

    /* Figure out if we really have 3 data bytes, or is this the
     * end.  If its the end, then do we have to pad the data?
     */
    if (datalen - count >= 3)
      extra_bytes = 0;
    else
      extra_bytes = datalen % 3; /* 0, 1, or 2 */
    
    /* We are now going to take care of 24 bits worth
     * of data.  If we do not HAVE 24 bits worth of 
     * data, then we will finish off with padding characters
     *
     * There will ALWAYS be at least one data byte if we
     * have gotten to this point, so we don't have to
     * check that case.
     */
    
    /* get the six highest bits of the first byte and shift it 
     * two places to the right to get the first character.  This
     * Will always be done no matter what extra_bytes has to say.
     * we DID get here in the first place, no?
     */  
    
    encoded[final_length++] = ENCODE_HEX(CHAR_ONE(data, count));
    
    /* now lets worry about the second character.  If we
     * only have one byte, then we get two characters and
     * two pads.  If we have two bytes, then we get three
     * characters and one pad.  If we have 0 extra bytes
     * then we have no padding at all.
     */
    
    switch (extra_bytes) {          /* for second character */
    case 1:			/* We only had one byte, == 2 chars */
      encoded[final_length++] = ENCODE_HEX(CHAR_TWO_OF_TWO(data, count));
      break;
    case 0:
    case 2:
      encoded[final_length++] = ENCODE_HEX(CHAR_TWO(data, count));
      break;
    }
    
    /* CHARACTER THREE */

    switch (extra_bytes) {          /* for third character */
    case 2:
      encoded[final_length++] = ENCODE_HEX(CHAR_THREE_OF_THREE(data, count));
      break;
    case 0:
      encoded[final_length++] = ENCODE_HEX(CHAR_THREE(data, count));
      break;
    case 1:
      /* Just a pad character */
      encoded[final_length++] = PAD_CHAR;
      break;
    }

    /* CHARACTER FOUR */

    switch (extra_bytes) {          /* for fourth character */
    case 0:
      encoded[final_length++] = ENCODE_HEX(CHAR_FOUR(data, count));
      break;
    case 1:
    case 2:
      /* Need a pad character */
      encoded[final_length++] = PAD_CHAR;
    }

  } /* for */
  
  /* Set the encoded length to the length of the string */
  if (encodedlen != NULL)
    *encodedlen = final_length;
  
  encoded[final_length] = '\0';         /* NULL terminate our char string! */
  return(CRN_OK);
}

/* Requires:	data be valid IA5 of length datalen, decoded be of at least
 *		length (((datalen+3)/4)*3) bytes long, which is stores
 *		in decodedlen.
 * Modifies:	decoded and decodedlen
 * Effects:	This will data data, and IA5 encoded string, and convert
 *		it to the 8-bit data string and place that into decoded,
 *		and set decodedlen to then length of the octet string.
 */
int
ia5_decode_data(char *data, u_int datalen, u_char *decoded, u_int *decodedlen)
{
  int count;
  int extra_bytes = 0;                        /* 0, 1, or 2 */
  int final_length = 0;
  /* This will tell us how many 
   *  chars we will have 
   * ((octetlen + 2)/3)*4     
   */
  
  /* Build the table if we need to. */
  if (!TableInited)
    ia5_build_decode_table();
  
  if (datalen%4 != 0)		/* Must be a multiple of 4! */
    return (CRN_ERR_DATALEN);
  
  count = (datalen/4)*3;
  if (data[datalen-1] == PAD_CHAR) {
    count--;
    if (data[datalen-2] == PAD_CHAR) 
      count--;
  }

  if (decodedlen != NULL)	/* Must have enough space */
    if (*decodedlen < count)
      return (CRN_ERR_DATALEN);
  
  /* Walk down the string of characters in blocks of four.  If the
   * first is a NULL, then we've gotten to the end.  Check for pad
   * characters, to make sure this works out.
   */

  /* XXX - If a character used is not a valid character as per RFC 1113,
   * it will be treated as a zero!
   */
  
  for (count = 0; count < datalen && data[count] != '\0'; count +=4) {
    
    /* Find out how many pad characters there are in this
     * four-character sequece.  There can be 0, 1, or 2
     */
    if (datalen - count > 4)
      extra_bytes = 0;
    else if (data[count+3] == PAD_CHAR) {
      extra_bytes = 1;
      if (data[count+2] == PAD_CHAR)
	extra_bytes = 2;
    }
    
    /* First byte */
    
    decoded[final_length++] = BYTE_ONE(data, count);

    /* Second byte */
    
    switch (extra_bytes) {
    case 0:
    case 1:
      decoded[final_length++] = BYTE_TWO(data, count);
    case 2:
      break;
    }
    
    /* Third byte */
    
    switch (extra_bytes) {
    case 0:
      decoded[final_length++] = BYTE_THREE(data, count);
    case 1:
    case 2:
      break;
    }
    
  } /* for */
  
  /* Set the final length of the octen string */
  if (decodedlen != NULL)
    *decodedlen = final_length;
  
  return(CRN_OK);      
}

/* Requires:	True
 * Modifies:	TableInited and DecodeTable
 * Effects:	Builds the decode table from the encode table
 */
void
ia5_build_decode_table()
{
  int i;

  /* Only init the table once */
  if (TableInited)
    return;

  TableInited = 1;

  /* Zero the table */
  BCLEAR(DecodeTable);

  /* Set the values for all the IA5 characters */
  for (i = 0; i < NUM_IA5_CHARS; i++)
    DecodeTable[EncodeTable[i]] = i;

  return;
}
