/*
 * Softcam plugin to VDR (C++)
 *
 * This code 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 code 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.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */
#include "stdafx.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>

#include "common.h"
#include "smartcard.h"
#include "misc.h"

#define DATAFILE "smartcard.conf"
#define ISO_FREQ 3571200 // Hz

//#define DEBUG_SER // debug serial functions
//#define SER_EMU   // use serial emulation (select one of the following)
//#define EMU_SECA  // fake Seca card
//#define EMU_IRD   // fake Irdeto/Beta card
//#define EMU_IRD_384 // fake ACS 384, if undefined ACS 383
//#define EMU_CRYPTO // fake Cryptoworks card
//#define NO_PTS_PROTO // disable PTS protocol (baudrate changes)

#ifdef DEBUG_SER
#define ds(x) { (x); }
#else
#define ds(x) ;
#endif

// -- cSerial ------------------------------------------------------------------

class cSerial {
private:
	char *devName;
	HANDLE fd;
	COMMTIMEOUTS CommTimeOuts;
	int currMode, statInv;
	bool invRST;
public:
	cSerial(const char *DevName, bool invCD, bool InvRST);
	~cSerial();
	bool Open(void);
	bool SetMode(int mode, int baud=9600);
	int CurrentMode(void) const { return currMode; }
	void Flush(void);
	int Read(unsigned char *mem, int len, int timeout, int initialTimeout=0);
	int Write(const unsigned char *mem, int len, int delay=0);
	void ToggleRTS(void);
	bool CheckCAR(void);
	void Close(void);
	const char *DeviceName(void) const { return devName; }
};

cSerial::cSerial(const char *DevName, bool invCD, bool InvRST)
{
	devName=_strdup(DevName);
	statInv=invCD ? MS_RLSD_ON:0;
	invRST=InvRST;
	fd=(HANDLE)-1; currMode=SM_NONE;
}

cSerial::~cSerial()
{
	Close();
	free(devName);
}

bool cSerial::Open(void)
{
	ds(printf("%s: open serial port\n",devName))
	fd = CreateFileA(devName, GENERIC_READ | GENERIC_WRITE, 
					0, NULL, OPEN_EXISTING,
					0, NULL);
	if(fd != INVALID_HANDLE_VALUE) {
		ds(printf("%s: set DTR/RTS\n",devName))
		EscapeCommFunction(fd,SETDTR);
		EscapeCommFunction(fd,invRST?SETRTS:CLRRTS);
		ds(printf("%s: init done\n",devName))
		return true;
	}
	else LogError("failed to open serial port %s: %s",devName,strerror(errno));
	return false;
}

void cSerial::Close(void)
{
  if(fd>=0) {
    ds(printf("%s: shutting down ... ",devName))
    Flush();
	CloseHandle(fd); fd=INVALID_HANDLE_VALUE;
	ds(printf("done\n"))
  }
}

bool cSerial::SetMode(int mode, int baud)
{
	if(fd>=0) {
		int bconst;
		switch(baud) {
			case   9600: bconst=  CBR_9600; break;
			case  19200: bconst= CBR_19200; break;
			case  38400: bconst= CBR_38400; break;
			case  57600: bconst= CBR_57600; break;
			case 115200: bconst=CBR_115200; break;
			default:
				d(printf("serial: unsupported baudrate %d\n",baud));
				return false;
		}
		ds(printf("serial: set serial options: "))
		DCB dcb;
		GetCommState(fd, &dcb);
		ds(printf("%d,",baud))
		dcb.ByteSize = 8;
		dcb.StopBits = TWOSTOPBITS;
		dcb.BaudRate = bconst;
		dcb.fDtrControl = DTR_CONTROL_DISABLE;
		dcb.fRtsControl = RTS_CONTROL_DISABLE;
		CommTimeOuts.ReadIntervalTimeout = 0;
		CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
		CommTimeOuts.ReadTotalTimeoutConstant = 100;
		CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
		CommTimeOuts.WriteTotalTimeoutConstant = 100;
		switch(mode) {
			case SM_8E2:
				ds(printf("8e2"))
				dcb.fParity = true;
				dcb.Parity = EVENPARITY;
				break;
			case SM_8N2:
				ds(printf("8n2"))
				dcb.fParity = false;
				break;
			case SM_8O2:
				ds(printf("8o2"))
				dcb.fParity = true;
				dcb.Parity = ODDPARITY;
				break;
			default:
				ds(printf("BAD MODE\n"));
				return false;
		}
		ds(printf("\n"))
		if(SetCommTimeouts(fd, &CommTimeOuts) && SetCommState(fd, &dcb)) {
			currMode=mode; Flush();
			return true;
		}
		else LogError("SetCommTimeouts/SetCommState failed: %s", strerror(errno));
	}
	return false;
}

