/*
 * Copyright (C) 2012, Redhat Inc.
 * Copyright (c) 2011, Collabora Ltd.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above
 *       copyright notice, this list of conditions and the
 *       following disclaimer.
 *     * 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.
 *     * The names of contributors to this software may not 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.
 *
 * Author: Stef Walter <stefw@collabora.co.uk>
 */

#include "config.h"

#include "attrs.h"
#include "buffer.h"
#include "compat.h"
#include "constants.h"
#include "debug.h"
#include "hash.h"
#include "pkcs11.h"
#include "pkcs11i.h"
#include "pkcs11x.h"

#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define ELEMS(x) (sizeof (x) / sizeof (x[0]))

bool
p11_attrs_terminator (const CK_ATTRIBUTE *attrs)
{
	return (attrs == NULL || attrs->type == CKA_INVALID);
}

CK_ULONG
p11_attrs_count (const CK_ATTRIBUTE *attrs)
{
	CK_ULONG count;

	if (attrs == NULL)
		return 0UL;

	for (count = 0; !p11_attrs_terminator (attrs); count++, attrs++);

	return count;
}

void
p11_attrs_free (void *attrs)
{
	CK_ATTRIBUTE *ats = attrs;
	int i;

	if (!attrs)
		return;

	for (i = 0; !p11_attrs_terminator (ats + i); i++) {
		p11_attr_clear (&ats[i]);
	}
	free (ats);
}

static CK_ATTRIBUTE *
attrs_build (CK_ATTRIBUTE *attrs,
             CK_ULONG count_to_add,
             bool take_values,
             bool override,
             CK_ATTRIBUTE * (*generator) (void *),
             void *state)
{
	CK_ATTRIBUTE *attr;
	CK_ATTRIBUTE *add;
	CK_ULONG current;
	CK_ULONG at;
	CK_ULONG j;
	CK_ULONG i;
	size_t length;
	void *new_memory;

	/* How many attributes we already have */
	current = p11_attrs_count (attrs);

	/* Reallocate for how many we need */
	length = current + count_to_add;
	return_val_if_fail (current <= length && length < SIZE_MAX, NULL);
	new_memory = reallocarray (attrs, length + 1, sizeof (CK_ATTRIBUTE));
	return_val_if_fail (new_memory != NULL, NULL);
	attrs = new_memory;

	at = current;
	for (i = 0; i < count_to_add; i++) {
		add = (generator) (state);

		/* Skip with invalid type */
		if (!add || add->type == CKA_INVALID)
			continue;

		attr = NULL;

		/* Do we have this attribute? */
		for (j = 0; attr == NULL && j < current; j++) {
			if (attrs[j].type == add->type) {
				attr = attrs + j;
				break;
			}
		}

		/* The attribute doesn't exist */
		if (attr == NULL) {
			attr = attrs + at;
			at++;

		/* The attribute exists and we're not overriding */
		} else if (!override) {
			if (take_values)
				free (add->pValue);
			continue;

		/* The attribute exists but we're overriding */
		} else {
			free (attr->pValue);
		}

		if (take_values) {
			memcpy (attr, add, sizeof (CK_ATTRIBUTE));
		} else {
			if (!p11_attr_copy (attr, add)) {
				return_val_if_reached (NULL);
			}
		}
	}

	/* Mark this as the end */
	(attrs + at)->type = CKA_INVALID;
	assert (p11_attrs_terminator (attrs + at));
	return attrs;
}

static CK_ATTRIBUTE *
vararg_generator (void *state)
{
	va_list *va = state;
	return va_arg (*va, CK_ATTRIBUTE *);
}

CK_ATTRIBUTE *
p11_attrs_build (CK_ATTRIBUTE *attrs,
                 ...)
{
	CK_ULONG count;
	va_list va;

	count = 0UL;
	va_start (va, attrs);
	while (va_arg (va, CK_ATTRIBUTE *))
		count++;
	va_end (va);

	va_start (va, attrs);
	attrs = attrs_build (attrs, count, false, true,
	                     vararg_generator, &va);
	va_end (va);

	return attrs;
}

static CK_ATTRIBUTE *
template_generator (void *state)
{
	CK_ATTRIBUTE **template = state;
	return (*template)++;
}

