/*
 * gbpserial.c: communicate with a GemPC410 smart card reader
 * using GemCore protocol
 * Copyright (C) 2001,2002 Ludovic Rousseau
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/*
 * $Id: gbpserial.c,v 1.43 2012/04/08 19:09:42 rousseau Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>

#include "gempc_ifdhandler.h"
#include "GemCore.h"
#include "gbpserial.h"
#include "Config.h"
#include "GCdebug.h"
#include "GCCmds.h"
#include "GCUtils.h"

/*
 * GBP (Gemplus Bloc Protocol) format
 *
 * +-----+-----+-----+---------+-----+
 * | NAD | PCB | LEN | DAT ... | EDC |
 * +-----+-----+-----+---------+-----+
 *
 * NAD: source and destination indentifier
 * PCB: block type
 *  I-Block (Information)
 * bit 7   6   5    4-0
 *   +---+---+---+--------+
 *   | 0 | S | 0 | unused |
 *   +---+---+---+--------+
 *   S = Sequence bit
 *
 *  R-Block (Repeat)
 * bit 7   6   5   4   3   2   1   0
 *   +---+---+---+---+---+---+---+---+
 *   | 1 | 0 | 0 | S | 0 | 0 | E | V |
 *   +---+---+---+---+---+---+---+---+
 *   V = Error verified by EDC
 *   E = Another error
 *
 *  S-Block (Synchro)
 * bit 7   6   5   4   3   2   1   0
 *   +---+---+---+---+---+---+---+---+
 *   | 1 | 1 | R | 0 | 0 | 0 | 0 | 0 |
 *   +---+---+---+---+---+---+---+---+
 *   R = Resync
 *     0 = request
 *     1 = response
 *
 * LEN: number of bytes in DAT
 * DAT: data transmitted
 * EDC: xor on NAD, PCB, LEN and DAT bytes
 *
 */

/* indexes in GbpBuffer[] */
#define NAD 0
#define PCB 1
#define LEN 2
#define DAT 3

/* NAD values */
#define NAD_HOST2IFD 0x42
#define NAD_IFD2HOST 0x24
#define NAD_XXX2HOST 0x04

/* communication timeout in seconds */
#define SERIAL_TIMEOUT 60

/*
 * Global variables containing the file handle of the serial port
 */
typedef struct {
	char *device;
	int fd;
	signed char bSeq;
} intrFace;


#if (PCSCLITE_MAX_READERS-16)
#error Edit this file and set the number of initialiser to PCSCLITE_MAX_READERS (default was 16 but it has changed)
#endif
static intrFace gbpDevice[PCSCLITE_MAX_READERS] = {
	[ 0 ... (PCSCLITE_MAX_READERS-1) ] = {NULL, -1, -1}
};

/* complete buffer + 3 bytes used by GBP */
static UCHAR GbpBuffer[PCSCLITE_MAX_READERS][CMD_BUF_SIZE + 3];

int ExplainGBP(const unsigned char buffer[], const int rv);

#define SetSerialSpeed(speed, bspeed, step) \
	/* set OS speed to speed bauds */ \
	cfsetspeed(&current_termios, bspeed); \
\
	DEBUG_INFO("Set serial port baudrate to " speed " (" step ")"); \
	if (tcsetattr(gbpDevice[reader].fd, TCSANOW, &current_termios) == -1) \
	{ \
		close(gbpDevice[reader].fd); \
		gbpDevice[reader].fd = -1; \
		DEBUG_INFO2("tcsetattr error: %s", strerror(errno)); \
 \
		return STATUS_UNSUCCESSFUL; \
	} \
 \
	/* empty in and out serial buffers */ \
	DEBUG_INFO("Flush serial buffers (" step ")"); \
	if (tcflush(gbpDevice[reader].fd, TCIOFLUSH)) \
		DEBUG_INFO2("tcflush() function error: %s", strerror(errno));

/*****************************************************************************
 *
 *				WriteGBP: Send bytes to the card reader
 *
 * remark: buffer already contains the byte length in [0]
 *
 *****************************************************************************/