void cSerial::Flush(void)
{
	if(fd>=0)
		PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT | 
						PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
}

int cSerial::Read(unsigned char *mem, int len, int timeout, int initialTimeout)
{
	ds(printf("%s: read len=%d timeout=%d:%d\n",devName,len,timeout,initialTimeout))
	bool incomplete=false;
	if(len<0) { len=-len; incomplete=true; }
	int to=initialTimeout>0 ? initialTimeout : timeout;
	int n=0;
	DWORD time1=GetTickCount();
	while(n<len) {
		DWORD r;
		ReadFile(fd, mem+n, len-n, &r, NULL);
		if(r==0) {
			SleepEx(30, false);
			if(to+time1<=GetTickCount() && n>=0 && incomplete) break;
		}
		n+=r; to=timeout;
	}
#ifdef DEBUG_SER
	HexDump(mem,n);
#endif
	return n;
}

int cSerial::Write(const unsigned char *mem, int len, int delay)
{
  ds(printf("%s: write len=%d delay=%d\n",devName,len,delay))
#ifdef DEBUG_SER
  HexDump(mem,len);
#endif
  Flush();
  int n=0;
  while(n<len) {
	  DWORD r;
	  WriteFile(fd, mem+n, delay>0?1:len-n,&r, NULL);
	  if(r>0) n+=r;
	  if(delay>0) cCondWait::SleepMs(delay);
  }
  return n;
}

void cSerial::ToggleRTS(void)
{
	DWORD mcr;
	DWORD cbReturned;
	bool result;

	ds(printf("%s: toggle RTS, now ",devName))
	result = DeviceIoControl(fd, 0x001B0078, NULL, 0, &mcr, 4, &cbReturned, 0);
	if(mcr&2)	{ ds(printf("off\n")) EscapeCommFunction(fd,CLRRTS); }
	else		{ ds(printf("on\n"))  EscapeCommFunction(fd,SETRTS); }
	Flush();
}

bool cSerial::CheckCAR(void)
{
	DWORD status;
	if (!GetCommModemStatus(fd, &status)) return false;
	status^=statInv; // invert status for broken cardreaders
	if(!(status & MS_RLSD_ON)) {
		ds(printf("%s: CAR is active\n",devName))
		return true;
	}
	else {
		ds(printf("%s: CAR is inactive\n",devName))
		return false;
	}
}

// -- cInfoStr -----------------------------------------------------------------

#define WLEN 512

cInfoStr::cInfoStr(void)
{
	current=work=0;
	blen=wlen=0;
}

cInfoStr::~cInfoStr()
{
	free(current);
	free(work);
}

bool cInfoStr::Get(char *buff, int len)
{
	cMutexLock lock(&mutex);
	if(current && current[0]) {
		strn0cpy(buff,current,len);
		return true;
	}
	return false;
}

void cInfoStr::Begin(void)
{
	free(work);
	work=0; blen=wlen=0;
}

bool cInfoStr::Check(int num)
{
	if(wlen-blen<num) {
		if(num<WLEN) num=WLEN;
		char *w=(char *)realloc(work,wlen+num);
		if(w) {
			wlen+=num;
			work=w;
		}
	}
	return work!=0;
}

