/*
 *
 */

#ifndef NO_INCLUDE_LOCL
#include "sec.h"
#include "error.h"
#include "common.h"
#include "cp210x.h"
#include "crd_ut10xx.h"
#endif

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef ERROR
#undef ERROR
#endif

#define ERROR(ut, m) ERROR_push_msg((ut)->err, m)

static volatile int connected_apps = 0;

/** \brief Table of supported ECC domains. */
static const CRD_UT10XX_ecc_params_t CRD_UT10XX_ecc_params[] = 
{
	/*
	 * int id;
	 * int field_octets;
	 * int order_octets;
	 * const char *name;
	 * int asn1_named_octets;
	 * const unsigned char *asn1_named;
	 * const unsigned char *asn1_named_alg;
	 * int asn1_specified_octets;
	 * const unsigned char *asn1_specified;
	 */
	/** \brief Paramethers defined in SEC-2 as secp160r1. */
	{
		SECP160R1,
		20,
		21,
		"secp160r1",
		sizeof(SECP160R1_named),
		SECP160R1_named,
		SECP160R1_subject_public_key_info_alg,
		sizeof(SECP160R1_specified),
		SECP160R1_specified
	},
	/** \brief Paramethers defined in SEC-2 as secp192r1. */
	{
		SECP192R1,
		24,
		24,
		"secp192r1",
		sizeof(SECP192R1_named),
		SECP192R1_named,
		SECP192R1_subject_public_key_info_alg,
		sizeof(SECP192R1_specified),
		SECP192R1_specified
	},
	/** \brief Paramethers defined in SEC-2 as secp224r1. */
	{
		SECP224R1,
		28,
		28,
		"secp224r1",
		sizeof(SECP224R1_named),
		SECP224R1_named,
		SECP224R1_subject_public_key_info_alg,
		sizeof(SECP224R1_specified),
		SECP224R1_specified
	},
	/** \brief Paramethers defined in SEC-2 as secp256r1. */
	{
		SECP256R1,
		32,
		32,
		"secp256r1",
		sizeof(SECP256R1_named),
		SECP256R1_named,
		SECP256R1_subject_public_key_info_alg,
		sizeof(SECP256R1_specified),
		SECP256R1_specified
	}
};

const CRD_UT10XX_ecc_params_t* 
CRD_UT10XX_get_ecc_params(int id)
{
	const CRD_UT10XX_ecc_params_t* params = CRD_UT10XX_ecc_params;
	int total = sizeof(CRD_UT10XX_ecc_params) / sizeof(CRD_UT10XX_ecc_params_t);
	int i;
	
	for (i = 0; i < total; i++)
	{
		if (params->id == id)
			return (params);
		
		params++;
	}
	
	return (0);
}

/**
 * \brief List of devices attached to host.
 */
typedef struct CRD_UT10XX_list_t
{
	/** \brief Total number of attached devices. */
	int total;
	/** \brief Pointer to first device. */
	CRD_UT10XX_t *begin;
	/** \brief Pointer to last device. */
	CRD_UT10XX_t *end;
} 
CRD_UT10XX_list_t;

/** 
 * \brief Global list of devices attached to host. 
 */
static CRD_UT10XX_list_t CRD_UT10XX_devices = 
{
	.total = 0,
	.begin = 0,
	.end = 0
};

int
CRD_UT10XX_lib_init(void)
{
	if (connected_apps++ == 0)
		usb_init();
	
	return (0);
}

int
CRD_UT10XX_lib_finish(void)
{
	if (--connected_apps == 0)
	{
		/* Delete all devices. */
		while (CRD_UT10XX_devices.begin)
		{
			CRD_UT10XX_devices.end = CRD_UT10XX_devices.begin->next;
			memset(CRD_UT10XX_devices.begin, 0, sizeof(CRD_UT10XX_t));
			free(CRD_UT10XX_devices.begin);
			CRD_UT10XX_devices.begin = CRD_UT10XX_devices.end;
		}
	}
	
	return (0);
}

int
CRD_UT10XX_lib_find_devices(void)
{
	CRD_UT10XX_t *ut;
	struct usb_bus *bus;
	struct usb_device *dev;
	
	/* Clear total number of attached devices. */
	CRD_UT10XX_devices.total = 0;
	
	/* Clear present flag in all devices on list. */
	for (ut = CRD_UT10XX_devices.begin; ut; ut = ut->next)
		ut->present = 0;
	
	/* Initialization of LibUSB data structures. */
	usb_find_busses();
	usb_find_devices();
	
	/* Search all buses and devices. */
	for (bus = usb_get_busses(); bus; bus = bus->next)
	{
		for (dev = bus->devices; dev; dev = dev->next)	
		{
			/* Looking for devices with specific VID and PID. */
			if ((dev->descriptor.idVendor == CP210X_VENDOR_ID) 
				&& (dev->descriptor.idProduct == CP210X_PRODUCT_ID))
			{
				/* Increase number of attached devices. */
				CRD_UT10XX_devices.total += 1;
				
				/* Looking for device in list. */
				for (ut = CRD_UT10XX_devices.begin; ut; ut = ut->next)
				{
					if (ut->hw->devnum == dev->devnum)
					{
						/* Check as present. */
						ut->present = 1;
						break;
					}
				}
				
				/* 
				 * If new device was attached then alloc memory and fill 
				 * its data structure.
				 */
				if ( (ut == 0) && ((ut = malloc(sizeof(CRD_UT10XX_t))) != 0) )
				{
					memset(ut, 0, sizeof(CRD_UT10XX_t));
					/* Check as present. */
					ut->present = 1;
					/* Assign pointer to LibUSB device structure. */
					ut->hw = dev;
					/* Read USB device product string and serial number. */
					memset(ut->product_string, 0, sizeof(ut->product_string));
					memset(ut->serial_number, 0, sizeof(ut->serial_number));
					ut->handle = usb_open(ut->hw);
					usb_get_string_simple(ut->handle, ut->hw->descriptor.iProduct, 
						ut->product_string, sizeof(ut->product_string) - 1);
					usb_get_string_simple(ut->handle, ut->hw->descriptor.iSerialNumber, 
						ut->serial_number, sizeof(ut->serial_number) - 1);
#					ifndef WIN32
					usb_reset(ut->handle);
#					endif
					usb_close(ut->handle);
					ut->handle = 0;
					/* Assign Basic interface functions. */
					ut->open = CP210X_CRD_UT10XX_open;
					ut->close = CP210X_CRD_UT10XX_close;
					ut->read = CP210X_CRD_UT10XX_read;
					ut->write = CP210X_CRD_UT10XX_write;
					/* Number of possible files is constant and equal 16. */
					ut->files_no = 16;
					
					/* Attach new device to the list. */
					if (CRD_UT10XX_devices.begin)
						CRD_UT10XX_devices.end->next = ut;
					
					else
						CRD_UT10XX_devices.begin = ut;
					
					CRD_UT10XX_devices.end = ut;
				}
			}
		}
	}
	
	/* Remove devices which are not present. */
	while (CRD_UT10XX_devices.begin && !CRD_UT10XX_devices.begin->present)
	{
		CRD_UT10XX_devices.end = CRD_UT10XX_devices.begin->next;
		memset(CRD_UT10XX_devices.begin, 0, sizeof(CRD_UT10XX_t));
		free(CRD_UT10XX_devices.begin);
		CRD_UT10XX_devices.begin = CRD_UT10XX_devices.end;
	}
	
	CRD_UT10XX_devices.end = CRD_UT10XX_devices.begin;
	
	while (CRD_UT10XX_devices.end && CRD_UT10XX_devices.end->next)
	{
		if (CRD_UT10XX_devices.end->next->present)
		{
			CRD_UT10XX_devices.end = CRD_UT10XX_devices.end->next;
		}
		else
		{
			ut = CRD_UT10XX_devices.end->next;
			CRD_UT10XX_devices.end->next = ut->next;
			memset(ut, 0, sizeof(CRD_UT10XX_t));
			free(ut);
		}
	}
	
	/* Return total number of devices attached to host. */
	return (CRD_UT10XX_devices.total);
}

