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

//#define DEBUG_MSG
#ifdef DEBUG_MSG
#define dm(x) { (x); }
#include <unistd.h>
#else
#define dm(x) ; 
#endif

// --- cFeature ----------------------------------------------------------------

cFeature Feature;

bool cFeature::keyfile=false;
bool cFeature::smartcard=false;
bool cFeature::network=false;
int cFeature::pcount=0;

void cFeature::NeedsKeyFile(void)
{
	if(!keyfile) dyn(printf("feature: using feature KEYFILE\n"))
	keyfile=true;
}

void cFeature::NeedsSmartCard(void)
{
	if(!smartcard) dyn(printf("feature: using feature SMARTCARD\n"))
	smartcard=true;
}

// -- cKeySnoop ----------------------------------------------------------------

cKeySnoop::cKeySnoop(cSystem *Sys, int Type, int Id, int Keynr)
{
	ok=false;
	sys=Sys; type=Type; id=Id; keynr=Keynr;
}

cKeySnoop::~cKeySnoop()
{
	if(!ok) 
		d(sys->KeyFail(type,id,keynr))
}

void cKeySnoop::OK(cPlainKey *pk)
{
	sys->KeyOK(pk);
	ok=true;
}

// -- cSystem ------------------------------------------------------------------

#define MAX_CHID 10

struct EcmCheck {
	union {
		struct {
			bool startFlag;
			int current, chids[MAX_CHID];
		} sys06;
	} caid;
};

cSystem::cSystem(const char *Name, int Pri)
{
	name=Name; pri=Pri;
	lastType=0; currentKeyStr[0]=0; doLog=true; cardNum=-1;
	check=new struct EcmCheck;
	memset(check,0,sizeof(struct EcmCheck));
	// default config
	hasLogger=false;
	needsLogger=false;
	expensive=false;
	needsDescrData=false;
	constant=false;
}

cSystem::~cSystem()
{
	delete check;
}

int cSystem::CheckECM(const cEcmInfo *ecm, const unsigned char *data, bool sync)
{
	switch(ecm->caId>>8) {
		case 0x06: // Irdeto
			{
				const int cur=data[4];
				const int max=data[5];
				const int chid=WORD(data,6,0xFFFF);
				// if multiple channel id's, use current one only
				if(sync && ecm->caId==0x0604 && check->caid.sys06.current>0 && chid!=check->caid.sys06.current)
					dep(printf("ecmcheck(%s): chid %04x != current %04x\n",name,chid,check->caid.sys06.current))
					return 2;
				// search for fake channel id's on Stream
				if(!sync && max>0 && max<MAX_CHID) {
					if(cur==0) check->caid.sys06.startFlag=true;
					if(check->caid.sys06.startFlag) {
						if(cur<=max) check->caid.sys06.chids[cur]=chid;
						if(cur==max) {
							for(int i=0 ; i<max ; i++) {
								if(check->caid.sys06.chids[i]==0) { 
									check->caid.sys06.startFlag=false;
									dep(printf("ecmcheck(%s): zero chid\n",name))
									return 1;
								}
								for(int j=i+1 ; j<=max ; j++) {
									if(check->caid.sys06.chids[i]==check->caid.sys06.chids[j]) {
										check->caid.sys06.startFlag=false;
										dep(printf("ecmcheck(%s): duplicate chid %04x\n",name,check->caid.sys06.chids[i]))
										return 1;
									}
								}
							}
						}
					}
				}
				break;
			}
	}
	return 0;
}

void cSystem::CheckECMResult(const cEcmInfo *ecm, const unsigned char *data, bool result)
{
	switch(ecm->caId>>8) {
		case 0x06: // Irdeto
			check->caid.sys06.current=result ? WORD(data,6,0xFFFF) : 0;
			break;
    }
}

