/* 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (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.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: oid.c,v $ $Revision: 1.1 $ $Date: 2000/03/31 19:16:22 $ $Name: NSS_3_1_1_RTM $";
#endif /* DEBUG */

/*
 * oid.c
 *
 * This file contains the implementation of the basic OID routines.
 */

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

#ifndef PKI1_H
#include "pki1.h"
#endif /* PKI1_H */

#include "plhash.h"
#include "plstr.h"

/*
 * NSSOID
 *
 * The public "methods" regarding this "object" are:
 *
 *  NSSOID_CreateFromBER   -- constructor
 *  NSSOID_CreateFromUTF8  -- constructor
 *  (there is no explicit destructor)
 * 
 *  NSSOID_GetDEREncoding
 *  NSSOID_GetUTF8Encoding

 * The non-public "methods" regarding this "object" are:
 *
 *  nssOID_CreateFromBER   -- constructor
 *  nssOID_CreateFromUTF8  -- constructor
 *  (there is no explicit destructor)
 * 
 *  nssOID_GetDEREncoding
 *  nssOID_GetUTF8Encoding
 *
 * In debug builds, the following non-public calls are also available:
 *
 *  nssOID_verifyPointer
 *  nssOID_getExplanation
 *  nssOID_getTaggedUTF8
 */

const NSSOID *NSS_OID_UNKNOWN = (NSSOID *)NULL;

/*
 * First, the public "wrappers"
 */

/*
 * NSSOID_CreateFromBER
 *
 * This routine creates an NSSOID by decoding a BER- or DER-encoded
 * OID.  It may return NULL upon error, in which case it 
 * will have created an error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_BER
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  An NSSOID upon success
 */

NSS_EXTERN NSSOID *
NSSOID_CreateFromBER
(
  NSSBER *berOid
)
{
  nss_ClearErrorStack();

#ifdef DEBUG
  /* 
   * NSSBERs can be created by the user, 
   * so no pointer-tracking can be checked.
   */

  if( (NSSBER *)NULL == berOid ) {
    nss_SetError(NSS_ERROR_INVALID_BER);
    return (NSSOID *)NULL;
  }

  if( (void *)NULL == berOid->data ) {
    nss_SetError(NSS_ERROR_INVALID_BER);
    return (NSSOID *)NULL;
  }
#endif /* DEBUG */
  
  return nssOID_CreateFromBER(berOid);
}

/*
 * NSSOID_CreateFromUTF8
 *
 * This routine creates an NSSOID by decoding a UTF8 string 
 * representation of an OID in dotted-number format.  The string may 
 * optionally begin with an octothorpe.  It may return NULL
 * upon error, in which case it will have created an error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_UTF8
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  An NSSOID upon success
 */

NSS_EXTERN NSSOID *
NSSOID_CreateFromUTF8
(
  NSSUTF8 *stringOid
)
{
  nss_ClearErrorStack();

#ifdef DEBUG
  /*
   * NSSUTF8s can be created by the user,
   * so no pointer-tracking can be checked.
   */

  if( (NSSUTF8 *)NULL == stringOid ) {
    nss_SetError(NSS_ERROR_INVALID_UTF8);
    return (NSSOID *)NULL;
  }
#endif /* DEBUG */

  return nssOID_CreateFromUTF8(stringOid);
}

/*
 * NSSOID_GetDEREncoding
 *
 * This routine returns the DER encoding of the specified NSSOID.
 * If the optional arena argument is non-null, the memory used will
 * be obtained from that arena; otherwise, the memory will be obtained
 * from the heap.  This routine may return return null upon error, in 
 * which case it will have created an error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_NSSOID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  The DER encoding of this NSSOID
 */

NSS_EXTERN NSSDER *
NSSOID_GetDEREncoding
(
  const NSSOID *oid,
  NSSDER *rvOpt,
  NSSArena *arenaOpt
)
{
  nss_ClearErrorStack();

#ifdef DEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSDER *)NULL;
  }

  if( (NSSArena *)NULL != arenaOpt ) {
    if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) {
      return (NSSDER *)NULL;
    }
  }
#endif /* DEBUG */

  return nssOID_GetDEREncoding(oid, rvOpt, arenaOpt);
}

/*
 * NSSOID_GetUTF8Encoding
 *
 * This routine returns a UTF8 string containing the dotted-number 
 * encoding of the specified NSSOID.  If the optional arena argument 
 * is non-null, the memory used will be obtained from that arena; 
 * otherwise, the memory will be obtained from the heap.  This routine
 * may return null upon error, in which case it will have created an
 * error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_NSSOID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  A pointer to a UTF8 string containing the dotted-digit encoding of 
 *      this NSSOID
 */

NSS_EXTERN NSSUTF8 *
NSSOID_GetUTF8Encoding
(
  const NSSOID *oid,
  NSSArena *arenaOpt
)
{
  nss_ClearErrorStack();

#ifdef DEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSUTF8 *)NULL;
  }

  if( (NSSArena *)NULL != arenaOpt ) {
    if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) {
      return (NSSUTF8 *)NULL;
    }
  }
#endif /* DEBUG */

  return nssOID_GetUTF8Encoding(oid, arenaOpt);
}

