/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BUILTINS_H
#include "builtins.h"
#endif /* BUILTINS_H */

/*
 * builtins/find.c
 *
 * This file implements the NSSCKMDFindObjects object for the
 * "builtin objects" cryptoki module.
 */

struct builtinsFOStr {
  NSSArena *arena;
  CK_ULONG n;
  CK_ULONG i;
  builtinsInternalObject **objs;
};

static void
builtins_mdFindObjects_Final
(
  NSSCKMDFindObjects *mdFindObjects,
  NSSCKFWFindObjects *fwFindObjects,
  NSSCKMDSession *mdSession,
  NSSCKFWSession *fwSession,
  NSSCKMDToken *mdToken,
  NSSCKFWToken *fwToken,
  NSSCKMDInstance *mdInstance,
  NSSCKFWInstance *fwInstance
)
{
  struct builtinsFOStr *fo = (struct builtinsFOStr *)mdFindObjects->etc;
  NSSArena *arena = fo->arena;

  nss_ZFreeIf(fo->objs);
  nss_ZFreeIf(fo);
  nss_ZFreeIf(mdFindObjects);
  if ((NSSArena *)NULL != arena) {
    NSSArena_Destroy(arena);
  }

  return;
}

static NSSCKMDObject *
builtins_mdFindObjects_Next
(
  NSSCKMDFindObjects *mdFindObjects,
  NSSCKFWFindObjects *fwFindObjects,
  NSSCKMDSession *mdSession,
  NSSCKFWSession *fwSession,
  NSSCKMDToken *mdToken,
  NSSCKFWToken *fwToken,
  NSSCKMDInstance *mdInstance,
  NSSCKFWInstance *fwInstance,
  NSSArena *arena,
  CK_RV *pError
)
{
  struct builtinsFOStr *fo = (struct builtinsFOStr *)mdFindObjects->etc;
  builtinsInternalObject *io;

  if( fo->i == fo->n ) {
    *pError = CKR_OK;
    return (NSSCKMDObject *)NULL;
  }

  io = fo->objs[ fo->i ];
  fo->i++;

  return nss_builtins_CreateMDObject(arena, io, pError);
}

static int
builtins_derUnwrapInt(unsigned char *src, int size, unsigned char **dest) {
    unsigned char *start = src;
    int len = 0;

    if (*src ++ != 2) {
	return 0;
    }
    len = *src++;
    if (len & 0x80) {
	int count = len & 0x7f;
	len =0;

	if (count+2 > size) {
	    return 0;
	}
	while (count-- > 0) {
	    len = (len << 8) | *src++;
	}
    }
    if (len + (src-start) != size) {
	return 0;
    }
    *dest = src;
    return len;
}

static CK_BBOOL
builtins_attrmatch
(
  CK_ATTRIBUTE_PTR a,
  const NSSItem *b
)
{
  PRBool prb;

  if( a->ulValueLen != b->size ) {
    /* match a decoded serial number */
    if ((a->type == CKA_SERIAL_NUMBER) && (a->ulValueLen < b->size)) {
	int len;
	unsigned char *data = NULL;

	len = builtins_derUnwrapInt(b->data,b->size,&data);
	if (data &&
	    (len == a->ulValueLen) && 
	    nsslibc_memequal(a->pValue, data, len, (PRStatus *)NULL)) {
	    return CK_TRUE;
	}
    }
    return CK_FALSE;
  }

  prb = nsslibc_memequal(a->pValue, b->data, b->size, (PRStatus *)NULL);

  if( PR_TRUE == prb ) {
    return CK_TRUE;
  } else {
    return CK_FALSE;
  }
}


static CK_BBOOL
builtins_match
(
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  builtinsInternalObject *o
)
{
  CK_ULONG i;

  for( i = 0; i < ulAttributeCount; i++ ) {
    CK_ULONG j;

    for( j = 0; j < o->n; j++ ) {
      if( o->types[j] == pTemplate[i].type ) {
        if( CK_FALSE == builtins_attrmatch(&pTemplate[i], &o->items[j]) ) {
          return CK_FALSE;
        } else {
          break;
        }
      }
    }

    if( j == o->n ) {
      /* Loop ran to the end: no matching attribute */
      return CK_FALSE;
    }
  }

  /* Every attribute passed */
  return CK_TRUE;
}

NSS_IMPLEMENT NSSCKMDFindObjects *
nss_builtins_FindObjectsInit
(
  NSSCKFWSession *fwSession,
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  CK_RV *pError
)
{
  /* This could be made more efficient.  I'm rather rushed. */
  NSSArena *arena;
  NSSCKMDFindObjects *rv = (NSSCKMDFindObjects *)NULL;
  struct builtinsFOStr *fo = (struct builtinsFOStr *)NULL;

  /*
   * 99% of the time we get 0 or 1 matches. So we start with a small
   * stack-allocated array to hold the matches and switch to a heap-allocated
   * array later if the number of matches exceeds STACK_BUF_LENGTH.
   */
  #define STACK_BUF_LENGTH 1
  builtinsInternalObject *stackTemp[STACK_BUF_LENGTH];
  builtinsInternalObject **temp = stackTemp;
  PRBool tempIsHeapAllocated = PR_FALSE;
  PRUint32 i;

  arena = NSSArena_Create();
  if( (NSSArena *)NULL == arena ) {
    goto loser;
  }

  rv = nss_ZNEW(arena, NSSCKMDFindObjects);
  if( (NSSCKMDFindObjects *)NULL == rv ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  fo = nss_ZNEW(arena, struct builtinsFOStr);
  if( (struct builtinsFOStr *)NULL == fo ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  fo->arena = arena;
  /* fo->n and fo->i are already zero */

  rv->etc = (void *)fo;
  rv->Final = builtins_mdFindObjects_Final;
  rv->Next = builtins_mdFindObjects_Next;
  rv->null = (void *)NULL;

  for( i = 0; i < nss_builtins_nObjects; i++ ) {
    builtinsInternalObject *o = (builtinsInternalObject *)&nss_builtins_data[i];

    if( CK_TRUE == builtins_match(pTemplate, ulAttributeCount, o) ) {
      if( fo->n == STACK_BUF_LENGTH ) {
        /* Switch from the small stack array to a heap-allocated array large
         * enough to handle matches in all remaining cases. */
        temp = nss_ZNEWARRAY((NSSArena *)NULL, builtinsInternalObject *,
                             fo->n + nss_builtins_nObjects - i);
        if( (builtinsInternalObject **)NULL == temp ) {
          *pError = CKR_HOST_MEMORY;
          goto loser;
        }
        tempIsHeapAllocated = PR_TRUE;
        (void)nsslibc_memcpy(temp, stackTemp,
                             sizeof(builtinsInternalObject *) * fo->n);
      }

      temp[ fo->n ] = o;
      fo->n++;
    }
  }

  fo->objs = nss_ZNEWARRAY(arena, builtinsInternalObject *, fo->n);
  if( (builtinsInternalObject **)NULL == fo->objs ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  (void)nsslibc_memcpy(fo->objs, temp, sizeof(builtinsInternalObject *) * fo->n);
  if (tempIsHeapAllocated) {
    nss_ZFreeIf(temp);
    temp = (builtinsInternalObject **)NULL;
  }

  return rv;

 loser:
  if (tempIsHeapAllocated) {
    nss_ZFreeIf(temp);
  }
  nss_ZFreeIf(fo);
  nss_ZFreeIf(rv);
  if ((NSSArena *)NULL != arena) {
     NSSArena_Destroy(arena);
  }
  return (NSSCKMDFindObjects *)NULL;
}