int 
CRD_UT10XX_lib_actualize_devices(void)
{
	CRD_UT10XX_t *ut;
	int key_num;
	
	int status;
	
	/* 
	 * For all devices attached to host read information about them 
	 * and their keys.
	 */
	for (ut = CRD_UT10XX_devices.begin; ut; ut = ut->next)
	{
		status = CRD_UT10XX_session_open(ut);
		
		if (status < 0)
			return (status);
		
		status = CRD_UT10XX_cmd_read_dev_info(ut);
		
		if (status < 0)
		{
			CRD_UT10XX_session_close(ut);
			return (status);
		}
		
		if ((ut->info.info & (1 << CONF_BIT)) 
			&& !(ut->info.info & (1 << LOCK_BIT)))
		{
			for (key_num = 0; key_num < ut->info.keys_num; key_num++)
			{
				status = CRD_UT10XX_cmd_read_key_info(ut, key_num);
				
				if (status < 0)
				{
					CRD_UT10XX_session_close(ut);
					return (status);
				}
			}
			
			status = CRD_UT10XX_fs_get_file_list(ut);
			
			if (status < 0)
			{
				CRD_UT10XX_session_close(ut);
				return (status);
			}
		}
		
		status = CRD_UT10XX_session_close(ut);
		
		if (status < 0)
			return (status);
	}
	
	return (0);
}

const char *
CRD_UT10XX_lib_error_desc(int err)
{
	const char *undef = 
		"undefined error (error specification is not compleat)";
	const char *lock = 
		"session could not be open (perhaps device was locked by other "
		"thread or application)";
	const char *unlock = 
		"session could not be close because it is not open";
	const char *closed = 
		"session is closed";
	const char *receive = 
		"message receiving error";
	const char *transmit = 
		"message transmition error";
	const char *toosmallbuf = 
		"buffer is too small (can not write everything)";
	const char *toolargebuf = 
		"buffer is too large (can not read everything)";
	const char *setup = 
		"unable to configure device interface";
	const char *wrongkey = 
		"key number is not supported by device";
	const char *notsupported = 
		"device is not supported by this software";
	const char *argrange = 
		"arguments are out of range";
	const char *notinit = 
		"not initialized parameter";
	const char *dev_wrongcmd = 
		"wrong command sended to device";
	const char *dev_cmdnotallowed = 
		"command is not allowed in this state of device";
	const char *dev_wrongpin =
		"wrong PIN, command not allowed";
	const char *dev_wrongecpoint = 
		"point passed to ECDH shared info primitive is not on curve";
	const char *dev_nokey = 
		"key you are trying to use is not generated";
	const char *dev_argrange = 
		"arguments sended to device are out of range";
	const char *dev_framesyntax = 
		"wrong communication frame syntax";
	const char *dev_framerange = 
		"wrong size of communication frame";
	const char *unknown = 
		"unknown error";
		
	switch (err)
	{
		case ERR_CRD_UT10XX_UNDEF:
			return (undef);
		
		case ERR_CRD_UT10XX_LOCK:
			return (lock);
		
		case ERR_CRD_UT10XX_UNLOCK:
			return (unlock);
		
		case ERR_CRD_UT10XX_SESSION_CLOSED:
			return (closed);
		
		case ERR_CRD_UT10XX_RECEIVE:
			return (receive);
			
		case ERR_CRD_UT10XX_TRANSMIT:
			return (transmit);
			
		case ERR_CRD_UT10XX_TOO_SMALL_BUF:
			return (toosmallbuf);
			
		case ERR_CRD_UT10XX_TOO_LARGE_BUF:
			return (toolargebuf);
			
		case ERR_CRD_UT10XX_SET_UP:
			return (setup);
			
		case ERR_CRD_UT10XX_WRONG_KEY:
			return (wrongkey);
			
		case ERR_CRD_UT10XX_NOT_SUPPORTED:
			return (notsupported);
		
		case ERR_CRD_UT10XX_ARG_RANGE:
			return (argrange);
		
		case ERR_CRD_UT10XX_NOT_INIT:
			return (notinit);
			
		case ERR_CRD_UT10XX_DEV_WRONG_CMD:
			return (dev_wrongcmd);
			
		case ERR_CRD_UT10XX_DEV_CMD_NOT_ALLOWED:
			return (dev_cmdnotallowed);
			
		case ERR_CRD_UT10XX_DEV_WRONG_PIN:
			return (dev_wrongpin);
			
		case ERR_CRD_UT10XX_DEV_WRONG_EC_POINT:
			return (dev_wrongecpoint);
		
		case ERR_CRD_UT10XX_DEV_NO_KEY:
			return (dev_nokey);
			
		case ERR_CRD_UT10XX_DEV_ARG_RANGE:
			return (dev_argrange);
			
		case ERR_CRD_UT10XX_DEV_FRAME_SYNTAX:
			return (dev_framesyntax);
			
		case ERR_CRD_UT10XX_DEV_FRAME_RANGE:
			return (dev_framerange);
	}
		
	return (unknown);
}

