/* Copyright (c) 2005-2008 DN-Systems GmbH http://rfdump.org

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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

/* $Header: /cvs/rfdump/src/rfid.c,v 1.54 2008/02/02 01:58:41 dirk Exp $ */

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

#include <gtk/gtk.h>

#include "acg.h"
#include "main.h"
#include "rfid.h"
#include "tools.h"

#ifndef IUCLC
#define IUCLC 0
#endif

int tcp_connect(const char *host, const char *port);

char* RFID_CMD_READ_PAGE="r";
char* RFID_CMD_WRITE_PAGE="w";
char* RFID_CMD_LOGIN;

int binary_mode=0;

int (*rfidReaderSetup)(int fd);
int (*rfidReaderInfo)(int fd);

static int baud_rate[] = {
#if 0
    B50,     50,
    B75,     75,
    B110,    110,
    B134,    134,
    B150,    150,
    B200,    200,
    B300,    300,
    B600,    600,
    B1200,   1200,
    B1800,   1800,
    B2400,   2400,
    B4800,   4800,
#ifdef B7200
    B7200,   7200,
#endif

#endif

    B9600,   9600,
#ifdef B14400
    B14400,  14400,
#endif
    B19200,  19200,
#ifdef B28800
    B28800,  28800,
#endif
    B38400,  38400,
#ifdef B57600
    B57600,  57600,
#endif
#ifdef B76800
    B76800,  76800,
#endif
#ifdef B115200
    B115200, 115200,
#endif
#ifdef B230400
    B230400, 230400,
#endif
#ifdef B307200
    B307200, 307200,
#endif
#ifdef B460800
    B460800, 460800,
#endif
#ifdef B500000
    B500000, 500000,
#endif
#ifdef B576000
    B576000, 576000,
#endif
#ifdef B921600
    B921600, 921600,
#endif
#ifdef B1000000
    B1000000,1000000,
#endif
#ifdef B1152000
    B1152000,1152000,
#endif
#ifdef B1500000
    B1500000,1500000,
#endif
#ifdef B2000000
    B2000000,2000000,
#endif
#ifdef B2500000
    B2500000,2500000,
#endif
#ifdef B3000000
    B3000000,3000000,
#endif
#ifdef B3500000
    B3500000,3500000,
#endif
#ifdef B4000000
    B4000000,4000000,
#endif
  };

static char* current_readerdevice;
static int current_baudrate;
static char lck[PATH_MAX];
static struct termios oldt;
void serial_cleanup()
{
  if(isatty(fd))
    {
      tcflush(fd, TCIOFLUSH);
      tcsetattr(fd, TCSANOW, &oldt);
    }

  if(fd>=0)
    {
      close(fd);
      fd=-1;
    }

  /* cleanup lockfile */
  if(lck[0])
    unlink(lck);
  lck[0]=0;

  if(current_readerdevice)
    {
      free(current_readerdevice);
      current_readerdevice=0;
    }
}