RESPONSECODE WriteGBP(DWORD lun, DWORD length, PUCHAR buffer)
{
	int rv, len, i;
	UCHAR edc;
	int reader = LunToReaderIndex(lun);
	UCHAR *gbpbuffer = GbpBuffer[reader];
	char debug_header[] = "-> 121234 ";

	sprintf(debug_header, "-> %06X ", (int)lun);

	/* PDU length = length + NAD + PCB + LEN */
	len = length + 3;

	/* NAD byte */
	gbpbuffer[NAD] = NAD_HOST2IFD;

	/* PCB byte */
	gbpbuffer[PCB] = (gbpDevice[reader].bSeq << 6);

	/* copy LEN and DATA */
	memcpy(gbpbuffer + LEN, buffer, length);

	/* checksum */
	edc = 0;
	for (i = 0; i < len - 1; i++)
		edc ^= gbpbuffer[i];

	/* EDC byte */
	gbpbuffer[len - 1] = edc;

	/* increment Seq: 0 -> 1 or 1 -> 0 */
	gbpDevice[reader].bSeq = (gbpDevice[reader].bSeq + 1) % 2;

	DEBUG_XXD(debug_header, gbpbuffer, len);
	rv = write(gbpDevice[reader].fd, gbpbuffer, len);

	if (rv < 0)
	{
		DEBUG_CRITICAL2("write error: %s", strerror(errno));
		return STATUS_UNSUCCESSFUL;
	}

	return STATUS_SUCCESS;
} /* WriteGBP */


/*****************************************************************************
 *
 *				ReadGBP: Receive bytes from the card reader
 *
 * remark: buffer contains the byte length in [0]
 *
 *****************************************************************************/
RESPONSECODE ReadGBP(DWORD lun, PDWORD length, PUCHAR buffer)
{
	int rv, len, to_read, already_read;
	int i, pcb, edc, hdr_processed;
	DWORD buffer_size;
	int reader = LunToReaderIndex(lun);
	UCHAR *gbpbuffer = GbpBuffer[reader];
	fd_set fdset;
	int fd = gbpDevice[reader].fd;
	struct timeval t;
	char debug_header[] = "<- 121234 ";

	sprintf(debug_header, "<- %06X ", (int)lun);

	len = sizeof(GbpBuffer[reader]);
	buffer_size = *length;

	/* error by default */
	*length = 0;

	/* We need at least 4 bytes */
	to_read = 4;
	already_read = 0;
	hdr_processed = FALSE;

	/* Read loop */
	while (already_read < to_read)
	{
		/* use select() to, eventually, timeout */
		FD_ZERO(&fdset);
		FD_SET(fd, &fdset);
		t.tv_sec = SERIAL_TIMEOUT;
		t.tv_usec = 0;

		i = select(fd+1, &fdset, NULL, NULL, &t);
		if (i == -1)
		{
			DEBUG_CRITICAL2("select: %s", strerror(errno));
			return STATUS_UNSUCCESSFUL;
		}
		else
			if (i == 0)
			{
				DEBUG_XXD(debug_header, gbpbuffer, already_read);
				DEBUG_COMM2("Timeout! (%d sec)", SERIAL_TIMEOUT);
				return STATUS_UNSUCCESSFUL;
			}

		rv = read(fd, gbpbuffer + already_read, len);
		if (rv < 0)
		{
			DEBUG_XXD(debug_header, gbpbuffer, already_read);
			DEBUG_COMM2("read error: %s", strerror(errno));
			return STATUS_UNSUCCESSFUL;
		}

		already_read += rv;
		len -= rv;

		/* Process header if we just encountered it */
		if ((already_read >= 4) && !hdr_processed) {
			/* NAD byte */
			if ((gbpbuffer[NAD] != NAD_IFD2HOST)
				&& (gbpbuffer[NAD] != NAD_XXX2HOST))
			{
				DEBUG_XXD(debug_header, gbpbuffer, rv);
				ExplainGBP(gbpbuffer, rv);
				DEBUG_COMM2("wrong NAD byte 0x%02X", gbpbuffer[NAD]);

				return STATUS_NAD_UNKNOWN;
			}

			/* PCB byte */
			pcb = gbpbuffer[PCB];

			if (pcb & 0xA0)
			{
				int ret;

				DEBUG_XXD(debug_header, gbpbuffer, rv);
				ret = ExplainGBP(gbpbuffer, rv);
				DEBUG_COMM("PCB error");

				return ret;
			}

			/* LEN byte (+4 bytes of protocol) */
			to_read = gbpbuffer[LEN] + 4;

			/* Don't process header anymore */
			hdr_processed = TRUE;
		}
	}

	/* calculate checksum */
	edc = 0;
	for (i = 0; i < to_read; i++)
		edc ^= gbpbuffer[i];

	DEBUG_XXD(debug_header, gbpbuffer, to_read);

	/* verify checksum */
	if (edc != 0)
	{
		DEBUG_COMM("wrong EDC");
		return STATUS_UNSUCCESSFUL;
	}

	/* copy up to buffer_size bytes */
	if (buffer_size > gbpbuffer[LEN])
		*length = gbpbuffer[LEN] + 1;
	else
		*length = buffer_size;

	memcpy(buffer, gbpbuffer + LEN, *length);

	return STATUS_SUCCESS;
} /* ReadGBP */