const char*
CRD_UT10XX_lib_detail_error_desc(const CRD_UT10XX_t *ut)
{
	return (ut->err);
}

int 
CRD_UT10XX_lib_detail_error_desc_to_file(const CRD_UT10XX_t *ut, const char *fmt, 
	FILE *fd)
{
	char err_stack[sizeof(ut->err)];
	const char *err;
	
	/* Copy error stack. */
	strcpy(err_stack, ut->err);
	
	/* Pritn all errors stored on stack to given file. */
	while ( (err = ERROR_pop(err_stack)) != 0 )
		fprintf(fd, fmt, err);
	
	return (0);
}

int
CRD_UT10XX_session_open(CRD_UT10XX_t *ut)
{
	int status;
	
	/* to be done - OPEN_CRITICAL_SECTION */
	if (ut->session_is_open)
		return (ERR_CRD_UT10XX_LOCK);
	
	memset(ut->err, 0, sizeof(ut->err));
	
	if ( (status = (*ut->open)(ut)) < 0 )
	{
		ERROR(ut, "could not open device");
		return (status);
	}
	
	ut->session_is_open = 1;
	/* to be done - CLOSE_CRITICAL_SECTION */
	
	return (0);
}

int
CRD_UT10XX_session_close(CRD_UT10XX_t *ut)
{
	int status;
	
	/* to be done - OPEN_CRITICAL_SECTION */
	if (!ut->session_is_open)
		return (ERR_CRD_UT10XX_UNLOCK);
	
	if ( (status = (*ut->close)(ut)) < 0 )
	{
		ERROR(ut, "could not close device");
		return (status);
	}
	
	ut->session_is_open = 0;
	/* to be done - CLOSE_CRITICAL_SECTION */
	
	return (0);
}

CRD_UT10XX_t*
CRD_UT10XX_get_first(void)
{
	return (CRD_UT10XX_devices.begin);
}

CRD_UT10XX_t*
CRD_UT10XX_find_by_index(int idx)
{
	CRD_UT10XX_t *ut = 0;
	
	/* Find pointer to device with given index. */
	if (idx < CRD_UT10XX_devices.total)
	{
		ut = CRD_UT10XX_devices.begin;
		while (idx--)
			ut = ut->next;
	}
	
	return (ut);
}

CRD_UT10XX_t*
CRD_UT10XX_find_by_name(const char *name)
{
	CRD_UT10XX_t *ut = CRD_UT10XX_devices.begin;
	
	/* Find pointer to device with given name. */
	while (ut)
	{
		if (strncmp(name, ut->info.name, sizeof(ut->info.name)) == 0)
			break;
			
		ut = ut->next;
	}
	
	return (ut);
}

CRD_UT10XX_t*
CRD_UT10XX_find_by_serial_number(const char *serial_number)
{
	CRD_UT10XX_t *ut = CRD_UT10XX_devices.begin;
	
	/* Find pointer to device with given name. */
	while (ut)
	{
		if (strncmp(serial_number, ut->serial_number, sizeof(ut->serial_number)) == 0)
			break;
			
		ut = ut->next;
	}
	
	return (ut);
}

int
CRD_UT10XX_find_index_of(CRD_UT10XX_t *ut)
{
	CRD_UT10XX_t *t = CRD_UT10XX_devices.begin;
	int idx = 0;
	
	while (t)
	{
		if (t == ut)
			break;
		
		t = t->next;
		idx++;
	}
	
	if (!t)
		idx = -1;
	
	return (idx);
}

int 
CRD_UT10XX_find_free_key(CRD_UT10XX_t *ut)
{
	int key_num;
	
	/* Searching index of free key. */
	for (key_num = 0; key_num < ut->info.keys_num; key_num++)
	{
		if ( (((ut->keys[key_num].info)) & (1 << GEN_BIT)) == 0 )
			return (key_num);
	}
	
	ERROR(ut, "there is no free key");
	return (-1);
}

int 
CRD_UT10XX_find_key_by_name(CRD_UT10XX_t *ut, const char *name)
{
	int key_num;
	
	/* Seaching index of key eith given name. */
	for (key_num = 0; key_num < ut->info.keys_num; key_num++)
	{
		if ( strcmp(name, ((ut->keys) + key_num)->name) == 0 )
			return (key_num);
	}
	
	ERROR(ut, "there is no key of given name");
	return (-1);
}

int
CRD_UT10XX_subject_public_key_info(const CRD_UT10XX_key_info_t *key, char *key_info, 
	int key_info_octets)
{
	const CRD_UT10XX_ecc_params_t *params = key->ecc_params;
	unsigned char *ptr = (unsigned char *)key_info;
	
	if (ptr == 0)
		return (8 + params->asn1_named_alg[1] + 2*params->field_octets);
	
	if (key_info_octets < 8 + params->asn1_named_alg[1] + 2*params->field_octets)
		return (ERR_CRD_UT10XX_TOO_SMALL_BUF);
	
	/*
	 * Create DER encoding of EC public key.
	 */
	
	/* SubjectPublicKeyInfo SEQUENCE */
	ptr[0] = 0x30;
	ptr[1] = 6 + params->asn1_named_alg[1] + 2*params->field_octets;
	/* algorithm AlgorithmIdentifier */
	memcpy(ptr + 2, params->asn1_named_alg, 2 + params->asn1_named_alg[1]);
	/* subjectPublicKey BIT STRING */
	ptr[4 + ptr[3]] = 0x03;
	ptr[5 + ptr[3]] = 2 + 2*params->field_octets;
	ptr[6 + ptr[3]] = 0x00;
	ptr[7 + ptr[3]] = 0x04;
	memcpy(key_info + 8 + ptr[3], key->pub_x, params->field_octets);
	memcpy(key_info + 8 + ptr[3] + params->field_octets, key->pub_y, 
		params->field_octets);
	
	return (2 + ptr[1]);
}