CK_ATTRIBUTE *
p11_attrs_buildn (CK_ATTRIBUTE *attrs,
                  const CK_ATTRIBUTE *add,
                  CK_ULONG count)
{
	return attrs_build (attrs, count, false, true,
	                    template_generator, &add);
}

CK_ATTRIBUTE *
p11_attrs_take (CK_ATTRIBUTE *attrs,
                CK_ATTRIBUTE_TYPE type,
                CK_VOID_PTR value,
                CK_ULONG length)
{
	CK_ATTRIBUTE attr = { type, value, length };
	CK_ATTRIBUTE *add = &attr;
	return attrs_build (attrs, 1, true, true,
	                    template_generator, &add);
}

CK_ATTRIBUTE *
p11_attrs_merge (CK_ATTRIBUTE *attrs,
                 CK_ATTRIBUTE *merge,
                 bool replace)
{
	CK_ATTRIBUTE *ptr;
	CK_ULONG count;

	if (attrs == NULL)
		return merge;

	ptr = merge;
	count = p11_attrs_count (merge);

	attrs = attrs_build (attrs, count, true, replace,
	                     template_generator, &ptr);

	/*
	 * Since we're supposed to own the merge attributes,
	 * free the container array.
	 */
	free (merge);

	return attrs;
}

CK_ATTRIBUTE *
p11_attrs_dup (const CK_ATTRIBUTE *attrs)
{
	CK_ULONG count;

	count = p11_attrs_count (attrs);
	return p11_attrs_buildn (NULL, attrs, count);
}

CK_ATTRIBUTE *
p11_attrs_find (CK_ATTRIBUTE *attrs,
                CK_ATTRIBUTE_TYPE type)
{
	CK_ULONG i;

	for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
		if (attrs[i].type == type)
			return attrs + i;
	}

	return NULL;
}

CK_ATTRIBUTE *
p11_attrs_findn (CK_ATTRIBUTE *attrs,
                 CK_ULONG count,
                 CK_ATTRIBUTE_TYPE type)
{
	CK_ULONG i;

	for (i = 0; i < count; i++) {
		if (attrs[i].type == type)
			return attrs + i;
	}

	return NULL;
}

bool
p11_attrs_find_bool (const CK_ATTRIBUTE *attrs,
                     CK_ATTRIBUTE_TYPE type,
                     CK_BBOOL *value)
{
	CK_ULONG i;

	for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
		if (attrs[i].type == type &&
		    attrs[i].ulValueLen == sizeof (CK_BBOOL) &&
		    attrs[i].pValue != NULL) {
			*value = *((CK_BBOOL *)attrs[i].pValue);
			return true;
		}
	}

	return false;
}

bool
p11_attrs_findn_bool (const CK_ATTRIBUTE *attrs,
                      CK_ULONG count,
                      CK_ATTRIBUTE_TYPE type,
                      CK_BBOOL *value)
{
	CK_ULONG i;

	for (i = 0; i < count; i++) {
		if (attrs[i].type == type &&
		    attrs[i].ulValueLen == sizeof (CK_BBOOL) &&
		    attrs[i].pValue != NULL) {
			*value = *((CK_BBOOL *)attrs[i].pValue);
			return true;
		}
	}

	return false;
}

bool
p11_attrs_find_ulong (const CK_ATTRIBUTE *attrs,
                      CK_ATTRIBUTE_TYPE type,
                      CK_ULONG *value)
{
	CK_ULONG i;

	for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
		if (attrs[i].type == type &&
		    attrs[i].ulValueLen == sizeof (CK_ULONG) &&
		    attrs[i].pValue != NULL) {
			*value = *((CK_ULONG *)attrs[i].pValue);
			return true;
		}
	}

	return false;
}

bool
p11_attrs_findn_ulong (const CK_ATTRIBUTE *attrs,
                       CK_ULONG count,
                       CK_ATTRIBUTE_TYPE type,
                       CK_ULONG *value)
{
	CK_ULONG i;

	for (i = 0; i < count; i++) {
		if (attrs[i].type == type &&
		    attrs[i].ulValueLen == sizeof (CK_ULONG) &&
		    attrs[i].pValue != NULL) {
			*value = *((CK_ULONG *)attrs[i].pValue);
			return true;
		}
	}

	return false;
}