void cInfoStr::Printf(const char *fmt, ...)
{
	int q=120;
	while(1) {
		if(Check(q)) {
			int s=wlen-blen;
			va_list ap;
			va_start(ap,fmt);
			q=vsnprintf(work+blen,s,fmt,ap);
			va_end(ap);
			if(q>=s) { // truncated
				q+=20;
				continue;
			}
			if(q>0) blen+=q;
		}
		break;
	}
}

void cInfoStr::Strcat(const char *str)
{
	if(str) Printf("%s",str);
}

void cInfoStr::Finish()
{
	cMutexLock lock(&mutex);
	free(current);
	current=work;
	work=0; blen=wlen=0;
}

// -- cSmartCard ---------------------------------------------------------------

static const int Ftable[16] = {
	372,372,558,744,1116,1488,1860,0,0,512,768,1024,1536,2048,0,0
};

static const float Dtable[16] = {
	0.0,1.0,2.0,4.0,8.0,16.0,0.0,0.0,
	0.0,0.0,0.5,0.25,0.125,0.0625,0.03125,0.015625
};

cSmartCard::cSmartCard(const struct CardConfig *Cfg, const struct StatusMsg *Msg)
{
	cfg=Cfg; msg=Msg;
	ser=0; atr=0; idStr[0]=0; cardUp=false; needsReset=true;
}

bool cSmartCard::GetCardIdStr(char *str, int len)
{
	strn0cpy(str,idStr,len);
	return (str[0]!=0);
}

bool cSmartCard::GetCardInfoStr(char *str, int len)
{
	return infoStr.Get(str,len);
}

bool cSmartCard::Setup(cSerial *Ser, const struct Atr *Atr, int Id)
{
	ser=Ser; atr=Atr; id=Id;
	if(cfg->SerMode==(ser->CurrentMode()) && Init()) {
		cardUp=true; needsReset=false;
		return true;
	}
	return false;
}

#define NEED(x) { \
                if(len+(x)>Atr->atrLen) { \
                    di(printf("SHORT ATR\n")) \
                    return false; \
                    } \
                }