/*
 * Next, some internal bookkeeping; including the OID "tag" table 
 * and the debug-version pointer tracker.
 */

/*
 * For implementation reasons (so NSSOIDs can be compared with ==),
 * we hash all NSSOIDs.  This is the hash table.
 */

static PLHashTable *oid_hash_table;

/*
 * And this is its lock.
 */

static PRLock *oid_hash_lock;

/*
 * This is the hash function.  We simply XOR the encoded form with
 * itself in sizeof(PLHashNumber)-byte chunks.  Improving this
 * routine is left as an excercise for the more mathematically
 * inclined student.
 */

static PR_CALLBACK PLHashNumber
oid_hash
(
  const void *key
)
{
  const NSSItem *item = (const NSSItem *)key;
  PLHashNumber rv = 0;

  PRUint8 *data = (PRUint8 *)item->data;
  PRUint32 i;
  PRUint8 *rvc = (PRUint8 *)&rv;

  for( i = 0; i < item->size; i++ ) {
    rvc[ i % sizeof(rv) ] ^= *data;
    data++;
  }

  return rv;
}

/*
 * This is the key-compare function.  It simply does a lexical
 * comparison on the encoded OID form.  This does not result in
 * quite the same ordering as the "sequence of numbers" order,
 * but heck it's only used internally by the hash table anyway.
 */

static PR_CALLBACK PRIntn
oid_hash_compare
(
  const void *k1,
  const void *k2
)
{
  PRIntn rv;

  const NSSItem *i1 = (const NSSItem *)k1;
  const NSSItem *i2 = (const NSSItem *)k2;

  PRUint32 size = (i1->size < i2->size) ? i1->size : i2->size;

  rv = (PRIntn)nsslibc_memcmp(i1->data, i2->data, size, (PRStatus *)NULL);
  if( 0 == rv ) {
    rv = i1->size - i2->size;
  }

  return !rv;
}

/*
 * The pointer-tracking code
 */

#ifdef DEBUG
extern const NSSError NSS_ERROR_INTERNAL_ERROR;

static nssPointerTracker oid_pointer_tracker;

static PRStatus
oid_add_pointer
(
  const NSSOID *oid
)
{
  PRStatus rv;

  rv = nssPointerTracker_initialize(&oid_pointer_tracker);
  if( PR_SUCCESS != rv ) {
    return rv;
  }

  rv = nssPointerTracker_add(&oid_pointer_tracker, oid);
  if( PR_SUCCESS != rv ) {
    NSSError e = NSS_GetError();
    if( NSS_ERROR_NO_MEMORY != e ) {
      nss_SetError(NSS_ERROR_INTERNAL_ERROR);
    }

    return rv;
  }

  return PR_SUCCESS;
}

#if defined(CAN_DELETE_OIDS)
/*
 * We actually don't define NSSOID deletion, since we keep OIDs
 * in a hash table for easy comparison.  Were we to, this is
 * what the pointer-removal function would look like.
 */

static PRStatus
oid_remove_pointer
(
  const NSSOID *oid
)
{
  PRStatus rv;

  rv = nssPointerTracker_remove(&oid_pointer_tracker, oid);
  if( PR_SUCCESS != rv ) {
    nss_SetError(NSS_ERROR_INTERNAL_ERROR);
  }

  return rv;
}
#endif /* CAN_DELETE_OIDS */

#endif /* DEBUG */

/*
 * All dynamically-added OIDs get their memory from one statically-
 * declared arena here, merely so that any cleanup code will have
 * an easier time of it.
 */

static NSSArena *oid_arena;

/*
 * This is the call-once function which initializes the hashtable.
 * It creates it, then prepopulates it with all of the builtin OIDs.
 * It also creates the aforementioned NSSArena.
 */

static PR_CALLBACK PRStatus
oid_once_func
(
  void
)
{
  PRUint32 i;
  
  /* Initialize the arena */
  oid_arena = nssArena_Create();
  if( (NSSArena *)NULL == oid_arena ) {
    goto loser;
  }

  /* Create the hash table lock */
  oid_hash_lock = PR_NewLock();
  if( (PRLock *)NULL == oid_hash_lock ) {
    nss_SetError(NSS_ERROR_NO_MEMORY);
    goto loser;
  }

  /* Create the hash table */
  oid_hash_table = PL_NewHashTable(0, oid_hash, oid_hash_compare,
                                   PL_CompareValues, 
                                   (PLHashAllocOps *)0,
                                   (void *)0);
  if( (PLHashTable *)NULL == oid_hash_table ) {
    nss_SetError(NSS_ERROR_NO_MEMORY);
    goto loser;
  }

  /* And populate it with all the builtins */
  for( i = 0; i < nss_builtin_oid_count; i++ ) {
    NSSOID *oid = (NSSOID *)&nss_builtin_oids[i];
    PLHashEntry *e = PL_HashTableAdd(oid_hash_table, &oid->data, oid);
    if( (PLHashEntry *)NULL == e ) {
      nss_SetError(NSS_ERROR_NO_MEMORY);
      goto loser;
    }

#ifdef DEBUG
    if( PR_SUCCESS != oid_add_pointer(oid) ) {
      goto loser;
    }
#endif /* DEBUG */
  }

  return PR_SUCCESS;

 loser:
  if( (PLHashTable *)NULL != oid_hash_table ) {
    PL_HashTableDestroy(oid_hash_table);
    oid_hash_table = (PLHashTable *)NULL;
  }

  if( (PRLock *)NULL != oid_hash_lock ) {
    PR_DestroyLock(oid_hash_lock);
    oid_hash_lock = (PRLock *)NULL;
  }

  if( (NSSArena *)NULL != oid_arena ) {
    (void)nssArena_Destroy(oid_arena);
    oid_arena = (NSSArena *)NULL;
  }

  return PR_FAILURE;
}