/*****************************************************************************
 *
 *				OpenGBP: open the port
 *
 *****************************************************************************/
RESPONSECODE OpenGBP(DWORD lun, LPSTR dev_name)
{
	struct termios current_termios;
	int ospeed, i;
	int reader = LunToReaderIndex(lun);

	DEBUG_COMM3("Lun: %lX, device: %s", lun, dev_name);

	/* check if the same channel is not already used */
	for (i=0; i<PCSCLITE_MAX_READERS; i++)
	{
		if (gbpDevice[i].device && strcmp(gbpDevice[i].device, dev_name) == 0)
		{
			DEBUG_CRITICAL2("Device %s already in use", dev_name);
			return STATUS_UNSUCCESSFUL;
		}
	}

	gbpDevice[reader].fd = open(dev_name, O_RDWR | O_NOCTTY);

	if (gbpDevice[reader].fd < 0)
	{
		DEBUG_CRITICAL3("open %s: %s", dev_name, strerror(errno));
		return STATUS_UNSUCCESSFUL;
	}

	/* set sequence number */
	gbpDevice[reader].bSeq = 0;

	/* set channel used */
	gbpDevice[reader].device = strdup(dev_name);

	/* empty in and out serial buffers */
	if (tcflush(gbpDevice[reader].fd, TCIOFLUSH))
			DEBUG_INFO2("tcflush() function error: %s", strerror(errno));

	/* get config attributes */
	if (tcgetattr(gbpDevice[reader].fd, &current_termios) == -1)
	{
		DEBUG_INFO2("tcgetattr() function error: %s", strerror(errno));
		close(gbpDevice[reader].fd);
		gbpDevice[reader].fd = -1;

		return STATUS_UNSUCCESSFUL;
	}

	/* get actual serial port baud rate */
	ospeed = cfgetospeed(&current_termios);

	current_termios.c_iflag = 0;
	current_termios.c_oflag = 0;	/* Raw output modes */
	current_termios.c_cflag = 0;	/* Raw output modes */

	/* Do not echo characters because if you connect
	 * to a host it or your
	 * modem will echo characters for you.
	 * Don't generate signals. */
	current_termios.c_lflag = 0;

	/* control flow - enable receiver - ignore modem
	 * control lines */
	current_termios.c_cflag = CREAD | CLOCAL;

	current_termios.c_cflag |= CS8;

	/* This is Non Canonical mode set.
	 * by setting c_cc[VMIN]=0 and c_cc[VTIME] = 10
	 * each read operation will wait for 10*1/10 = 1 sec
	 * If single byte is received, read function returns.
	 * if timer expires, read() returns 0.
	 * Minimum bytes to read
	 *current_termios.c_cc[VMIN] = 0;
	 * Time between two bytes read (VTIME x 0.10 s)
	 * 10 seconds may not be the correct maximum value
	 * If the card becomes mute the reader will tell us
	 * If the reader becomes mute (crash or serial comm pb.) we won't know
	 *current_termios.c_cc[VTIME] = 100;
	 *
	 * Removed since it does not work under FreeBSD 4.7-RC2
	 * replaced with select() */

	/* Algorithm:
	 *   GCCmdRestart
	 *   if Host speed = 9600
	 *     Set Host to 38400
	 *     GCCmdConfigureSIOLine
	 *     GCCmdSetMode
	 *     if fail
	 *       Set Host to 9600
	 *       GCCmdConfigureSIOLine
	 *       Set Host to 38400
	 *       GCCmdSetMode
	 *       if fail
	 *         error
	 *       else
	 *         OK
	 *     else
	 *       OK
	 *   else
	 *     GCCmdConfigureSIOLine
	 *     GCCmdSetMode
	 *     if fail
	 *       Set Host to 9600
	 *       GCCmdConfigureSIOLine
	 *       Set Host to 38400
	 *       GCCmdSetMode
	 *       if fail
	 *         error
	 *       else
	 *         OK
	 *     else
	 *       (re)Set Host to 38400 (for timeouts)
	 *       OK
	 *
	 * note: the GCCmdConfigureSIOLine will OFTEN fails since the GemCore
	 * response is sent using the NEW speed but GCMakeCommand do not know how
	 * to handle this. Thats is why we to not test the return code but use
	 * a call to GCCmdSetMode() instead.
	 *
	 * four different cases
	 * 1. Host 9600 - IFD 9600
	 * (normal case after computer and IFD powerup)
	 *   Set Host to 38400
	 *   1.a GCCmdConfigureSIOLine (will fail)
	 *   Set Host to 9600
	 *   1.b GCCmdConfigureSIOLine (success)
	 *   Set Host to 38400
	 *
	 * 2. Host 9600 - IFD 38400
	 * (normal case after driver restart on [Open|Free]BSD, Linux)
	 *   Set Host to 38400
	 *   2.a GCCmdConfigureSIOLine (success)
	 *
	 * 3. Host 38400 - IFD 9600
	 * (normal case after IFD powerdown and up)
	 *   3.a GCCmdConfigureSIOLine (fail)
	 *   Set Host to 9600
	 *   3.b GCCmdConfigureSIOLine (success)
	 *   Set Host to 38400
	 *
	 * 4. Host 38400 - IFD 38400
	 * (normal case after driver restart on Linux)
	 *   4.a GCCmdConfigureSIOLine (success) */

	/* The serial port is set to 9600 ? */
	if (ospeed == B9600)
	{
		/* cases 1 and 2 */

		/* set OS speed to 38400 bauds */
		SetSerialSpeed("38400", B38400, "1");

		/* Configure IFD baud rate */
		GCCmdConfigureSIOLine(lun, 38400);

		/* try to communicate */
		if (GCCmdSetMode(lun, IFD_MODE_ROSNOTLP) != IFD_SUCCESS)
		{
			/* case 1. Host was 9600 - IFD was 9600 */

			DEBUG_CRITICAL("GCCmdSetMode failed (1.a)");

			/* set OS speed to 9600 bauds */
			SetSerialSpeed("9600", B9600, "1.a");

			/* Configure IFD baud rate */
			GCCmdConfigureSIOLine(lun, 38400);

			/* set OS speed to 38400 bauds */
			SetSerialSpeed("38400", B38400, "1.b");

			/* try to communicate */
			if (GCCmdSetMode(lun, IFD_MODE_ROSNOTLP) != IFD_SUCCESS)
			{
				DEBUG_CRITICAL("GCCmdSetMode failed (1.b)");
				return STATUS_UNSUCCESSFUL;
			}
			else
				DEBUG_INFO("GCCmdSetMode success (1.b)");
		}
		else
		{
			/* case 2. Host was 9600 - IFD was 38400 */
			DEBUG_INFO("GCCmdSetMode success (2.a)");

			/* Nothing more to do */
		}
	}
	else
	{
		/* cases 3 and 4 */

		DEBUG_INFO("Serial port baudrate already set to 38400 (3)");

		/* try to communicate */
		if (GCCmdSetMode(lun, IFD_MODE_ROSNOTLP) == IFD_SUCCESS)
		{
			/* case 4. Host was 38400 - IFD was 38400 */
			DEBUG_INFO("GCCmdSetMode success (4.a)");

			/* Nothing more to do */
		}
		else
		{
			/* Configure IFD baud rate */
			GCCmdConfigureSIOLine(lun, 38400);

			/* empty in and out serial buffers */
			DEBUG_INFO("Flush serial buffers (3)");
			if (tcflush(gbpDevice[reader].fd, TCIOFLUSH))
				DEBUG_INFO2("tcflush() function error: %s", strerror(errno));

			/* try to communicate */
			if (GCCmdSetMode(lun, IFD_MODE_ROSNOTLP) != IFD_SUCCESS)
			{
				/* case 3. Host was 38400 - IFD was 9600 */

				DEBUG_CRITICAL("GCCmdSetMode failed (3.a)");

				/* set OS speed to 9600 bauds */
				SetSerialSpeed("9600", B9600, "3.a");

				/* Configure IFD baud rate */
				GCCmdConfigureSIOLine(lun, 38400);

				/* set OS speed to 38400 bauds */
				SetSerialSpeed("38400", B38400, "3.b");

				/* try to communicate */
				if (GCCmdSetMode(lun, IFD_MODE_ROSNOTLP) != IFD_SUCCESS)
				{
					DEBUG_CRITICAL("GCCmdSetMode failed (3.b)");
					return STATUS_UNSUCCESSFUL;
				}
				else
					DEBUG_INFO("GCCmdSetMode success (3.b)");
			}
			else
			{
				/* case 4. Host was 38400 - IFD was 38400 */
				DEBUG_INFO("GCCmdSetMode success (4.a)");

				/* Nothing more to do */
			}
		}
	}

	return STATUS_SUCCESS;
} /* OpenGBP */