/**
 * \brief Single pass of communication protocol.
 *
 * \param[in,out] ut -
 *   device.
 * \param[in] out -
 *   buffer with data to send to device.
 * \param[in] out_octets - 
 *   number of data octets in \a out buffer.
 * \param[out] in -
 *   buffer for data received from device.
 * \param[in] in_octets -
 *   size of \a in buffer in octets.
 *
 * \return
 *   - nonegative number of received octets if everything work properly,
 *   - negative error code in other case.
 */
static int
CRD_UT10XX_single_pass(CRD_UT10XX_t *ut, const unsigned char *out, int out_octets, 
	unsigned char *in, int in_octets)
{
	int timeout = CRD_UT10XX_TIME_OUT;
	int status;
	
	if (out[1] == CMD_DEV_INIT)
		timeout = CRD_UT10XX_LONG_TIME_OUT;
	
	/* to be done - OPEN_CRITICAL_SECTION */
	if (!ut->session_is_open)
		return (ERR_CRD_UT10XX_SESSION_CLOSED);
	/* to be done - CLOSE_CRITICAL_SECTION */
	
	if ( (status = (*ut->write)(ut, out, out_octets)) < 0 )
	{
		(*ut->close)(ut);
		ERROR(ut, "could not write to device");
		return (status);
	}
	
	if ( (status = (*ut->read)(ut, in, in_octets, timeout)) < 0 )
	{
		(*ut->close)(ut);
		ERROR(ut, "could not read from device");
		return (status);
	}
	
	if ( (status == 2) && (in[0] == DF_ERR) )
	{
		ERROR(ut, "host receive error frame from device");
		return (ERR_CRD_UT10XX_DEV - in[1]);
	}
	
	return (status);
}