bool cSmartCard::ParseAtr(struct Atr *Atr, int id)
{
	// default values
	Atr->histLen=0; Atr->T=0; Atr->F=372; Atr->D=1.0; Atr->N=0; Atr->WI=10;
	Atr->BWI=4; Atr->CWI=0; Atr->Tspec=-1;

	const unsigned char *atr=Atr->atr;
	if(atr[0]==0x03) {
		di(printf("iso: %d: indirect convention detected\n",id))
		cSmartCard::Invert(Atr->atr,Atr->atrLen);
		Atr->convention=SM_INDIRECT;
	}
	else if(atr[0]==0x3B) {
		di(printf("iso: %d: direct convention detected\n",id))
		Atr->convention=SM_DIRECT;
	}
	else {
		di(printf("iso: %d: byte mode not supported 0x%02x\n",id,atr[0]))
		return false;
    }

	// TS TO
	Atr->histLen=atr[1]&0x0F;
	int Y=atr[1]&0xF0, i=1, len=2;
	di(printf("iso: %d: atr decoding TS=%02x hist=%d Y%d=%02x ",id,atr[0],Atr->histLen,i,Y))
	do {
		if(Y&0x10) { // TAi
			NEED(1);
			di(printf("TA%d=%02x ",i,atr[len]))
			if(i==1) {
				Atr->TA1=atr[len];
				Atr->F=Ftable[(atr[len]>>4)&0x0F];
				Atr->D=Dtable[ atr[len]    &0x0F];
				di(printf("F=%d D=%f ",Atr->F,Atr->D))
			}
			else if(i==2) {
				Atr->Tspec=atr[len]&0x0F;
				di(printf("Tspec=%d ",Atr->Tspec))
			}
			else if(i==3) {
				di(printf("IFSC=%d ",atr[len]))
			}
			len++;
		}
		if(Y&0x20) { // TBi
			NEED(1);
			di(printf("TB%d=%02x ",i,atr[len]))
			if(i==3) {
				Atr->BWI=(atr[len]>>4)&0x0F;
				Atr->CWI=atr[len]&0x0F;
				di(printf("BWI=%d CWI=%d ",Atr->BWI,Atr->CWI))
			}
			len++;
		}
		if(Y&0x40) { // TCi
			NEED(1);
			di(printf("TC%d=%02x ",i,atr[len]))
			if(i==1) {
				Atr->N=atr[len];
				di(printf("N=%d ",Atr->N))
			}
			else if(i==2) {
				Atr->WI=atr[len];
				di(printf("WI=%d ",Atr->WI))
			}
			else if(i==3) {
				di(printf("CHK=%s ",atr[len]&1 ? "CRC16":"LRC"))
			}
			len++;
		}
		if(Y&0x80) { // TDi
			NEED(1);
			di(printf("TD%d=%02x ",i,atr[len]))
			if(i==1) {
				Atr->T=atr[len]&0x0F;
				di(printf("T=%d ",Atr->T))
			}
			else {
				di(printf("Tn=%d ",atr[len]&0x0F))
			}
			Y=atr[len]&0xF0;
			len++;
		}
		else Y=0;
		i++;
		di(printf("Y%d=%02x ",i,Y))
	} while(Y);
	di(printf("\n")) 

	NEED(Atr->histLen);
#ifdef DEBUG_ISO
	printf("iso: %d: historical:",id);
	DUMP(&atr[len],Atr->histLen);
	printf("iso: %d: historical: '",id);
	for(int i=0 ; i<Atr->histLen ; i++) printf("%c",isprint(atr[len+i]) ? atr[len+i] : '.');
	printf("'\n");
#endif
	memcpy(Atr->hist,&atr[len],Atr->histLen);
	len+=Atr->histLen;

	// TCK
	if(Atr->T!=0 && len+1<=Atr->atrLen) {
		len++;
		unsigned char cs=XorSum(atr+1,len-1);
		// according to the ISO the initial TS byte isn't checksumed, but
		// some cards do so. In this case the checksum is equal TS.
		if(cs==0) di(printf("iso: %d: atr checksum ok\n",id))
		else if(cs==atr[0]) di(printf("iso: %d: atr checksum is TS (not ISO compliant)\n",id))
		else {
			di(printf("iso: %d: atr checksum FAILED (cs:%02x)\n",id,cs))
			return false;
		}
	}
	else di(printf("iso: %d: atr checksum not given/not required\n",id))

	if(Atr->atrLen>len) di(printf("iso: %d: long atr read=%d len=%d\n",id,Atr->atrLen,len))
	Atr->wwt=960*Atr->WI*Atr->F/3571200*1000;
	di(printf("iso: %d: indicated wwt=%d ms (at 3.5712 MHz)\n",id,Atr->wwt))
	return true;
}

void cSmartCard::Invert(unsigned char *data, int n)
{
	static const unsigned char swaptab[] =  { 15,7,11,3,13,5,9,1,14,6,10,2,12,4,8,0 };
	for(int i=n-1; i>=0; i--)
		data[i]=(swaptab[data[i]&0x0f]<<4) | swaptab[data[i]>>4];
}

int cSmartCard::SerRead(unsigned char *data, int len, int to)
{
	if(atr->T==1 || atr->T==14) {
		int r=Read(data,len,to);
		if(r<0) Test(false);
		return r;
	}
	else {
		di(printf("iso: %d: SerRead() not allowed for ISO compliant cards (T=%d)\n",id,atr->T))
		return -1;
	}
}

int cSmartCard::SerWrite(const unsigned char *data, int len)
{
	if(atr->T==1 || atr->T==14) {
		int r=Write(data,len);
		if(r<0) Test(false);
		return r;
	}
	else {
		di(printf("iso: %d: SerWrite() not allowed for ISO compliant cards (T=%d)\n",id,atr->T))
		return -1;
	}
}

