/*
 * 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 <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
//#include <sys/mman.h>
#include <sys/stat.h>

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

//#define KEYFILE      "SoftCam.Key"
#define KEYFILE      "EmuKeys.Key"
#define CACACHE_FILE "ca.cache"

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

cFileMap::cFileMap(const char *Filename, bool Rw)
{
  filename=strdup(Filename);
//  m_DLLInstance.Log("Filemap:%s\n",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) {
           delete [] addr;
           addr=0;
           len=0;
  }
//  if(fd>=0) { close(fd); fd=-1; }
}

bool cFileMap::Map(void)
{
  if(addr) { count++; return true; }
  if(!failed)
  {
      fptr=fopen(filename,rw?"rwb":"rb");
      if(fptr)
      {
              rewind(fptr);
              fseek(fptr,0,SEEK_END);
              len=ftell(fptr);
              rewind(fptr);
              addr=new unsigned char[len];
              fread(addr,len,1,fptr);
              fclose(fptr);
              m_DLLInstance.Log("[FS]%s len:%d\n",filename,len);
              return true;
      }//cannot open file
      else failed=true;
  }
  return false;
}

bool cFileMap::Unmap(void)
{

  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);
    FILE* f=fopen(filename,"wb");
    fwrite(addr,len,1,f);
    fclose(f);
}

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

cFileMaps filemaps;

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

cFileMaps::~cFileMaps()
{
  cFileMap *fm=First();
  while(fm) {
    cFileMap *next=Next(fm);
    Del(fm);
    fm=next;
    }
  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;  
}

// -- 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(fprintf(camlog,"loaders: registering loader %s\n",ld->id))
  ld->next=first;
  first=ld;
}

void cLoaders::LoadCache(const char *cfgdir)
{
  //lock.Lock();
  const char *name=AddDirectory(cfgdir,CACACHE_FILE);
  cacheFile=strdup(name);
//  if(access(name,F_OK)==0) {
    isyslog("loading ca cache from %s",name);
    d(printf("loaders: loading ca cache from %s\n",name))
    FILE *f=fopen(name,"r");
    if(f) {
      char buf[256];
      cLoader *ld=0;
      while(fgets(buf,sizeof(buf),f)) {
        if(buf[0]=='#') continue;
        if(!strncmp(buf,":::",3)) { // new loader section
          ld=FindLoader(stripspace(&buf[3]));
          if(!ld) esyslog("unknown loader section '%s'",&buf[3]);
          }
        else if(!strncmp(buf,"ECM:",4) || !strncmp(buf,"KEY:",4)) {  // compatibility, old format
          buf[3]=0; ld=FindLoader(buf);
          if(ld && ld->ParseLine(&buf[4],true)) ld->Modified(); // save the new format right away
          ld=0;
          }
        else if(ld) {
          if(!ld->ParseLine(buf,true)) {
            d(printf("loaders: '%s' failed on line '%s'\n",ld->Id(),buf))
            }
          }
        }
      fclose(f);
      }
    else LOG_ERROR_STR(name);
  //  }
  //lock.Unlock();
}

void cLoaders::SaveCache(void)
{
//  lock.Lock();
  FILE* f;
  if(cacheFile && IsModified()) {
    //cSafeFile f(cacheFile);
    f=fopen(cacheFile,"w");
    //if(f.Open()) {
    if (f){
      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();
      fclose(f);
      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;
}

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

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

cEcmInfo::cEcmInfo(cEcmInfo *e)
{
  Setup(); SetName(e->name);
  ecm_pid=e->ecm_pid;
  caId=e->caId;
  provId=e->provId;
  needData=e->needData;
  Update(e);
  prgId=e->prgId;
  source=e->source;
  transponder=e->transponder;
  cached=true;
}

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=needData=false;
  name=0; data=0;
  prgId=source=transponder=-1;
}

void cEcmInfo::Update(cEcmInfo *e)
{
  if(e->data) AddData(e->data,e->dataLen);
}

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();
  needData=true;
  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;
}

cEcmData::cEcmData(cEcmInfo *e)
:cEcmInfo(e)
{
  del=false;
}

bool cEcmData::Parse(const char *buf)
{
  char Name[64];
  int nu=0;
  Name[0]=0;
  if(sscanf(buf,"%d:%x:%x:%63[^:]:%x:%x:%x:%d\n",&prgId,&source,&transponder,Name,&caId,&provId,&ecm_pid,&nu)<7)
    return false;
  SetName(Name);
  needData=(nu!=0);
  return true;
}

bool cEcmData::Save(FILE *f)
{
  fprintf(f,"%d:%x:%x:%s:%x:%x:%x:%d\n",
            prgId,source,transponder,name,caId,provId,ecm_pid,needData);
  return ferror(f)==0;
}

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

cEcmCache ecmcache;

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

void cEcmCache::New(cEcmInfo *e)
{
//  Lock();
  cEcmData *dat;
  if(!(dat=Exists(e))) {
    dat=new cEcmData(e);
    Add(dat);
    Modified();
   // d(printf("ecmcache: added prgId=%d source=%x transponder=%x ecm=%x\n",e->prgId,e->source,e->transponder,e->ecm_pid))
    }
  else if(strcasecmp(e->name,dat->name)) {
    dat->SetName(e->name);
    Modified();
    }
//  Unlock();
}

cEcmData *cEcmCache::Exists(cEcmInfo *e)
{
  cEcmData *dat=First();
  while(dat) {
    if(!dat->IsDeleted() &&
        dat->prgId==e->prgId && dat->source==e->source && dat->transponder==e->transponder &&
        dat->caId==e->caId && dat->ecm_pid==e->ecm_pid && dat->provId==e->provId)
      return dat;
    dat=cList<cEcmData>::Next(dat);
    }
  return 0;
}

int cEcmCache::GetCached(cList<cEcmInfo> *list, int PrgId, int Source, int Transponder)
{
  int n=0;
  list->Clear();
//  Lock();
  cEcmData *dat=First();
  while(dat) {
    if(!dat->IsDeleted() && dat->prgId==PrgId && dat->source==Source && dat->transponder==Transponder) {
      cEcmInfo *e=new cEcmInfo(dat);
      if(e) {
        //d(printf("ecmcache: from cache: system %s (%04x) id %04x with ecm %x\n",e->name,e->caId,e->provId,e->ecm_pid))
        list->Add(e);
        n++;
        }
      }
    dat=cList<cEcmData>::Next(dat);
    }
//  Unlock();
  return n;
}

void cEcmCache::Delete(cEcmInfo *e)
{
//  Lock();
  cEcmData *dat=Exists(e);
  if(dat) {
    dat->Delete();
    Modified();
  //  d(printf("ecmcache: invalidated prgId=%d source=%x transponder=%x ecm=%x\n",dat->prgId,dat->source,dat->transponder,dat->ecm_pid))
    }
//  Unlock();
}

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=cList<cEcmData>::Next(dat);
     }
   Modified(!res);
//   Unlock();
   return res;
}

bool cEcmCache::ParseLine(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);
}

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

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

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

cPlainKeys keys;

cPlainKeyType *cPlainKeys::first=0;

cPlainKeys::cPlainKeys(void)
:cLoader("KEY")
{}

void cPlainKeys::Register(cPlainKeyType *pkt, bool Super)
{
//  d(fprintf(camlog,"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, cPlainKey *key)
{
  //Lock();
  if(key) key=cList<cPlainKey>::Next(key); else key=First();
  while(key) {
    if(!key->IsInvalid() && key->type==Type && key->id==Id && key->keynr==Keynr) break;
    key=cList<cPlainKey>::Next(key);
    }
 // Unlock();
  return key;
}

bool cPlainKeys::NewKey(int Type, int Id, int Keynr, void *Key, int Keylen)
{
  cPlainKey *k=0;
  //BVEB

  while((k=FindKey(Type,Id,Keynr,k)))
    if(k->Cmp(Key,Keylen)) return false;

  cPlainKey *nk=NewFromType(Type);
  if(nk) {
    nk->Set(Type,Id,Keynr,Key,Keylen);
    nk->SetAuto();

    //Lock();
    Ins(nk,First());
    Modified();
    //Unlock();

    char *str=nk->Print();

    isyslog("key update for ID %c %.2x key number %02x %s",nk->type,nk->id,nk->keynr,str);
    free(str);

    Supersede(nk,true);
    return true;
    }
  else esyslog("ERROR: no memory for new key ID %c %.2x %02x!",nk->type,nk->id,nk->keynr);

  return false;
}

bool cPlainKeys::Supersede(cPlainKey *nk, bool log)
{
  bool res=false;
  if(nk->CanSupersede()) {
    cPlainKey *k=0;
    while((k=FindKey(nk->type,nk->id,nk->keynr,k))) {
      if(k!=nk && !k->IsInvalid()) {
        k->SetInvalid(); res=true;
        if(k->IsAuto()) Modified();
#ifdef DEBUG
        if(log) {
           char *str=k->Print();
           d(printf("key: supersedes key: %c %.2x %02x %s%s\n",k->type,k->id,k->keynr,str,k->IsAuto()?" (auto)":""))
           free(str);
           }
#endif
        }
      }
    }
  return res;
}

bool cPlainKeys::Load(const char *cfgdir)
{
  //Lock();
  Clear();
  //BVEB

  const char *name;
  FILE *f=fopen(name=AddDirectory(cfgdir,KEYFILE),"r");
  if(f) {

    char buff[300];
    while(fgets(buff,sizeof(buff),f)) {
      char *line=skipspace(stripspace(buff));
      if(line[0]==0 || line[0]==';') continue; // skip empty & comment lines
      if(!ParseLine(line,false)) { 
       // esyslog("ERROR: keyfile has error in line '%s'",buff);
        d(printf("plainkeys: keyfile format error line '%s'\n",buff))
        }
      }
    fclose(f);
    }

  int n=Count();
  d(m_DLLInstance.Log("[Keys]%d keys from %s\n",n,name))
  if(n) {
#ifdef DEBUG_KEYS
    cPlainKey *dat=First();
    while(dat) {
      if(!dat->IsInvalid()) { m_DLLInstance.Log("plainkeys: "); dat->Save(g_stdout); }
      dat=cList<cPlainKey>::Next(dat);
      }
    fflush(g_stdout);
#endif
    }
 // Unlock();

  return (n!=0);
}

cPlainKey *cPlainKeys::NewFromType(int type)
{
  cPlainKeyType *pkt=first;
  while(pkt) {
    if(pkt->type==type) return pkt->Create();
    pkt=pkt->next;
    }
  //esyslog("Unknown key type '%c'",type);
  return 0;
}

bool cPlainKeys::ParseLine(char *line, bool fromCache)
{
  char *s=skipspace(line);
  cPlainKey *k=NewFromType(toupper(*s));
  if(k) {
    if(k->Parse((char *)line)) {
      Add(k);
      if(fromCache) {
        k->SetAuto();
        Supersede(k,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=cList<cPlainKey>::Next(dat);
    }
  Modified(!res);
  //Unlock();
  return res;
}
