/*
 * 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <unistd.h>
#include "common.h"
#include "debug.h"
#include "nagra.h"
#include "win32lacks.h"
#include "xnprintf.h"
#include <openssl/bn.h>
#include "openssl-compat.h"
//////
//#include <vdr/tools.h>

int minEcmTime=400; // ms

// -- cPlainKeyNagra -----------------------------------------------------------

#define PLAINLEN_NAGRA_H 8
#define PLAINLEN_NAGRA_I 16
#define PLAINLEN_NAGRA_B 64
#define PLAINLEN_NAGRA_IE 24
#define PLAINLEN_NAGRA_BE 96

static cPlainKeyTypeReg<cPlainKeyNagra,'N'> KeyReg;

cPlainKeyNagra::cPlainKeyNagra(bool Super)
:cDualKey(Super,true)
{}

bool cPlainKeyNagra::IsBNKey(void) const
{
  switch(keynr & C2MASK) {
    // Nagra1
    case MBC('M','1'):
    case MBC('E','1'):
    case MBC('N','1'):
    case MBC('N','2'):
    // Nagra2
    case MBC(N2_MAGIC,N2_EMM_G_R):
    case MBC(N2_MAGIC,N2_EMM_S_R):
    case MBC(N2_MAGIC,N2_EMM_G_R|N2_EMM_SEL):
    case MBC(N2_MAGIC,N2_EMM_S_R|N2_EMM_SEL):

      return true;
    }

//******** EMM-S Keys Modification - 12/26/06
// EMM-s key check
  if ((keynr>>24) == N2_EMM_S) {
    switch(keynr & 0xFF) {
      case N2_EMM_S_R:
      case N2_EMM_G_R:
//printf("*** IsBNKey=%02X %02X***\n",(keynr>>24),(keynr & 0xFF));
        return true;
    }
//******** EMM-S Keys Modification - 12/26/06
  }
  return false;
}

int cPlainKeyNagra::ParseTag(const char *tag, const char * &line)
{
  int r=-1;
  const int l=strlen(tag);
  if(!strncasecmp(line,tag,l)) {
    line+=l; r=0;
    while(*line!=0 && isdigit(*line)) {
      r=r*10 + *line-'0';
      line++;
      }
    line=skipspace(line);
    }
  return r;
}

void cPlainKeyNagra::GetTagDef(int nr, int &romnr, int &pk, int &keytype)
{
  romnr  =DEF_ROM;
  pk     =DEF_PK;
  keytype=DEF_TYPE;
  nr&=C2MASK;
  if(nr=='V' || nr==MBC('N','2') || nr==MBC('M','1'))
    pk=0; // different default
}

bool cPlainKeyNagra::Parse(const char *line)
{
  unsigned char sid[2];
  const char *sline=line;
  int len;
  if(GetChar(line,&type,1) && (len=GetHex(line,sid,2,false))) {
    type=toupper(type); id=Bin2Int(sid,len); 
    line=skipspace(line);
    bool ok;
    if(!strncasecmp(line,"NN",2)) {
      line=skipspace(line+2);
      unsigned char skeynr;
      if((ok=GetHex(line,&skeynr,1)))
        keynr=MBC(N2_MAGIC,skeynr);
	//printf("NN keynr = %2x [%s]\n",keynr,line);
      }
//******** EMM-S Keys Modification - 12/26/06
	else if (!strncasecmp(line,"S",1)) {
	  int CamID = 0;
	  int tempValue = 0;
	  int CamByte[3];
	  char camnr[7];
	  bool CamExisting = false;
	  memset(camnr,'\0',7);
	  char tempstring[3];
	  memset(tempstring,'\0',3);
	  line=skipspace(line+2);
	  sprintf(camnr,"%.6s",line);
	  for (int it=0; it<6; it+=2) {
		sprintf(tempstring,"%c%c",camnr[it],camnr[it+1]);
	    CamByte[tempValue] = strtol(tempstring,NULL,16);
		//CamID = MBC(CamID,CamByte[tempValue]);
		//CamID ^= CamByte[tempValue];	//XOR values to get seed
		tempValue++;
	  }
	  for (int is=0; is<keys.EMMptr; is++) {
		  if ( keys.EMMs[is][0] == CamByte[0] &&
			   keys.EMMs[is][1] == CamByte[1] &&
			   keys.EMMs[is][2] == CamByte[2] )
			CamExisting = true;
	  }
	  CamID=CID(N2_EMM_S,CamByte[0],CamByte[1],CamByte[2]);
	//printf("*** %02x %02x %02x - CID = %06X\n",CamByte[0],CamByte[1],CamByte[2],CamID);
	  if (CamExisting == false) { 
		keys.EMMs[keys.EMMptr][0] = CamByte[0];
		keys.EMMs[keys.EMMptr][1] = CamByte[1];
		keys.EMMs[keys.EMMptr][2] = CamByte[2];
		keys.EMMs[keys.EMMptr][3] = CamID;
////printf("*** EMMptr = %02d [%02x %02x %02x %02x %02x %02x]\n",keys.EMMptr,keys.EMMs[keys.EMMptr][0],keys.EMMs[keys.EMMptr][1],keys.EMMs[keys.EMMptr][2],CamByte[0],CamByte[1],CamByte[2]);
		keys.EMMptr++;
	  }
	  line=skipspace(line+6);
      unsigned char skeynr;
      if((ok=GetHex(line,&skeynr,1)))
        keynr=MBC(CamID,skeynr);
//        keynr=MBC(MBC(0x53,CamID),skeynr);	// 0x53 = 'S'
//        keynr=MBC(CamID,skeynr);
//	printf("S keynr = (%X) [%s]\n",keynr,line);
	}
//******** EMM-S Keys Modification - 12/26/06
	else {
      bool tags;
      switch(toupper(*line)) {
        case 'E':
        case 'N': ok=GetChar(line,&keynr,2); tags=true; break;
        case 'M': ok=GetChar(line,&keynr,2); tags=false; break;
        case 'V': ok=GetChar(line,&keynr,1); tags=true; break;
        default:  {
                  unsigned char skeynr;
                  ok=GetHex(line,&skeynr,1); keynr=skeynr; tags=false; break;
                  }
	//printf("keynr = %2x\n",keynr);
        }
      if(ok) {
        int romnr, pk, keytype;
        GetTagDef(keynr,romnr,pk,keytype);
        line=skipspace(line);
        while(tags) {
          int r;
          if((r=ParseTag("ROM",line))>=0) {
            if(r>0 && r<=15) romnr=r;
            else d(printf("nagrakey: ignoring bad ROM number %d\n",r));
            }
          else if((r=ParseTag("TYP",line))>=0) {
            if(r>=0 && r<=1) keytype=r;
            else d(printf("nagrakey: ignoring bad key type %d\n",r));
            }
          else if((r=ParseTag("PK",line))>=0) {
            if(r>=0 && r<=2) pk=r;
            else d(printf("nagrakey: ignoring bad PK key number %d\n",pk));
            }
          else {
            tags=false;
            if(pk!=0 && (keynr=='V' || keynr==MBC('N','2'))) {
              pk=0;
              d(printf("nagrakey: auto-adjusting to PK0 for N2/V key\n"))
              }
            if(romnr!=0 && keytype!=1) {
              keytype=1;
              d(printf("nagrakey: auto-adjusting to TYP1 for ROM key\n"))
              }
            }
          }
        if(IsBNKey() || keynr=='V') keynr=ADDC3(keynr,KEYSET(romnr,pk,keytype));
        }
	}

    unsigned char skey[PLAINLEN_NAGRA_BE];
    int keylen=PLAINLEN_NAGRA_BE;
    len=GetHex(line,skey,keylen,false);
    if(   (!IsBNKey() && (len==PLAINLEN_NAGRA_H || len==PLAINLEN_NAGRA_I || len==PLAINLEN_NAGRA_IE))
       || ( IsBNKey() && (len==PLAINLEN_NAGRA_B || len==PLAINLEN_NAGRA_BE)))
      keylen=len;
    if(len==keylen) {
      if((keynr=='V' || keynr==MBC(N2_MAGIC,0x03) || keynr==MBC(N2_MAGIC,(0x03|N2_EMM_SEL))) && CheckNull(skey,len))
        d(printf("nagrakey: FAKE verify keys will cause problems. Please remove them!\n"))
      SetKey(skey,len);
      return true;
      }
    }
//  FormatError("nagra",sline);
  d(printf("nagrakey: bad key format '%s'\n",sline))
  return false;
}

cString cPlainKeyNagra::PrintKeyNr(void)
{
  if(C2(keynr)==N2_MAGIC)
    return cString::sprintf("NN %.2X",keynr&0xFF);
  int k=keynr & C2MASK;
  if(!IsBNKey() && k!='V')
    return cString::sprintf("%.2X",keynr);
  char nr[32];
  int q=0;
  if(IsBNKey()) nr[q++]=(keynr>>8) & 0xff;
  nr[q++]=keynr & 0xff;
  nr[q]=0;
  int romnr, pk, keytype;
  GetTagDef(keynr,romnr,pk,keytype);
  if(ROM(keynr) !=romnr  ) q+=_snprintf(nr+q,sizeof(nr)-q," ROM%d",ROM(keynr));
  if(PK(keynr)  !=pk     ) q+=_snprintf(nr+q,sizeof(nr)-q," PK%d",PK(keynr));
  if(TYPE(keynr)!=keytype) q+=_snprintf(nr+q,sizeof(nr)-q," TYP%d",TYPE(keynr));
  return nr;
}

// -- cNagra -------------------------------------------------------------------

cNagra::cNagra(void)
{
  BN_set_word(pubExp,3);
}

void cNagra::CreateRSAPair(const unsigned char *key, const unsigned char *data, BIGNUM *e, BIGNUM *m)
{
  // Calculate P and Q from data
  cBN p,q;
  CreatePQ(key,p,q);
  ExpandPQ(p,q,data,e,m);
}

void cNagra::ExpandPQ(BIGNUM *p, BIGNUM *q, const unsigned char *data, BIGNUM *e, BIGNUM *m)
{
  // Calculate N=P*Q (modulus)
  cBNctx ctx;
  BN_mul(m,p,q,ctx);
  if(data) BN_bin2bn(data,64,e); // set provided data as E1
  else {                         // else calculate the 'official' one
    // E = ( ( ( (P-1) * (Q-1) * 2) + 1) / 3)
    BN_sub_word(p,1);
    BN_sub_word(q,1);
    BN_mul(e,p,q,ctx);
    BN_mul_word(e,2);
    BN_add_word(e,1);
    BN_div_word(e,3);
    }
}