int cSmartCard::Read(unsigned char *data, int len, int to)
{
	int r=ser->Read(data,len,cfg->serTO,to);
	if(atr->convention==SM_INDIRECT && r>0) Invert(data,r);
	return r;
}

int cSmartCard::Write(const unsigned char *data, int len)
{
	unsigned char *tmp = new unsigned char[len];
	if(atr->convention==SM_INDIRECT) {
		memcpy(tmp,data,len);
		Invert(tmp,len);
		data=tmp;
	}
	int r=ser->Write(data,len);
	if(r>0) {
		unsigned char *buff = new unsigned char[r];
		int rr=ser->Read(buff,r,cfg->serTO);
		if(rr<0) r=rr;
		delete buff;
	}
	delete tmp;
	return r;
}

int cSmartCard::Procedure(unsigned char ins, int restLen)
{
	int r;
	unsigned char buff;
	di(printf("iso: %d: <- PROC: ",id))
	do {
		do {
			if(Read(&buff,1,cfg->workTO)<=0) return -1;
			di(printf("%02x ",buff))
		} while(buff==0x60);

		if((buff&0xF0)==0x60 || (buff&0xF0)==0x90) { // SW1/SW2
			sb[0]=buff;
			if(Read(&buff,1)<=0)  return -1;
			di(printf("%02x\n",buff))
			sb[1]=buff;
			return 0;
		}
		else {
			if((buff&0xFE)==(ins&0xFE)) r=restLen;
			else if((~buff&0xFE)==(ins&0xFE)) r=1;
			else {
				di(printf("cannot handle procedure %02x (ins=%02x)\n",buff,ins))
				return -1;
			}
			if(r>restLen) {
				di(printf("data overrun r=%d restLen=%d\n",r,restLen))
				return -1;
			}
		}
	} while(r==0);
	di(printf("\n"))
	return r;
}

bool cSmartCard::IsoRead(const unsigned char *cmd, unsigned char *data)
{
	if(atr->T==0) { // only for ISO complaint cards
		di(printf("iso: %d: -> INS:",id));
		DUMP(cmd,CMD_LEN);
		if(Write(cmd,CMD_LEN)<0) return Test(false);
		int tr=cmd[LEN_IDX] ? cmd[LEN_IDX] : 256;
		int len=0;
		while(1) {
			int r=Procedure(cmd[INS_IDX],tr-len);
			if(r<=0) return Test(r==0);
			if(Read(data+len,r)<0) return Test(false);
			di(printf("iso: %d: <- DATA:",id));
			DUMP(data+len,r);
			len+=r;
		}
	}
	else di(printf("iso: %d: can't IsoRead() from incompatible card (T=%d)\n",id,atr->T))
	return false;
}

bool cSmartCard::IsoWrite(const unsigned char *cmd, const unsigned char *data)
{
	if(atr->T==0) { // only for ISO complaint cards
		di(printf("iso: %d: -> INS:",id));
		DUMP(cmd,CMD_LEN);
		if(Write(cmd,CMD_LEN)<0) return Test(false);
		int len=0;
		while(1) {
			int r=Procedure(cmd[INS_IDX],cmd[LEN_IDX]-len);
			if(r<=0) return Test(r==0);
			if(Write(data+len,r)<0) return Test(false);
			di(printf("iso: %d: -> DATA:",id));
			DUMP(data+len,r);
			len+=r;
		}
	}
	else di(printf("iso: %d: can't IsoWrite() to incompatible card (T=%d)\n",id,atr->T))
	return false;
}

bool cSmartCard::Test(bool res)
{
	if(!res) {
		TriggerReset();
		di(printf("smartcard: %d: reset triggered\n",id))
	}
	return res;
}