int rfidConnectReader(const char *readerdevice, int baudrate, int binarymode_)
{
  g_assert(readerdevice);

  serial_cleanup();

  /* check for network device */
  {
    char *port=strchr(readerdevice, ':');
    if(port)
      {
	*port++=0;
	return fd=tcp_connect(readerdevice,port);
      }
  }

#if defined(__linux__)
  /* check lockfile */
  {
    int pos=strlen(readerdevice)-1;
    while(pos>=0)
      {
	if(readerdevice[pos]=='/')
	  break;
	--pos;
      }

    SNPRINTF(lck, sizeof(lck), "/var/lock/LCK..%s", readerdevice+pos+1);
    if(access(lck, F_OK) == 0)
      {
	fprintf(stderr, "Serial port lockfile detected: %s. Quit other application which uses the serial port or remove the lockfile.\n", lck);
	return -1;
      }

    /* create lockfile */
    FILE *f=fopen(lck, "w");
    if(!f)
      {
	fprintf(stderr, "Could not create lockfile %s : %s\n", lck, strerror(errno));
	return -1;
      }

    char *user="";
    if(getenv("USER"))
      user=getenv("USER");
    fprintf(f, "%i rfdump %s\n", getpid(), user);
    fclose(f);
  }
#endif

  atexit(serial_cleanup);

  /* open */
#if B2400==2400 && B9600==9600 && B38400==38400
  int br=baudrate;
#else
  int br=-1, i;
  for(i=0; i<sizeof_array(baud_rate); i+=2)
    if(baud_rate[i]==baudrate)
      {
	br=baud_rate[i+1];
	break;
      }
  if(br<0)
    {
      for(i=1; i<sizeof_array(baud_rate); i+=2)
	if(baud_rate[i]==baudrate)
	  {
	    br=baudrate;
	    baudrate=baud_rate[i-1];
	    break;
	  }
    }
  if(br<0)
    {
      fprintf(stderr, "unknown baud rate %i == 0x%08X\n", baudrate, baudrate);
      return -1;
    }
#endif
  fprintf(stderr, "using serial device %s %i baud\n", readerdevice, br);

  fd = open(readerdevice, O_RDWR | O_NOCTTY);
  if (fd < 0)
    {
      fprintf(stderr, "Unable to connect to card reader device at %s: %s\n", readerdevice, strerror(errno));
      return -1;
    }

  tcflush(fd, TCIOFLUSH);

  struct termios curt;
  if(tcgetattr(fd, &curt) < 0)
    {
      fprintf(stderr, "could not get serial device attributes: %s\n", strerror(errno));
      return -1;
    }
  memcpy(&oldt, &curt, sizeof(curt));

  /* baud rate */
  cfsetispeed(&curt, baudrate);
  cfsetospeed(&curt, baudrate);

  /* data bits */
  curt.c_cflag &= ~CSIZE;
  curt.c_cflag |= CS8;

  /* no hardware handshake */
  curt.c_cflag &= ~CRTSCTS;
  curt.c_cflag |= CLOCAL;

  /* enable receiver */
  curt.c_cflag |= CREAD;

  /* ignore modem control lines */
  curt.c_cflag |= CLOCAL;

  /* 1 stop bit */
  curt.c_cflag &= ~CSTOPB;
  /* no parity */
  curt.c_cflag &= ~PARENB;	/* clear parity enable */
  curt.c_iflag |= IGNPAR;	/* ignore parity errors */
  curt.c_iflag &= ~INPCK;	/* disable parity check */

  /* no software flow control */
  curt.c_iflag &= ~(IXON|IXOFF);

  /* disable input processing */
  curt.c_iflag &= ~(INPCK|PARMRK|BRKINT|INLCR|ICRNL|IUCLC|IXANY);
  /* ignore break */
  curt.c_iflag |= IGNBRK;

  /* no local flags */
  curt.c_lflag = 0;

  curt.c_oflag=0;

  /* set read timeout = .1s */
  curt.c_cc[VTIME]=0;
  /* set min read bytes */
  curt.c_cc[VMIN]=1;

  if(tcsetattr(fd, TCSANOW, &curt) < 0)
    {
      fprintf(stderr, "could not set serial device attributes: %s\n", strerror(errno));
      return -1;
    }

  tcflush(fd, TCIOFLUSH);

  current_readerdevice=strdup(readerdevice);
  current_baudrate=br;
  binary_mode=binarymode_;
  return fd;
}

int rfidReadAllPages(int fd, struct RFIDTag *tag)
{
  g_assert(rfidReadPage);
  g_assert(fd >= 0);
  g_assert(tag);

  int adr;

  for(adr=0; adr<sizeof_array(tag->mem); ++adr)
    {
      g_free(tag->mem[adr]);
      tag->mem[adr]=0;
    }

  if(tag->memSize > sizeof_array(tag->mem))
    tag->memSize=sizeof_array(tag->mem);

  for(adr=0; adr<tag->memSize && scan_run; ++adr)
    {
      read_memory_tick(adr);

      char page[255]; *page=0;
      if(rfidReadPage(fd, adr, page, sizeof(page)) < 0)
	{
	  fprintf(stderr, "could not read page %02X\n", adr);
	  return -1;
	}
      usleep(10000);		/* some serial drivers make shit when used too fast... */
    }

  tag->memSize=adr;
  return 0;
}