void *
p11_attrs_find_value (CK_ATTRIBUTE *attrs,
                      CK_ATTRIBUTE_TYPE type,
                      size_t *length)
{
	CK_ULONG i;

	for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
		if (attrs[i].type == type &&
		    attrs[i].ulValueLen != 0 &&
		    attrs[i].ulValueLen != (CK_ULONG)-1 &&
		    attrs[i].pValue != NULL) {
			if (length)
				*length = attrs[i].ulValueLen;
			return attrs[i].pValue;
		}
	}

	return NULL;
}

CK_ATTRIBUTE *
p11_attrs_find_valid (CK_ATTRIBUTE *attrs,
                      CK_ATTRIBUTE_TYPE type)
{
	CK_ULONG i;

	for (i = 0; !p11_attrs_terminator (attrs + i); i++) {
		if (attrs[i].type == type &&
		    attrs[i].pValue != NULL &&
		    attrs[i].ulValueLen != 0 &&
		    attrs[i].ulValueLen != (CK_ULONG)-1)
			return attrs + i;
	}

	return NULL;
}

bool
p11_attrs_remove (CK_ATTRIBUTE *attrs,
                  CK_ATTRIBUTE_TYPE type)
{
	CK_ULONG count;
	CK_ULONG i;

	count = p11_attrs_count (attrs);
	for (i = 0; i < count; i++) {
		if (attrs[i].type == type)
			break;
	}

	if (i == count)
		return false;

	if (attrs[i].pValue) {
		p11_attr_clear (&attrs[i]);
	}

	memmove (attrs + i, attrs + i + 1, (count - (i + 1)) * sizeof (CK_ATTRIBUTE));
	attrs[count - 1].type = CKA_INVALID;
	return true;
}

void
p11_attrs_purge (CK_ATTRIBUTE *attrs)
{
	int in, out;

	for (in = 0, out = 0; !p11_attrs_terminator (attrs + in); in++) {
		if (attrs[in].ulValueLen == (CK_ULONG)-1) {
			free (attrs[in].pValue);
			attrs[in].pValue = NULL;
			attrs[in].ulValueLen = 0;
		} else {
			if (in != out)
				memcpy (attrs + out, attrs + in, sizeof (CK_ATTRIBUTE));
			out++;
		}
	}

	attrs[out].type = CKA_INVALID;
	assert (p11_attrs_terminator (attrs + out));

}

bool
p11_attrs_match (const CK_ATTRIBUTE *attrs,
                 const CK_ATTRIBUTE *match)
{
	CK_ATTRIBUTE *attr;

	for (; !p11_attrs_terminator (match); match++) {
		attr = p11_attrs_find ((CK_ATTRIBUTE *)attrs, match->type);
		if (!attr)
			return false;
		if (!p11_attr_equal (attr, match))
			return false;
	}

	return true;
}

bool
p11_attrs_matchn (const CK_ATTRIBUTE *attrs,
                  const CK_ATTRIBUTE *match,
                  CK_ULONG count)
{
	CK_ATTRIBUTE *attr;
	CK_ULONG i;

	for (i = 0; i < count; i++) {
		attr = p11_attrs_find ((CK_ATTRIBUTE *)attrs, match[i].type);
		if (!attr)
			return false;
		if (!p11_attr_equal (attr, match + i))
			return false;
	}

	return true;

}


bool
p11_attr_match_value (const CK_ATTRIBUTE *attr,
                      const void *value,
                      ssize_t length)
{
	if (length < 0)
		length = strlen (value);
	return (attr != NULL &&
	        attr->ulValueLen == length &&
	        (attr->pValue == value ||
	         (attr->pValue && value &&
	          memcmp (attr->pValue, value, attr->ulValueLen) == 0)));
}

bool
p11_attr_equal (const void *v1,
                const void *v2)
{
	const CK_ATTRIBUTE *one = v1;
	const CK_ATTRIBUTE *two = v2;

	return (one == two ||
		(one && two && one->type == two->type &&
		 p11_attr_match_value (one, two->pValue, two->ulValueLen)));
}