bool cSmartCard::Status(void)
{
	const struct StatusMsg *m=msg;
	while(m->sb[0]!=0xFF) {
		if(sb[0]==m->sb[0] && (m->sb[1]==0xFF || sb[1]==m->sb[1])) {
			if(!m->retval) di(printf("smartcard: %d: %s (status: %02x %02x)\n",id,m->message,sb[0],sb[1]))
			return m->retval;
		}
		m++;
	}
	di(printf("smartcard: %d: unknown (status: %02x %02x)\n",id,sb[0],sb[1]))
	return false;
}

int cSmartCard::CheckSctLen(const unsigned char *data, int off)
{
	int l=SCT_LEN(data);
	if(l+off > MAX_LEN) {
		di(printf("smartcard: section too long %d > %d\n",l,MAX_LEN-off))
		l=-1;
	}
	return l;
}

// -- cSmartCardData -----------------------------------------------------------

cSmartCardData::cSmartCardData(int Ident)
{
	ident=Ident;
}

// -- cSmartCardLink -----------------------------------------------------------

cSmartCardLink::cSmartCardLink(const char *Name, int Id)
{
	name=Name; id=Id;
	cSmartCards::Register(this);
}

// -- cSmartCards --------------------------------------------------------------

cSmartCardLink *cSmartCards::first=0;

cSmartCards smartcards;

cSmartCards::cSmartCards(void)
:cThread("SmartcardWatcher")
{
	for(int i=0 ; i<MAX_PORTS ; i++) ports[i].Serial=0;
	run=false; firstRun=true;
}

cSmartCards::~cSmartCards()
{
	if(run) {
		run=false;
		Cancel(2);
	}
	Lock();
	for(int i=0 ; i<MAX_PORTS ; i++) {
		if(ports[i].Serial) {
			delete ports[i].Card;
			delete ports[i].Serial;
			ports[i].Serial=0;
		}
	}
	Unlock();
}

void cSmartCards::Register(cSmartCardLink *scl)
{
  d(printf("smartcards: registering %s smartcard (id %x)\n",scl->name,scl->id))
  scl->next=first;
  first=scl;
}

bool cSmartCards::AddPort(const char *devName, bool invCD, bool invRST)
{
	Lock();
	for(int i=0 ; i<MAX_PORTS ; i++) {
		if(!ports[i].Serial) {
			cSerial *ser=new cSerial(devName,invCD,invRST);
			if(ser && ser->Open()) {
				ports[i].Card=0;
				ports[i].CardId=0;
				ports[i].UseCount=0;
				ports[i].Dead=false;
				ports[i].PortNum=i;
				ports[i].Serial=ser;
				Unlock();
				d(printf("smartcards: added serial port %s as port %d (%s CD, %s RESET, CLOCK %d)\n",
						devName,i,invCD?"inverse":"normal",invRST?"inverse":"normal",ports[i].Clock))
				return true;
			}
			LogError("failed to open serial port %s\n",devName);
			delete ser;
			break;
		}
		else if (!strcmp(ports[i].Serial->DeviceName(), devName)) {
			LogError("duplicate serial port %s. Check your config!\n", devName);
			return false;
		}
	}
	Unlock();
	LogError("only %d serial ports supported\n",MAX_PORTS);
	return false;
}

void cSmartCards::LoadData(const char *cfgdir)
{
	Lock();
	dataList.Clear();
	ConfRead("smartcard data",AddDirectory(cfgdir,DATAFILE));
	Unlock();
}

bool cSmartCards::ParseLine(const char *line, bool fromCache)
{
	char *r=(char*)index(line,':');
	if(!r) return false;
	for(cSmartCardLink *scl=first; scl; scl=scl->next) {
		if(!strncasecmp(scl->name,line,r-line)) {
			cSmartCardData *scd=scl->CreateData();
			if(scd && scd->Parse(r+1)) {
				dataList.Add(scd);
				break;
			}
			else {
				delete scd;
				return false;
			}
		}
	}
	return true;
}

cSmartCardData *cSmartCards::FindCardData(cSmartCardData *param)
{
	cMutexLock lock(&mutex);
	for(cSmartCardData *cd=dataList.First(); cd; cd=dataList.Next(cd))
		if(cd->ident==param->ident && cd->Matches(param))
			return cd;
	return 0;
}