int rfidWriteAllPages(int fd, struct RFIDTag *tag)
{
  g_assert(rfidWritePage);
  g_assert(fd >= 0);
  g_assert(tag);

  int adr = 0;
  while (adr < tag->memSize)
    {
      write_memory_tick(adr);

      if (tag->mem[adr])
	if(rfidWritePage(fd, adr, tag->mem[adr]) < 0)
	  return -1;

      adr ++;
    }

  return 0;
}

int rfidIncrementCookie(int fd, struct RFIDTag *tag)
{
  g_assert(fd >= 0);
  g_assert(tag);

  char page[255];

  /* read counter */
  if(rfidReadPage(fd, tag->memSize-1, page, sizeof(page)) < 0)
    return -1;

  /* increment counter */
  const int counter=atoi(page)+1;
  SNPRINTF(page, sizeof(page), "%08d", counter);

  /* write counter */
  strcpy(tag->mem[tag->memSize-1], page);
  if(rfidWritePage(fd, tag->memSize-1, page) < 0)
    return -1;

  return counter;
}

int rfidInitCookie(int fd, struct RFIDTag *tag, char *cookieID)
{
  g_assert(fd >= 0);
  g_assert(tag);
  g_assert(cookieID);

  if(rfidWritePage(fd, tag->memSize-2, cookieID) < 0)
    return -1;
  if(rfidWritePage(fd, tag->memSize-1, "00000000") < 0)
    return -1;

  return 0;
}

int rfidReadLine(int fd, char *result, int bufsize)
{
  g_assert(fd >= 0);
  g_assert(result);
  g_assert(bufsize > 0);

  unsigned char buf[bufsize+10];
  int pos = 0;
  int timeout=2;//10;		/* wait for 10s for on line */

  buf[0] = 0;

  while(pos < bufsize-1)
    {
      fd_set rfds;
      FD_ZERO(&rfds);
      FD_SET(fd, &rfds);

      struct timeval tv; tv.tv_sec=1, tv.tv_usec=0; /* wait 1s */

      const int s=select(fd+1, &rfds, NULL, NULL, &tv);
      if(s < 0)
	{
	  fprintf(stderr, "error in select(): %s\n", strerror(errno));
	  return -1;
	}
      else if(s == 0)
	{
	  if(--timeout == 0)
	    return -1;

	  poll_gtk();
	}
      else if(FD_ISSET(fd, &rfds))
	{
	  unsigned char inputChar;
	  const int numRead = read(fd,&inputChar,1);

	  if (numRead == 1)
	    {
#if defined(DOJDEBUG) && 0
	      fprintf(stderr, "read %02X %c\n", inputChar, isprint(inputChar)?inputChar:'.');
#endif
	      buf[pos] = inputChar;

	      if(binary_mode)
		{
		  if(buf[pos]==3) /* ETX */
		    {
		      ++pos;
		      break;
		    }
		}
	      else
		{
		  /* search for newline */
		  if(buf[pos] == '\n')
		    {
		      buf[pos]=0;	/* remove newline from result */
		      if(pos>0 && buf[pos-1]=='\r')
			buf[pos-1]=0;	/* remove cr too */
		      break;
		    }
		}

	      buf[++pos] = 0;	/* set end-of-string */
	    }
	  else if(numRead < 0)
	    {
	      fprintf(stderr, "error while read: %s\n", strerror(errno));
	      return -1;
	    }
	}
    }

  //fprintf(stderr, "rfidReadLine:%s\n", buf);

  /* check for binary frame */
  if(binary_mode)
    {
      if(pos < 6)
	{
	  fprintf(stderr, "Received frame too short: %i bytes\n", pos);
	  return -1;
	}

      int framelen=buf[2];
      if(framelen==0) framelen=256;
      if(framelen != pos-5)
	{
	  fprintf(stderr, "Invalid Framelength: %i!=%i\n", framelen, pos-5);
	  return -1;
	}

      if(buf[0]==2 && buf[pos-1]==3)
	{
	  unsigned char bcc=0;
	  int i;
	  for(i=1; i<pos-2; ++i)
	    bcc^=buf[i];
	  if(buf[pos-2] != bcc)
	    {
	      fprintf(stderr, "Checksum error on result frame %02X!=%02X\n", buf[pos-2], bcc);
	      return -1;
	    }
	  memcpy(result, buf+3, pos-5);
	  result[pos-5]=0;
	  return pos-5;
	}
      else
	{
	  fprintf(stderr, "Invalid Framebytes %02X %02X\n", buf[0], buf[pos-1]);
	  return -1;
	}

    }
  else
    {
      strcpy(result, (char*)buf);
      return strlen(result);
    }

  return 0;
}

