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

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

#define SYSTEM_CONAX         0x0B00

#define SYSTEM_NAME          "SC-Conax"
#define SYSTEM_PRI           -5
#define SYSTEM_CAN_HANDLE(x) ((x)==SYSTEM_CONAX)

#define SC_NAME "Conax"
#define SC_ID   MAKE_SC_ID('C','o','n','x')

// -- cSystemScConax ------------------------------------------------------------------

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

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

// -- cSystemLinkScConax --------------------------------------------------------------

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

static cSystemLinkScConax staticInit;

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

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

// -- cSmartCardConax -----------------------------------------------------------------

#define STDENTTAG 0x32
#define PPVENTTAG 0x39
#define ADDR_SIZE 7

struct stdent {
	unsigned short id;
	char name[16], date[4][16];
	unsigned int pbm[2];
};

class cSmartCardConax : public cSmartCard, private cProviders {
private:
	int sysId, cardVer, currency;
	bool emmInitDone;
	//
	int GetLen(void);
public:
	cSmartCardConax(void);
	virtual bool Init(void);
	virtual bool Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw);
	virtual bool CanHandle(unsigned short SysId);
};

static const struct StatusMsg msgs[] = {
  { { 0x90,0x00 }, "Instruction executed without errors", true },
  { { 0x90,0x11 }, "Bad instruction", false },
  { { 0x90,0x12 }, "Access denied, check your subscription", false },
  { { 0x90,0x13 }, "Bit error detected", false },
  { { 0x90,0x16 }, "Insuficient info", false },
  { { 0x94,0x00 }, "Instruction executed without errors, waiting for user interaction", true },
  { { 0x98,0xFF }, "Instruction accepted, data to be read", true },
  { { 0x9c,0xFF }, "Instruction accepted, data to be read", true },
  { { 0xFF,0xFF }, 0, false }
  };

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

cSmartCardConax::cSmartCardConax(void)
:cSmartCard(&cardCfg,msgs)
{
  sysId=0; cardVer=0; currency=0;
  emmInitDone=false;
}

bool cSmartCardConax::CanHandle(unsigned short SysId)
{
	return (SysId==sysId);
}

int cSmartCardConax::GetLen(void)
{
	return (sb[0]==0x98 || sb[0]==0x9C || (sb[0]==0x90 && sb[1]==0x00)) ? sb[1] : -1;
}