void cSmartCards::LaunchWatcher(void)
{
	for(int i=0 ; i<MAX_PORTS ; i++)
		if(ports[i].Serial) { run=true; Start(); return; }
	firstRun=false;
	LogWarn("no smartcard interface defined!");
}

bool cSmartCards::HaveCard(int id)
{
	Lock();
	while(firstRun) cond.Wait(mutex);
	bool res=false;
	for(int i=0 ; i<MAX_PORTS ; i++)
		if(ports[i].Serial && ports[i].CardId==id) res=true;
	Unlock();
	return res;
}

cSmartCard *cSmartCards::LockCard(int id)
{
	Lock();
	while(firstRun) cond.Wait(mutex);
	for(int i=0 ; i<MAX_PORTS ; i++) {
		if(ports[i].Serial && ports[i].CardId==id) {
			ports[i].UseCount++;
			Unlock();
			cSmartCard *sc=ports[i].Card;
			sc->Lock();
			if(CardInserted(ports[i].Serial) && sc->CardUp() && !sc->NeedsReset())
				return sc;
			// if failed, unlock the card and decrement UseCount
			sc->Unlock();
			Lock();
			ports[i].UseCount--;
			cond.Broadcast();
			break;
		}
	}
	Unlock();
	return 0;
}

void cSmartCards::ReleaseCard(cSmartCard *sc)
{
	sc->Unlock();
	Lock();
	for(int i=0 ; i<MAX_PORTS ; i++) {
		if(ports[i].Card==sc) {
			ports[i].UseCount--;
			cond.Broadcast();
			Unlock();
			return;
		}
	}
	Unlock();
	d(printf("smartcards: bad thing(TM), can't find port in ReleaseCard()\n"))
}

bool cSmartCards::CardInserted(cSerial *ser)
{
	return ser->CheckCAR();
}

bool cSmartCards::CardReset(struct Port *port)
{
	if(Reset(port)<2 || !cSmartCard::ParseAtr(&port->Atr,port->PortNum))
		return false;
	if(!DoPTS(port)) {
		// reset card again and continue without PTS
		if(Reset(port)<2 || !cSmartCard::ParseAtr(&port->Atr,port->PortNum))
			return false;
	}
	return true;
}

static const char *serModes[] = { 0,"8e2","8o2","8n2" };

int cSmartCards::Reset(struct Port *port)
{
	cSerial *ser=port->Serial;
	d(printf("smartcards: %d: reseting card (sermode %s)\n",port->PortNum,serModes[ser->CurrentMode()]));
	ser->ToggleRTS();
	cCondWait::SleepMs(20);
	ser->ToggleRTS();
	int r=ser->Read(port->Atr.atr,-MAX_ATR_LEN,800,2000);
	port->Atr.atrLen=r;
	if(r>0) {
		di(printf("iso: %d: <- ATR len=%d:",port->PortNum,r));
		DUMP(port->Atr.atr,r);
	}
	return r;
}