static int readpage_std(int fd, int pageNo, char *page, int pagesize)
{
  g_assert(fd >= 0);
  g_assert(page);
  g_assert(pagesize > 0);

  if(pageNo > tag.memSize)
    return -1;

  char rfidCmd[255];

  /* write command to card reader */
  tcflush(fd, TCIOFLUSH);
  g_assert(RFID_CMD_READ_PAGE);
  SNPRINTF(rfidCmd, sizeof(rfidCmd), "%s%02X", RFID_CMD_READ_PAGE, pageNo);
  if(rfidWrite(fd, rfidCmd) < 0)
    return -1;

  /* read response */
  if(rfidReadLine(fd, page, pagesize) < 0)
    return -1;

  //fprintf(stderr, "readpage_std(%02X):%s\n", pageNo, page)
;
  const int len=strlen(page);
  if(len <= 0)
    {
      fprintf(stderr, "readpage_std(%02X): no result\n", pageNo);
      return 0;
    }
  if(len == 1)
    {
      const char *errstr="unknown error";
      switch(page[0])
	{
	case 'C': errstr="CRC Error"; break;
	case 'F': errstr="Read Failure"; break;
	case 'N': errstr="No tag in field"; break;
	case 'O': errstr="Operation mode"; break;
	case 'R': errstr="Page out of Range"; break;
	case 'X': errstr="Permission denied"; break;
	}
      fprintf(stderr, "readpage_std(%02X):%s:%s\n", pageNo, errstr, page);
      return 0;
    }
  if(len/2 != tag.pageSize)
    {
      fprintf(stderr, "readpage_std(%02X):%s: pagesize %i != expected pagesize %i\n", pageNo, page, len, tag.pageSize);
      return -1;
    }

  int i;
  for(i=0; i<len; ++i)
    if(!isxdigit(page[i]))
      return -1;

  g_free(tag.mem[pageNo]);
  tag.mem[pageNo] = g_strdup(page);

  return 0;
}

static int writepage_std(int fd, int pageNo, char *page)
{
  g_assert(pageNo >= 0);
  g_assert(page);
  g_assert(fd >= 0);

  /* send cmd to reader */
  tcflush(fd, TCIOFLUSH);
  char rfidCmd[255];
  g_assert(RFID_CMD_WRITE_PAGE);
  SNPRINTF(rfidCmd, sizeof(rfidCmd), "%s%02X%s", RFID_CMD_WRITE_PAGE, pageNo, page);

  fprintf(stderr, "WRITE:%s:", rfidCmd);

  if(rfidWrite(fd, rfidCmd) < 0)
    {
      fprintf(stderr, "err_write\n");
      return -1;
    }
  if(rfidReadLine(fd, rfidCmd, sizeof(rfidCmd)) < 0)
    {
      fprintf(stderr, "err_result\n");
      return -1;
    }
  const int len1=strlen(page), len2=strlen(rfidCmd);
  int i;
  for(i=0; i<len1 && i<len2; ++i)
    if(toupper(page[i]) != toupper(rfidCmd[i]))
      {
	fprintf(stderr, "could not write page %02X: '%s'!='%s'\n", pageNo, page, rfidCmd);
	return -1;
      }

  /* update tag structure */
  if(pageNo < tag.memSize)
    {
      char *tmp=g_strdup(page);
      g_free(tag.mem[pageNo]);
      tag.mem[pageNo]=tmp;
    }

  fprintf(stderr, "ok\n");
  return 0;
}

int (*rfidReadPage)(int fd, int pageNo, char *page, int pagesize) = readpage_std;
int (*rfidWritePage)(int fd, int pageNo, char *page) = writepage_std;

