/*
 * 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 "system.h"
#include "system-common.h"
#include "smartcard.h"
#include "parse.h"
#include "opts.h"

#define SYSTEM_NAME			"SC-Cryptoworks"
#define SYSTEM_PRI			-5

#define SC_NAME	"Cryptoworks"
#define SC_ID	MAKE_SC_ID('C','r','W','o')

static int disableParental=0;

// -- cSystemScCryptoworks ---------------------------------------------------------------

class cSystemScCryptoworks : public cSystemScCore {
public:
	cSystemScCryptoworks(void);
};

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

// -- cSystemLinkScCryptoworks --------------------------------------------------------

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

static cSystemLinkScCryptoworks staticInit;

cSystemLinkScCryptoworks::cSystemLinkScCryptoworks(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
	static const char *rat[] = {
		"don't touch",
		"disable"
	};

	opts=new cOpts(SYSTEM_NAME,1);
	opts->Add(new cOptSel("DisableParental","SC-Cryptoworks: Parental rating",&disableParental,sizeof(rat)/sizeof(char *),rat));
	Feature.NeedsSmartCard();
}

bool cSystemLinkScCryptoworks::CanHandle(unsigned short SysId)
{
	bool res=false;
	cSmartCard *card=smartcards.LockCard(SC_ID);
	if(card) {
		res=card->CanHandle(SysId);
		smartcards.ReleaseCard(card);
	}
	return res;
}

// -- cSmartCardCryptoworks -----------------------------------------------------------

class cSmartCardCryptoworks : public cSmartCard, public cIdSet {
private:
	int caid;
	//
	int GetLen(void);
	bool EndOfData(void);
	bool SelectFile(int file);
	int ReadRecord(unsigned char *buff, int num);
public:
	cSmartCardCryptoworks(void);
	virtual bool Init(void);
	virtual bool Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw);
	virtual bool CanHandle(unsigned short CaId);
};

static const struct StatusMsg msgs[] = {
	{ { 0x90,0x00 }, "Instruction executed without errors", true },
	{ { 0x92,0x40 }, "Memory problem", false },
	{ { 0x94,0x02 }, "Out of range", false },
	{ { 0x94,0x04 }, "File not found", false },
	{ { 0x98,0x04 }, "Verification failed (wrong PIN)", false },
	{ { 0x98,0x05 }, "Wrong signature", false },
	{ { 0x98,0x40 }, "Verification failed, card blocked (deblocking by uplink required)", false },
	{ { 0x9F,0xFF }, "Instruction accepted, data to be read", true },
	{ { 0xFF,0xFF }, 0, false }
};

static const struct CardConfig cardCfg = {
	SM_8E2,1000,100
};

struct chid_dat {
	unsigned int chid, version;
	unsigned char id, status;
	char from[16], to[16], name[16];
};

cSmartCardCryptoworks::cSmartCardCryptoworks(void)
:cSmartCard(&cardCfg,msgs)
{}

int cSmartCardCryptoworks::GetLen(void)
{
	return (sb[0]==0x9F) ? sb[1] : -1;
}

bool cSmartCardCryptoworks::EndOfData(void)
{
	return sb[0]==0x94 && sb[1]==0x02;
}

bool cSmartCardCryptoworks::SelectFile(int file)
{
	static unsigned char insa4[] = { 0xA4,0xA4,0x00,0x00,0x02,0x00,0x00 };
	insa4[5]=file>>8;
	insa4[6]=file&0xFF;
	return IsoWrite(insa4,&insa4[5]) && Status();
}

int cSmartCardCryptoworks::ReadRecord(unsigned char *buff, int num)
{
	static unsigned char insa2[] = { 0xA4,0xA2,0x00,0x00,0x01,0x00 };
	static unsigned char insb2[] = { 0xA4,0xB2,0x00,0x00,0x00 };
	insa2[5]=num;
	if(IsoWrite(insa2,&insa2[5]) && Status() && (num=GetLen())>0) {
		insb2[4]=num;
		if(IsoRead(insb2,buff) && Status()) return num;
	}
	return -1;
}

bool cSmartCardCryptoworks::Init(void)
{
	if(atr->histLen<6 || atr->hist[1]!=0xC4 || atr->hist[4]!=0x8F || atr->hist[5]!=0xF1) {
		di(printf("smartcardcryptoworks: doesn't look like a Cryptoworks card\n"))
		return false;
	}
	infoStr.Begin();
	infoStr.Strcat("Cryptoworks smartcard\n");
	unsigned char buff[MAX_LEN];
	if(!SelectFile(0x2F01) || ReadRecord(buff,0xD1)<4) {
		di(printf("smartcardcryptoworks: reading record 2f01/d1 failed\n"))
		return false;
	}
	caid=buff[2]*256+buff[3];
	if(ReadRecord(buff,0x80)<7) {
		di(printf("smartcardcryptoworks: reading record 2f01/80 failed\n"))
		return false;
	}
	SetCard(new cCardCryptoworks(&buff[2]));
	_snprintf(idStr,sizeof(idStr),"%s (V.%d)",SC_NAME,atr->hist[2]);
	char str[20];
	infoStr.Printf(	"Card v.%d (PINcount=%d)\n"
					"Caid %04x Serial %s\n",
					atr->hist[2],atr->hist[3],caid,HexStr(str,&buff[2],5));
	di(printf("smartcardcryptoworks: card v.%d (pindown=%d) caid %04x serial %s\n",atr->hist[2],atr->hist[3],caid,HexStr(str,&buff[2],5)))
	if(ReadRecord(buff,0x9F)>=3) {
		char *n="(unknown)";
		if(ReadRecord(buff+10,0xC0)>=18) n=(char *)buff+10+2;
		infoStr.Printf("Issuer:     0x%02x (%.16s)\n",buff[2],n);
		di(printf("smartcardcryptoworks: card issuer: 0x%02x %.16s\n",buff[2],n))
	}

	// read entitlements
	static unsigned char insb8[] = { 0xA4,0xB8,0x00,0x00,0x0C };
	unsigned char provId[16];
	unsigned int count=0;
	insb8[2]=insb8[3]=0x00;
	while(IsoRead(insb8,buff) && !EndOfData() && Status()) {
		if(buff[0]==0xDF && buff[1]==0x0A) {
			int fileno=(buff[4]&0x3F)*256+buff[5];
			if((fileno&0xFF00)==0x1F00) {
				provId[count++]=fileno&0xFF;
				if(count>=sizeof(provId)) break;
			}
		}
		insb8[2]=insb8[3]=0xFF;
	}
	for(unsigned int i=0; i<count ; i++) {
		if(SelectFile(0x1F00+provId[i])) {
			char *n="(unknown)";
			if(SelectFile(0x0E11) && ReadRecord(buff,0xD6)>=18) n=(char *)buff+2;
			infoStr.Printf("Provider %d: 0x%02x (%.16s)\n",i,provId[i],n);
			di(printf("smartcardcryptoworks: provider %d: 0x%02x %.16s\n",i,provId[i],n))
			static unsigned char insa2_0[] = { 0xA4,0xA2,0x01,0x00,0x03,0x83,0x01,0x00 };
			static unsigned char insa2_1[] = { 0xA4,0xA2,0x01,0x00,0x05,0x8C,0x00,0x00,0x00,0x00 };
			static unsigned char insb2[] = { 0xA4,0xB2,0x00,0x00,0x00 };
			static const unsigned int fn[] = { 0x0f00,0x0f20,0x0f40,0x0f60,0 };
			static const char *fnName[] = { "Download","Subscriptions","PPV Events","PPV Events",0 };
			bool first=true;
			for(int j=0; fn[j]; j++) {
				if(SelectFile(fn[j])) {
					insa2_0[7]=provId[i];
					unsigned char *ins=(j==1) ? insa2_1 : insa2_0;
					int l;
					if(IsoWrite(ins,&ins[5]) && Status() && (l=GetLen())>0) {
						if(first) {
							infoStr.Printf("id|chid|st|date             |name\n");
							di(printf("smartcardcryptoworks: | id | chid | stat | date              | version  | name        \n"))
							first=false;
						}
						infoStr.Printf(	"--+----+--+-----------------+------\n"
										"%s\n",
										fnName[j]);
						di(printf(  "smartcardcryptoworks: +----+------+------+-------------------+----------+-------------\n"
									"smartcardcryptoworks: | %s\n",
									fnName[j]))
						insb2[3]=0x00;
						insb2[4]=l;
						while(IsoRead(insb2,buff) && !EndOfData() && Status()) {
							struct chid_dat chid;
							memset(&chid,0,sizeof(chid));
							for(int k=0; k<l; k+=buff[k+1]+2) {
								switch(buff[k]) {
									case 0x83:
										chid.id=buff[k+2];
										break;
									case 0x8c:
										chid.status=buff[k+2];
										chid.chid=buff[k+3]*256+buff[k+4];
										break;
									case 0x8D:
										_snprintf(chid.from,sizeof(chid.from),"%02d.%02d.%02d",buff[k+3]&0x1F,((buff[k+2]&1)<<3)+(buff[k+3]>>5),(1990+(buff[k+2]>>1))%100);
										_snprintf(chid.to  ,sizeof(chid.to)  ,"%02d.%02d.%02d",buff[k+5]&0x1F,((buff[k+4]&1)<<3)+(buff[k+5]>>5),(1990+(buff[k+4]>>1))%100);
										break;
									case 0xD5:
										_snprintf(chid.name,sizeof(chid.name),"%.12s",&buff[k+2]);
										chid.version=Bin2Int(&buff[k+14],4);
										break;
								}
							}
							infoStr.Printf("%02x|%04x|%02x|%s-%s|%.12s\n",
								chid.id,chid.chid,chid.status,chid.from,chid.to,chid.name);
							di(printf("smartcardcryptoworks: | %02x | %04x |  %02x  | %s-%s | %08x | %.12s\n",
								chid.id,chid.chid,chid.status,chid.from,chid.to,chid.version,chid.name))
							insb2[3]=0x01;
						}
					}
				}
			}
		}
	}


	// disable parental rating
	if(disableParental) {
		static unsigned char ins24[] = { 0xA4,0x24,0x00,0x01,0x05,0x00,0x00,0x00,0x00,0x00 };
		if(SelectFile(0x2F11) && ReadRecord(buff,atr->hist[3])>=7) {
			di(printf("smartcardcryptoworks: your card PIN is %.4s\n",&buff[2]))
			memcpy(&ins24[5],&buff[2],4);
			// parental rating
			// 0x00      - undefined
			// 0x01-0x0f - minimum age=rating+3 years
			// 0x10-0xff - reserved for provider usage
			ins24[9]=0;
			if(IsoWrite(ins24,&ins24[5]) && Status())
				di(printf("smartcardcryptoworks: parental rating set to %02x\n",ins24[9]))
			else
				di(printf("smartcardcryptoworks: failed to set parental rating.\n"))
		}
		else di(printf("smartcardcryptoworks: failed to read card PIN.\n"))
	}

	infoStr.Finish();
	return true;
}

bool cSmartCardCryptoworks::CanHandle(unsigned short CaId)
{
	return CaId==caid;
}

bool cSmartCardCryptoworks::Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw)
{
	static unsigned char ins4c[] = { 0xA4,0x4C,0x00,0x00,0x00 };
	static unsigned char insc0[] = { 0xA4,0xC0,0x00,0x00,0x00 };

	int l=CheckSctLen(data,-5);
	if(l>5) {
		unsigned char buff[MAX_LEN];
		ins4c[4]=l-5;
		if(IsoWrite(ins4c,&data[5]) && Status() && (l=GetLen())>0) {
			insc0[4]=l;
			if(IsoRead(insc0,buff) && Status()) {
				int r=0;
				for(int i=0; i<l; ) {
					int n=buff[i+1];
					switch(buff[i]) {
						case 0xDB: // CW
							if(n==0x10) {
								memcpy(cw,&buff[i+2],16);
								r|=1;
							}
							break;
						case 0xDF: // signature
							if(n==0x08 && (buff[i+2]&0x50)==0x50 && !(buff[i+3]&0x01) && (buff[i+5]&0x80))
								r|=2;
							break;
					}
					i+=n+2;
				}
				return r==3;
			}
		}
	}
	return false;
}

// -- cSmartCardLinkCryptoworks -------------------------------------------------------

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

static cSmartCardLinkCryptoworks staticScInit;