/*
 * This is NSPR's once-block.
 */

static PRCallOnceType oid_call_once;

/*
 * And this is our multiply-callable internal init routine, which
 * will call-once our call-once function.
 */

static PRStatus
oid_init
(
  void
)
{
  return PR_CallOnce(&oid_call_once, oid_once_func);
}

#ifdef DEBUG

/*
 * nssOID_verifyPointer
 *
 * This method is only present in debug builds.
 *
 * If the specified pointer is a valid pointer to an NSSOID object, 
 * this routine will return PR_SUCCESS.  Otherwise, it will put an 
 * error on the error stack and return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_NSSOID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  PR_SUCCESS if the pointer is valid
 *  PR_FAILURE if it isn't
 */

NSS_EXTERN PRStatus
nssOID_verifyPointer
(
  const NSSOID *oid
)
{
  PRStatus rv;

  rv = oid_init();
  if( PR_SUCCESS != rv ) {
    return PR_FAILURE;
  }

  rv = nssPointerTracker_initialize(&oid_pointer_tracker);
  if( PR_SUCCESS != rv ) {
    return PR_FAILURE;
  }

  rv = nssPointerTracker_verify(&oid_pointer_tracker, oid);
  if( PR_SUCCESS != rv ) {
    nss_SetError(NSS_ERROR_INVALID_NSSOID);
    return PR_FAILURE;
  }

  return PR_SUCCESS;
}
#endif /* DEBUG */

/*
 * oid_sanity_check_ber
 *
 * This routine merely applies some sanity-checking to the BER-encoded
 * OID.
 */

static PRStatus
oid_sanity_check_ber
(
  NSSBER *berOid
)
{
  PRUint32 i;
  PRUint8 *data = (PRUint8 *)berOid->data;

  /*
   * The size must be longer than zero bytes.
   */

  if( berOid->size <= 0 ) {
    return PR_FAILURE;
  }

  /*
   * In general, we can't preclude any number from showing up
   * someday.  We could probably guess that top-level numbers
   * won't get very big (beyond the current ccitt(0), iso(1),
   * or joint-ccitt-iso(2)).  However, keep in mind that the
   * encoding rules wrap the first two numbers together, as
   *
   *     (first * 40) + second
   *
   * Also, it is noted in the specs that this implies that the
   * second number won't go above forty.
   *
   * 128 encodes 3.8, which seems pretty safe for now.  Let's
   * check that the first byte is less than that.
   *
   * XXX This is a "soft check" -- we may want to exclude it.
   */

  if( data[0] >= 0x80 ) {
    return PR_FAILURE;
  }

  /*
   * In a normalised format, leading 0x80s will never show up.
   * This means that no 0x80 will be preceeded by the final
   * byte of a sequence, which would naturaly be less than 0x80.
   * Our internal encoding for the single-digit OIDs uses 0x80,
   * but the only places we use them (loading the builtin table,
   * and adding a UTF8-encoded OID) bypass this check.
   */

  for( i = 1; i < berOid->size; i++ ) {
    if( (0x80 == data[i]) && (data[i-1] < 0x80) ) {
      return PR_FAILURE;
    }
  }

  /*
   * The high bit of each octet indicates that following octets
   * are included in the current number.  Thus the last byte can't
   * have the high bit set.
   */

  if( data[ berOid->size-1 ] >= 0x80 ) {
    return PR_FAILURE;
  }

  /*
   * Other than that, any byte sequence is legit.
   */
  return PR_SUCCESS;
}

/*
 * nssOID_CreateFromBER
 *
 * This routine creates an NSSOID by decoding a BER- or DER-encoded
 * OID.  It may return NULL upon error, in which case it 
 * will have set an error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_BER
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  An NSSOID upon success
 */

NSS_EXTERN NSSOID *
nssOID_CreateFromBER
(
  NSSBER *berOid
)
{
  NSSOID *rv;
  PLHashEntry *e;
  
  if( PR_SUCCESS != oid_init() ) {
    return (NSSOID *)NULL;
  }

  if( PR_SUCCESS != oid_sanity_check_ber(berOid) ) {
    nss_SetError(NSS_ERROR_INVALID_BER);
    return (NSSOID *)NULL;
  }

  /*
   * Does it exist?
   */
  PR_Lock(oid_hash_lock);
  rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, berOid);
  (void)PR_Unlock(oid_hash_lock);
  if( (NSSOID *)NULL != rv ) {
    /* Found it! */
    return rv;
  }

  /*
   * Doesn't exist-- create it.
   */
  rv = nss_ZNEW(oid_arena, NSSOID);
  if( (NSSOID *)NULL == rv ) {
    return (NSSOID *)NULL;
  }

  rv->data.data = nss_ZAlloc(oid_arena, berOid->size);
  if( (void *)NULL == rv->data.data ) {
    return (NSSOID *)NULL;
  }

  rv->data.size = berOid->size;
  nsslibc_memcpy(rv->data.data, berOid->data, berOid->size);