unsigned int
p11_attr_hash (const void *data)
{
	const CK_ATTRIBUTE *attr = data;
	uint32_t hash = 0;

	if (attr != NULL) {
		p11_hash_murmur3 (&hash,
		                  &attr->type, sizeof (attr->type),
		                  attr->pValue, (size_t)attr->ulValueLen,
		                  NULL);
	}

	return hash;
}

bool
p11_attr_copy (CK_ATTRIBUTE *dst, const CK_ATTRIBUTE *src)
{
	memcpy (dst, src, sizeof (CK_ATTRIBUTE));

	if (!src->pValue) {
		return true;
	}

	if (src->ulValueLen == 0) {
		dst->pValue = malloc (1);
	} else {
		dst->pValue = malloc (src->ulValueLen);
	}
	if (!dst->pValue) {
		return_val_if_reached (false);
	}

	assert (dst->ulValueLen >= src->ulValueLen);

	if (!IS_ATTRIBUTE_ARRAY (src)) {
		memcpy (dst->pValue, src->pValue, src->ulValueLen);
	} else {
		CK_ATTRIBUTE *child_dst;
		const CK_ATTRIBUTE *child_src;
		size_t i;

		for (i = 0, child_dst = dst->pValue, child_src = src->pValue;
		     i < src->ulValueLen / sizeof (CK_ATTRIBUTE);
		     i++, child_dst++, child_src++) {
			if (!p11_attr_copy (child_dst, child_src)) {
				return_val_if_reached (false);
			}
		}
	}

	return true;
}

void
p11_attr_clear (CK_ATTRIBUTE *attr)
{
	if (IS_ATTRIBUTE_ARRAY (attr) && attr->pValue) {
		CK_ATTRIBUTE *child;
		size_t i;

		for (i = 0, child = attr->pValue;
		     i < attr->ulValueLen / sizeof (CK_ATTRIBUTE);
		     i++, child++) {
			p11_attr_clear (child);
		}
	}
	free (attr->pValue);
}

static void
buffer_append_printf (p11_buffer *buffer,
                      const char *format,
                      ...) GNUC_PRINTF(2, 3);

static void
buffer_append_printf (p11_buffer *buffer,
                      const char *format,
                      ...)
{
	char *string;
	va_list va;

	va_start (va, format);
	if (vasprintf (&string, format, va) < 0) {
		va_end (va);
		return_if_reached ();
	}
	va_end (va);

	p11_buffer_add (buffer, string, -1);
	free (string);
}

static bool
attribute_is_ulong_of_type (const CK_ATTRIBUTE *attr,
                            CK_ULONG type)
{
	if (attr->type != type)
		return false;
	if (attr->ulValueLen != sizeof (CK_ULONG))
		return false;
	if (!attr->pValue)
		return false;
	return true;
}

static bool
attribute_is_trust_value (const CK_ATTRIBUTE *attr)
{
	switch (attr->type) {
	case CKA_TRUST_DIGITAL_SIGNATURE:
	case CKA_TRUST_NON_REPUDIATION:
	case CKA_TRUST_KEY_ENCIPHERMENT:
	case CKA_TRUST_DATA_ENCIPHERMENT:
	case CKA_TRUST_KEY_AGREEMENT:
	case CKA_TRUST_KEY_CERT_SIGN:
	case CKA_TRUST_CRL_SIGN:
	case CKA_TRUST_SERVER_AUTH:
	case CKA_TRUST_CLIENT_AUTH:
	case CKA_TRUST_CODE_SIGNING:
	case CKA_TRUST_EMAIL_PROTECTION:
	case CKA_TRUST_IPSEC_END_SYSTEM:
	case CKA_TRUST_IPSEC_TUNNEL:
	case CKA_TRUST_IPSEC_USER:
	case CKA_TRUST_TIME_STAMPING:
		break;
	default:
		return false;
	}

	return attribute_is_ulong_of_type (attr, attr->type);
}