void cSystem::KeyOK(cPlainKey *pk)
{
	if(pk->type!=lastType || pk->id!=lastId || pk->keynr!=lastKeynr) {
		lastType=pk->type; lastId=pk->id; lastKeynr=pk->keynr;
		strn0cpy(currentKeyStr,pk->ToString(),sizeof(currentKeyStr));
#ifdef DEBUG
		d(printf("system: using key %s\n\n",*pk->ToString(true)))
		doLog=true;
#endif
	}
}

void cSystem::KeyOK(const char *txt)
{
	_snprintf(currentKeyStr,sizeof(currentKeyStr),"%s (%s)\n",txt?txt:name,"undisclosed key");
#ifdef DEBUG
	doLog=true;
#endif
}

void cSystem::KeyFail(int type, int id, int keynr)
{
#ifdef DEBUG
	if(type!=lastType || id!=lastId || keynr!=lastKeynr) {
		lastType=type; lastId=id; lastKeynr=keynr;
		if(doLog)
			printf("system: no key found for %c %.2x %.2x\n\n",lastType,lastId,lastKeynr);
	}
#endif
}

// -- cSystemLink --------------------------------------------------------------

cSystemLink::cSystemLink(const char *Name, int Pri)
{
	name=Name; pri=Pri;
	opts=0; noFF=false;
	cSystems::Register(this);
}

cSystemLink::~cSystemLink()
{
	delete opts;
}

// -- cSystems -----------------------------------------------------------------

cSystemLink *cSystems::first=0;

void cSystems::Register(cSystemLink *sysLink)
{
	dyn(printf("systems: registering CA system %s, pri %d\n",sysLink->name,sysLink->pri))
	sysLink->next=first;
	first=sysLink;
}

cSystem *cSystems::FindBySysId(unsigned short SysId, bool ff, int oldPri)
{
	// all pri's are negative!
	// oldPri = 0 -> get highest pri system
	// oldPri < 0 -> get highest pri system with pri<oldPri
	// oldPri > 0 -> get lowest pri system

	cSystemLink *sl=first, *csl=0;
	while(sl) {
		if((!ff || !sl->noFF) && sl->CanHandle(SysId)
			&& sl->pri<oldPri
			&& (!csl
				|| (oldPri<=0 && sl->pri>csl->pri)
				|| (oldPri>0 && sl->pri<csl->pri)
				)
			) csl=sl;
		sl=sl->next;
	}
	if(csl) return csl->Create();
	return 0;
}

bool cSystems::Init(const char *cfgdir)
{
	cSystemLink *sl=first;
#ifdef DEBUG
	printf("** registered systems:\n");
	while(sl) {
		printf("** %-16s  (pri %3d)\n",sl->name,sl->pri);
		sl=sl->next;
	}
	sl=first;
#endif
	while(sl) {
		if(!sl->Init(cfgdir)) return false;
		sl=sl->next;
	}
	return true;
}

void cSystems::Clean(void)
{
  cSystemLink *sl=first;
  while(sl) {
    sl->Clean();
    sl=sl->next;
    }
}

// -- cMsgCache ----------------------------------------------------------------

#define FREE   0x00 // modes
#define FAIL1  0x01
#define FAIL2  0x3D
#define FAILN  0x3E
#define GOOD   0x3F
#define MASK   0x3F
#define QUEUED 0x40
#define WAIT   0x80

struct Cache {
	int crc;
	int mode;
};

cMsgCache::cMsgCache(int NumCache, int StoreSize)
{
	numCache=NumCache;
	storeSize=StoreSize;
	ptr=0; stores=0; maxFail=2;
	caches=MALLOC(struct Cache,numCache);
	if(caches) {
		Clear();
		if(storeSize>0) {
			stores=MALLOC(unsigned char,numCache*storeSize);
			if(!stores) d(printf("msgcache: no memory for store area\n"))
		}
	}
	else d(printf("msgcache: no memory for cache\n"))
}

cMsgCache::~cMsgCache()
{
	free(caches);
	free(stores);
}

void cMsgCache::SetMaxFail(int maxfail)
{
	maxFail=min(maxfail,FAILN);
	dm(printf("msgcache: %d/%p: maxFail set to %d\n",getpid(),this,maxFail))
}

