/*
 * 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 <string.h>

#include "common.h"
#include "system.h"
#include "system-common.h"
#include "smartcard.h"
#include "parse.h"

#define SYSTEM_VIACCESS      0x0500

#define SYSTEM_NAME          "SC-Viaccess"
#define SYSTEM_PRI           -5
#define SYSTEM_CAN_HANDLE(x) ((x)==SYSTEM_VIACCESS)

#define SC_NAME "Viaccess"
#define SC_ID   MAKE_SC_ID('V','i','a','s')

// -- cSystemScViaccess ------------------------------------------------------------------

class cSystemScViaccess : public cSystemScCore { //, private cTPS {
public:
	cSystemScViaccess(void);
};

cSystemScViaccess::cSystemScViaccess(void)
:cSystemScCore(SYSTEM_NAME,SYSTEM_PRI,SC_ID,"SC Viaccess")
{
	hasLogger=true;
}

// -- cSystemLinkScViaccess --------------------------------------------------------------

class cSystemLinkScViaccess : public cSystemLink {
public:
	cSystemLinkScViaccess(void);
	virtual bool CanHandle(unsigned short SysId);
	virtual cSystem *Create(void) { return new cSystemScViaccess; }
};

static cSystemLinkScViaccess staticInit;

cSystemLinkScViaccess::cSystemLinkScViaccess(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
	Feature.NeedsSmartCard();
}

bool cSystemLinkScViaccess::CanHandle(unsigned short SysId)
{
	return smartcards.HaveCard(SC_ID) && SYSTEM_CAN_HANDLE(SysId);
}

// -- cProviderScViaccess ----------------------------------------------------------

class cProviderScViaccess : public cProviderViaccess {
public:
	unsigned char availKeys[16];
	char name[33];
	//
	cProviderScViaccess(const unsigned char *id, const unsigned char *s):cProviderViaccess(id,s) {}
};

// -- cSmartCardViaccess -----------------------------------------------------------------

class cSmartCardViaccess : public cSmartCard, public cIdSet {
private:
	unsigned char lastId[3];
	//
	unsigned char GetSW1(void) { return sb[1]; }
	bool CheckKey(cProviderScViaccess *p, const unsigned char keynr);
	bool SetProvider(const unsigned char *id);
public:
	cSmartCardViaccess(void);
	virtual bool Init(void);
	virtual bool Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw);
};

static const struct StatusMsg msgs[] = {
	{ { 0x6b,0x00 }, "Instruction not supported", false },
	{ { 0x6d,0x00 }, "Instruction not supported", false },
	{ { 0x90,0x00 }, "Instruction executed without errors", true },
	{ { 0x90,0x08 }, "Instruction executed without errors", true },
	{ { 0xFF,0xFF }, 0, false }
};

static const struct CardConfig cardCfg = {
	SM_8O2,500,300
};

cSmartCardViaccess::cSmartCardViaccess(void)
:cSmartCard(&cardCfg,msgs)
{
	memset(lastId,0,sizeof(lastId));
}

bool cSmartCardViaccess::Init(void)
{
	static const unsigned char verifyBytes[] = { 0x90,0x00 };
	if(atr->histLen<7 && memcmp(atr->hist+(atr->histLen-2),verifyBytes,sizeof(verifyBytes))) {
		di(printf("smartcardviaccess: doesn't look like a Viaccess card\n"))
		return false;
	}

	infoStr.Begin();
	infoStr.Strcat("Viaccess smartcard\n");
	char *ver=0;
	switch((atr->hist[atr->histLen-4]<<8)|atr->hist[atr->histLen-3]) {
		case 0x6268: ver="2.3"; break;
		case 0x6668: ver="2.4(?)"; break;
		case 0xa268:
		default: ver="unknown"; break;
	}

	di(printf("smartcardviaccess: card v.%s\n",ver))
	_snprintf(idStr,sizeof(idStr),"%s (V.%s)",SC_NAME,ver);

	static unsigned char insac[] = { 0xca, 0xac, 0x00, 0x00, 0x00 }; // select data
	static unsigned char insb8[] = { 0xca, 0xb8, 0x00, 0x00, 0x00 }; // read selected data
	static unsigned char insa4[] = { 0xca, 0xa4, 0x00, 0x00, 0x00 }; // select issuer
	static unsigned char insc0[] = { 0xca, 0xc0, 0x00, 0x00, 0x00 }; // read data item
	unsigned char buff[MAX_LEN];

	ResetIdSet();
	insac[2]=0xa4; // request unique id
	if(!IsoWrite(insac,buff) || !Status()) {
		di(printf("smartcardviaccess: failed to request ua\n"))
		return false;
	}
	insb8[4]=0x07; // read unique id
	if(!IsoRead(insb8,buff) || !Status() || buff[1]!=0x05) {
		di(printf("smartcardviaccess: failed to read ua\n"))
		return false;
	}
	SetCard(new cCardViaccess(&buff[2]));
	di(printf("smartcardviaccess: card UA: %llu\n",Bin2LongLong(&buff[2],5)))
	infoStr.Printf("Card v.%s UA %010llu\n",ver,Bin2LongLong(&buff[2],5));

	insa4[2]=0x00; // select issuer 0
	if(!IsoWrite(insa4,buff) || !Status()) {
		di(printf("smartcardviaccess: failed to select issuer 0\n"))
		return false;
	}
	do {
		insc0[4]=0x1a; // show provider properties
		if(!IsoRead(insc0,buff) || !Status()) {
			di(printf("smartcardviaccess: failed to read prov properties\n"))
			return false;
		}

		unsigned char buff2[MAX_LEN];
		insac[2]=0xa5; // request sa
		if(!IsoWrite(insac,buff2) || !Status()) {
			di(printf("smartcardviaccess: failed to request sa\n"))
			return false;
		}
		insb8[4]=0x06; // read sa
		if(!IsoRead(insb8,buff2) || !Status()) {
			di(printf("smartcardviaccess: failed to read sa\n"))
			return false;
		}
		cProviderScViaccess *p=new cProviderScViaccess(&buff[0],&buff2[2]);
		if(p) {
			AddProv(p);
			memcpy(p->availKeys,buff+10,sizeof(p->availKeys));

			insac[2]=0xa7; // request name
			if(!IsoWrite(insac,buff) || !Status()) {
				di(printf("smartcardviaccess: failed to request prov name\n"))
				return false;
			}
			insb8[4]=0x02; // read name nano + len
			if(!IsoRead(insb8,buff) || !Status()) {
				di(printf("smartcardviaccess: failed to read prov name length\n"))
				return false;
			}
			unsigned int nameLen=buff[1];
			if(nameLen>=sizeof(p->name)) {
				di(printf("smartcardviaccess: provider name buffer overflow\n"))
				nameLen=sizeof(p->name)-1;
			}
			insb8[4]=nameLen;
			if(!IsoRead(insb8,buff) || !Status()) {
				di(printf("smartcardviaccess: failed to read prov name\n"))
				return false;
			}
			memcpy(p->name,buff,nameLen); p->name[nameLen]=0;

			di(printf("smartcardviaccess: provider %06x (%s)\n",(int)p->ProvId(),p->name))
			infoStr.Printf("Prov %06x (%s) SA %08u\n",(int)p->ProvId(),p->name,Bin2Int(&buff2[2],4));
		}
		else di(printf("smartcardviaccess: no memory for provider\n"))

		insa4[2]=0x02; // next issuer
		if(!IsoWrite(insa4,buff) || !Status()) {
			di(printf("smartcardviaccess: failed to select next issuer\n"))
			return false;
		}
	} while(GetSW1()==0x00);
	infoStr.Finish();
	return true;
}

bool cSmartCardViaccess::CheckKey(cProviderScViaccess *p, const unsigned char keynr)
{
	for(unsigned int j=0; j<sizeof(p->availKeys); j++)
		if(p->availKeys[j]==keynr) return true;
	return false;
}

bool cSmartCardViaccess::SetProvider(const unsigned char *id)
{
	static const unsigned char insa4[] = { 0xca,0xa4,0x04,0x00,0x03 };

	if(id[0]!=lastId[0] || id[1]!=lastId[1] || (id[2]&0xF0)!=lastId[2]) {
		memcpy(lastId,id,3);
		lastId[2]&=0xF0;
		if(!IsoWrite(insa4,lastId) || !Status()) return false;
    }
	return true;
}

bool cSmartCardViaccess::Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw)
{
	static unsigned char ins88[] = { 0xca,0x88,0x00,0x00,0x00 }; // set ecm
	static unsigned char insf8[] = { 0xca,0xf8,0x00,0x00,0x00 }; // set geographic info 
	static unsigned char insc0[] = { 0xca,0xc0,0x00,0x00,0x12 }; // read dcw

	cProviderScViaccess *p=(cProviderScViaccess *)FindProv(data);
	if(p) {
		unsigned char keynr=cParseViaccess::KeyNr(data);
		if(CheckKey(p,keynr)) {
			if(!SetProvider(cParseViaccess::ProvIdPtr(data))) return false;

			const unsigned char *start=cParseViaccess::NanoStart(data)+5;
			const unsigned char *ecm88Data=start;
			int ecm88Len=SCT_LEN(data)-(start-data);
			int ecmf8Len=0;
			while(ecm88Len>0 && ecm88Data[0]<0xA0) {
				const int nanoLen=ecm88Data[1]+2;
				ecmf8Len+=nanoLen;
				ecm88Len-=nanoLen; ecm88Data+=nanoLen;
			}
			if(ecmf8Len) {
				insf8[3]=keynr;
				insf8[4]=ecmf8Len;
				if(!IsoWrite(insf8,start) || !Status()) return false;
			}

			ins88[2]=ecmf8Len?1:0;
			ins88[3]=keynr;
			ins88[4]=ecm88Len;
			if(!IsoWrite(ins88,ecm88Data) || !Status()) return false; // request dcw
			unsigned char buff[MAX_LEN];
			if(!IsoRead(insc0,buff) || !Status()) return false; // read dcw
			switch(buff[0]) {
				case 0xe8: // even
					if(buff[1]==8) { memcpy(cw,buff+2,8); return true; }
					break;
				case 0xe9: // odd
					if(buff[1]==8) { memcpy(cw+8,buff+2,8); return true; }
					break;
				case 0xea: // complete
					if(buff[1]==16) { memcpy(cw,buff+2,16); return true; }
					break;
			}
		}
		else di(printf("smartcardviaccess: key not found on card\n"))
	}
	else di(printf("smartcardviaccess: provider not found on card\n"))
	return false;
}

// -- cSmartCardLinkViaccess -------------------------------------------------------------

class cSmartCardLinkViaccess : public cSmartCardLink {
public:
	cSmartCardLinkViaccess(void):cSmartCardLink(SC_NAME,SC_ID) {}
	virtual cSmartCard *Create(void) { return new cSmartCardViaccess(); }
};

static cSmartCardLinkViaccess staticScInit;