static bool
attribute_is_sensitive (const CK_ATTRIBUTE *attr,
                        CK_OBJECT_CLASS klass)
{
	/*
	 * Don't print any just attribute, since they may contain
	 * sensitive data
	 */

	switch (attr->type) {
	#define X(x) case x: return false;
	X (CKA_CLASS)
	X (CKA_TOKEN)
	X (CKA_PRIVATE)
	X (CKA_LABEL)
	X (CKA_APPLICATION)
	X (CKA_OBJECT_ID)
	X (CKA_CERTIFICATE_TYPE)
	X (CKA_ISSUER)
	X (CKA_SERIAL_NUMBER)
	X (CKA_AC_ISSUER)
	X (CKA_OWNER)
	X (CKA_ATTR_TYPES)
	X (CKA_TRUSTED)
	X (CKA_CERTIFICATE_CATEGORY)
	X (CKA_JAVA_MIDP_SECURITY_DOMAIN)
	X (CKA_URL)
	X (CKA_HASH_OF_SUBJECT_PUBLIC_KEY)
	X (CKA_HASH_OF_ISSUER_PUBLIC_KEY)
	X (CKA_CHECK_VALUE)
	X (CKA_KEY_TYPE)
	X (CKA_SUBJECT)
	X (CKA_ID)
	X (CKA_SENSITIVE)
	X (CKA_ENCRYPT)
	X (CKA_DECRYPT)
	X (CKA_WRAP)
	X (CKA_UNWRAP)
	X (CKA_SIGN)
	X (CKA_SIGN_RECOVER)
	X (CKA_VERIFY)
	X (CKA_VERIFY_RECOVER)
	X (CKA_DERIVE)
	X (CKA_START_DATE)
	X (CKA_END_DATE)
	X (CKA_MODULUS_BITS)
	X (CKA_PRIME_BITS)
	/* X (CKA_SUBPRIME_BITS) */
	/* X (CKA_SUB_PRIME_BITS) */
	X (CKA_VALUE_BITS)
	X (CKA_VALUE_LEN)
	X (CKA_EXTRACTABLE)
	X (CKA_LOCAL)
	X (CKA_NEVER_EXTRACTABLE)
	X (CKA_ALWAYS_SENSITIVE)
	X (CKA_KEY_GEN_MECHANISM)
	X (CKA_MODIFIABLE)
	X (CKA_SECONDARY_AUTH)
	X (CKA_AUTH_PIN_FLAGS)
	X (CKA_ALWAYS_AUTHENTICATE)
	X (CKA_WRAP_WITH_TRUSTED)
	X (CKA_WRAP_TEMPLATE)
	X (CKA_UNWRAP_TEMPLATE)
	X (CKA_HW_FEATURE_TYPE)
	X (CKA_RESET_ON_INIT)
	X (CKA_HAS_RESET)
	X (CKA_PIXEL_X)
	X (CKA_PIXEL_Y)
	X (CKA_RESOLUTION)
	X (CKA_CHAR_ROWS)
	X (CKA_CHAR_COLUMNS)
	X (CKA_COLOR)
	X (CKA_BITS_PER_PIXEL)
	X (CKA_CHAR_SETS)
	X (CKA_ENCODING_METHODS)
	X (CKA_MIME_TYPES)
	X (CKA_MECHANISM_TYPE)
	X (CKA_REQUIRED_CMS_ATTRIBUTES)
	X (CKA_DEFAULT_CMS_ATTRIBUTES)
	X (CKA_SUPPORTED_CMS_ATTRIBUTES)
	X (CKA_ALLOWED_MECHANISMS)
	X (CKA_X_ASSERTION_TYPE)
	X (CKA_X_CERTIFICATE_VALUE)
	X (CKA_X_PURPOSE)
	X (CKA_X_PEER)
	X (CKA_X_DISTRUSTED)
	X (CKA_X_CRITICAL)
	X (CKA_PUBLIC_KEY_INFO)
	X (CKA_NSS_URL)
	X (CKA_NSS_EMAIL)
	X (CKA_NSS_SMIME_INFO)
	X (CKA_NSS_SMIME_TIMESTAMP)
	X (CKA_NSS_PKCS8_SALT)
	X (CKA_NSS_PASSWORD_CHECK)
	X (CKA_NSS_EXPIRES)
	X (CKA_NSS_KRL)
	X (CKA_NSS_PQG_COUNTER)
	X (CKA_NSS_PQG_SEED)
	X (CKA_NSS_PQG_H)
	X (CKA_NSS_PQG_SEED_BITS)
	X (CKA_NSS_MODULE_SPEC)
	X (CKA_TRUST_DIGITAL_SIGNATURE)
	X (CKA_TRUST_NON_REPUDIATION)
	X (CKA_TRUST_KEY_ENCIPHERMENT)
	X (CKA_TRUST_DATA_ENCIPHERMENT)
	X (CKA_TRUST_KEY_AGREEMENT)
	X (CKA_TRUST_KEY_CERT_SIGN)
	X (CKA_TRUST_CRL_SIGN)
	X (CKA_TRUST_SERVER_AUTH)
	X (CKA_TRUST_CLIENT_AUTH)
	X (CKA_TRUST_CODE_SIGNING)
	X (CKA_TRUST_EMAIL_PROTECTION)
	X (CKA_TRUST_IPSEC_END_SYSTEM)
	X (CKA_TRUST_IPSEC_TUNNEL)
	X (CKA_TRUST_IPSEC_USER)
	X (CKA_TRUST_TIME_STAMPING)
	X (CKA_TRUST_STEP_UP_APPROVED)
	X (CKA_CERT_SHA1_HASH)
	X (CKA_CERT_MD5_HASH)
	X (CKA_IBM_OPAQUE)
	X (CKA_IBM_RESTRICTABLE)
	X (CKA_IBM_NEVER_MODIFIABLE)
	X (CKA_IBM_RETAINKEY)
	X (CKA_IBM_ATTRBOUND)
	X (CKA_IBM_KEYTYPE)
	X (CKA_IBM_CV)
	X (CKA_IBM_MACKEY)
	X (CKA_IBM_USE_AS_DATA)
	X (CKA_IBM_STRUCT_PARAMS)
	X (CKA_IBM_STD_COMPLIANCE1)
	X (CKA_IBM_PROTKEY_EXTRACTABLE)
	X (CKA_IBM_PROTKEY_NEVER_EXTRACTABLE)
	X (CKA_IBM_OPAQUE_PKEY)
	X (CKA_IBM_DILITHIUM_KEYFORM)
	X (CKA_IBM_DILITHIUM_RHO)
	X (CKA_IBM_DILITHIUM_T1)
	case CKA_VALUE:
		return (klass != CKO_CERTIFICATE &&
			klass != CKO_X_CERTIFICATE_EXTENSION);
	#undef X
	}

	return true;
}