#ifdef DEBUG
  rv->tag = "<runtime>";
  rv->expl = "(OID registered at runtime)";
#endif /* DEBUG */

  PR_Lock(oid_hash_lock);
  e = PL_HashTableAdd(oid_hash_table, &rv->data, rv);
  (void)PR_Unlock(oid_hash_lock);
  if( (PLHashEntry *)NULL == e ) {
    nss_ZFreeIf(rv->data.data);
    nss_ZFreeIf(rv);
    nss_SetError(NSS_ERROR_NO_MEMORY);
    return (NSSOID *)NULL;
  }

#ifdef DEBUG
  {
    PRStatus st;
    st = oid_add_pointer(rv);
    if( PR_SUCCESS != st ) {
      PR_Lock(oid_hash_lock);
      (void)PL_HashTableRemove(oid_hash_table, &rv->data);
      (void)PR_Unlock(oid_hash_lock);
      (void)nss_ZFreeIf(rv->data.data);
      (void)nss_ZFreeIf(rv);
      return (NSSOID *)NULL;
    }
  }
#endif /* DEBUG */

  return rv;
}

/*
 * oid_sanity_check_utf8
 *
 * This routine merely applies some sanity-checking to the 
 * UTF8-encoded OID.
 */

static PRStatus
oid_sanity_check_utf8
(
  NSSUTF8 *s
)
{
  /*
   * It may begin with an octothorpe, which we skip.
   */

  if( '#' == *s ) {
    s++;
  }

  /*
   * It begins with a number
   */

  if( (*s < '0') || (*s > '9') ) {
    return PR_FAILURE;
  }

  /*
   * First number is only one digit long
   *
   * XXX This is a "soft check" -- we may want to exclude it
   */

  if( (s[1] != '.') && (s[1] != '\0') ) {
    return PR_FAILURE;
  }

  /*
   * Every character is either a digit or a period
   */

  for( ; '\0' != *s; s++ ) {
    if( ('.' != *s) && ((*s < '0') || (*s > '9')) ) {
      return PR_FAILURE;
    }

    /* No two consecutive periods */
    if( ('.' == *s) && ('.' == s[1]) ) {
      return PR_FAILURE;
    }
  }

  /*
   * The last character isn't a period
   */

  if( '.' == *--s ) {
    return PR_FAILURE;
  }

  return PR_SUCCESS;
}

static PRUint32
oid_encode_number
(
  PRUint32 n,
  PRUint8 *dp,
  PRUint32 nb
)
{
  PRUint32 a[5];
  PRUint32 i;
  PRUint32 rv;

  a[0] = (n >> 28) & 0x7f;
  a[1] = (n >> 21) & 0x7f;
  a[2] = (n >> 14) & 0x7f;
  a[3] = (n >>  7) & 0x7f;
  a[4] =  n        & 0x7f;

  for( i = 0; i < 5; i++ ) {
    if( 0 != a[i] ) {
      break;
    }
  }

  if( 5 == i ) {
    i--;
  }

  rv = 5-i;
  if( rv > nb ) {
    return rv;
  }

  for( ; i < 4; i++ ) {
    *dp = 0x80 | a[i];
    dp++;
  }

  *dp = a[4];

  return rv;
}

/*
 * oid_encode_huge
 *
 * This routine will convert a huge decimal number into the DER 
 * encoding for oid numbers.  It is not limited to numbers that will
 * fit into some wordsize, like oid_encode_number.  But it's not
 * necessarily very fast, either.  This is here in case some joker
 * throws us an ASCII oid like 1.2.3.99999999999999999999999999.
 */

