/*
 * 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 <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include <linux/dvb/ca.h>
#include <vdr/dvbdevice.h>

#include "common.h"
#include "cam.h"
#include "sc.h"
#include "filter.h"
#include "system.h"
#include "data.h"
#include "misc.h"
#include "i18n.h"
#include "vdr-compat.h"

#ifdef VDR3
#include <vdr/channels.h>
#include <vdr/pat.h>
#endif

#define IDLE_SLEEP          0 // idleTime when sleeping
#define IDLE_GETCA        200 // idleTime when waiting for ca descriptors
#define IDLE_GETCA_SLOW 20000 // idleTime if no enc. system
#define IDLE_NO_SYNC      800 // idleTime when not in sync
#define IDLE_SYNC        2000 // idleTime when in sync
#define IDLE_CAT        20000 // idleTime while scanning CAT

#define CW_REPEAT_TIME   2000 // rewrite CW after X ms
#define LOG_COUNT        3    // stop logging after X complete ECM cycles
#define MIN_LOGGER_TIME  120  // min. time (s) between changes of logger sys
#define ECM_DATA_TIME    3000 // time to wait for ECM data updates
#define SWITCH_TIME     15000 // delay before switching to FTA channel

// -- cLogger ------------------------------------------------------------------

class cLogger : public cAction {
private:
  cSystem *sys;
  bool ownSys;
  cEcmInfo *ecm;
  int cardNum;
  int mode, stopTime, catTime;
protected:
  cFilter *AddFilter(int Pid, int Section, int Mask, int IdleTime=0, bool Crc=false);
  virtual void Process(cFilter *filter, unsigned char *data, int len);
public:
  cLogger(cSystem *Sys, cEcmInfo *Ecm, int CardNum);
  virtual ~cLogger();
  void OwnSystem(void) { ownSys=true; }
  };

cLogger::cLogger(cSystem *Sys, cEcmInfo *Ecm, int CardNum)
:cAction("logger",CardNum)
{
  sys=Sys; ecm=new cEcmInfo(Ecm); cardNum=CardNum;
  mode=0; stopTime=catTime=0; ownSys=false;
  // set CAT filter
  AddFilter(1,0x01,0xFF,IDLE_CAT,true);
}

cLogger::~cLogger()
{
  DelAllFilter();
  if(ownSys) delete sys;
  delete ecm;
}

cFilter *cLogger::AddFilter(int Pid, int Section, int Mask, int IdleTime, bool Crc)
{
  cFilter *filter=IdleFilter();
  if(!filter) filter=NewFilter(0);
  if(filter) {
    if(Pid>1) filter->SetBuffSize(32768);
    filter->SetIdleTime(IdleTime);
    filter->Start(Pid,Section,Mask,Crc);
    dl(printf("logger %d: added filter pid=0x%.4x sct=0x%.2x/0x%.2x idle=%d crc=%d\n",
             cardNum,Pid,Section,Mask,IdleTime,Crc))
    }
  else d(printf("logger %d: no free slot or filter failed to open\n",cardNum))
  return filter;
}

void cLogger::Process(cFilter *filter, unsigned char *data, int len)
{
  switch(mode) {
    case 0: // scan CAT
      if(data && len>0) {
#ifdef LOG_CAT
        LogDump(data,len,"CAT sys 0x%04x id 0x%02x",caSys,id);
#endif
        if(data[0]==0x01) {
          struct SctPids pids;
          sys->GetEMMPids(ecm,data,len-4,&pids);
          if(pids.num) {
            filter->Stop();
            for(int i=0; i<pids.num; i++)
              AddFilter(pids.pid[i].pid,pids.pid[i].sct,pids.pid[i].mask);
            mode=1;
            stopTime=ScSetup.LoggerTimeout ? time(0)+ScSetup.LoggerTimeout : 0;
            break;
            }
          }
        }
#ifdef DEBUG
      if(time_ms()-catTime>60000) {
        d(printf("logger %d: system 0x%.4x id 0x%.2x not in CAT\n",cardNum,ecm->caId,ecm->provId))
        catTime=time_ms();
        }
#endif
      break;

    case 1:
      if(data && len>0) {
#ifdef LOG_EMM
        LogDump(data,len,"EMM sys 0x%04x id 0x%02x pid 0x%04x",ecm->caId,ecm->provId,filter->Pid());
#endif
        sys->ProcessEMM(filter->Pid(),ecm,data,len);
        }
      if(stopTime && time(0)>=stopTime) {
        d(printf("logger %d: logger timeout, stopping\n",cardNum))
        DelAllFilter();
        stopTime=0;
        }
      break;
    }
}

// -- cCam ---------------------------------------------------------------

cCam::cCam(const cDevice *dev, int CardNum, int CamDev)
:cAction("cam",CardNum)
{
  device=(cDevice *)dev;
  cardNum=CardNum;
  camDev=CamDev;

  sys=0; softCSA=ff=false; noKey=true; mode=-1;
  logger=0; loggerEcm=0; loggerSys=0; loggerTime=0;
  prg=0; source=0; transponder=0;
  ignorePrg=-1; ignoreSource=-1; ignoreTransponder=-1;
#ifdef DEBUG_TURN
  turns=0;
#endif
#ifdef HAVE_SOFTCSA
  if(camDev<0) {
    softCSA=true;
    isyslog("Software decrypting activated on card %d",cardNum);
    }
#endif
  ff=device->HasDecoder();
  if(!(filter=NewFilter(IDLE_SLEEP)))
    esyslog("ERROR: failed to open ECM filter for softcam %d",cardNum);
}

cCam::~cCam()
{
  DeleteLogger();
  DelAllFilter(); // delete filters before sys for multi-threading reasons
  DeleteSys();
  if(camDev>0) close(camDev);
}

int cCam::ProvidesCa(const unsigned short *ids)
{
#ifdef DEBUG
  printf("softcam %d: check ProvidesCa:",cardNum);
  for(int i=0 ; ids[i]; i++) printf(" %04x",ids[i]);
  printf(" -> ");
#endif
  int n=cSystems::Provides(ids,ff);
  d(printf("n=%d\n",n))
  return n;
}

void cCam::IgnoreChannel(int Prg, int Source, int Transponder)
{
  //d(printf("softcam %d: now ignoring SID %d, source %x, transponder %x\n",cardNum,Prg,Source,Transponder))
  ignorePrg=Prg; ignoreSource=Source; ignoreTransponder=Transponder;
}

void cCam::SetChannel(int Prg, int Source, int Transponder)
{
  if(ignorePrg==Prg && ignoreSource==Source && ignoreTransponder==Transponder) {
    //d(printf("softcam %d: requested ignore of new SID %d, source %x, transponder %x\n",cardNum,Prg,Source,Transponder))
    ignorePrg=-1; ignoreSource=-1; ignoreTransponder=-1;
    }
  else if(Prg!=prg || Source!=source || Transponder!=transponder) {
    d(printf("softcam %d: setting new SID %d, source %x, transponder %x\n",cardNum,Prg,Source,Transponder))
    Lock();
    prg=Prg; source=Source; transponder=Transponder; mode=0;
    filter->Wakeup();
    Unlock();
    }
  else {
    if(prg!=0 || Prg!=0) {
      d(printf("softcam %d: ignoring new SID %d, source %x, transponder %x\n",cardNum,Prg,Source,Transponder))
      }
    }
  if(Prg>0 && Source==0) d(printf("softcam %d: WARNING source is 0. This may not work. Check your channels.conf!!\n",cardNum))
}

void cCam::WriteCW(int index, bool resend)
{
  ca_descr_t ca_descr;
  int i;

  if(!sys) {
    d(printf("softcam %d: sys==NULL in WriteCW\n",cardNum))
    return;
    }
  unsigned char *cw=sys->CW();
  for(i=0; i<16; i+=4) cw[i+3]=cw[i]+cw[i+1]+cw[i+2];

  ca_descr.index=index;
  if(resend || memcmp(&cw[0],lastEvenCW,8)) {
    memcpy(lastEvenCW,&cw[0],8);
    ca_descr.parity=0;
    for(i=7; i>=0; i--) ca_descr.cw[i]=cw[i];
    if(!softCSA) { CHECK(ioctl(camDev,CA_SET_DESCR,&ca_descr)); }
#ifdef HAVE_SOFTCSA
    else device->SetCADescr(&ca_descr);
#endif
    DTURN('E')
    }

  if(resend || memcmp(&cw[8],lastOddCW,8)) {
    memcpy(lastOddCW,&cw[8],8);
    ca_descr.parity=1;
    for(i=7; i>=0; i--) ca_descr.cw[i]=cw[i+8];
    if(!softCSA) { CHECK(ioctl(camDev,CA_SET_DESCR,&ca_descr)); }
#ifdef HAVE_SOFTCSA
    else device->SetCADescr(&ca_descr);
#endif
    DTURN('O')
    }
}

void cCam::Process(cFilter *filter, unsigned char *data, int len)
{
#ifdef DEBUG_TURN
  static const char turn[] = { '/','-','\\','|','X' };
  printf("\b%c",turn[turns]); fflush(stdout);
  turns=(turns+1)%sizeof(turn);
#endif

  int now=time_ms();
  switch(mode) {
    case -1:
      filter->SetIdleTime(IDLE_SLEEP);
      break;

    case 0:
      SetLogger(0);
      StopEcm();
#ifdef DEBUG_TURN
      printf("softcam %d: loop\n",cardNum);
#endif
      if(!prg) { mode=-1; break; }

      parity=0xFF; count=resendTime=receive=switchTime=0;
      sync=false; dolog=LOG_COUNT;
      NewEcm();
      filter->SetIdleTime(IDLE_GETCA);
      startecm=now;
      mode=1;
      // fall through

    case 1:
      if(!ecm && !JumpEcm()) {
        if(now-startecm>IDLE_GETCA_SLOW) {
          d(printf("softcam %d: no encryption system found\n",cardNum))
          filter->SetIdleTime(IDLE_GETCA_SLOW/4);
          startecm=now;
          }
        break;
        }
      SetLogger(ecm);
      lastsync=now;
      filter->SetIdleTime(IDLE_NO_SYNC);
      mode=2;
      break;
          
    case 2:
      if(!UpdateEcm() && now<=lastsync+ECM_DATA_TIME) break;
      mode=3;
      // fall through

    case 3:
      if(data && len>0 && sys) {
#ifdef LOG_ECM
        LogDump(data,len,"ECM sys 0x%04x id 0x%02x pid 0x%04x",ecm->caId,ecm->provId,filter->Pid());
#endif
        bool resend=false;
        if(resendTime && now>=resendTime) {
          DTURN('T')
          resend=true; resendTime=0;
          }

        if(data[0]==0x80 || data[0]==0x81) {
          if(sys->CheckECM(ecm,data)) {
            if(resend || parity!=(data[0]&1)) {
              if(sys->ProcessECM(ecm,data)) {
                dolog=LOG_COUNT; sys->DoLog(true);
                WriteCW(0,resend || !sync);
#ifdef DEBUG_TURN
                if(!resend) { printf("\b[%d] ",now-lastsync); fflush(stdout); }
#endif
                lastsync=now; noKey=false; resendTime=0;
                if(!sync) {
                  EcmOk(); sync=true;
                  filter->SetIdleTime(IDLE_SYNC);
                  d(printf("softcam %d: correct key found\n",cardNum))
                  if(!softCSA) {
                    resendTime=now+CW_REPEAT_TIME;
                    if(device->Receiving(true)) receive=1;
                    }
                  if(!receive || ScSetup.LoggerActive<=1) SetLogger(ecm,true);
                  }
                parity=data[0]&1;
                DTURN('+')
                }
              else {
                parity=0xFF;
                filter->Flush();
                if(!sync) count++;
                if(dolog) DTURN('>')
                }
              }
            }
          else {
            sync=false; count=0; parity=0xFF;
            }
          }
        }
      else {
        if(!sync) count++;
        if(dolog) DTURN('»')
        }

      if(!sync) {
        if(count>=sys->MaxEcmTry()) {
          EcmFail(); JumpEcm();
          if(!ecm) {
            JumpEcm();
            if(!ecm) { // this should not happen!
              d(printf("softcam %d: BUG: empty ecm list in sync loop\n",cardNum))
              mode=0; break;
              }
#ifdef DEBUG
            if(dolog && !--dolog) {
              sys->DoLog(false);
              printf("softcam %d: stopping console log until valid key is found\n",cardNum);
              }
#endif
            }
          SetLogger(ecm,sys->NeedsLogger());
          count=0;
          lastsync=now; mode=2;
          }
        }

      if(receive>=0 && device->Receiving(true)) {
        receive=-1; switchTime=0;
        d(printf("softcam %d: receive mode detected\n",cardNum))
        if(sync && !softCSA && ScSetup.LoggerActive>1)
          SetLogger(0);  // stop logging during recordings/transfermode
        }
      if(receive<0 && !device->Receiving(true)) {
        if(sync) SetLogger(ecm,true);
        receive=0;
        d(printf("softcam %d: receive mode ended",cardNum))
        if(!device->IsPrimaryDevice() || (sys && sys->Expensive())) {
          switchTime=time_ms()+SWITCH_TIME;
          d(printf(" (%ds switch delay)",SWITCH_TIME/1000))
          }
        d(printf("\n"))
        }
      if(switchTime && !receive && time_ms()>switchTime) {
        cChannel *Channel;  // switch channel to FTA
        int i=1;
        do {
          Channel=Channels.GetByNumber(i++);
          } while(Channel && Channel->Ca()>=MAXDEVICES); 
        if(Channel) {
          device->SwitchChannel(Channel,true);
          d(printf("softcam %d: switched to FTA\n",cardNum))
          break;
          }
        }
      break;
    }
}

void cCam::DeleteSys(void)
{
  if(logger && loggerSys==sys) logger->OwnSystem();
  else delete sys;
  sys=0;
}

void cCam::DeleteLogger(void)
{
  delete logger;
  logger=0; loggerSys=0; loggerEcm=0; loggerTime=0;
}

void cCam::SetLogger(cEcmInfo *e, bool force)
{
  if(!e || !ScSetup.LoggerActive) {
    if(logger) d(printf("softcam %d: logger stopped\n",cardNum))
    DeleteLogger();
    }
  else if(sys && ((e!=loggerEcm && time(0)>=loggerTime) || force)) {
    DeleteLogger();
    if(sys->HasLogger()) {
      logger=new cLogger(sys,e,cardNum);
      loggerEcm=e;
      loggerSys=sys;
      loggerTime=time(0)+MIN_LOGGER_TIME;
      d(printf("softcam %d: started logger sys=0x%.4x id=0x%x\n",cardNum,e->caId,e->provId))
      }
    else d(printf("softcam %d: no logger for sys=0x%.4x id=0x%x\n",cardNum,e->caId,e->provId))
    }
}

char *cCam::CurrentKeyStr(void)
{
  if(noKey || !sys) return 0;
  return strdup(sys->CurrentKeyStr());
}

cEcmInfo *cCam::NewEcm(void)
{
  ecmcache.GetCached(&ecmList,prg,source,transponder);
  ecm=0;
  return JumpEcm();
}

void cCam::StopEcm(void)
{
  filter->Stop();
  DeleteSys();
}

bool cCam::UpdateEcm(void)
{
  if(ecm->NeedData() && !ecm->Data()) {
    ParseCAInfo();
    if(!ecm->Data()) return false;
    }
  return true;
}

cEcmInfo *cCam::JumpEcm(void)
{
  noKey=true;
  if(ecm) { // try lower pri systems
    DeleteSys();
    sys=cSystems::FindBySysId(ecm->caId,ff,lastPri);
    if(sys) {
      lastPri=sys->Pri();
      sys->DoLog(dolog!=0); sys->CardNum(cardNum);
      if(dolog) d(printf("softcam %d: now try system %s with pri=%d\n",cardNum,sys->Name(),sys->Pri()))
      return ecm; 
      }
    }

  StopEcm(); lastPri=0;
  if(!ecm) {
    ParseCAInfo();
    ecm=ecmList.First();
    }
  else ecm=ecmList.Next(ecm);

  if(ecm) {
    if(ecm->Cached()) { // try last working system first
      sys=cSystems::FindBySysName(ecm->caId,ff,ecm->name);
      }
    if(!sys) {  
      sys=cSystems::FindBySysId(ecm->caId,ff,0);
      if(!sys) {
        d(printf("softcam %d: no system found for %04x%s\n",cardNum,ecm->caId,ecm->Cached()?" (from cache)":""))
        return JumpEcm();
        }
      lastPri=sys->Pri();
      }
    sys->DoLog(dolog!=0); sys->CardNum(cardNum);
    filter->Start(ecm->ecm_pid,0x80,0xfe,false);

    if(dolog) d(printf("softcam %d: try system %s (%04x) id %04x with ecm %x%s (pri=%d)\n",
                     cardNum,sys->Name(),ecm->caId,ecm->provId,ecm->ecm_pid,ecm->Cached()?" (cached)":"",sys->Pri()))
    }
  return ecm;
}

void cCam::EcmOk(void)
{
  ecm->SetName(sys->Name());
  ecmcache.New(ecm);
  ecm->Fail(false);
  cEcmInfo *e=ecmList.First();
  while(e) {
    if(e->Cached() && e->Failed())
      ecmcache.Delete(e);
    e=ecmList.Next(e);
    }
  cSoftCAM::Save();
}

void cCam::EcmFail(void)
{
  ecm->Fail(true);
}

void cCam::ParseCAInfo(void)
{
  unsigned char buff[2048];
  static const unsigned short casys[] = { 0xFFFF,0 };
#ifndef VDR3
  int len=cSIProcessor::GetCaDescriptors(source,transponder,prg,casys,sizeof(buff),buff);
#else
  bool streamFlag;
  int len=GetCaDescriptors(source,transponder,prg,casys,sizeof(buff),buff,streamFlag);
#endif
  if(len>0) {
    if(dolog) d(printf("softcam %d: got CaDescriptors (len=%d)\n",cardNum,len))
#ifdef LOG_PMT
    LogDump(buff,len,"PMT");
#endif
    int index=0;
    while(index<len) {
      int s=buff[index+1];
      if(buff[index]==0x09) {
#ifdef DEBUG
        if(dolog) {
          printf("softcam %d: descriptor",cardNum);
          for(int i=0 ; i<s ; i++) printf(" %02x",buff[index+2+i]);
          printf("\n");
          }
#endif
        int sysId=WORD(buff,index+2,0xFFFF);
        int sysPri=0;
        cSystem *sys;
        while((sys=cSystems::FindBySysId(sysId,ff,sysPri))) {
          sysPri=sys->Pri();
          struct Ecms ecms;
          sys->GetECMs(sysId,&buff[index+2],s,&ecms);
          delete sys;
          if(ecms.num) {
            for(int i=0; i<ecms.num; i++) {
              cEcmInfo *n=ecms.ecm[i];
              if(dolog) d(printf("softcam %d: found %04x (%s) id %04x with ecm %x ",cardNum,n->caId,n->name,n->provId,n->ecm_pid))
              cEcmInfo *e=ecmList.First();
              while(e) {
                if(e->caId==n->caId && e->ecm_pid==n->ecm_pid && e->provId==n->provId) {
                  if(e->NeedData()) {
                    e->Update(n);
                    if(e->Data() && dolog) d(printf("(updated) "))
                    }
                  if(dolog) d(printf("(already present)\n"))
                  delete n; n=0;
                  break;
                  }
                e=ecmList.Next(e);
                }
              if(n) {
                if(dolog) d(printf("(new)\n"))
                n->SetSource(prg,source,transponder);
                ecmList.Add(n);
                }
              }
            break;
            }
          }
        if(sysPri==0 && dolog) d(printf("softcam %d: no module available for system %04x\n",cardNum,sysId))
        }
      s+=2; index+=s;
      }
    }
  else if(len<0)
    d(printf("softcam %d: CA parse buffer overflow\n",cardNum))
}