static void
format_class (p11_buffer *buffer,
              CK_OBJECT_CLASS klass)
{
	const char *string = p11_constant_name (p11_constant_classes, klass);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "0x%08lX", klass);
}

static void
format_assertion_type (p11_buffer *buffer,
                       CK_X_ASSERTION_TYPE type)
{
	const char *string = p11_constant_name (p11_constant_asserts, type);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "0x%08lX", type);
}

static void
format_key_type (p11_buffer *buffer,
                 CK_KEY_TYPE type)
{
	const char *string = p11_constant_name (p11_constant_keys, type);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "0x%08lX", type);
}

static void
format_certificate_type (p11_buffer *buffer,
                         CK_CERTIFICATE_TYPE type)
{
	const char *string = p11_constant_name (p11_constant_certs, type);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "0x%08lX", type);
}

static void
format_trust_value (p11_buffer *buffer,
                    CK_TRUST trust)
{
	const char *string = p11_constant_name (p11_constant_trusts, trust);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "0x%08lX", trust);
}

static void
format_certificate_category (p11_buffer *buffer,
                             CK_ULONG category)
{
	const char *string = p11_constant_name (p11_constant_categories, category);
	if (string != NULL)
		buffer_append_printf (buffer, "%lu (%s)", category, string);
	else
		buffer_append_printf (buffer, "%lu", category);
}

static void
format_attribute_type (p11_buffer *buffer,
                       CK_ULONG type)
{
	const char *string = p11_constant_name (p11_constant_types, type);
	if (string != NULL)
		p11_buffer_add (buffer, string, -1);
	else
		buffer_append_printf (buffer, "CKA_0x%08lX", type);
}