#define MIFARE_KEY_LEN 12
static char mifare_keya[MIFARE_KEY_LEN+1], mifare_keyb[MIFARE_KEY_LEN+1];
static int readpage_mifare(int fd, int pageNo, char *page, int pagesize)
{
  g_assert(fd >= 0);
  g_assert(pageNo >= 0);
  g_assert(page);
  g_assert(pagesize > 0);

  if(!RFID_CMD_LOGIN)
    return -1;

  char cmd[256];
  const int sectorNo=pageNo/4;
  char *key;

  /* check key a */
  if(tag.keya[pageNo])
    key=tag.keya[pageNo];
  else
    key=mifare_keya;

  //fprintf(stderr, "readpage_mifare(%02X):keya=%s\n", pageNo, key);

  SNPRINTF(cmd, sizeof(cmd), "%s%02XAA%s", RFID_CMD_LOGIN, sectorNo, key);
  if(rfidWrite(fd, cmd) < 0)
    return -1;

  /* read response */
  if(rfidReadLine(fd, cmd, sizeof(cmd)) < 0)
    return -1;

  if(cmd[0]=='L')
    {
      if(!tag.keya[pageNo])
	tag.keya[pageNo]=g_strdup(key);
      return readpage_std(fd, pageNo, page, pagesize);
    }

#if 0
#define FPRINTF(stream, format, a...) fprintf(stream, format, ##a)
#else
#define FPRINTF(stream, format, a...)
#endif

  if(cmd[0]=='X' || cmd[0]=='N')
    {
      FPRINTF(stderr, "checkb\n");

      if(rfidSelect(fd, cmd, sizeof(cmd)) < 0)
	{
	  FPRINTF(stderr, "s\n");
	  return -1;
	}

      if(cmd[0]!='M')
	{
	  FPRINTF(stderr, "m:%s\n", cmd);
	  return -1;
	}

      /* check key b */
      if(tag.keya[pageNo])
	key=tag.keyb[pageNo];
      else
	key=mifare_keyb;

      //fprintf(stderr, "readpage_mifare(%02X):keyb=%s\n", pageNo, key);

      SNPRINTF(cmd, sizeof(cmd), "%s%02XBB%s", RFID_CMD_LOGIN, sectorNo, key);
      if(rfidWrite(fd, cmd) < 0)
	{
	  FPRINTF(stderr, "a\n");
	  return -1;
	}

      /* read response */
      if(rfidReadLine(fd, cmd, sizeof(cmd)) < 0)
	{
	  FPRINTF(stderr, "b\n");
	  return -1;
	}

      if(cmd[0]=='X' || cmd[0]=='N')
	goto loginfail;

      if(cmd[0]=='L')
	{
	  if(!tag.keyb[pageNo])
	    tag.keyb[pageNo]=g_strdup(key);

	  if(readpage_std(fd, pageNo, page, pagesize) < 0)
	    {
	      FPRINTF(stderr, "d\n");
	      return -1;
	    }
	  if(page[0]=='F')
	    {
	    loginfail: ;
	      /* set page string to '----' */
	      int len=tag.pageSize*2;
	      if(len-1>pagesize)
		len=pagesize;
	      memset(page, '-', len);
	      page[len]=0;
	      /* activate card again */
	      if(rfidSelect(fd, cmd, sizeof(cmd)) < 0)
		return -1;
	    }
	  return 0;
	}

      fprintf(stderr, "readpage_mifare(%02X):keyb error:%s\n", pageNo, cmd);
    }
  else
    fprintf(stderr, "readpage_mifare(%02X):keya error:%s\n", pageNo, cmd);

#undef FPRINTF

  /* activate card again */
  if(rfidSelect(fd, cmd, sizeof(cmd)) < 0)
    return -1;

  return 0;
}