bool cSmartCardConax::Init(void)
{
	static const unsigned char cnxHist[] = { '0','B','0','0' }; // XXX is this always true ?

	sysId=0; cardVer=0; currency=0;
	if(atr->histLen<4 || memcmp(atr->hist, cnxHist, atr->histLen)) {
		di(printf("smartcardconax: doesn't look like a Conax card\n"))
		return false;
	}

	infoStr.Begin();
	infoStr.Strcat("Conax smartcard\n");
	static unsigned char ins26[] = { 0xdd, 0x26, 0x00, 0x00, 0x03 };
	static unsigned char insc6[] = { 0xdd, 0xc6, 0x00, 0x00, 0x03 };
	static unsigned char insca[] = { 0xdd, 0xca, 0x00, 0x00, 0x00 };
	static const unsigned char hostVer[] = { 0x10,0x01,0x01 };  // host version
	unsigned char buff[MAX_LEN];
	int l;
	if(!IsoWrite(ins26,hostVer) || !Status() || (l=GetLen())<=0) {
		di(printf("smartcardconax: card init failed (1)\n"))
		return false;
	}
	insca[4]=l;
	if(!IsoRead(insca,buff) || !Status()) {
		di(printf("smartcardconax: card init failed (2)\n"))
		return false;
	}
	for(int i=0; i<l; i+=buff[i+1]+2) {
		switch(buff[i]) {
			case 0x20: cardVer=buff[i+2]; break;
			case 0x28: sysId=(buff[i+2]<<8)+buff[i+3]; break;
			case 0x2f: currency=(buff[i+2]<<8)+buff[i+3]; break;
		}
	}
	infoStr.Printf("Card v.%d Caid %04x\n",cardVer,sysId);
	di(printf("smartcardconax: card v.%d\n",cardVer))
	_snprintf(idStr,sizeof(idStr),"%s (V.%d)",SC_NAME,cardVer);

	static const unsigned char stdEntits[] = { 0x1C,0x01,0x00 };
	if(IsoWrite(insc6,stdEntits) && Status() && (l=GetLen())>0) {
		infoStr.Printf("Subscriptions:\n");
		infoStr.Printf("|id  |name        |date             |\n");
		infoStr.Printf("+----+------------+-----------------+\n");

		do {
			insca[4]=l;
			if(!IsoRead(insca,buff) || !Status()) {
				di(printf("smartcardconax: failed to read entitlements\n"))
				break;
			}
			for(int i=0; i<l; i+=buff[i+1]+2) {
				if(buff[i]!=STDENTTAG) {
					di(printf("smartcardconax: bad entitlement format %02x != %02x\n",buff[i],STDENTTAG))
					break;
				}

				struct stdent ent;
				memset(&ent,0,sizeof(ent));
				ent.id=(buff[i+2]<<8)|buff[i+3];

				int date=0, pbm=0;
				int max=i+buff[i+1]+2;
				for(int j=i+4; j<max; j+=buff[j+1]+2) {
					switch(buff[j]) {
						case 0x01: // prov. name
							_snprintf(ent.name,sizeof(ent.name),"%.12s",&buff[j+2]);
							break;
						case 0x30: // date
							if(date<=3) {
								_snprintf(ent.date[date],sizeof(ent.date[0]),"%02d.%02d.%02d",buff[j+2]&0x1F,buff[j+3]&0xF,(1990+((buff[j+3]>>4)+((buff[j+2]>>5)&0x7)*10))%100);
								date++;
							}
							break;
						case 0x20:
							if(pbm<=1) {
								ent.pbm[pbm]=(buff[j+2]<<24)|(buff[j+3]<<16)|(buff[j+4]<<8)|buff[j+5];
								pbm++;
							}
							break;
					}
				}
				infoStr.Printf("|%04X|%s|%s-%s|\n",ent.id,ent.name,ent.date[0],ent.date[1]);
				infoStr.Printf("|    |            |%s-%s\n",ent.date[2],ent.date[3]);
			}
		} while((l=GetLen())>0);
	}
	else
		di(printf("smartcardconax: requesting entitlements failed\n"))

	emmInitDone=false;
	infoStr.Finish();
	return true;
}

bool cSmartCardConax::Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw)
{
	static unsigned char insa2[] = { 0xDD,0xA2,0x00,0x00,0x00 };
	static unsigned char insca[] = { 0xDD,0xCA,0x00,0x00,0x00 };

	int l;
	if((l=CheckSctLen(data,3))<=0) return false;
	unsigned char buff[MAX_LEN];
	buff[0]=0x14;
	buff[1]=l+1;
	buff[2]=0;
	memcpy(buff+3,data,l);

	insa2[4]=l+3;
	if(!IsoWrite(insa2,buff) || !Status() || (l=GetLen())<=0) return false;
	int gotIdx=0;
	do {
		insca[4]=l;
		if(!IsoRead(insca,buff) || !Status()) return false;
		for(int i=0; i<l; i+=buff[i+1]+2) {
			switch(buff[i]) {
				case 0x25:
					if(buff[i+1]>=13) {
						int idx=buff[i+4];
						if(idx<=1) {
							gotIdx|=(1<<idx);
							memcpy(cw+idx*8,&buff[i+7],8);
						}
					}
					break;
			}
		}
	} while((l=GetLen())>0);
	if(gotIdx!=3) di(printf("smartcardconax: strange, only got index %d cw. Failing... (this may be a bug)\n",gotIdx==1?0:1))
	return gotIdx==3;
}

// -- cSmartCardLinkConax -------------------------------------------------------------

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

static cSmartCardLinkConax staticScInit;