static void
format_some_bytes (p11_buffer *buffer,
                   void *bytes,
                   CK_ULONG length)
{
	unsigned char ch;
	const unsigned char *data = bytes;
	CK_ULONG i;

	if (bytes == NULL) {
		p11_buffer_add (buffer, "NULL", -1);
		return;
	}

	p11_buffer_add (buffer, "\"", 1);
	for (i = 0; i < length && i < 128; i++) {
		ch = data[i];
		if (ch == '\t')
			p11_buffer_add (buffer, "\\t", -1);
		else if (ch == '\n')
			p11_buffer_add (buffer, "\\n", -1);
		else if (ch == '\r')
			p11_buffer_add (buffer, "\\r", -1);
		else if (ch >= 32 && ch < 127)
			p11_buffer_add (buffer, &ch, 1);
		else
			buffer_append_printf (buffer, "\\x%02x", ch);
	}

	if (i < length)
		buffer_append_printf (buffer, "...");
	p11_buffer_add (buffer, "\"", 1);
}

void
p11_attr_format (p11_buffer *buffer,
                 const CK_ATTRIBUTE *attr,
                 CK_OBJECT_CLASS klass)
{
	p11_buffer_add (buffer, "{ ", -1);
	format_attribute_type (buffer, attr->type);
	p11_buffer_add (buffer, " = ", -1);
	if (attr->ulValueLen == CKA_INVALID) {
		buffer_append_printf (buffer, "(-1) INVALID");
	} else if (attribute_is_ulong_of_type (attr, CKA_CLASS)) {
		format_class (buffer, *((CK_OBJECT_CLASS *)attr->pValue));
	} else if (attribute_is_ulong_of_type (attr, CKA_X_ASSERTION_TYPE)) {
		format_assertion_type (buffer, *((CK_X_ASSERTION_TYPE *)attr->pValue));
	} else if (attribute_is_ulong_of_type (attr, CKA_CERTIFICATE_TYPE)) {
		format_certificate_type (buffer, *((CK_CERTIFICATE_TYPE *)attr->pValue));
	} else if (attribute_is_ulong_of_type (attr, CKA_CERTIFICATE_CATEGORY)) {
		format_certificate_category (buffer, *((CK_ULONG *)attr->pValue));
	} else if (attribute_is_ulong_of_type (attr, CKA_KEY_TYPE)) {
		format_key_type (buffer, *((CK_KEY_TYPE *)attr->pValue));
	} else if (attribute_is_trust_value (attr)) {
		format_trust_value (buffer, *((CK_TRUST *)attr->pValue));
	} else if (attribute_is_sensitive (attr, klass)) {
		buffer_append_printf (buffer, "(%lu) NOT-PRINTED", attr->ulValueLen);
	} else {
		buffer_append_printf (buffer, "(%lu) ", attr->ulValueLen);
		format_some_bytes (buffer, attr->pValue, attr->ulValueLen);
	}
	p11_buffer_add (buffer, " }", -1);
}

void
p11_attrs_format (p11_buffer *buffer,
                  const CK_ATTRIBUTE *attrs,
                  int count)
{
	CK_BBOOL first = CK_TRUE;
	CK_OBJECT_CLASS klass;
	int i;

	if (count < 0)
		count = p11_attrs_count (attrs);

	if (!p11_attrs_findn_ulong (attrs, CKA_CLASS, count, &klass))
		klass = CKA_INVALID;

	buffer_append_printf (buffer, "(%d) [", count);
	for (i = 0; i < count; i++) {
		if (first)
			p11_buffer_add (buffer, " ", 1);
		else
			p11_buffer_add (buffer, ", ", 2);
		first = CK_FALSE;
		p11_attr_format (buffer, attrs + i, klass);
	}
	p11_buffer_add (buffer, " ]", -1);
}

char *
p11_attrs_to_string (const CK_ATTRIBUTE *attrs,
                     int count)
{
	p11_buffer buffer;
	if (!p11_buffer_init_null (&buffer, 128))
		return_val_if_reached (NULL);
	p11_attrs_format (&buffer, attrs, count);
	return p11_buffer_steal (&buffer, NULL);
}

char *
p11_attr_to_string (const CK_ATTRIBUTE *attr,
                    CK_OBJECT_CLASS klass)
{
	p11_buffer buffer;
	if (!p11_buffer_init_null (&buffer, 32))
		return_val_if_reached (NULL);
	p11_attr_format (&buffer, attr, klass);
	return p11_buffer_steal (&buffer, NULL);
}