static int writepage_mifare(int fd, int pageNo, char *page)
{
  g_assert(fd >= 0);
  g_assert(pageNo >= 0);
  g_assert(page);

  char cmd[256];
  const int sectorNo=pageNo/4;

  /* check key a */
  g_assert(RFID_CMD_LOGIN);
  SNPRINTF(cmd, sizeof(cmd), "%s%02XAA%s", RFID_CMD_LOGIN, sectorNo, mifare_keya);
  if(rfidWrite(fd, cmd) < 0)
    return -1;

  /* read response */
  if(rfidReadLine(fd, cmd, sizeof(cmd)) < 0)
    return -1;

  if(cmd[0]=='L')
    return writepage_std(fd, pageNo, page);

  if(cmd[0]=='X')
    {
      if(rfidSelect(fd, cmd, sizeof(cmd)) < 0)
	return -1;

      /* check key b */
      SNPRINTF(cmd, sizeof(cmd), "%s%02XBB%s", RFID_CMD_LOGIN, sectorNo, mifare_keya);
      if(rfidWrite(fd, cmd) < 0)
	return -1;

      /* read response */
      if(rfidReadLine(fd, cmd, sizeof(cmd)) < 0)
	return -1;

      if(cmd[0]=='L')
	return writepage_std(fd, pageNo, page);
    }

  /* activate card again */
  if(rfidSelect(fd, cmd, sizeof(cmd)) < 0)
    return -1;

  return 0;
}

void tag_clear(struct RFIDTag *t)
{
  int i;

  g_assert(t);

  t->tagID[0] = '\0';
  t->tagTypeName[0] = '\0';
  t->tagManufacturerName[0] = '\0';
  t->memSize=1;
  t->pageSize=4;
  for(i=0; i<sizeof_array(t->mem); ++i)
    {
      g_free(t->mem[i]);
      t->mem[i] = NULL;
    }
  for(i=0; i<sizeof_array(t->keya); ++i)
    {
      g_free(t->keya[i]);
      t->keya[i]=NULL;
    }
  for(i=0; i<sizeof_array(t->keyb); ++i)
    {
      g_free(t->keyb[i]);
      t->keyb[i]=NULL;
    }
}

void tag_copy(struct RFIDTag *dst, const struct RFIDTag *src)
{
  int i;

  g_assert(src);
  g_assert(dst);

  g_strlcpy(dst->tagID, src->tagID, sizeof_array(dst->tagID));
  g_strlcpy(dst->tagTypeName, src->tagTypeName, sizeof_array(dst->tagTypeName));
  g_strlcpy(dst->tagManufacturerName, src->tagManufacturerName, sizeof_array(dst->tagManufacturerName));
  dst->memSize=src->memSize;
  dst->pageSize=src->pageSize;
  for(i=0; i<sizeof_array(dst->mem); ++i)
    {
      g_free(dst->mem[i]);
      dst->mem[i]=g_strdup(src->mem[i]);
    }
  for(i=0; i<sizeof_array(dst->keya); ++i)
    {
      g_free(dst->keya[i]);
      dst->keya[i]=g_strdup(src->keya[i]);
    }
  for(i=0; i<sizeof_array(dst->keyb); ++i)
    {
      g_free(dst->keyb[i]);
      dst->keyb[i]=g_strdup(src->keyb[i]);
    }
}

uint8_t tag_getbyte(const struct RFIDTag *t, const unsigned n)
{
  g_assert(t);
  if(n>=t->memSize*t->pageSize)
    return 0;
  const unsigned page=n / t->pageSize;
  const unsigned i=n % t->pageSize;
  if(! t->mem[page])
    return 0;
  if(strlen(t->mem[page]) < i*2)
    return 0;
  const uint8_t b=getCharValue(t->mem[page][i*2])*16 + getCharValue(t->mem[page][i*2+1]);
#if 0
  fprintf(stderr, "tag_getbyte(,%i)=%02X %c\n", n, b, isprint(b)?b:'.');
#endif
  return b;
}

int tag_setbyte(struct RFIDTag *t, const unsigned n, const uint8_t b)
{
  g_assert(t);
  if(n>=t->memSize*t->pageSize)
    return -1;
  const unsigned page=n / t->pageSize;
  const unsigned i=n % t->pageSize;
  if(! t->mem[page])
    {
      t->mem[page]=g_strnfill(t->pageSize*2, '0');
      g_assert(t->mem[page]);
    }
  else if(strlen(t->mem[page]) < t->pageSize*2)
    {
      char *s=g_strndup(t->mem[page], t->pageSize*2);
      g_assert(s);
      g_free(t->mem[page]);
      t->mem[page]=s;
    }
  t->mem[page][i*2]=dec2hex(b>>4);
  t->mem[page][i*2+1]=dec2hex(b&15);

  return b;
}