static PRUint32
oid_encode_huge
(
  NSSUTF8 *s,
  NSSUTF8 *e,
  PRUint8 *dp,
  PRUint32 nb
)
{
  PRUint32 slen = (e-s);
  PRUint32 blen = (slen+1)/2;
  PRUint8 *st = (PRUint8 *)NULL;
  PRUint8 *bd = (PRUint8 *)NULL;
  PRUint32 i;
  PRUint32 bitno;
  PRUint8 *last;
  PRUint8 *first;
  PRUint32 byteno;
  PRUint8 mask;

  /* We'll be munging the data, so duplicate it */
  st = (PRUint8 *)nss_ZAlloc((NSSArena *)NULL, slen);
  if( (PRUint8 *)NULL == st ) {
    return 0;
  }

  /* Don't know ahead of time exactly how long we'll need */
  bd = (PRUint8 *)nss_ZAlloc((NSSArena *)NULL, blen);
  if( (PRUint8 *)NULL == bd ) {
    (void)nss_ZFreeIf(st);
    return 0;
  }

  /* Copy the original, and convert ASCII to numbers */
  for( i = 0; i < slen; i++ ) {
    st[i] = (PRUint8)(s[i] - '0');
  }

  last = &st[slen-1];
  first = &st[0];

  /*
   * The way we create the binary version is by looking at it 
   * bit by bit.  Start with the least significant bit.  If the
   * number is odd, set that bit.  Halve the number (with integer
   * division), and go to the next least significant bit.  Keep 
   * going until the number goes to zero.
   */
  for( bitno = 0; ; bitno++ ) {
    PRUint8 *d;

    byteno = bitno/7;
    mask = (PRUint8)(1 << (bitno%7));

    /* Skip leading zeroes */
    for( ; first < last; first ++ ) {
      if( 0 != *first ) {
        break;
      }
    }

    /* Down to one number and it's a zero?  Done. */
    if( (first == last) && (0 == *last) ) {
      break;
    }

    /* Last digit is odd?  Set the bit */
    if( *last & 1 ) {
      bd[ byteno ] |= mask;
    }


    /* 
     * Divide the number in half.  This is just a matter
     * of going from the least significant digit upwards,
     * halving each one.  If any digit is odd (other than
     * the last, which has already been handled), add five
     * to the digit to its right.
     */
    *last /= 2;

    for( d = &last[-1]; d >= first; d-- ) {
      if( *d & 1 ) {
        d[1] += 5;
      }

      *d /= 2;
    }
  }

  /* Is there room to write the encoded data? */
  if( (byteno+1) > nb ) {
    return (byteno+1);
  }

  /* Trim any leading zero that crept in there */
  for( ; byteno > 0; byteno-- ) {
    if( 0 != bd[ byteno ] ) {
      break;
    }
  }

  /* Copy all but the last, marking the "continue" bit */
  for( i = 0; i < byteno; i++ ) {
    dp[i] = bd[ byteno-i ] | 0x80;
  }
  /* And the last with the "continue" bit clear */
  dp[byteno] = bd[0];

  (void)nss_ZFreeIf(bd);
  (void)nss_ZFreeIf(st);
  return (byteno+1);
}

/*
 * oid_encode_string
 *
 * This routine converts a dotted-number OID into a DER-encoded
 * one.  It assumes we've already sanity-checked the string.
 */

extern const NSSError NSS_ERROR_INTERNAL_ERROR;

static NSSOID *
oid_encode_string
(
  NSSUTF8 *s
)
{
  PRUint32 nn = 0; /* number of numbers */
  PRUint32 nb = 0; /* number of bytes (estimated) */
  NSSUTF8 *t;
  PRUint32 nd = 0; /* number of digits */
  NSSOID *rv;
  PRUint8 *dp;
  PRUint32 a, b;
  PRUint32 inc;

  /* Dump any octothorpe */
  if( '#' == *s ) {
    s++;
  }

  /* Count up the bytes needed */
  for( t = s; '\0' != *t; t++ ) {
    if( '.' == *t ) {
      nb += (nd+1)/2; /* errs on the big side */
      nd = 0;
      nn++;
    } else {
      nd++;
    }
  }
  nb += (nd+1)/2;
  nn++;

  if( 1 == nn ) {
    /*
     * We have our own "denormalised" encoding for these,
     * which is only used internally.
     */
    nb++;
  }

  /* 
   * Allocate.  Note that we don't use the oid_arena here.. this is
   * because there really isn't a "free()" for stuff allocated out of
   * arenas (at least with the current implementation), so this would
   * keep using up memory each time a UTF8-encoded OID were added.
   * If need be (if this is the first time this oid has been seen),
   * we'll copy it.
   */
  rv = nss_ZNEW((NSSArena *)NULL, NSSOID);
  if( (NSSOID *)NULL == rv ) {
    return (NSSOID *)NULL;
  }

  rv->data.data = nss_ZAlloc((NSSArena *)NULL, nb);
  if( (void *)NULL == rv->data.data ) {
    (void)nss_ZFreeIf(rv);
    return (NSSOID *)NULL;
  }

  dp = (PRUint8 *)rv->data.data;

  a = atoi(s);

  if( 1 == nn ) {
    dp[0] = '\x80';
    inc = oid_encode_number(a, &dp[1], nb-1);
    if( inc >= nb ) {
      goto loser;
    }
  } else {
    for( t = s; '.' != *t; t++ ) {
      ;
    }

    t++;
    b = atoi(t);
    inc = oid_encode_number(a*40+b, dp, nb);
    if( inc > nb ) {
      goto loser;
    }
    dp += inc;
    nb -= inc;
    nn -= 2;

    while( nn-- > 0 ) {
      NSSUTF8 *u;

      for( ; '.' != *t; t++ ) {
        ;
      }

      t++;

      for( u = t; ('\0' != *u) && ('.' != *u); u++ ) {
        ;
      }

      if( (u-t > 9) ) {
        /* In the billions.  Rats. */
        inc = oid_encode_huge(t, u, dp, nb);
      } else {
        b = atoi(t);
        inc = oid_encode_number(b, dp, nb);
      }

      if( inc > nb ) {
        goto loser;
      }
      dp += inc;
      nb -= inc;
    }
  }

  return rv;

 loser:
  nss_SetError(NSS_ERROR_INTERNAL_ERROR);
  return (NSSOID *)NULL;
}