void cMsgCache::Clear(void)
{
	cMutexLock lock(&mutex);
	memset(caches,0,sizeof(struct Cache)*numCache);
	ptr=0;
	dm(printf("msgcache: %d/%p: clear\n",getpid(),this))
}

struct Cache *cMsgCache::FindMsg(int crc)
{
	int i=ptr;
	while(1) {
		if(--i<0) i=numCache-1;
		struct Cache * const s=&caches[i];
		if(!s->mode) break;
		if(s->crc==crc) return s;
		if(i==ptr) break;
	}
	return 0;
}

// returns:
// -1 - msg cached as failed (max failed)
// 0  - msg cached as good, result stored
// >0 - msg not cached, queue id
int cMsgCache::Get(const unsigned char *msg, int len, unsigned char *store)
{
	int crc=crc32_le(0,msg,len);
	cMutexLock lock(&mutex);
	if(!caches || (storeSize>0 && !stores)) return -1; // sanity
	struct Cache *s;
	while((s=FindMsg(crc))) {
		if(!(s->mode&QUEUED)) break;
		s->mode|=WAIT;
		dm(printf("msgcache: %d/%p: msg already queued. waiting to complete\n",getpid()this,))
		wait.Wait(mutex);
	}
	int id;
	if(!s) {
		while(1) {
			s=&caches[ptr];
			if(!(s->mode&QUEUED)) break;
			s->mode|=WAIT;
			dm(printf("msgcache: %d/%p: queue overwrite protection id=%d\n",getpid(),this,ptr+1))
			wait.Wait(mutex); // don't overwrite queued msg's
		}
		id=ptr+1;
		s->crc=crc;
		s->mode=QUEUED;
		dm(printf("msgcache: %d/%p: queued msg with id=%d\n",getpid(),this,id))
		ptr++; if(ptr>=numCache) { ptr=0; dm(printf("msgcache: roll-over (%d)\n",numCache)) }
		return id;
	}
	else {
		id=(s-&caches[0])+1;
		switch(s->mode) {
			case GOOD:
				if(store && storeSize>0)
					memcpy(store,&stores[(id-1)*storeSize],storeSize);
				dm(printf("msgcache: %d/%p: msg is cached as GOOD (%d)\n",getpid(),this,id))
				return 0;;
			default:
				if (s->mode>=FAIL1 && s->mode<=FAIL2) {
					dm(printf("msgcache: %d/%p: msg is cached as FAIL%d (%d)\n",getpid(),this,s->mode,id))
					s->mode|=QUEUED;
					return id;
				}
				dm(printf("msgcache: %d/%p: msg is cached as FAILN (%d)\n",getpid(),this,id))
				return -1;
		}
	}
}

int cMsgCache::Cache(int id, bool result, const unsigned char *store)
{
	cMutexLock lock(&mutex);
	if(id<1 || !caches || (storeSize>0 && !stores)) return 0; // sanity
	struct Cache *s=&caches[id-1];
	dm(printf("msgcache: %d/%p: de-queued msg with id=%d ",getpid(),this,id))
	if(s->mode&WAIT) wait.Broadcast();
	if(result) {
		if(store && storeSize>0)
			memcpy(&stores[(id-1)*storeSize],store,storeSize);
		s->mode=GOOD;
		dm(printf("(GOOD)\n"))
		return 0;
	}
	else {
		switch(s->mode&MASK) {
			case GOOD:
				s->mode=FREE;
				// fall through
			default:
				if (s->mode>=FAIL1 && s->mode<=FAIL2 || s->mode==FREE) {
					s->mode=(s->mode&MASK)+1;
					if(s->mode<maxFail) {
						dm(printf("(FAIL%d)\n",s->mode))
						break;
					}
					// fall through
				} else break;
			case FAILN:
				s->mode=FAILN;
				dm(printf("(FAILN)\n"))
				break;
		}
    return s->mode;
	}
}