void tag_copydata(struct RFIDTag *dst, const struct RFIDTag *src)
{
  int i;

  g_assert(src);
  g_assert(dst);

  for(i=0; i<src->memSize*src->pageSize && i<dst->memSize*dst->pageSize; ++i)
    tag_setbyte(dst, i, tag_getbyte(src, i));

  for(i=0; i<sizeof_array(dst->keya); ++i)
    {
      g_free(dst->keya[i]);
      dst->keya[i]=g_strdup(src->keya[i]);
    }
  for(i=0; i<sizeof_array(dst->keyb); ++i)
    {
      g_free(dst->keyb[i]);
      dst->keyb[i]=g_strdup(src->keyb[i]);
    }
}

int rfidReset(int fd)
{
  g_assert(fd >= 0);

  tag_clear(&tag);

  /* reset reader */
  tcflush(fd, TCIOFLUSH);
  char rfidCmd[255];
  int attempt;
  for(attempt=0; attempt<2; ++attempt)
    {
      fprintf(stderr, "RESET\n");

      rfidCmd[0] = binary_mode?'v':'x';
      rfidCmd[1] = '\0';

      if(rfidWrite(fd, rfidCmd) < 0)
	return -1;
      /* sleep 1s */
      int i=0;
      while(++i<=10)
	{
	  usleep(100000);
	  poll_gtk();
	}

      rfidCmd[0]=0;
      if(rfidReadLine(fd, rfidCmd, sizeof(rfidCmd)) < 0)
	{
	  fprintf(stderr, "reset resulted in nothing\n");
	  return -1;
	}

      fprintf(stderr, "%s\n", rfidCmd);

      if(rfidCmd[0] != '?')
	break;
    }

  /* check for known readers */
  fprintf(stderr, "reader: %s\n", rfidCmd);
  if(!strcmp(rfidCmd, "ISO 0.9v"))
    {
      rfidReaderSetup=acg_setup_iso_0_9;
      rfidReaderInfo=acg_printinfo_iso_0_9;
      goto reader_found;
    }
  if(!strcmp(rfidCmd, "Dual 2.1") || !strcmp(rfidCmd, "Dual 2.2") || !strcmp(rfidCmd, "MultiISO 1.0"))
    {
      RFID_CMD_READ_PAGE="rb";
      RFID_CMD_WRITE_PAGE="wb";
      RFID_CMD_LOGIN="l";
      if(!strcmp(rfidCmd, "MultiISO 1.0"))
	{
	  rfidReaderSetup=acg_setup_multiiso_1_0;
	  rfidReaderInfo=acg_printinfo_multiiso_1_0;
	}
      else
	{
	  rfidReaderSetup=acg_setup_dual_2_2;
	  rfidReaderInfo=acg_printinfo_dual_2_2;
	}
      goto reader_found;
    }
  if(!strcmp(rfidCmd, "LFX 1.0"))
    {
      RFID_CMD_READ_PAGE="rb";
      RFID_CMD_WRITE_PAGE="wb";
      RFID_CMD_LOGIN="l";
      rfidReaderSetup=acg_setup_lfx_1_0;
      rfidReaderInfo=acg_printinfo_lfx_1_0;
      goto reader_found;
    }

  fprintf(stderr, "unknown reader\n");
  return -1;

 reader_found:

  /* disable continuous select */
  if(!binary_mode)
    {
      rfidCmd[0] = '.';
      rfidCmd[1] = '\0';
      rfidWrite(fd, rfidCmd);
      usleep(500000);
    }

  tcflush(fd, TCIOFLUSH);

  return 0;
}

static void mifare_set_key(char *key, const char *s)
{
  g_assert(key);
  g_assert(s);
  int i;
  for(i=0; i<MIFARE_KEY_LEN && i<strlen(s); ++i)
    key[i]=s[i];
  for(;    i<MIFARE_KEY_LEN; ++i)
    key[i]='0';
  mifare_keya[MIFARE_KEY_LEN]=0;
}