/*
 * nssOID_CreateFromUTF8
 *
 * This routine creates an NSSOID by decoding a UTF8 string 
 * representation of an OID in dotted-number format.  The string may 
 * optionally begin with an octothorpe.  It may return NULL
 * upon error, in which case it will have set an error on the error 
 * stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_STRING
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  An NSSOID upon success
 */

NSS_EXTERN NSSOID *
nssOID_CreateFromUTF8
(
  NSSUTF8 *stringOid
)
{
  NSSOID *rv = (NSSOID *)NULL;
  NSSOID *candidate = (NSSOID *)NULL;
  PLHashEntry *e;

  if( PR_SUCCESS != oid_init() ) {
    return (NSSOID *)NULL;
  }

  if( PR_SUCCESS != oid_sanity_check_utf8(stringOid) ) {
    nss_SetError(NSS_ERROR_INVALID_STRING);
    return (NSSOID *)NULL;
  }

  candidate = oid_encode_string(stringOid);
  if( (NSSOID *)NULL == candidate ) {
    /* Internal error only */
    return rv;
  }

  /*
   * Does it exist?
   */
  PR_Lock(oid_hash_lock);
  rv = (NSSOID *)PL_HashTableLookup(oid_hash_table, &candidate->data);
  (void)PR_Unlock(oid_hash_lock);
  if( (NSSOID *)NULL != rv ) {
    /* Already exists.  Delete my copy and return the original. */
    (void)nss_ZFreeIf(candidate->data.data);
    (void)nss_ZFreeIf(candidate);
    return rv;
  }

  /* 
   * Nope.  Add it.  Remember to allocate it out of the oid arena.
   */

  rv = nss_ZNEW(oid_arena, NSSOID);
  if( (NSSOID *)NULL == rv ) {
    goto loser;
  }

  rv->data.data = nss_ZAlloc(oid_arena, candidate->data.size);
  if( (void *)NULL == rv->data.data ) {
    goto loser;
  }

  rv->data.size = candidate->data.size;
  nsslibc_memcpy(rv->data.data, candidate->data.data, rv->data.size);

  (void)nss_ZFreeIf(candidate->data.data);
  (void)nss_ZFreeIf(candidate);

#ifdef DEBUG
  rv->tag = "<runtime>";
  rv->expl = "(OID registered at runtime)";
#endif /* DEBUG */

  PR_Lock(oid_hash_lock);
  e = PL_HashTableAdd(oid_hash_table, &rv->data, rv);
  (void)PR_Unlock(oid_hash_lock);
  if( (PLHashEntry *)NULL == e ) {
    nss_SetError(NSS_ERROR_NO_MEMORY);
    goto loser;
  }

#ifdef DEBUG
  {
    PRStatus st;
    st = oid_add_pointer(rv);
    if( PR_SUCCESS != st ) {
      PR_Lock(oid_hash_lock);
      (void)PL_HashTableRemove(oid_hash_table, &rv->data);
      (void)PR_Unlock(oid_hash_lock);
      goto loser;
    }
  }
#endif /* DEBUG */

  return rv;

 loser:
  if( (NSSOID *)NULL != candidate ) {
    (void)nss_ZFreeIf(candidate->data.data);
  }
  (void)nss_ZFreeIf(candidate);

  if( (NSSOID *)NULL != rv ) {
    (void)nss_ZFreeIf(rv->data.data);
  }
  (void)nss_ZFreeIf(rv);

  return (NSSOID *)NULL;
}

/*
 * nssOID_GetDEREncoding
 *
 * This routine returns the DER encoding of the specified NSSOID.
 * If the optional arena argument is non-null, the memory used will
 * be obtained from that arena; otherwise, the memory will be obtained
 * from the heap.  This routine may return return null upon error, in 
 * which case it will have set an error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_OID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  The DER encoding of this NSSOID
 */

NSS_EXTERN NSSDER *
nssOID_GetDEREncoding
(
  const NSSOID *oid,
  NSSDER *rvOpt,
  NSSArena *arenaOpt
)
{
  const NSSItem *it;
  NSSDER *rv;

  if( PR_SUCCESS != oid_init() ) {
    return (NSSDER *)NULL;
  }

#ifdef NSSDEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSDER *)NULL;
  }

  if( (NSSArena *)NULL != arenaOpt ) {
    if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) {
      return (NSSDER *)NULL;
    }
  }
#endif /* NSSDEBUG */

  it = &oid->data;

  if( (NSSDER *)NULL == rvOpt ) {
    rv = nss_ZNEW(arenaOpt, NSSDER);
    if( (NSSDER *)NULL == rv ) {
      return (NSSDER *)NULL;
    }
  } else {
    rv = rvOpt;
  }

  rv->data = nss_ZAlloc(arenaOpt, it->size);
  if( (void *)NULL == rv->data ) {
    if( rv != rvOpt ) {
      (void)nss_ZFreeIf(rv);
    }
    return (NSSDER *)NULL;
  }

  rv->size = it->size;
  nsslibc_memcpy(rv->data, it->data, it->size);

  return rv;
}