int 
CRD_UT10XX_cmd_dev_init(CRD_UT10XX_t *ut)
{
	unsigned char out[2];
	unsigned char in[2];
	int status;
	
	out[0] = HF_CMD;
	out[1] = CMD_DEV_INIT;
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_dev_conf(CRD_UT10XX_t *ut)
{
	unsigned char out[34];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_DEV_CONF;
	out[2] = (ut->conf.info) & (1 << UPCNA_BIT);
	out[3] = 0;
	strncpy((char *)out + 4, ut->conf.name, 10);
	out[14] = 0;
	out[15] = 0;
	out[16] = (ut->conf.so_pin_limit) & 0xff;
	out[17] = (ut->conf.user_pin_limit) & 0xff;
	memcpy(out + 18, ut->conf.so_pin, 8);
	memcpy(out + 26, ut->conf.user_pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	memset(&ut->conf, 0, sizeof(ut->conf));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_read_dev_info(CRD_UT10XX_t *ut)
{
	unsigned char out[2];
	unsigned char in[25];
	int status;
	int octets;
	
	out[0] = HF_CMD;
	out[1] = CMD_READ_DEV_INFO;
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	octets = status;
	
	if (octets != sizeof(in))
	{
		ERROR(ut, "received frame syntax error");
		return (ERR_CRD_UT10XX_RECEIVE);
	}
	
	ut->info.info = in[1];
	strncpy(ut->info.name, (char *)in + 3, 10);
	ut->info.name[10] = '\0';
	ut->info.so_pin_trys = in[13];
	ut->info.user_pin_trys = in[14];
	ut->info.so_pin_limit = in[15];
	ut->info.user_pin_limit = in[16];
	ut->info.main_ver = in[17];
	ut->info.sub_ver = in[18];
	ut->info.public_mem = 256 * (int)in[19] + (int)in[20];
	ut->info.public_objs = ut->info.public_mem / PUBLIC_OBJ_OCTETS;
	ut->info.keys_num = in[21];
	ut->info.ecc_num = in[22];
	ut->info.ecc_params = CRD_UT10XX_get_ecc_params(256 * (int)in[23] + 
		(int)in[24]);
	
	if ( !ut->info.ecc_params )
	{
		ERROR(ut, "received ECC domain paramethers are not supported");
		return (ERR_CRD_UT10XX_NOT_SUPPORTED);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_read_key_info(CRD_UT10XX_t *ut, int key_num)
{
	unsigned char out[3];
	unsigned char in[DF_MAX];
	int status;
	int field_octets;
	int octets;
	
	out[0] = HF_CMD;
	out[1] = CMD_READ_KEY_INFO;
	out[2] = key_num;
	
	if ( (key_num < 0) || (ut->info.keys_num <= key_num) 
		|| ((sizeof(ut->keys)/sizeof(ut->keys[0])) <= key_num) )
	{
		ERROR(ut, "key number is out of range for this device");
		return (ERR_CRD_UT10XX_WRONG_KEY);
	}
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	octets = status;

	ut->keys[key_num].info = in[1];
	ut->keys[key_num].ecc_params = CRD_UT10XX_get_ecc_params(
		256 * (int)in[2] + (int)in[3]);

	field_octets = ut->info.ecc_params->field_octets;
	
	if (octets != 25 + 2*field_octets)
	{
		ut->keys[key_num].info = 0;
		ERROR(ut, "host received wrong number of key octets");
		return(ERR_CRD_UT10XX_RECEIVE);
	}
	
	strncpy(ut->keys[key_num].name, (char *)in + 4, 21);
	ut->keys[key_num].name[22] = '\0';

	memset(ut->keys[key_num].pub_x, 0, sizeof(ut->keys[key_num].pub_x));
	memset(ut->keys[key_num].pub_y, 0, sizeof(ut->keys[key_num].pub_y));
	
	memcpy(ut->keys[key_num].pub_x, in + 25, field_octets);
	memcpy(ut->keys[key_num].pub_y, in + 25 + field_octets, field_octets);
	
	return (0);
}

int 
CRD_UT10XX_cmd_read_public_obj(CRD_UT10XX_t *ut, int obj_num, char *obj)
{
	unsigned char out[4];
	unsigned char in[1 + PUBLIC_OBJ_OCTETS];
	int status;
	
	out[0] = HF_CMD;
	out[1] = CMD_READ_PUBLIC_OBJ;
	out[2] = (obj_num >> 8) & 0xFF;
	out[3] = obj_num & 0xFF;
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	memcpy(obj, in + 1, status - 1);
	
	return (0);
}

int 
CRD_UT10XX_cmd_rbg_generate_bits(CRD_UT10XX_t *ut, char *buf, int octets)
{
	unsigned char out[3];
	unsigned char in[DF_MAX];
	int status;
	
	out[0] = HF_CMD;
	out[1] = CMD_RBG_GENERATE_BITS;
	
	while (octets > 120)
	{
		out[2] = 120;
		
		status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
		
		if (status < 0)
		{
			ERROR(ut, CRD_UT10XX_lib_error_desc(status));
			return (status);
		}
			
		memcpy(buf, in + 1, 120);
		buf += 120;
		octets -= 120;
	}
	
	out[2] = octets;
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
		
	memcpy(buf, in + 1, status - 1);
	
	return (0);
}

int 
CRD_UT10XX_cmd_dev_unlock(CRD_UT10XX_t *ut, const char *pin)
{
	unsigned char out[11];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_DEV_UNLOCK;
	out[2] = CRD_UT10XX_SO_PIN_ID;
	
	if (strlen(pin) > 8)
	{
		ERROR(ut, "PIN is too long (at most 8 characters)");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	strncpy((char *)out + 3, pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return status;
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_dev_erase(CRD_UT10XX_t *ut, const char *pin)
{
	unsigned char out[11];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_DEV_ERASE;
	out[2] = CRD_UT10XX_SO_PIN_ID;
	
	if (strlen(pin) > 8)
	{
		ERROR(ut, "PIN is too long (at most 8 characters)");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	strncpy((char *)out + 3, pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return status;
	}
	
	memset(ut->keys, 0, sizeof(ut->keys));
	
	return (0);
}

int 
CRD_UT10XX_cmd_change_so_pin(CRD_UT10XX_t *ut, const char *pin, const char *new_pin)
{
	unsigned char out[19];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_CHANGE_SO_PIN;
	out[2] = CRD_UT10XX_SO_PIN_ID;
	
	if ((strlen(pin) > 8) || (strlen(new_pin) > 8))
	{
		ERROR(ut, "PIN is too long (at most 8 characters)");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	strncpy((char *)out + 3, pin, 8);
	strncpy((char *)out + 11, new_pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return status;
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_ecdsa_sign(CRD_UT10XX_t *ut, const char *pin, int key_num, 
	char *sign, int sign_octets, const char *digest, int digest_octets)
{
	unsigned char out[HF_MAX];
	unsigned char in[DF_MAX];
	int status;
	int octets;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_ECDSA_SIGN;
	out[2] = CRD_UT10XX_USER_PIN_ID;
	
	if (strlen(pin) > 8)
	{
		ERROR(ut, "PIN is too long (at most 8 characters)");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	if ( (key_num < 0) || (ut->info.keys_num <= key_num) || 
		((sizeof(ut->keys)/sizeof(ut->keys[0])) <= key_num) )
	{
		ERROR(ut, "key number is out of range for this device");
		return (ERR_CRD_UT10XX_WRONG_KEY);
	}
		
	strncpy((char *)out + 3, pin, 8);
	out[11] = key_num;
	
	digest_octets = digest_octets < 64 ? digest_octets : 64;
	memcpy(out + 12, digest, digest_octets);
	
	status = CRD_UT10XX_single_pass(ut, out, 12 + digest_octets, in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	octets = status - 1;
	
	if (sign_octets < octets)
	{
		ERROR(ut, "buffer for signature is too small");
		return (ERR_CRD_UT10XX_TOO_SMALL_BUF);
	}
	
	memcpy(sign, in + 1, octets);
	
	return (octets);
}

int 
CRD_UT10XX_cmd_change_user_pin(CRD_UT10XX_t *ut, const char *pin, int pin_id, 
	const char *new_pin)
{
	unsigned char out[19];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_CHANGE_USER_PIN;
	out[2] = pin_id;
	
	if ((strlen(pin) > 8) || (strlen(new_pin) > 8))
	{
		ERROR(ut, "PIN is too long (at most 8 characters)");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	strncpy((char *)out + 3, pin, 8);
	strncpy((char *)out + 11, new_pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return status;
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_generate_key(CRD_UT10XX_t *ut, const char *pin, int pin_id, 
	const char *key_name, int key_num)
{
	unsigned char out[36];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_GENERATE_KEY;
	
	if ( (strlen(pin) > 8) || ((pin_id != CRD_UT10XX_SO_PIN_ID) 
		&& (pin_id != CRD_UT10XX_USER_PIN_ID)) || (strlen(key_name) > 21) )
	{
		ERROR(ut, "PIN is too long or its id is wrong or key name "
			"is too long");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	if ( (key_num < 0) || (ut->info.keys_num <= key_num) 
		|| ((sizeof(ut->keys)/sizeof(ut->keys[0])) <= key_num) )
	{
		ERROR(ut, "key number is out of range for this device");
		return (ERR_CRD_UT10XX_WRONG_KEY);
	}
	
	if (ut->info.ecc_params == 0)
	{
		ERROR(ut, "ecc_params field is not initialized");
		return (ERR_CRD_UT10XX_NOT_INIT);
	}
	
	out[2] = pin_id;
	strncpy((char *)out + 3, pin, 8);
	out[11] = key_num;
	out[12] = 0;
	out[13] = ((ut->info.ecc_params->id) >> 8) & 0xff;
	out[14] = ((ut->info.ecc_params->id) >> 0) & 0xff;
	strncpy((char *)out + 15, key_name, 21);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_delete_key(CRD_UT10XX_t *ut, const char *pin, int pin_id, 
	int key_num)
{
	unsigned char out[12];
	unsigned char in[2];
	int status;
	
	memset(out, 0, sizeof(out));
	
	out[0] = HF_CMD;
	out[1] = CMD_DELETE_KEY;
	
	if ( (strlen(pin) > 8) || ((pin_id != CRD_UT10XX_SO_PIN_ID) 
		&& (pin_id != CRD_UT10XX_USER_PIN_ID)) )
	{
		ERROR(ut, "PIN is too long or its id is wrong");
		return (ERR_CRD_UT10XX_ARG_RANGE);
	}
	
	if ( (key_num < 0) || (ut->info.keys_num <= key_num) 
		|| ((sizeof(ut->keys)/sizeof(ut->keys[0])) <= key_num) )
	{
		ERROR(ut, "key of this number is out of range for this device");
		return (ERR_CRD_UT10XX_WRONG_KEY);
	}
	
	out[2] = pin_id;
	strncpy((char *)out + 3, pin, 8);
	out[11] = key_num;
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_write_public_obj(CRD_UT10XX_t *ut, const char *pin, int pin_id, 
	int obj_num, const char *obj)
{
	unsigned char out[13 + PUBLIC_OBJ_OCTETS];
	unsigned char in[2];
	int status;
	
	out[0] = HF_CMD;
	out[1] = CMD_WRITE_PUBLIC_OBJ;
	out[2] = pin_id;
	strncpy((char *)out + 3, pin, 8);
	out[11] = (obj_num >> 8) & 0xFF;
	out[12] = obj_num & 0xFF;
	memcpy(out + 13, obj, PUBLIC_OBJ_OCTETS);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_cmd_try_pin(CRD_UT10XX_t *ut, const char *pin, int pin_id)
{
	unsigned char out[11];
	unsigned char in[2];
	int status;
	
	out[0] = HF_CMD;
	out[1] = CMD_TRY_PIN;
	out[2] = pin_id;
	strncpy((char *)out + 3, pin, 8);
	
	status = CRD_UT10XX_single_pass(ut, out, sizeof(out), in, sizeof(in));
	
	memset(out, 0, sizeof(out));
	
	if (status < 0)
	{
		ERROR(ut, CRD_UT10XX_lib_error_desc(status));
		return (status);
	}
	
	return (0);
}

int 
CRD_UT10XX_fs_get_file_list(CRD_UT10XX_t *ut)
{
	unsigned char obj[PUBLIC_OBJ_OCTETS];
	int status;
	int i;
	int j;
	
	for (i = 0; i < 8; i++)
		ut->free_sectors[i] = 0;
	
	for (i = 8; i < 128; i++)
		ut->free_sectors[i] = i;
	
	for (i = 0; i < ut->files_no; i++)
	{
		status = CRD_UT10XX_cmd_read_public_obj(ut, i, (char *)obj);
		
		if (status < 0)
		{
			ERROR(ut, "can not read file information table");
			return (status);
		}
		
		ut->file_types[i] = obj[0];
		
		if (ut->file_types[i] != CRD_UT10XX_FT_EMPTY)
		{
			ut->file_sizes[i] = 256 * (int)obj[2] + (int)obj[3];
			memcpy(ut->file_names_tb + 32*i, obj + 4, 28);
			ut->file_names[i] = ut->file_names_tb + 32*i;
			
			for (j = 32; j < 64; j++)
			{
				if (obj[j] == 0)
					break;
				
				ut->free_sectors[obj[j]] = 0;
			}
		}
	}
	
	return (0);
}

int
CRD_UT10XX_fs_clear(CRD_UT10XX_t *ut, const char *pin, int pin_id)
{
	unsigned char obj[PUBLIC_OBJ_OCTETS];
	int status;
	int i;
	
	memset(obj, 0, PUBLIC_OBJ_OCTETS);
	
	for (i = 0; i < 256; i++)
	{
		status = CRD_UT10XX_cmd_write_public_obj(ut, pin, pin_id, i, (char *)obj);
		
		if (status < 0)
		{
			ERROR(ut, "could not write to device public memory");
			return (status);
		}
	}
	
	return (0);
}

int
CRD_UT10XX_fs_scan(CRD_UT10XX_t *ut, char *str)
{
	unsigned char mem[16384];
	unsigned char *finfo;
	int status;
	int offset;
	int i;
	int j;
	
	for (i = 0, offset = 0; offset < sizeof(mem); i++, offset += PUBLIC_OBJ_OCTETS)
	{
		status = CRD_UT10XX_cmd_read_public_obj(ut, i, (char *)mem + offset);
		
		if (status < 0)
		{
			ERROR(ut, "could not read device public memory");
			return (status);
		}
	}
	
	for (i = 0, offset = 0; i < 16; i++, offset += PUBLIC_OBJ_OCTETS)
	{
		finfo = mem + offset;
		
		str += sprintf(str, "[%01X] FILE INFO\n", i);
		
		switch (finfo[0])
		{
			case CRD_UT10XX_FT_EMPTY:
				str += sprintf(str, "\ttype: EMPTY\n");
				break;
			case CRD_UT10XX_FT_CRT:
				str += sprintf(str, "\ttype: CRT\n");
				break;
			default:
				str += sprintf(str, "\ttype: UNKNOWN\n");
				break;
		}
		
		str += sprintf(str, "\tsize: %d\n", 256 * (int)finfo[2] + (int)finfo[3]);
		str += sprintf(str, "\tname: %s\n", finfo + 4);
		str += sprintf(str, "\tsectors:");
		
		for (j = 32; j < 64; j++)
		{
			if (finfo[j] == 0)
				break;
			
			str += sprintf(str, " %d", finfo[j]);
		}
		
		str += sprintf(str, "\n\t\n\t");
		
		for (j = 0; j < 64; j++)
		{
			str += sprintf(str, ":%02X", finfo[j]);
			
			if (j % 16 == 15)
				str += sprintf(str, "\n\t");
		}
		
		str += sprintf(str, "\n");
	}
	
	for (i = 8, offset = 16 * PUBLIC_OBJ_OCTETS; i < 128; i++, offset += 128)
	{
		str += sprintf(str, "[%02X] SECTOR\n\t", i);
		
		for (j = 0; j < 128; j++)
		{
			str += sprintf(str, ":%02X", mem[offset + j]);
			
			if (j % 16 == 15)
				str += sprintf(str, "\n\t");
		}
		
		str += sprintf(str, "\n");
	}
	
	return (0);
}

CRD_UT10XX_file_t* 
CRD_UT10XX_file_open(CRD_UT10XX_t *ut, const char *file_name)
{
	CRD_UT10XX_file_t *fd = 0;
	unsigned char obj[PUBLIC_OBJ_OCTETS];
	int status;
	int i;
	int j;
	
	for (i = 0; i < ut->files_no; i++)
	{
		if ( (ut->file_types[i] == CRD_UT10XX_FT_EMPTY)
			|| (strcmp(file_name, ut->file_names[i]) != 0) )
		{
			continue;
		}
		
		fd = malloc(sizeof(*fd));
		
		if (fd == 0)
		{
			ERROR(ut, "could not allocate memory");
			return (0);;
		}
		
		memset(fd, 0, sizeof(*fd));
		
		fd->ut = ut;
		fd->num = i;
		fd->type = ut->file_types[i];
		fd->size = ut->file_sizes[i];
		strcpy(fd->name, file_name);
		
		status = CRD_UT10XX_cmd_read_public_obj(ut, i, (char *)obj);
		
		if (status < 0)
		{
			ERROR(ut, "could not read file information table");
			free(fd);
			return (0);
		}
		
		for (j = 32; j < 64; j++)
		{
			if (obj[j] == 0)
				break;
			
			fd->sectors[j - 32] = obj[j];
		}
		
		for (j = 0; j < 32; j++)
		{
			if (fd->sectors[j] == 0)
				break;
			
			/* Read first part of file sector. */
			status = CRD_UT10XX_cmd_read_public_obj(ut, 2*fd->sectors[j], (char *)obj);
			
			if (status < 0)
			{
				ERROR(ut, "could not read file contents");
				free(fd);
				return (0);
			}
			
			memcpy(fd->contents + (2*j) * PUBLIC_OBJ_OCTETS, obj, PUBLIC_OBJ_OCTETS);
			
			/* Read second part of file sector. */
			status = CRD_UT10XX_cmd_read_public_obj(ut, 2*fd->sectors[j] + 1, (char *)obj);
			
			if (status < 0)
			{
				ERROR(ut, "could not read file contents");
				free(fd);
				return (0);
			}
			
			memcpy(fd->contents + (2*j + 1) * PUBLIC_OBJ_OCTETS, obj, 
				PUBLIC_OBJ_OCTETS);
		}
			
		fd->fpos = 0;
		fd->flushed = 1;
	}
	
	return (fd);
}

CRD_UT10XX_file_t* 
CRD_UT10XX_file_create(CRD_UT10XX_t *ut, const char *file_name, int type)
{
	CRD_UT10XX_file_t *fd = 0;
	int i;
	
	for (i = 0; i < ut->files_no; i++)
	{
		if ( (ut->file_types[i] != CRD_UT10XX_FT_EMPTY)
			&& (strcmp(file_name, ut->file_names[i]) == 0) )
		{
			ERROR(ut, "there exist file with that name");
			return (0);
		}
	}
	
	for (i = 0; i < ut->files_no; i++)
	{
		if (ut->file_types[i] != CRD_UT10XX_FT_EMPTY)
			continue;
		
		fd = malloc(sizeof(*fd));
		
		if (fd == 0)
		{
			ERROR(ut, "could not allocate memory");
			return (0);
		}
		
		memset(fd, 0, sizeof(*fd));
		
		fd->ut = ut;
		fd->num = i;
		fd->type = type;
		fd->size = 0;
		strcpy(fd->name, file_name);
		fd->fpos = 0;
		fd->flushed = 0;
#if 0
fprintf(stderr, "file created successfully\n");
fprintf(stderr, "\tfile num: %d\n", fd->num);
fprintf(stderr, "\tfile type: %02X\n", fd->type);
fprintf(stderr, "\tfile size: %d\n", fd->size);
fprintf(stderr, "\tfile name: %s\n\n", fd->name);
#endif
		break;
	}
	
	return (fd);
}

int 
CRD_UT10XX_file_type(CRD_UT10XX_file_t *fd)
{
	return (fd->type);
}

int
CRD_UT10XX_file_type_set_printable_mask(CRD_UT10XX_file_t *fd)
{
	int i;
	
	for (i = 0; i < fd->size; i++)
	{
		if (!isgraph(fd->contents[i]) && !isspace(fd->contents[i]))
		{
			fd->type &= ~CRD_UT10XX_FTM_PRINT;
			return (0);
		}
	}
	
	fd->type |= CRD_UT10XX_FTM_PRINT;
	
	return (1);
}

int 
CRD_UT10XX_file_rename(CRD_UT10XX_file_t *fd, const char *new_name, int type)
{
	if (28 < strlen(new_name))
	{
		ERROR(fd->ut, "new file name is too long");
		return (/*err*/-1);
	}
	
	fd->type = type;
	strcpy(fd->name, new_name);
	fd->flushed = 0;
	
	return (0);
}

int 
CRD_UT10XX_file_set_fpos(CRD_UT10XX_file_t *fd, int fpos)
{
	if (fpos == CRD_UT10XX_FPOS_END)
	{
		fd->fpos = fd->size;
	}
	else if (fpos == CRD_UT10XX_FPOS_BEGIN)
	{
		fd->fpos = 0;
	}
	else if (fpos == CRD_UT10XX_FPOS_CUR)
	{
		fd->fpos = fd->fpos;
	}
	else if (fpos < fd->size)
	{
		fd->fpos = fpos;
	}
	else
	{
		ERROR(fd->ut, "there is no such position in this file");
		return (/*err*/-1);
	}
	
	return (fd->fpos);
}

int 
CRD_UT10XX_file_read(CRD_UT10XX_file_t *fd, char *buf, int size)
{
	int end = fd->fpos + size;
	int nr = 0;
	
	if (size <= 0)
	{
		ERROR(fd->ut, "size have to be positive");
		return (/*err*/-1);
	}
	
	while ( (fd->fpos < fd->size) && (fd->fpos < end) )
	{
		*buf++ = fd->contents[fd->fpos++];
		nr += 1;
	}
	
	return (nr);
}

int 
CRD_UT10XX_file_write(CRD_UT10XX_file_t *fd, const char *buf, int size)
{
	int avaliable = 0;
	int need = 0;
	int i;
	int j;
	
	if (size <= 0)
	{
		ERROR(fd->ut, "size have to be positive number");
		return (/*err*/-1);
	}
	
	if (sizeof(fd->contents) < fd->fpos + size)
	{
		ERROR(fd->ut, "too much data to write");
		return (/*err*/-1);
	}
	
	if (fd->size < fd->fpos + size)
	{
		need = ((fd->fpos + size + 127) / 128) - ((fd->size + 127) / 128);
		
		for (i = 0; i < 128; i++)
		{
			if (fd->ut->free_sectors[i] != 0)
				avaliable += 1;
		}
		
		if (avaliable < need)
		{
			ERROR(fd->ut, "there is no enought free memory on device");
			return (/*err*/-1);
		}
		
		j = (fd->size + 127) / 128;
		
		for (i = 0; i < 128; i++)
		{
			if (need == 0)
				break;
			
			if (fd->ut->free_sectors[i] != 0)
			{
				fd->ut->free_sectors[i] = 0;
				fd->sectors[j++] = i;
				need -= 1;
			}
		}
		
		fd->size = fd->fpos + size;
	}
	
	memcpy(fd->contents + fd->fpos, buf, size);
	fd->fpos += size;
	fd->flushed = 0;
	
	return (size);
}

int 
CRD_UT10XX_file_flush(CRD_UT10XX_file_t *fd, const char *pin, int pin_id)
{
	unsigned char obj[PUBLIC_OBJ_OCTETS];
	int fnum = fd->num;
	int status;
	int i;
	
	/* There is no new data to flush. */
	if (fd->flushed == 1)
		return (0);

	/* Drop information about file to device. */
	memset(obj, 0, sizeof(obj));
	
	obj[0] = fd->type;
	obj[2] = (fd->size >> 8) & 0xFF;
	obj[3] = fd->size & 0xFF;
	strcpy((char *)obj + 4, fd->name);
	
	for (i = 0; i < 32; i++)
		obj[i + 32] = fd->sectors[i];

	status = CRD_UT10XX_cmd_write_public_obj(fd->ut, pin, pin_id, fnum, (char *)obj);
	
	if (status < 0)
	{
		ERROR(fd->ut, "could not write file information to device");
		return (status);
	}
	
	/* Drop information about file to fd->ut structure. */
	fd->ut->file_types[fnum] = fd->type;
	fd->ut->file_sizes[fnum] = fd->size;
	strcpy(fd->ut->file_names_tb + fnum*32, fd->name);
	fd->ut->file_names[fnum] = fd->ut->file_names_tb + fnum*32;
	
	/* Write contents of file to device. */
	for (i = 0; i < 32; i++)
	{
		if (fd->sectors[i] == 0)
			break;
		
		/* Write first part of sector to public memory. */
		status = CRD_UT10XX_cmd_write_public_obj(fd->ut, pin, pin_id, 
			2*fd->sectors[i], fd->contents + (2*i) * PUBLIC_OBJ_OCTETS);
		
		if (status < 0)
		{
			ERROR(fd->ut, "could not write file contents to device");
			return (status);
		}
		
		/* Write second part of sector to public memory. */
		status = CRD_UT10XX_cmd_write_public_obj(fd->ut, pin, pin_id, 
			2*fd->sectors[i] + 1, fd->contents + (2*i + 1) * PUBLIC_OBJ_OCTETS);
		
		if (status < 0)
		{
			ERROR(fd->ut, "could not write file contents to device");
			return (status);
		}
	}
	
	fd->flushed = 1;
	
	return (0);
}

int 
CRD_UT10XX_file_delete(CRD_UT10XX_file_t *fd, const char *pin, int pin_id)
{
	unsigned char obj[PUBLIC_OBJ_OCTETS];
	int fnum = fd->num;
	int status;
	int i;
	
	memset(obj, 0, sizeof(obj));
	
	status = CRD_UT10XX_cmd_write_public_obj(fd->ut, pin, pin_id, fnum, (char *)obj);
	
	if (status < 0)
	{
		ERROR(fd->ut, "");
		return (status);
	}
	
	for (i = 0; i < 32; i++)
	{
		if (fd->sectors[i] == 0)
			break;
		
		fd->ut->free_sectors[fd->sectors[i]] = fd->sectors[i];
	}
	
	fd->ut->file_types[fnum] = CRD_UT10XX_FT_EMPTY;
	fd->ut->file_sizes[fnum] = 0;
	memset(fd->ut->file_names_tb + 32*fnum, 0, 32);
	fd->ut->file_names[fnum] = 0;
	
	memset(fd, 0, sizeof(*fd));
	free(fd);
	
	return (0);
}

int 
CRD_UT10XX_file_close(CRD_UT10XX_file_t *fd)
{
	int nd;
	int i;
	
	if (fd)
	{
		/* Remove reserved sectors from unflushed file. */
		if (fd->flushed == 0)
		{
			/* Determine number of unflushed sectors. */
			nd = ((fd->size + 127) / 128) - ((fd->ut->file_sizes[fd->num] 
				+ 127) / 128);
			
			/* Determine index of first unreserved sector. */
			for (i = 0; i < 32; i++)
			{
				if (fd->sectors[i] == 0)
					break;
			}
			
			/* Add reserved but unflushed sectors to free_sectors table. */
			while (nd-- > 0)
			{
				i -= 1;
				fd->ut->free_sectors[fd->sectors[i]] = fd->sectors[i];
			}
		}
		
		/* Clear file descriptor and free it. */
		memset(fd, 0, sizeof(*fd));
		free(fd);
	}
	
	return (0);
}

