/*
 * 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 <fcntl.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "unixem/mman.h"
#include <sys/stat.h>

#include "common.h"
#include "data.h"
#include "misc.h"

#include "asprintf.h"
#include "main.h"
extern class DllClass m_DLLInstance;

#define KEY_FILE		"SoftCam.Key"
#define CACACHE_FILE	"ca.cache"
#define EXT_AU_INT		(15*60*1000) // ms interval for external AU
#define EXT_AU_MIN		( 2*60*1000) // ms min. interval for external AU

#define MAP_SHARED 1 //?
#define MS_ASYNC   0 //?

#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif

// -- cFileMap -----------------------------------------------------------------

cFileMap::cFileMap(const char *Filename, bool Rw)
{
	filename=_strdup(Filename);
	rw=Rw;
	fd=-1; count=len=0; addr=0; failed=false;
}

cFileMap::~cFileMap()
{
	Clean();
	free(filename);
}

bool cFileMap::IsFileMap(const char *Name, bool Rw)
{
	return (!strcmp(Name,filename) && (!Rw || rw));
}

void cFileMap::Clean(void)
{
	if(addr) { munmap(addr,len); addr=0; len=0; }
	if(fd>=0) { _close(fd); fd=-1; }
}

bool cFileMap::Map(void)
{
	cMutexLock lock(this);
	if(addr) { count++; return true; }
	if(!failed) {
		struct __stat64 ds;
		if(!_stat64(filename,&ds)) {
			if(S_ISREG(ds.st_mode)) {
				fd=_open(filename,rw ? O_RDWR : O_RDONLY);
				if(fd>=0) {
					unsigned char *map=(unsigned char *)mmap(0,ds.st_size,rw ? (PROT_READ|PROT_WRITE):(PROT_READ),MAP_SHARED,fd,0);
					if(map!=MAP_FAILED) {
						addr=map; len=ds.st_size; count=1;
						return true;
					}
					else d(printf("filemap: mapping failed on %s: %s\n",filename,strerror(errno)))
					_close(fd); fd=-1;
				}
				else d(printf("filemap: error opening %s: %s\n",filename,strerror(errno)))
			}
			else d(printf("filemap: %s is not a regular file\n",filename))
		}
		else d(printf("filemap: can't stat %s: %s\n",filename,strerror(errno)))
		failed=true; // don't try this one over and over again
	}
	return false;
}

bool cFileMap::Unmap(void)
{
	cMutexLock lock(this);
	if(addr) {
		if(!(--count)) { Clean(); return true; }
		else Sync();
	}
	return false;
}

void cFileMap::Sync(void)
{
	cMutexLock lock(this);
	if(addr) msync(addr,len,MS_ASYNC);
}

// -- cFileMaps ----------------------------------------------------------------

cFileMaps filemaps;

cFileMaps::cFileMaps(void)
{
	cfgDir=0;
}

cFileMaps::~cFileMaps()
{
	Clear();
	free(cfgDir);
}

void cFileMaps::SetCfgDir(const char *CfgDir)
{
	free(cfgDir);
	cfgDir=_strdup(CfgDir);
}

cFileMap *cFileMaps::GetFileMap(const char *name, const char *domain, bool rw)
{
	cMutexLock lock(this);
	char path[256];
	_snprintf(path,sizeof(path),"%s\\%s\\%s",cfgDir,domain,name);
	cFileMap *fm=First();
	while(fm) {
		if(fm->IsFileMap(path,rw)) return fm;
		fm=Next(fm);
	}
	fm=new cFileMap(path,rw);
	Add(fm);
	return fm;
}

// -- cConfRead ----------------------------------------------------------------

bool cConfRead::ConfRead(const char *type, const char *filename)
{
	bool res=false;
	FILE *f=fopen(filename,"r");
	if(f) {
		res=true;
		LogInfo("loading %s from %s",type,filename);
		char buff[1024];
		while(fgets(buff,sizeof(buff),f)) {
			if(!index(buff,'\n') && !feof(f))
				d(printf("confread: %s: fgets readbuffer overflow\n",type))
			char *line=skipspace(stripspace(buff));
			if(line[0]==0 || line[0]==';' || line[0]=='#') continue; // skip empty & comment lines
			if(!ParseLine(line,false)) {
				LogError("file '%s' has error in line '%s'",filename,buff);
				res=false;
			}
		}
	fclose(f);
	}
	else LogError("Failed to open file '%s': %s",filename,strerror(errno));
	return res;
}

// -- cLoader ------------------------------------------------------------------

cLoader::cLoader(const char *Id)
{
	id=Id; modified=false;
	cLoaders::Register(this);
}

// -- cLoaders -----------------------------------------------------------------

cLoader *cLoaders::first=0;
cMutex cLoaders::lock;
char *cLoaders::cacheFile=0;

void cLoaders::Register(cLoader *ld)
{
	d(printf("loaders: registering loader %s\n",ld->id))
	ld->next=first;
	first=ld;
}

#define F_OK 4

void cLoaders::LoadCache(const char *cfgdir)
{
	lock.Lock();
	cacheFile=_strdup(AddDirectory(cfgdir,CACACHE_FILE));
	if(_access(cacheFile,F_OK)==0) {
		LogInfo("loading ca cache from %s",cacheFile);
		FILE *f=fopen(cacheFile,"r");
		if(f) {
			char buf[512];
			cLoader *ld=0;
			while(fgets(buf,sizeof(buf),f)) {
				if(!index(buf,'\n'))
					d(printf("loaders: fgets readbuffer overflow\n"))
				if(buf[0]=='#') continue;
				if(!strncmp(buf,":::",3)) { // new loader section
					ld=FindLoader(stripspace(&buf[3]));
					if(!ld) {
						d(printf("loaders: unknown loader section '%s', adding dummy\n",&buf[3]))
					}
				}
				else if(ld) {
					if(!ld->ParseLine(buf,true)) {
						d(printf("loaders: '%s' failed on line '%s'\n",ld->Id(),buf))
					}
				}
			}
			fclose(f);
		}
		else printf("ERROR: %s\n",cacheFile);
	}
	lock.Unlock();
}

void cLoaders::SaveCache(void)
{
	lock.Lock();
	if(cacheFile && IsModified()) {
		cSafeFile f(cacheFile);
		if(f.Open()) {
			fprintf(f,"## This is a generated file. DO NOT EDIT!!\n"
                "## This file will be OVERWRITTEN WITHOUT WARNING!!\n");

			cLoader *ld=first;
			while(ld) {
				fprintf(f,":::%s\n",ld->Id());
				if(!ld->Save(f)) break;
				ld=ld->next;
			}
			f.Close();
			d(printf("loaders: saved cache to file\n"))
		}
	}
	lock.Unlock();
}

bool cLoaders::IsModified(void)
{
	bool res=false;
//	lock.Lock();
	cLoader *ld=first;
	while(ld) {
		if(ld->IsModified()) {
			res=true; break;
		}
		ld=ld->next;
	}
//	lock.Unlock();
	return res;
}

cLoader *cLoaders::FindLoader(const char *id)
{
//	lock.Lock();
	cLoader *ld=first;
	while(ld) {
		if(!strcmp(id,ld->Id())) break;
		ld=ld->next;
	}
//	lock.Unlock();
	return ld;
}

// -- cLineDummy ---------------------------------------------------------------

class cLineDummy : public cSimpleItem {
private:
	char *store;
public:
	cLineDummy(void);
	~cLineDummy();
	bool Parse(const char *line);
	bool Save(FILE *f);
};

cLineDummy::cLineDummy(void)
{
	store=0;
}

cLineDummy::~cLineDummy()
{
	free(store);
}

bool cLineDummy::Parse(const char *line)
{
	free(store);
	store=strdup(line);
	return store!=0;
}

bool cLineDummy::Save(FILE *f)
{
	fprintf(f,"%s",store);
	return ferror(f)==0;
}

// -- cLoaderDummy -------------------------------------------------------------

cLoaderDummy::cLoaderDummy(const char *id)
:cLoader(id)
{}

bool cLoaderDummy::ParseLine(const char *line, bool fromCache)
{
	if(fromCache) {
		cLineDummy *k=new cLineDummy;
		if(k) {
			if(k->Parse(line)) Add(k);
			else delete k;
			return true;
		}
		LogError("not enough memory for %s loader dummy!",Id());
	}
	return false;
}

bool cLoaderDummy::Save(FILE *f)
{
	bool res=true;
	for(cLineDummy *k=First(); k; k=Next(k))
		if(!k->Save(f)) { res=false; break; }
	Modified(!res);
	return res;
}

// -- cEcmInfo -----------------------------------------------------------------

cEcmInfo::cEcmInfo(void)
{
	Setup();
}

cEcmInfo::cEcmInfo(const char *Name, int Pid, int CaId, int ProvId)
{
	Setup(); SetName(Name);
	ecm_pid=Pid;
	caId=CaId;
	provId=ProvId;
}

cEcmInfo::~cEcmInfo()
{
	ClearData();
	free(name);
}

void cEcmInfo::Setup(void)
{
	cached=failed=false;
	name=0; data=0;
	prgId=source=transponder=-1;
	ecm_table=0x80;
}

bool cEcmInfo::Compare(const cEcmInfo *e)
{
	return prgId==e->prgId && source==e->source && transponder==e->transponder &&
			caId==e->caId && ecm_pid==e->ecm_pid && provId==e->provId;
}

void cEcmInfo::SetSource(int PrgId, int Source, int Transponder)
{
	prgId=PrgId;
	source=Source;
	transponder=Transponder;
}

void cEcmInfo::ClearData(void)
{
	free(data); data=0;
}

bool cEcmInfo::AddData(const unsigned char *Data, int DataLen)
{
	ClearData();
	data=MALLOC(unsigned char,DataLen);
	if(data) {
		memcpy(data,Data,DataLen);
		dataLen=DataLen;
	}
	else d(printf("ecminfo: malloc() failed in AddData()\n"))
	return (data!=0);
}

void cEcmInfo::SetName(const char *Name)
{
	free(name);
	name=_strdup(Name);
}

// -- cEcmData -----------------------------------------------------------------

cEcmData::cEcmData(void)
:cEcmInfo()
{
	del=false;
}

bool cEcmData::Parse(const char *buf)
{
	char Name[64];
	int nu=0, num;
	Name[0]=0;
	if(sscanf(buf,"%d:%x:%x:%63[^:]:%x/%x:%x:%x/%x:%d%n",&prgId,&source,&transponder,Name,&caId,&emmCaId,&provId,&ecm_pid,&ecm_table,&nu,&num)>=9) {
		SetName(Name);
		const char *line=buf+num;
		if(nu>0 && *line++==':') {
			unsigned char *dat = new unsigned char[nu];
//			unsigned char dat[nu];
			if(GetHex(line,dat,nu,true)==nu) AddData(dat,nu);
			delete dat;
		}
		return true;
	}
	return false;
}

bool cEcmData::Save(FILE *f)
{
	fprintf(f,"%d:%x:%x:%s:%x/%x:%x:%x/%x",prgId,source,transponder,name,caId,emmCaId,provId,ecm_pid,ecm_table);
	if(data) {
		char *str = new char [dataLen*2+2];
		fprintf(f,":%d:%s\n",dataLen,HexStr(str,data,dataLen));
		delete str;
	}
	else
		fprintf(f,":0\n");
	return ferror(f)==0;
}

// -- cEcmCache ----------------------------------------------------------------

cEcmCache ecmcache;

cEcmCache::cEcmCache(void)
:cLoader("ECM") 
{}

cEcmData *cEcmCache::Exists(cEcmInfo *e)
{
	for(cEcmData *dat=First(); dat; dat=Next(dat))
		if(!dat->IsDeleted() && dat->Compare(e)) return dat;
	return 0;
}

void cEcmCache::Load(void)
{
  Clear();
}

bool cEcmCache::Save(FILE *f)
{
	bool res=true;
	Lock();
	cEcmData *dat=First();
	while(dat) {
		if(!dat->IsDeleted() && !dat->Save(f)) { res=false; break; }
		dat=Next(dat);
	}
	Modified(!res);
	Unlock();
	return res;
}

bool cEcmCache::ParseLine(const char *line, bool fromCache)
{
	bool res=false;
	cEcmData *dat=new cEcmData;
	if(dat && dat->Parse(line)) {
		if(!Exists(dat)) { Add(dat); dat=0; }
		res=true;
	}
	delete dat;
	return res;
}

// -- cPlainKey ----------------------------------------------------------------

cPlainKey::cPlainKey(bool CanSupersede)
{
	au=del=false;
	super=CanSupersede;
}

bool cPlainKey::Set(int Type, int Id, int Keynr, void *Key, int Keylen)
{
	type=Type; id=Id; keynr=Keynr;
	return SetKey(Key,Keylen);
}

cString cPlainKey::PrintKeyNr(void)
{
	return cString::sprintf("%02X",keynr);
}

int cPlainKey::IdSize(void)
{
	return id>0xFF ? (id>0xFFFF ? 6 : 4) : 2;
}

bool cPlainKey::Save(FILE *f)
{
	fprintf(f,"%s\n",*ToString(false));
	return ferror(f)==0;
}

cString cPlainKey::ToString(bool hide)
{
	return cString::sprintf(hide ? "%c %.*X %s %.4s..." : "%c %.*X %s %s",type,IdSize(),id,*PrintKeyNr(),*Print());
}

// -- cMutableKey --------------------------------------------------------------

cMutableKey::cMutableKey(bool Super)
:cPlainKey(Super)
{
	real=0;
}

cMutableKey::~cMutableKey()
{
	delete real;
}

bool cMutableKey::SetKey(void *Key, int Keylen)
{
	delete real;
	return (real=Alloc()) && real->SetKey(Key,Keylen);
}

int cMutableKey::Size(void)
{
	return real->Size();
}

bool cMutableKey::Cmp(void *Key, int Keylen)
{
	return real->Cmp(Key,Keylen);
}

bool cMutableKey::Cmp(cPlainKey *k)
{
	cMutableKey *mk=dynamic_cast<cMutableKey *>(k); // high magic ;)
	return real->Cmp(mk ? mk->real : k);
}

void cMutableKey::Get(void *mem)
{
	real->Get(mem);
}

cString cMutableKey::Print(void)
{
	return real->Print();
}

// -- cPlainKeyType ------------------------------------------------------------

cPlainKeyType::cPlainKeyType(int Type, bool Super)
{
	type=Type;
	cPlainKeys::Register(this,Super);
}

// -- cPlainKeys ---------------------------------------------------------------

const char *externalAU=0;

cPlainKeys keys;

cPlainKeyType *cPlainKeys::first=0;

cPlainKeys::cPlainKeys(void)
:cLoader("KEY")
,cThread("ExternalAU")
{
	mark=0;
}

void cPlainKeys::Register(cPlainKeyType *pkt, bool Super)
{
	d(printf("plainkeys: registering key type %c%s\n",pkt->type,Super?" (super)":""))
	pkt->next=first;
	first=pkt;
}

cPlainKey *cPlainKeys::FindKey(int Type, int Id, int Keynr, int Size, cPlainKey *key)
{
	Lock();
	if(key) key=Next(key); else key=First();
	while(key) {
		if(!key->IsInvalid() && key->type==Type && key->id==Id && key->keynr==Keynr && (Size<0 || key->Size()==Size)) break;
		key=Next(key);
	}
	if(!key) ExternalUpdate();
	Unlock();
	return key;
}

bool cPlainKeys::NewKey(int Type, int Id, int Keynr, void *Key, int Keylen)
{
	cPlainKey *k=0;
	while((k=FindKey(Type,Id,Keynr,-1,k)))
		if(k->Cmp(Key,Keylen)) return false;

	cPlainKey *nk=NewFromType(Type);
	if(nk) {
		nk->Set(Type,Id,Keynr,Key,Keylen);
		AddNewKey(nk,2,true);
		return true;
	}
	else LogError("no memory for new key ID %c $.2x!",Type,Id);
	return false;
}

void cPlainKeys::AddNewKey(cPlainKey *nk, int mode, bool log)
{
	if(mode>=1) {
		nk->SetAuto();
		if(log) LogInfo("key update for ID %s",*nk->ToString(true));
		if(nk->CanSupersede()) {
			cPlainKey *k=0;
			while((k=FindKey(nk->type,nk->id,nk->keynr,nk->Size(),k))) {
				if(!k->IsInvalid()) {
					k->SetInvalid();
					if(k->IsAuto()) Modified();
					if(log) d(printf("key: supersedes key: %s%s\n",*k->ToString(true),k->IsAuto()?" (auto)":""))
				}
			}
		}
	}
	Lock();
	switch(mode) {
		case 0: Add(nk); break;
		case 1: if(!mark) Ins(nk); else Add(nk,mark);
				mark=nk;
				break;
		case 2: Ins(nk); Modified(); break;
	}
	Unlock();
}

bool cPlainKeys::Load(const char *cfgdir)
{
	char filepath[_MAX_PATH];
	Lock();
	Clear(); mark=0;

	if (strlen(m_DLLInstance.SCpath)) {
		sprintf(filepath,"%s",m_DLLInstance.SCpath);
	} else {
		sprintf(filepath,"%s",cfgdir);
	}
	cString cname=AddDirectory(filepath,KEY_FILE);
	ConfRead("keys",cname);
	int n=Count();
	d(printf("plainkeys: loaded %d keys from %s\n",n,*cname))
	if(n) {
#ifdef DEBUG_KEYS
		cPlainKey *dat=First();
		while(dat) {
			if(!dat->IsInvalid()) { printf("plainkeys: "); dat->Save(stdout); }
			dat=Next(dat);
		}
		fflush(stdout);
#endif
	}
	Unlock();
	return (n!=0);
}

void cPlainKeys::HouseKeeping(void)
{
	cLoaders::SaveCache();
	if(trigger.TimedOut()) {
		trigger.Set(EXT_AU_INT);
		ExternalUpdate();
	}
}

void cPlainKeys::ExternalUpdate(void)
{
	if(externalAU && last.TimedOut()) {
		Lock();
		Start();
		Unlock();
	}
}

void cPlainKeys::Action(void)
{
	last.Set(EXT_AU_MIN);
	d(printf("plainkeys: starting external AU...\n"))
	cPipe pipe;
	if(pipe.Open(externalAU,"r")) {
	}
	pipe.Close();
	d(printf("plainkeys: external AU done\n"))
}

cPlainKey *cPlainKeys::NewFromType(int type)
{
	cPlainKeyType *pkt=first;
	while(pkt) {
		if(pkt->type==type) return pkt->Create();
		pkt=pkt->next;
	}
	d(printf("plainkeys: unknown key type '%c', adding dummy\n",type))
	return 0;
}

bool cPlainKeys::ParseLine(const char *line, bool fromCache)
{
	char *s=skipspace(line);
	cPlainKey *k=NewFromType(toupper(*s));
	if(k) {
		if(k->Parse((char *)line)) AddNewKey(k,fromCache?1:0,false);
		else delete k;
		return true;
	}
	return false;
}

bool cPlainKeys::Save(FILE *f)
{
	bool res=true;
	Lock();
	cPlainKey *dat=First();
	while(dat) {
		if(dat->IsAuto() && !dat->IsInvalid()) {
			if(!dat->Save(f)) { res=false; break; }
		}
		dat=Next(dat);
	}
	Modified(!res);
	Unlock();
	return res;
}