bool cSmartCards::DoPTS(struct Port *port)
{
#ifdef NO_PTS_PROTO
	return true;
#else
	const struct Atr *atr=&port->Atr;
	if(atr->F!=372 || atr->D!=1.0) { // PTS required
		cSerial *ser=port->Serial;
		const int id=port->PortNum;
		int baud=(int)(3571200.0*atr->D/atr->F);
		di(printf("iso: %d: starting PTS cycle: new baudrate %d",id,baud))
		baud=((baud+50)/100)*100;
		di(printf(", rounded %d\n",baud))

		unsigned char req[4], conf[16];
		req[0]=0xFF;
		req[1]=0x10 | atr->T;
		req[2]=atr->TA1;
		req[3]=XorSum(req,3);
#ifdef DEBUG_ISO
		printf("iso: %d: PTS request:",id);
		DUMP(req,4);
#endif
		if(ser->Write(req,4)!=4) {
			di(printf("iso: %d: PTS request, write failed\n",id))
			return false;
		}
		if(ser->Read(conf,4,100,100)!=4) {
			di(printf("iso: %d: PTS request, echo readback failed\n",id))
			return false;
		}
		int n=ser->Read(conf,-16,200,1000);
#ifdef DEBUG_ISO
		if(n>0) {
			printf("iso: %d: PTS confirm:",id);
			DUMP(conf,n);
		}
#endif
		if(n<1 || conf[0]!=0xFF || XorSum(conf,n)!=0) {
			di(printf("iso: %d: PTS confirm, bad format\n",id))
			return false;
		}
		if(n<4 || conf[1]!=req[1] || conf[2]!=req[2]) {
			di(printf("iso: %d: PTS not confirmed\n",id))
			return false;
		}
		if(!ser->SetMode(ser->CurrentMode(),baud)) {
			di(printf("iso: %d: setting baudrate %d failed\n",id,baud))
			return false;
		}
	}
	return true;
#endif // NO_PTS_PROTO
}

void cSmartCards::SetPort(struct Port *port, cSmartCard *sc, int id, bool dead)
{
	Lock();
	while(port->UseCount) cond.Wait(mutex);
	delete port->Card;
	port->Card=sc;
	port->CardId=id;
	port->Dead=dead;
	port->UseCount=0;
	Unlock();
}

void cSmartCards::Action(void)
{
	while(run) {
		for(int i=0 ; i<MAX_PORTS ;) {
			struct Port *port=&ports[i];
			cSerial *ser=port->Serial;
			if(ser) {
				if(port->Card) {
					cSmartCard *sc=port->Card;
					sc->Lock();
					if(!CardInserted(ser)) {
						sc->Unlock(); sc=0;
						d(printf("smartcards: %d: card removed (UseCount=%d)\n",port->PortNum,port->UseCount))
						SetPort(port,0,0,false);
					}
					else if(sc->NeedsReset()) {
						d(printf("smartcards: %d: card reset requested\n",port->PortNum))
							if(!ser->SetMode(ser->CurrentMode())
								|| !CardReset(port)
								|| !sc->Setup(ser,&port->Atr,port->PortNum)) {
							sc->Unlock(); sc=0;
							d(printf("smartcards: %d: card re-init failed\n",port->PortNum))
							SetPort(port,0,0,true);
						}
					}
					if(sc) sc->Unlock();
				}
				else if(CardInserted(ser)) {
					if(!port->Dead) {
						d(printf("smartcards: %d: new card inserted\n",port->PortNum))
						for(int mode=SM_NONE+1 ; mode<SM_MAX ; mode++) {
							if(ser->SetMode(mode)) {
								if(CardReset(port)) {
									cSmartCardLink *scl=first;
									while(scl) {
										d(printf("smartcards: %d: checking for %s card\n",port->PortNum,scl->name))
										cSmartCard *sc=scl->Create();
										if(sc && sc->Setup(ser,&port->Atr,port->PortNum)) {
											SetPort(port,sc,scl->id,false);
											goto next; // ugly, any better solution?
										}
										delete sc;
										scl=scl->next;
									}
									d(printf("smartcards: %d: no card handler found\n",port->PortNum))
								}
								else d(printf("smartcards: %d: reset/atr error\n",port->PortNum))
							}
							else d(printf("smartcards: %d: failed to set serial mode %s\n",port->PortNum,serModes[mode]))
						}
						port->Dead=true;
						d(printf("smartcards: %d: can't initialise new card, ignoring port until card reinserted\n",port->PortNum))
					}
				}
				else {
					if(port->Dead) d(printf("smartcards: %d: card removed, port reactivated\n",port->PortNum))
					port->Dead=false;
				}
			}
next:	i++;
		}
		if(firstRun) {
			Lock();
			cond.Broadcast();
			firstRun=false;
			Unlock();
		}
		cCondWait::SleepMs(300);
	}
	run=false;
}