/*****************************************************************************
 *
 *				CloseGBP: close the port
 *
 *****************************************************************************/
RESPONSECODE CloseGBP(DWORD lun)
{
	int reader = LunToReaderIndex(lun);

	close(gbpDevice[reader].fd);
	gbpDevice[reader].fd = -1;

	gbpDevice[reader].bSeq = -1;

	free(gbpDevice[reader].device);
	gbpDevice[reader].device = NULL;

	return STATUS_SUCCESS;
} /* CloseGBP */


/*****************************************************************************
 *
 *				ExplainGBP: print a textual error explanation
 *
 *****************************************************************************/
int ExplainGBP(const unsigned char buffer[], const int rv)
{
	int ret = 0;

	if (rv < 4)
	{
		DEBUG_COMM2("GBP bloc too short: %d instead of min 4", rv);
		return STATUS_DEVICE_PROTOCOL_ERROR;
	}

	switch (buffer[0])
	{
		case 0x42:
			DEBUG_COMM("  NAD: host to gemcore");
			break;
		case 0x24:
			DEBUG_COMM("  NAD: gemcore to host");
			break;
		default:
			DEBUG_COMM2("  NAD: UNKNOWN value 0x%02X", buffer[0]);
	}

	switch (buffer[1])
	{
		case 0x00:
			DEBUG_COMM("  PCB: I-block S=0");
			break;
		case 0x40:
			DEBUG_COMM("  PCB: I-block S=1");
			break;
		case 0x80:
			DEBUG_COMM("  PCB: R-block S=0, EDC error=0, another error=0");
			ret = STATUS_PCB_REP_0;
			break;
		case 0x90:
			DEBUG_COMM("  PCB: R-block S=1, EDC error=0, another error=0");
			ret = STATUS_PCB_REP_1;
			break;
		case 0x81:
			DEBUG_COMM("  PCB: R-block S=0, EDC error=1, another error=0");
			ret = STATUS_PCB_REP_0;
			break;
		case 0x91:
			DEBUG_COMM("  PCB: R-block S=1, EDC error=1, another error=0");
			ret = STATUS_PCB_REP_1;
			break;
		case 0x82:
			DEBUG_COMM("  PCB: R-block S=0, EDC error=0, another error=1");
			ret = STATUS_PCB_REP_0;
			break;
		case 0x92:
			DEBUG_COMM("  PCB: R-block S=1, EDC error=0, another error=1");
			ret = STATUS_PCB_REP_1;
			break;
		case 0x83:
			DEBUG_COMM("  PCB: R-block S=0, EDC error=1, another error=1");
			ret = STATUS_PCB_REP_0;
			break;
		case 0x93:
			DEBUG_COMM("  PCB: R-block S=1, EDC error=1, another error=1");
			ret = STATUS_PCB_REP_1;
			break;
		case 0xB0:
			DEBUG_COMM("  PCB: S-block resynch request");
			ret = STATUS_PCB_SYNC_REQ;
			break;
		case 0xE0:
			DEBUG_COMM("  PCB: S-block resynch response");
			ret = STATUS_PCB_SYNC_RESP;
			break;
		default:
			DEBUG_COMM2("  PCB: UNKNOWN value %02X", buffer[1]);
			break;
	}

	DEBUG_COMM2("  LEN: %02X bytes", buffer[2]);

	if (rv > 4)
		switch (buffer[3])
		{
			case 0x00:
				DEBUG_COMM("  S: no error");
				break;
			case 0x01:
				DEBUG_COMM("  S: unknown driver");
				break;
			case 0x02:
				DEBUG_COMM("  S: operation impossible with this driver");
				break;
			case 0x03:
				DEBUG_COMM("  S: incorrect number of arguments");
				break;
			case 0x04:
				DEBUG_COMM("  S: reader command unknown");
				break;
			case 0x05:
				DEBUG_COMM("  S: response too long for the buffer");
				break;
			case 0x10:
				DEBUG_COMM("  S: resonse error at the card reset");
				break;
			case 0x12:
				DEBUG_COMM("  S: message too long");
				break;
			case 0x13:
				DEBUG_COMM("  S: byte reading error returned by an asynchronous cadr");
				break;
			case 0x15:
				DEBUG_COMM("  S: card powered down");
				break;
			case 0x1B:
				DEBUG_COMM("  S: a command has been sent with an incorrect number of parameters");
				break;
			case 0x1C:
				DEBUG_COMM("  S: overlap on writing flash memory");
				break;
			case 0x1D:
				DEBUG_COMM("  S: the TCK check byte is incorrect in a micropocessor card ATR");
				break;
			case 0x1E:
				DEBUG_COMM("  S: an attempt has been made to write a write-protected external memory");
				break;
			case 0x1F:
				DEBUG_COMM("  S: incorrect data has been send to external memory");
				break;
			case 0xA0:
				DEBUG_COMM("  S: error in the card reset response");
				break;
			case 0xA1:
				DEBUG_COMM("  S: card protocol error");
				break;
			case 0xA2:
				DEBUG_COMM("  S: card malfuntion");
				break;
			case 0xA3:
				DEBUG_COMM("  S: parity error");
				break;
			case 0xA4:
				DEBUG_COMM("  S: card has been chaining (T=1)");
				break;
			case 0xA5:
				DEBUG_COMM("  S: reader has aborted chaining (T=1)");
				break;
			case 0xA6:
				DEBUG_COMM("  S: resynch successfully performed by GemCore");
				break;
			case 0xA7:
				DEBUG_COMM("  S: protocol type selection (PTS) error");
				break;
			case 0xB0:
				DEBUG_COMM("  S: GemCP410 command not supported");
				break;
			case 0xCF:
				DEBUG_COMM("  S: other key already pressed");
				break;
			case 0xE4:
				DEBUG_COMM("  S: the card has just send an invalid procedure byte");
				break;
			case 0xE5:
				DEBUG_COMM("  S: the card has interrupted an exchange");
				break;
			case 0xE7:
				DEBUG_COMM("  S: error returned by the card");
				break;
			case 0xF7:
				DEBUG_COMM("  S: card removed");
				break;
			case 0xF8:
				DEBUG_COMM("  S: the cadr is consuming too much electricity");
				break;
			case 0xFB:
				DEBUG_COMM("  S: card missing");
				break;
			default:
				DEBUG_COMM2("  S: UNKNOWN ERROR %02X", buffer[3]);
				break;
		}

	return ret;
} /* ExplainGBP */

/*****************************************************************************
 *
 *				ExplainGBP: print a textual error explanation
 *
 *****************************************************************************/
int SetGBPSeqNumber(DWORD lun, int seq)
{
	int reader = LunToReaderIndex(lun);

	if (gbpDevice[reader].bSeq != -1)
	{
		gbpDevice[reader].bSeq = seq;
		return FALSE;
	}
	else
		return TRUE;

} /* SetGBPSeqNumber */