/*
 * nssOID_GetUTF8Encoding
 *
 * This routine returns a UTF8 string containing the dotted-number 
 * encoding of the specified NSSOID.  If the optional arena argument 
 * is non-null, the memory used will be obtained from that arena; 
 * otherwise, the memory will be obtained from the heap.  This routine
 * may return null upon error, in which case it will have set an error
 * on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_OID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  A pointer to a UTF8 string containing the dotted-digit encoding of 
 *      this NSSOID
 */

NSS_EXTERN NSSUTF8 *
nssOID_GetUTF8Encoding
(
  const NSSOID *oid,
  NSSArena *arenaOpt
)
{
  NSSUTF8 *rv;
  PRUint8 *end;
  PRUint8 *d;
  PRUint8 *e;
  char *a;
  char *b;
  PRUint32 len;

  if( PR_SUCCESS != oid_init() ) {
    return (NSSUTF8 *)NULL;
  }

#ifdef NSSDEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSUTF8 *)NULL;
  }

  if( (NSSArena *)NULL != arenaOpt ) {
    if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) {
      return (NSSUTF8 *)NULL;
    }
  }
#endif /* NSSDEBUG */

  a = (char *)NULL;

  /* d will point to the next sequence of bytes to decode */
  d = (PRUint8 *)oid->data.data;
  /* end points to one past the legitimate data */
  end = &d[ oid->data.size ];

#ifdef NSSDEBUG
  /*
   * Guarantee that the for(e=d;e<end;e++) loop below will
   * terminate.  Our BER sanity-checking code above will prevent
   * such a BER from being registered, so the only other way one
   * might show up is if our dotted-decimal encoder above screws
   * up or our generated list is wrong.  So I'll wrap it with
   * #ifdef NSSDEBUG and #endif.
   */
  if( end[-1] & 0x80 ) {
    nss_SetError(NSS_ERROR_INTERNAL_ERROR);
    return (NSSUTF8 *)NULL;
  }
#endif /* NSSDEBUG */

  /*
   * Check for our pseudo-encoded single-digit OIDs
   */
  if( (*d == 0x80) && (2 == oid->data.size) ) {
    /* Funky encoding.  The second byte is the number */
    a = PR_smprintf("%lu", (PRUint32)d[1]);
    if( (char *)NULL == a ) {
      nss_SetError(NSS_ERROR_NO_MEMORY);
      return (NSSUTF8 *)NULL;
    }
    goto done;
  }

  for( ; d < end; d = &e[1] ) {
    
    for( e = d; e < end; e++ ) {
      if( 0 == (*e & 0x80) ) {
        break;
      }
    }
    
    if( ((e-d) > 4) || (((e-d) == 4) && (*d & 0x70)) ) {
      /* More than a 32-bit number */
    } else {
      PRUint32 n = 0;
      
      switch( e-d ) {
      case 4:
        n |= ((PRUint32)(e[-4] & 0x0f)) << 28;
      case 3:
        n |= ((PRUint32)(e[-3] & 0x7f)) << 21;
      case 2:
        n |= ((PRUint32)(e[-2] & 0x7f)) << 14;
      case 1:
        n |= ((PRUint32)(e[-1] & 0x7f)) <<  7;
      case 0:
        n |= ((PRUint32)(e[-0] & 0x7f))      ;
      }
      
      if( (char *)NULL == a ) {
        /* This is the first number.. decompose it */
        PRUint32 one = (n/40), two = (n%40);
        
        a = PR_smprintf("%lu.%lu", one, two);
        if( (char *)NULL == a ) {
          nss_SetError(NSS_ERROR_NO_MEMORY);
          return (NSSUTF8 *)NULL;
        }
      } else {
        b = PR_smprintf("%s.%lu", a, n);
        if( (char *)NULL == b ) {
          PR_smprintf_free(a);
          nss_SetError(NSS_ERROR_NO_MEMORY);
          return (NSSUTF8 *)NULL;
        }
        
        PR_smprintf_free(a);
        a = b;
      }
    }
  }

 done:
  /*
   * Even if arenaOpt is NULL, we have to copy the data so that
   * it'll be freed with the right version of free: ours, not
   * PR_smprintf_free's.
   */
  len = PL_strlen(a);
  rv = (NSSUTF8 *)nss_ZAlloc(arenaOpt, len);
  if( (NSSUTF8 *)NULL == rv ) {
    PR_smprintf_free(a);
    return (NSSUTF8 *)NULL;
  }

  nsslibc_memcpy(rv, a, len);
  PR_smprintf_free(a);

  return rv;
}

/*
 * nssOID_getExplanation
 *
 * This method is only present in debug builds.
 *
 * This routine will return a static pointer to a UTF8-encoded string
 * describing (in English) the specified OID.  The memory pointed to
 * by the return value is not owned by the caller, and should not be
 * freed or modified.  Note that explanations are only provided for
 * the OIDs built into the NSS library; there is no way to specify an
 * explanation for dynamically created OIDs.  This routine is intended
 * only for use in debugging tools such as "derdump."  This routine
 * may return null upon error, in which case it will have placed an
 * error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_NSSOID
 *
 * Return value:
 *  NULL upon error
 *  A static pointer to a readonly, non-caller-owned UTF8-encoded
 *    string explaining the specified OID.
 */