void rfidGetTagID(char *select, char *tagID)
{
  g_assert(select);
  g_assert(tagID);

  switch(*select)
    {
    case 'M':
      rfidReadPage=readpage_mifare;
      rfidWritePage=writepage_mifare;
      mifare_set_key(mifare_keya, gtk_entry_get_text(GTK_ENTRY(keya)));
      mifare_set_key(mifare_keyb, gtk_entry_get_text(GTK_ENTRY(keyb)));
      //fprintf(stderr, "KEYA:%s KEYB:%s\n", mifare_keya, mifare_keyb);
      break;

    default:
      rfidReadPage=readpage_std;
      rfidWritePage=writepage_std;
    }

  strcpy(tagID, select+1);
}

int rfidWrite(int fd, char *cmd)
{
  g_assert(fd >= 0);
  g_assert(cmd);

  int len=strlen(cmd);
  if(len<=0)
    {
      fprintf(stderr, "rfidWrite(): empty command\n");
      return -1;
    }
  const int maxlen=255;
  if(len>maxlen)
    {
      fprintf(stderr, "rfidWrite(): length of cmd '%s' is %i chars and too long.\n", cmd, len);
      return -1;
    }

  unsigned char buf[maxlen+5+1];
  if(binary_mode)
    {
      buf[0]=2;			/* STX */
      buf[1]=0xFF;		/* Station ID, 0xFF=all stations */
      buf[2]=len;
      memcpy(buf+3, cmd, len);	/* copy cmd */

      /* calculate BCC checksum */
      unsigned char bcc=buf[1];
      int i;
      for(i=0; i<len+1; ++i)
	bcc^=buf[i+2];
      buf[3+len]=bcc;

      buf[3+len+1]=3;		/* ETX */
      buf[3+len+2]=0;		/* c-string termination */
      len+=5;
      cmd=(char*)buf;
    }

#if defined(DOJDEBUG) && 0
  fprintf(stderr, "rfidWrite:%s:", cmd);
  int i;
  for(i=0; i<len; ++i)
    fprintf(stderr, " %02X", ((unsigned char*)cmd)[i]);
  fprintf(stderr, "\n");
#endif

  int w=0;
  while(len>0)
    {
      const int W=write(fd, cmd+w, len);
      if(W>0)
	{
	  w+=W;
	  len-=W;
	}
      else
	{
	  fprintf(stderr, "could not write cmd '%s' : %s\n", cmd, strerror(errno));
	  return -1;
	}
    }
  return w;
}

int rfidSelect(int fd, char *result, int result_size)
{
  g_assert(fd >= 0);
  g_assert(result);
  g_assert(result_size > 0);

  rfidWrite(fd, "s");

  if(binary_mode)
    {
      int r=rfidReadLine(fd, result, result_size);
      if(r<=1) return r;

      unsigned char buf[result_size*2+10], *res=(unsigned char*)result;
      buf[0]=result[0];
      int s,d;
      for(s=1,d=1; s<r; ++s)
	{
	  buf[d++]=dec2hex(res[s]>>4);
	  buf[d++]=dec2hex(res[s]&0xF);
	}
      buf[d]=0;

      strcpy(result, (char*)buf);
      return strlen(result);
    }

  return rfidReadLine(fd, result, result_size);
}

int search_baud_rate()
{
  if(!current_readerdevice)
    {
      fprintf(stderr, "don't know which reader device to use\n");
      return -1;
    }

  char *readerdevice=strdup(current_readerdevice);

  int i,ret=-1;
  for(i=0; i<sizeof_array(baud_rate); i+=2)
    {
      fprintf(stderr, "trying %i baud\n", baud_rate[i+1]);

      fd = rfidConnectReader(readerdevice, baud_rate[i], binary_mode);
      if(fd < 0)
	{
	  fprintf(stderr, "could not open serial port and set to %i baud\n", baud_rate[i+1]);
	  goto finish;
	}
      if(rfidReset(fd) == 0)
	{
	  fprintf(stderr, "found reader\n");
	  ret=current_baudrate;
	  goto finish;
	}
    }

 finish:
  free(readerdevice);
  return ret;
}