#ifdef DEBUG
NSS_EXTERN const NSSUTF8 *
nssOID_getExplanation
(
  NSSOID *oid
)
{
  if( PR_SUCCESS != oid_init() ) {
    return (const NSSUTF8 *)NULL;
  }

#ifdef NSSDEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSUTF8 *)NULL;
  }
#endif /* NSSDEBUG */

  return oid->expl;
}

extern const NSSError NSS_ERROR_INVALID_NSSOID;
#endif /* DEBUG */

/*
 * nssOID_getTaggedUTF8
 *
 * This method is only present in debug builds.
 *
 * This routine will return a pointer to a caller-owned UTF8-encoded
 * string containing a tagged encoding of the specified OID.  Note
 * that OID (component) tags are only provided for the OIDs built 
 * into the NSS library; there is no way to specify tags for 
 * dynamically created OIDs.  This routine is intended for use in
 * debugging tools such as "derdump."   If the optional arena argument
 * is non-null, the memory used will be obtained from that arena; 
 * otherwise, the memory will be obtained from the heap.  This routine 
 * may return return null upon error, in which case it will have set 
 * an error on the error stack.
 *
 * The error may be one of the following values
 *  NSS_ERROR_INVALID_NSSOID
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  A pointer to a UTF8 string containing the tagged encoding of
 *      this NSSOID
 */

#ifdef DEBUG
NSS_EXTERN NSSUTF8 *
nssOID_getTaggedUTF8
(
  NSSOID *oid,
  NSSArena *arenaOpt
)
{
  NSSUTF8 *rv;
  char *raw;
  char *c;
  char *a = (char *)NULL;
  char *b;
  PRBool done = PR_FALSE;
  PRUint32 len;

  if( PR_SUCCESS != oid_init() ) {
    return (NSSUTF8 *)NULL;
  }

#ifdef NSSDEBUG
  if( PR_SUCCESS != nssOID_verifyPointer(oid) ) {
    return (NSSUTF8 *)NULL;
  }

  if( (NSSArena *)NULL != arenaOpt ) {
    if( PR_SUCCESS != nssArena_verifyPointer(arenaOpt) ) {
      return (NSSUTF8 *)NULL;
    }
  }
#endif /* NSSDEBUG */

  a = PR_smprintf("{");
  if( (char *)NULL == a ) {
    nss_SetError(NSS_ERROR_NO_MEMORY);
    return (NSSUTF8 *)NULL;
  }

  /*
   * What I'm doing here is getting the text version of the OID,
   * e.g. 1.2.12.92, then looking up each set of leading numbers
   * as oids.. e.g. "1," then "1.2," then "1.2.12," etc.  Each of
   * those will have the leaf tag, and I just build up the string.
   * I never said this was the most efficient way of doing it,
   * but hey it's a debug-build thing, and I'm getting really tired
   * of writing this stupid low-level PKI code.
   */

  /* I know it's all ASCII, so I can use char */
  raw = (char *)nssOID_GetUTF8Encoding(oid, (NSSArena *)NULL);
  if( (char *)NULL == raw ) {
    return (NSSUTF8 *)NULL;
  }

  for( c = raw; !done; c++ ) {
    NSSOID *lead;
    char *lastdot;

    for( ; '.' != *c; c++ ) {
      if( '\0' == *c ) {
        done = PR_TRUE;
        break;
      }
    }

    *c = '\0';
    lead = nssOID_CreateFromUTF8((NSSUTF8 *)raw);
    if( (NSSOID *)NULL == lead ) {
      PR_smprintf_free(a);
      nss_ZFreeIf(raw);
      nss_SetError(NSS_ERROR_NO_MEMORY);
      return (NSSUTF8 *)NULL;
    }

    lastdot = PL_strrchr(raw, '.');
    if( (char *)NULL == lastdot ) {
      lastdot = raw;
    }

    b = PR_smprintf("%s %s(%s) ", a, lead->tag, &lastdot[1]);
    if( (char *)NULL == b ) {
      PR_smprintf_free(a);
      nss_ZFreeIf(raw);
      /* drop the OID reference on the floor */
      nss_SetError(NSS_ERROR_NO_MEMORY);
      return (NSSUTF8 *)NULL;
    }

    PR_smprintf_free(a);
    a = b;

    if( !done ) {
      *c = '.';
    }
  }

  nss_ZFreeIf(raw);

  b = PR_smprintf("%s }", a);
  if( (char *)NULL == b ) {
    PR_smprintf_free(a);
    nss_SetError(NSS_ERROR_NO_MEMORY);
    return (NSSUTF8 *)NULL;
  }

  len = PL_strlen(b);
  
  rv = (NSSUTF8 *)nss_ZAlloc(arenaOpt, len+1);
  if( (NSSUTF8 *)NULL == rv ) {
    PR_smprintf_free(b);
    return (NSSUTF8 *)NULL;
  }

  nsslibc_memcpy(rv, b, len);
  PR_smprintf_free(b);

  return rv;
}

extern const NSSError NSS_ERROR_INVALID_NSSOID;
extern const NSSError NSS_ERROR_NO_MEMORY;
#endif /* DEBUG */
