summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY6
-rw-r--r--Makefile4
-rw-r--r--ci.c344
-rw-r--r--ci.h61
-rw-r--r--device.c86
-rw-r--r--menu.c34
-rw-r--r--mtd.c323
-rw-r--r--mtd.h187
-rw-r--r--remux.h8
-rw-r--r--tools.h18
10 files changed, 968 insertions, 103 deletions
diff --git a/HISTORY b/HISTORY
index 2c758be2..98848c6b 100644
--- a/HISTORY
+++ b/HISTORY
@@ -8882,7 +8882,7 @@ Video Disk Recorder Revision History
- Added a short sleep to cTSBuffer::Action() to avoid high CPU usage (thanks to
Sergey Chernyavskiy).
-2017-02-21: Version 2.3.3
+2017-03-18: Version 2.3.3
- Added 'S3W ABS-3A' to sources.conf (thanks to Frank Richter).
- Fixed a possible deadlock in the recordings handler thread.
@@ -8914,3 +8914,7 @@ Video Disk Recorder Revision History
contained an empty string).
- PIDs can now be added to and deleted from a cReceiver while it is attached to
a cDevice, without having to detach it first and re-attach it afterwards.
+- Implemented support for MTD ("Multi Transponder Decryption"). This allows a CAM
+ that is capable of decrypting more than one channel ("Multi Channel Decryption")
+ to decrypt channels from different transponders. See the remarks in mtd.h on
+ what a derived cCamSlot class needs to do in order to activate MTD.
diff --git a/Makefile b/Makefile
index f3dac168..b38b7a06 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
-# $Id: Makefile 4.2 2017/01/08 11:07:19 kls Exp $
+# $Id: Makefile 4.3 2017/02/27 16:11:57 kls Exp $
.DELETE_ON_ERROR:
@@ -69,7 +69,7 @@ SILIB = $(LSIDIR)/libsi.a
OBJS = args.o audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o\
dvbplayer.o dvbspu.o dvbsubtitle.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\
- lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o positioner.o\
+ lirc.o menu.o menuitems.o mtd.o nit.o osdbase.o osd.o pat.o player.o plugin.o positioner.o\
receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\
skinclassic.o skinlcars.o skins.o skinsttng.o sourceparams.o sources.o spu.o status.o svdrp.o themes.o thread.o\
timers.o tools.o transfer.o vdr.o videodir.o
diff --git a/ci.c b/ci.c
index 8f2256e3..e4f43209 100644
--- a/ci.c
+++ b/ci.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: ci.c 4.6 2017/02/21 14:17:07 kls Exp $
+ * $Id: ci.c 4.7 2017/03/18 15:20:27 kls Exp $
*/
#include "ci.h"
@@ -19,6 +19,7 @@
#include <time.h>
#include <unistd.h>
#include "device.h"
+#include "mtd.h"
#include "pat.h"
#include "receiver.h"
#include "remux.h"
@@ -118,6 +119,7 @@ private:
cVector<int> emmPids;
uchar buffer[2048]; // 11 bit length, max. 2048 byte
uchar *bufp;
+ uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs!
int length;
void AddEmmPid(int Pid);
void DelEmmPids(void);
@@ -157,6 +159,7 @@ void cCaPidReceiver::DelEmmPids(void)
void cCaPidReceiver::Receive(const uchar *Data, int Length)
{
if (TsPid(Data) == CATPID) {
+ cMtdCamSlot *MtdCamSlot = dynamic_cast<cMtdCamSlot *>(Device()->CamSlot());
const uchar *p = NULL;
if (TsPayloadStart(Data)) {
if (Data[5] == SI::TableIdCAT) {
@@ -166,6 +169,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
if (v != catVersion) {
if (Data[11] == 0 && Data[12] == 0) { // section number, last section number
if (length > TS_SIZE - 8) {
+ if (MtdCamSlot)
+ esyslog("ERROR: need to implement multi packet CAT handling for MTD!");
int n = TS_SIZE - 13;
memcpy(buffer, Data + 13, n);
bufp = buffer + n;
@@ -180,6 +185,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
dsyslog("multi table CAT section - unhandled!");
catVersion = v;
}
+ else if (MtdCamSlot)
+ MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
}
}
}
@@ -205,12 +212,16 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
for (int i = 0; i < length - 4; i++) { // -4 = checksum
if (p[i] == 0x09) {
int CaId = int(p[i + 2] << 8) | p[i + 3];
- int EmmPid = int(((p[i + 4] & 0x1F) << 8)) | p[i + 5];
+ int EmmPid = Peek13(p + i + 4);
AddEmmPid(EmmPid);
+ if (MtdCamSlot)
+ MtdMapPid(const_cast<uchar *>(p + i + 4), MtdCamSlot->MtdMapper());
switch (CaId >> 8) {
case 0x01: for (int j = i + 7; j < p[i + 1] + 2; j += 4) {
- EmmPid = (int(p[j] & 0x0F) << 8) | p[j + 1];
+ EmmPid = Peek13(p + j);
AddEmmPid(EmmPid);
+ if (MtdCamSlot)
+ MtdMapPid(const_cast<uchar *>(p + j), MtdCamSlot->MtdMapper());
}
break;
}
@@ -220,6 +231,9 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
p = NULL;
bufp = 0;
length = 0;
+ memcpy(mtdCatBuffer, Data, TS_SIZE);
+ if (MtdCamSlot)
+ MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
}
}
}
@@ -266,7 +280,12 @@ void cCaActivationReceiver::Receive(const uchar *Data, int Length)
else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) {
dsyslog("CAM %d: activated!", camSlot->SlotNumber());
Skins.QueueMessage(mtInfo, tr("CAM activated!"));
+ cDevice *d = Device();
Detach();
+ if (d) {
+ if (cCamSlot *s = d->CamSlot())
+ s->CancelActivation(); // this will delete *this* object, so no more code referencing *this* after this call!
+ }
}
}
}
@@ -757,6 +776,7 @@ public:
void SetListManagement(uint8_t ListManagement);
uint8_t ListManagement(void) { return capmt.Get(0); }
void AddPid(int Pid, uint8_t StreamType);
+ void MtdMapPids(cMtdMapper *MtdMapper);
};
cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
@@ -817,6 +837,84 @@ void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data)
esyslog("ERROR: adding CA descriptor without Pid!");
}
+static int MtdMapCaDescriptor(uchar *p, cMtdMapper *MtdMapper)
+{
+ // See pat.c: cCaDescriptor::cCaDescriptor() for the layout of the data!
+ if (*p == SI::CaDescriptorTag) {
+ int l = *++p;
+ if (l >= 4) {
+ MtdMapPid(p + 3, MtdMapper);
+ return l + 2;
+ }
+ else
+ esyslog("ERROR: wrong length (%d) in MtdMapCaDescriptor()", l);
+ }
+ else
+ esyslog("ERROR: wrong tag (%d) in MtdMapCaDescriptor()", *p);
+ return -1;
+}
+
+static int MtdMapCaDescriptors(uchar *p, cMtdMapper *MtdMapper)
+{
+ int Length = p[0] * 256 + p[1];
+ if (Length >= 3) {
+ p += 3;
+ int m = Length - 1;
+ while (m > 0) {
+ int l = MtdMapCaDescriptor(p, MtdMapper);
+ if (l > 0) {
+ p += l;
+ m -= l;
+ }
+ }
+ }
+ return Length + 2;
+}
+
+static int MtdMapStream(uchar *p, cMtdMapper *MtdMapper)
+{
+ // See ci.c: cCiCaPmt::AddPid() for the layout of the data!
+ MtdMapPid(p + 1, MtdMapper);
+ int l = MtdMapCaDescriptors(p + 3, MtdMapper);
+ if (l > 0)
+ return l + 3;
+ return -1;
+}
+
+static int MtdMapStreams(uchar *p, cMtdMapper *MtdMapper, int Length)
+{
+ int m = Length;
+ while (m >= 5) {
+ int l = MtdMapStream(p, MtdMapper);
+ if (l > 0) {
+ p += l;
+ m -= l;
+ }
+ else
+ break;
+ }
+ return Length;
+}
+
+void cCiCaPmt::MtdMapPids(cMtdMapper *MtdMapper)
+{
+ uchar *p = capmt.Data();
+ int m = capmt.Length();
+ if (m >= 3) {
+ MtdMapSid(p + 1, MtdMapper);
+ p += 4;
+ m -= 4;
+ if (m >= 2) {
+ int l = MtdMapCaDescriptors(p, MtdMapper);
+ if (l >= 0) {
+ p += l;
+ m -= l;
+ MtdMapStreams(p, MtdMapper, m);
+ }
+ }
+ }
+}
+
// --- cCiConditionalAccessSupport -------------------------------------------
// CA Enable Ids:
@@ -897,8 +995,12 @@ void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
case AOT_CA_PMT_REPLY: {
dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId());
if (!repliesToQuery) {
- dsyslog("CAM %d: replies to QUERY - multi channel decryption possible", Tc()->CamSlot()->SlotNumber());
+ dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", Tc()->CamSlot()->SlotNumber());
repliesToQuery = true;
+ if (Tc()->CamSlot()->MtdAvailable()) {
+ dsyslog("CAM %d: supports multi transponder decryption (MTD)", Tc()->CamSlot()->SlotNumber());
+ Tc()->CamSlot()->MtdActivate(true);
+ }
}
state = 5; // got ca pmt reply
int l = 0;
@@ -1650,6 +1752,14 @@ public:
programNumber = ProgramNumber;
modified = false;
}
+ bool Active(void)
+ {
+ for (cCiCaPidData *p = pidList.First(); p; p = pidList.Next(p)) {
+ if (p->active)
+ return true;
+ }
+ return false;
+ }
};
// --- cCiAdapter ------------------------------------------------------------
@@ -1725,15 +1835,18 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData, cCamSlot *MasterSlot
caPidReceiver = WantsTsData ? new cCaPidReceiver : NULL;
caActivationReceiver = NULL;
slotIndex = -1;
+ mtdAvailable = false;
+ mtdHandler = NULL;
lastModuleStatus = msReset; // avoids initial reset log message
resetTime = 0;
resendPmt = false;
- source = transponder = 0;
for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway
tc[i] = NULL;
- CamSlots.Add(this);
- slotNumber = Index() + 1;
+ if (MasterSlot)
+ slotNumber = MasterSlot->SlotNumber();
if (ciAdapter) {
+ CamSlots.Add(this);
+ slotNumber = Index() + 1;
ciAdapter->AddCamSlot(this);
Reset();
}
@@ -1747,26 +1860,38 @@ cCamSlot::~cCamSlot()
delete caActivationReceiver;
CamSlots.Del(this, false);
DeleteAllConnections();
+ delete mtdHandler;
+}
+
+cCamSlot *cCamSlot::MtdSpawn(void)
+{
+ cMutexLock MutexLock(&mutex);
+ if (mtdHandler)
+ return mtdHandler->GetMtdCamSlot(this);
+ return this;
}
bool cCamSlot::Assign(cDevice *Device, bool Query)
{
cMutexLock MutexLock(&mutex);
+ if (Device == assignedDevice)
+ return true;
if (ciAdapter) {
+ int OldDeviceNumber = 0;
+ if (assignedDevice && !Query) {
+ OldDeviceNumber = assignedDevice->DeviceNumber() + 1;
+ if (caPidReceiver)
+ assignedDevice->Detach(caPidReceiver);
+ assignedDevice->SetCamSlot(NULL);
+ assignedDevice = NULL;
+ }
if (ciAdapter->Assign(Device, true)) {
- if (!Device && assignedDevice) {
- if (caPidReceiver)
- assignedDevice->Detach(caPidReceiver);
- assignedDevice->SetCamSlot(NULL);
- }
- if (!Query || !Device) {
+ if (!Query) {
StopDecrypting();
- source = transponder = 0;
if (ciAdapter->Assign(Device)) {
- int OldDeviceNumber = assignedDevice ? assignedDevice->DeviceNumber() + 1 : 0;
- assignedDevice = Device;
if (Device) {
Device->SetCamSlot(this);
+ assignedDevice = Device;
if (caPidReceiver) {
caPidReceiver->Reset();
Device->AttachReceiver(caPidReceiver);
@@ -1787,6 +1912,16 @@ bool cCamSlot::Assign(cDevice *Device, bool Query)
return false;
}
+bool cCamSlot::Devices(cVector<int> &CardIndexes)
+{
+ cMutexLock MutexLock(&mutex);
+ if (mtdHandler)
+ return mtdHandler->Devices(CardIndexes);
+ if (assignedDevice)
+ CardIndexes.Append(assignedDevice->CardIndex());
+ return CardIndexes.Size() > 0;
+}
+
void cCamSlot::NewConnection(void)
{
cMutexLock MutexLock(&mutex);
@@ -1812,8 +1947,6 @@ void cCamSlot::DeleteAllConnections(void)
void cCamSlot::Process(cTPDU *TPDU)
{
cMutexLock MutexLock(&mutex);
- if (caActivationReceiver && !caActivationReceiver->IsAttached())
- CancelActivation();
if (TPDU) {
int n = TPDU->Tcid();
if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) {
@@ -1824,9 +1957,9 @@ void cCamSlot::Process(cTPDU *TPDU)
for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
if (tc[i]) {
if (!tc[i]->Process()) {
- Reset();
- return;
- }
+ Reset();
+ return;
+ }
}
}
if (moduleCheckTimer.TimedOut()) {
@@ -1836,6 +1969,7 @@ void cCamSlot::Process(cTPDU *TPDU)
case msNone:
dbgprotocol("Slot %d: no module present\n", slotNumber);
isyslog("CAM %d: no module present", slotNumber);
+ MtdActivate(false);
DeleteAllConnections();
CancelActivation();
break;
@@ -1852,7 +1986,7 @@ void cCamSlot::Process(cTPDU *TPDU)
dbgprotocol("Slot %d: module ready\n", slotNumber);
isyslog("CAM %d: module ready", slotNumber);
NewConnection();
- resendPmt = caProgramList.Count() > 0;
+ resendPmt = true;
break;
default:
esyslog("ERROR: unknown module status %d (%s)", ms, __FUNCTION__);
@@ -1861,8 +1995,14 @@ void cCamSlot::Process(cTPDU *TPDU)
}
moduleCheckTimer.Set(MODULE_CHECK_INTERVAL);
}
- if (resendPmt)
- SendCaPmt(CPCI_OK_DESCRAMBLING);
+ if (resendPmt && Ready()) {
+ if (mtdHandler) {
+ mtdHandler->StartDecrypting();
+ resendPmt = false;
+ }
+ else if (caProgramList.Count())
+ StartDecrypting();
+ }
processed.Broadcast();
}
@@ -1922,12 +2062,18 @@ void cCamSlot::StartActivation(void)
void cCamSlot::CancelActivation(void)
{
cMutexLock MutexLock(&mutex);
- delete caActivationReceiver;
- caActivationReceiver = NULL;
+ if (mtdHandler)
+ mtdHandler->CancelActivation();
+ else {
+ delete caActivationReceiver;
+ caActivationReceiver = NULL;
+ }
}
bool cCamSlot::IsActivating(void)
{
+ if (mtdHandler)
+ return mtdHandler->IsActivating();
return caActivationReceiver;
}
@@ -2001,54 +2147,113 @@ cCiEnquiry *cCamSlot::GetEnquiry(void)
return NULL;
}
-void cCamSlot::SendCaPmt(uint8_t CmdId)
+cCiCaPmtList::~cCiCaPmtList()
+{
+ for (int i = 0; i < caPmts.Size(); i++)
+ delete caPmts[i];
+}
+
+cCiCaPmt *cCiCaPmtList::Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
+{
+ cCiCaPmt *p = new cCiCaPmt(CmdId, Source, Transponder, ProgramNumber, CaSystemIds);
+ caPmts.Append(p);
+ return p;
+}
+
+void cCiCaPmtList::Del(cCiCaPmt *CaPmt)
+{
+ if (caPmts.RemoveElement(CaPmt))
+ delete CaPmt;
+}
+
+bool cCamSlot::RepliesToQuery(void)
{
cMutexLock MutexLock(&mutex);
cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
- if (cas) {
- const int *CaSystemIds = cas->GetCaSystemIds();
- if (CaSystemIds && *CaSystemIds) {
- if (caProgramList.Count()) {
- for (int Loop = 1; Loop <= 2; Loop++) {
- for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
- if (p->modified || resendPmt) {
- bool Active = false;
- cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds);
- for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
- if (q->active) {
- CaPmt.AddPid(q->pid, q->streamType);
- Active = true;
- }
- }
- if ((Loop == 1) != Active) { // first remove, then add
- if (caPidReceiver) {
- int CaPids[MAXRECEIVEPIDS + 1];
- if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) {
- if (Loop == 1)
- caPidReceiver->DelPids(CaPids);
- else
- caPidReceiver->AddPids(CaPids);
- }
- }
- if (cas->RepliesToQuery())
- CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE);
- if (Active || cas->RepliesToQuery())
- cas->SendPMT(&CaPmt);
- p->modified = false;
- }
- }
+ return cas && cas->RepliesToQuery();
+}
+
+void cCamSlot::BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper)
+{
+ cMutexLock MutexLock(&mutex);
+ CaPmtList.caPmts.Clear();
+ const int *CaSystemIds = GetCaSystemIds();
+ if (CaSystemIds && *CaSystemIds) {
+ if (caProgramList.Count()) {
+ for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
+ if (p->modified || resendPmt) {
+ bool Active = p->Active();
+ cCiCaPmt *CaPmt = CaPmtList.Add(Active ? CmdId : CPCI_NOT_SELECTED, source, transponder, p->programNumber, CaSystemIds);
+ for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) {
+ if (q->active)
+ CaPmt->AddPid(q->pid, q->streamType);
}
+ if (caPidReceiver) {
+ int CaPids[MAXRECEIVEPIDS + 1];
+ if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) {
+ if (Active)
+ caPidReceiver->AddPids(CaPids);
+ else
+ caPidReceiver->DelPids(CaPids);
+ }
+ }
+ if (RepliesToQuery())
+ CaPmt->SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE);
+ if (MtdMapper)
+ CaPmt->MtdMapPids(MtdMapper);
+ p->modified = false;
}
- resendPmt = false;
- }
- else {
- cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL);
- cas->SendPMT(&CaPmt);
+ }
+ }
+ }
+}
+
+void cCamSlot::SendCaPmts(cCiCaPmtList &CaPmtList)
+{
+ cMutexLock MutexLock(&mutex);
+ cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
+ if (cas) {
+ for (int i = 0; i < CaPmtList.caPmts.Size(); i++)
+ cas->SendPMT(CaPmtList.caPmts[i]);
+ }
+ resendPmt = false;
+}
+
+void cCamSlot::SendCaPmt(uint8_t CmdId)
+{
+ cMutexLock MutexLock(&mutex);
+ cCiCaPmtList CaPmtList;
+ BuildCaPmts(CmdId, CaPmtList);
+ SendCaPmts(CaPmtList);
+}
+
+void cCamSlot::MtdEnable(void)
+{
+ mtdAvailable = true;
+}
+
+void cCamSlot::MtdActivate(bool On)
+{
+ if (McdAvailable() && MtdAvailable()) {
+ if (On) {
+ if (!mtdHandler) {
+ dsyslog("CAM %d: activating MTD support", SlotNumber());
+ mtdHandler = new cMtdHandler;
}
}
+ else if (mtdHandler) {
+ dsyslog("CAM %d: deactivating MTD support", SlotNumber());
+ delete mtdHandler;
+ mtdHandler = NULL;
+ }
}
}
+int cCamSlot::MtdPutData(uchar *Data, int Count)
+{
+ return mtdHandler->Put(Data, Count);
+}
+
const int *cCamSlot::GetCaSystemIds(void)
{
cMutexLock MutexLock(&mutex);
@@ -2058,6 +2263,8 @@ const int *cCamSlot::GetCaSystemIds(void)
int cCamSlot::Priority(void)
{
+ if (mtdHandler)
+ return mtdHandler->Priority();
cDevice *d = Device();
return d ? d->Priority() : IDLEPRIORITY;
}
@@ -2107,7 +2314,7 @@ void cCamSlot::SetPid(int Pid, bool Active)
}
return;
}
- }
+ }
}
}
@@ -2178,15 +2385,14 @@ void cCamSlot::StartDecrypting(void)
void cCamSlot::StopDecrypting(void)
{
cMutexLock MutexLock(&mutex);
- if (caProgramList.Count()) {
- caProgramList.Clear();
- SendCaPmt(CPCI_NOT_SELECTED);
- }
+ caProgramList.Clear();
}
bool cCamSlot::IsDecrypting(void)
{
cMutexLock MutexLock(&mutex);
+ if (mtdHandler)
+ return mtdHandler->IsDecrypting();
if (caProgramList.Count()) {
for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
if (p->modified)
diff --git a/ci.h b/ci.h
index a66cd2a2..2e99a1b6 100644
--- a/ci.h
+++ b/ci.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: ci.h 4.2 2017/01/23 11:27:39 kls Exp $
+ * $Id: ci.h 4.3 2017/03/18 14:18:37 kls Exp $
*/
#ifndef __CI_H
@@ -13,6 +13,7 @@
#include <stdint.h>
#include <stdio.h>
#include "channels.h"
+#include "ringbuffer.h"
#include "thread.h"
#include "tools.h"
@@ -124,10 +125,23 @@ class cCiSession;
class cCiCaProgramData;
class cCaPidReceiver;
class cCaActivationReceiver;
+class cMtdHandler;
+class cMtdMapper;
+class cMtdCamSlot;
+class cCiCaPmt;
+
+struct cCiCaPmtList {
+ cVector<cCiCaPmt *> caPmts;
+ ~cCiCaPmtList();
+ cCiCaPmt *Add(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds);
+ void Del(cCiCaPmt *CaPmt);
+ };
class cCamSlot : public cListObject {
friend class cCiAdapter;
friend class cCiTransportConnection;
+ friend class cCiConditionalAccessSupport;
+ friend class cMtdCamSlot;
private:
cMutex mutex;
cCondVar processed;
@@ -146,13 +160,40 @@ private:
int source;
int transponder;
cList<cCiCaProgramData> caProgramList;
- const int *GetCaSystemIds(void);
- void SendCaPmt(uint8_t CmdId);
+ bool mtdAvailable;
+ cMtdHandler *mtdHandler;
void NewConnection(void);
void DeleteAllConnections(void);
void Process(cTPDU *TPDU = NULL);
void Write(cTPDU *TPDU);
cCiSession *GetSessionByResourceId(uint32_t ResourceId);
+ void MtdActivate(bool On);
+ ///< Activates (On == true) or deactivates (On == false) MTD.
+protected:
+ virtual const int *GetCaSystemIds(void);
+ virtual void SendCaPmt(uint8_t CmdId);
+ virtual bool RepliesToQuery(void);
+ ///< Returns true if the CAM in this slot replies to queries and thus
+ ///< supports MCD ("Multi Channel Decryption").
+ void BuildCaPmts(uint8_t CmdId, cCiCaPmtList &CaPmtList, cMtdMapper *MtdMapper = NULL);
+ ///< Generates all CA_PMTs with the given CmdId and stores them in the given CaPmtList.
+ ///< If MtdMapper is given, all SIDs and PIDs will be mapped accordingly.
+ void SendCaPmts(cCiCaPmtList &CaPmtList);
+ ///< Sends the given list of CA_PMTs to the CAM.
+ void MtdEnable(void);
+ ///< Enables MTD support for this CAM. Note that actual MTD operation also
+ ///< requires a CAM that supports MCD ("Multi Channel Decryption").
+ int MtdPutData(uchar *Data, int Count);
+ ///< Sends at most Count bytes of the given Data to the individual MTD CAM slots
+ ///< that are using this CAM.
+ ///< Returns the number of bytes actually processed.
+public:
+ bool McdAvailable(void) { return RepliesToQuery(); }
+ ///< Returns true if this CAM supports MCD ("Multi Channel Decyption").
+ bool MtdAvailable(void) { return mtdAvailable; }
+ ///< Returns true if this CAM supports MTD ("Multi Transponder Decryption").
+ bool MtdActive(void) { return mtdHandler != NULL; }
+ ///< Returns true if MTD is currently active.
public:
cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false, cCamSlot *MasterSlot = NULL);
///< Creates a new CAM slot for the given CiAdapter.
@@ -175,6 +216,13 @@ public:
cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; }
///< Returns this CAM slot's master slot, or a pointer to itself if it is a
///< master slot.
+ cCamSlot *MtdSpawn(void);
+ ///< If this CAM slot can do MTD ("Multi Transponder Decryption"),
+ ///< a call to this function returns a cMtdCamSlot with this CAM slot
+ ///< as its master. Otherwise a pointer to this object is returned, which
+ ///< means that MTD is not supported.
+ void TriggerResendPmt(void) { resendPmt = true; }
+ ///< Tells this CAM slot to resend the list of CA_PMTs to the CAM.
virtual bool Assign(cDevice *Device, bool Query = false);
///< Assigns this CAM slot to the given Device, if this is possible.
///< If Query is 'true', the CI adapter of this slot only checks whether
@@ -190,6 +238,10 @@ public:
///< class function.
cDevice *Device(void) { return assignedDevice; }
///< Returns the device this CAM slot is currently assigned to.
+ bool Devices(cVector<int> &CardIndexes);
+ ///< Adds the card indexes of any devices that currently use this CAM to
+ ///< the given CardIndexes. This can be more than one in case of MTD.
+ ///< Returns true if the array is not empty.
bool WantsTsData(void) const { return caPidReceiver != NULL; }
///< Returns true if this CAM slot wants to receive the TS data through
///< its Decrypt() function.
@@ -308,7 +360,8 @@ public:
///< the CAM's control). If no decrypted TS packet is currently available, NULL
///< shall be returned. If no data from Data can currently be processed, Count
///< shall be set to 0 and the same Data pointer will be offered in the next
- ///< call to Decrypt().
+ ///< call to Decrypt(). See mtd.h for further requirements if this CAM can
+ ///< do MTD ("Multi Transponder Decryption").
///< A derived class that implements this function will also need
///< to set the WantsTsData parameter in the call to the base class
///< constructor to true in order to receive the TS data.
diff --git a/device.c b/device.c
index 0c8b0018..b6a6ee10 100644
--- a/device.c
+++ b/device.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: device.c 4.7 2017/02/21 13:38:01 kls Exp $
+ * $Id: device.c 4.8 2017/03/18 15:45:53 kls Exp $
*/
#include "device.h"
@@ -281,8 +281,13 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
continue; // CAM slot can't be used with this device
bool ndr;
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basically able to do the job
- if (NumUsableSlots && !HasInternalCam && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j))
- ndr = true; // using a different CAM slot requires detaching receivers
+ if (NumUsableSlots && !HasInternalCam) {
+ if (cCamSlot *csi = device[i]->CamSlot()) {
+ cCamSlot *csj = CamSlots.Get(j);
+ if ((csj->MtdActive() ? csi->MasterSlot() : csi) != csj)
+ ndr = true; // using a different CAM slot requires detaching receivers
+ }
+ }
// Put together an integer number that reflects the "impact" using
// this device would have on the overall system. Each condition is represented
// by one bit in the number (or several bits, if the condition is actually
@@ -319,12 +324,75 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
if (NeedsDetachReceivers)
d->DetachAllReceivers();
if (s) {
- if (s->Device() != d) {
- if (s->Device())
- s->Device()->DetachAllReceivers();
- if (d->CamSlot())
- d->CamSlot()->Assign(NULL);
- s->Assign(d);
+ // Some of the following statements could probably be combined, but let's keep them
+ // explicit so we can clearly see every single aspect of the decisions made here.
+ if (d->CamSlot()) {
+ if (s->MtdActive()) {
+ if (s == d->CamSlot()->MasterSlot()) {
+ // device d already has a proper CAM slot, so nothing to do here
+ }
+ else {
+ // device d has a CAM slot, but it's not the right one
+ d->CamSlot()->Assign(NULL);
+ s = s->MtdSpawn();
+ s->Assign(d);
+ }
+ }
+ else {
+ if (s->Device()) {
+ if (s->Device() != d) {
+ // CAM slot s is currently assigned to a different device than d
+ if (Priority > s->Priority()) {
+ s->Device()->DetachAllReceivers();
+ d->CamSlot()->Assign(NULL);
+ s->Assign(d);
+ }
+ else {
+ d = NULL;
+ s = NULL;
+ }
+ }
+ else {
+ // device d already has a proper CAM slot, so nothing to do here
+ }
+ }
+ else {
+ if (s != d->CamSlot()) {
+ // device d has a CAM slot, but it's not the right one
+ d->CamSlot()->Assign(NULL);
+ s->Assign(d);
+ }
+ else {
+ // device d already has a proper CAM slot, so nothing to do here
+ }
+ }
+ }
+ }
+ else {
+ // device d has no CAM slot, ...
+ if (s->MtdActive()) {
+ // ... so we assign s with MTD support
+ s = s->MtdSpawn();
+ s->Assign(d);
+ }
+ else {
+ // CAM slot s has no MTD support ...
+ if (s->Device()) {
+ // ... but it is assigned to a different device, so we reassign it to d
+ if (Priority > s->Priority()) {
+ s->Device()->DetachAllReceivers();
+ s->Assign(d);
+ }
+ else {
+ d = NULL;
+ s = NULL;
+ }
+ }
+ else {
+ // ... and is not assigned to any device, so we just assign it to d
+ s->Assign(d);
+ }
+ }
}
}
else if (d->CamSlot() && !d->CamSlot()->IsDecrypting())
diff --git a/menu.c b/menu.c
index 642432ac..685e94f6 100644
--- a/menu.c
+++ b/menu.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.c 4.21 2017/01/23 12:01:48 kls Exp $
+ * $Id: menu.c 4.22 2017/03/18 14:27:50 kls Exp $
*/
#include "menu.h"
@@ -3770,15 +3770,18 @@ bool cMenuSetupCAMItem::Changed(void)
else if (camSlot->IsActivating())
// TRANSLATORS: note the leading blank!
Activating = tr(" (activating)");
+ cVector<int> CardIndexes;
for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
- if (CamSlot == camSlot || CamSlot->MasterSlot() == camSlot) {
- if (cDevice *Device = CamSlot->Device()) {
- if (!**AssignedDevice)
- AssignedDevice = cString::sprintf(" %s", tr("@ device"));
- AssignedDevice = cString::sprintf("%s %d", *AssignedDevice, Device->CardIndex() + 1);
- }
- }
+ if (CamSlot == camSlot || CamSlot->MasterSlot() == camSlot)
+ CamSlot->Devices(CardIndexes);
}
+ if (CardIndexes.Size() > 0) {
+ AssignedDevice = cString::sprintf(" %s", tr("@ device"));
+ CardIndexes.Sort(CompareInts);
+ for (int i = 0; i < CardIndexes.Size(); i++)
+ AssignedDevice = cString::sprintf("%s %d", *AssignedDevice, CardIndexes[i] + 1);
+ }
+
cString buffer = cString::sprintf(" %d %s%s%s", camSlot->SlotNumber(), CamName, *AssignedDevice, Activating);
if (strcmp(buffer, Text()) != 0) {
SetText(buffer);
@@ -3874,14 +3877,13 @@ eOSState cMenuSetupCAM::Activate(void)
if (cDevice *Device = cDevice::GetDevice(i)) {
if (Device->ProvidesChannel(Channel)) {
if (Device->Priority() < LIVEPRIORITY) { // don't interrupt recordings
- if (CamSlot->CanActivate()) {
- if (CamSlot->Assign(Device, true)) { // query
- cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again
- if (CamSlot->Assign(Device)) {
- if (Device->SwitchChannel(Channel, true)) {
- CamSlot->StartActivation();
- return osContinue;
- }
+ if (CamSlot->Assign(Device, true)) { // query
+ cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again
+ CamSlot = CamSlot->MtdSpawn();
+ if (CamSlot->Assign(Device)) {
+ if (Device->SwitchChannel(Channel, true)) {
+ CamSlot->StartActivation();
+ return osContinue;
}
}
}
diff --git a/mtd.c b/mtd.c
new file mode 100644
index 00000000..1ae2ccff
--- /dev/null
+++ b/mtd.c
@@ -0,0 +1,323 @@
+/*
+ * mtd.c: Multi Transponder Decryption
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: mtd.c 1.1 2017/03/18 14:31:34 kls Exp $
+ */
+
+#include "mtd.h"
+#include "receiver.h"
+
+//#define DEBUG_MTD
+#ifdef DEBUG_MTD
+#define DBGMTD(a...) dsyslog(a)
+#else
+#define DBGMTD(a...)
+#endif
+
+//#define KEEPPIDS // for testing and debugging - USE ONLY IF YOU KNOW WHAT YOU ARE DOING!
+
+#define MAX_REAL_PIDS MAXPID // real PIDs are 13 bit (0x0000 - 0x1FFF)
+#ifdef KEEPPIDS
+#define MAX_UNIQ_PIDS MAXPID
+#define UNIQ_PID_MASK 0x1FFF
+#else
+#define MAX_UNIQ_PIDS 256 // uniq PIDs are 8 bit (0x00 - 0xFF)
+#define UNIQ_PID_MASK 0x00FF
+#define UNIQ_PID_SHIFT 8
+#endif // KEEPPIDS
+
+// --- cMtdHandler -----------------------------------------------------------
+
+cMtdHandler::cMtdHandler(void)
+{
+}
+
+cMtdHandler::~cMtdHandler()
+{
+ for (int i = 0; i < camSlots.Size(); i++) {
+ dsyslog("CAM %d/%d: deleting MTD CAM slot", camSlots[i]->MasterSlot()->SlotNumber(), i + 1);
+ delete camSlots[i];
+ }
+}
+
+cMtdCamSlot *cMtdHandler::GetMtdCamSlot(cCamSlot *MasterSlot)
+{
+ for (int i = 0; i < camSlots.Size(); i++) {
+ if (!camSlots[i]->Device()) {
+ dsyslog("CAM %d/%d: reusing MTD CAM slot", MasterSlot->SlotNumber(), i + 1);
+ return camSlots[i];
+ }
+ }
+ dsyslog("CAM %d/%d: creating new MTD CAM slot", MasterSlot->SlotNumber(), camSlots.Size() + 1);
+ cMtdCamSlot *s = new cMtdCamSlot(MasterSlot, camSlots.Size());
+ camSlots.Append(s);
+ return s;
+}
+
+int cMtdHandler::Put(const uchar *Data, int Count)
+{
+ // TODO maybe handle more than one TS packet?
+ if (Count > TS_SIZE)
+ Count = TS_SIZE;
+ else if (Count < TS_SIZE)
+ return 0;
+ int Pid = TsPid(Data);
+ if (Pid == CATPID)
+ return Count; // this is the original CAT with mapped PIDs
+#ifdef KEEPPIDS
+ int Index = 0;
+#else
+ int Index = (Pid >> UNIQ_PID_SHIFT) - 1;
+#endif // KEEPPIDS
+ if (Index >= 0 && Index < camSlots.Size())
+ return camSlots[Index]->PutData(Data, Count);
+ else
+ esyslog("ERROR: invalid MTD number (%d) in PID %d (%04X)", Index + 1, Pid, Pid);
+ return Count; // no such buffer - let's just drop the data so nothing stacks up
+}
+
+int cMtdHandler::Priority(void)
+{
+ int p = IDLEPRIORITY;
+ for (int i = 0; i < camSlots.Size(); i++)
+ p = max(p, camSlots[i]->Priority());
+ return p;
+}
+
+bool cMtdHandler::IsDecrypting(void)
+{
+ for (int i = 0; i < camSlots.Size(); i++) {
+ if (camSlots[i]->IsDecrypting())
+ return true;
+ }
+ return false;
+}
+
+void cMtdHandler::StartDecrypting(void)
+{
+ for (int i = 0; i < camSlots.Size(); i++) {
+ if (camSlots[i]->Device()) {
+ camSlots[i]->TriggerResendPmt();
+ camSlots[i]->StartDecrypting();
+ }
+ }
+}
+
+void cMtdHandler::CancelActivation(void)
+{
+ for (int i = 0; i < camSlots.Size(); i++)
+ camSlots[i]->CancelActivation();
+}
+
+bool cMtdHandler::IsActivating(void)
+{
+ for (int i = 0; i < camSlots.Size(); i++) {
+ if (camSlots[i]->IsActivating())
+ return true;
+ }
+ return false;
+}
+
+bool cMtdHandler::Devices(cVector<int> &CardIndexes)
+{
+ for (int i = 0; i < camSlots.Size(); i++)
+ camSlots[i]->Devices(CardIndexes);
+ return CardIndexes.Size() > 0;
+}
+
+// --- cMtdMapper ------------------------------------------------------------
+
+#define MTD_INVALID_PID 0xFFFF
+
+class cMtdMapper {
+private:
+ int number;
+ int masterCamSlotNumber;
+ uint16_t uniqPids[MAX_REAL_PIDS]; // maps a real PID to a unique PID
+ uint16_t realPids[MAX_UNIQ_PIDS]; // maps a unique PID to a real PID
+ cVector<uint16_t> uniqSids;
+ uint16_t MakeUniqPid(uint16_t RealPid);
+public:
+ cMtdMapper(int Number, int MasterCamSlotNumber);
+ ~cMtdMapper();
+ uint16_t RealToUniqPid(uint16_t RealPid) { if (uniqPids[RealPid]) return uniqPids[RealPid]; return MakeUniqPid(RealPid); }
+ uint16_t UniqToRealPid(uint16_t UniqPid) { return realPids[UniqPid & UNIQ_PID_MASK]; }
+ uint16_t RealToUniqSid(uint16_t RealSid);
+ void Clear(void);
+ };
+
+cMtdMapper::cMtdMapper(int Number, int MasterCamSlotNumber)
+{
+ number = Number;
+ masterCamSlotNumber = MasterCamSlotNumber;
+ Clear();
+}
+
+cMtdMapper::~cMtdMapper()
+{
+}
+
+uint16_t cMtdMapper::MakeUniqPid(uint16_t RealPid)
+{
+#ifdef KEEPPIDS
+ uniqPids[RealPid] = realPids[RealPid] = RealPid;
+ DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
+ return uniqPids[RealPid];
+#else
+ for (int i = 0; i < MAX_UNIQ_PIDS; i++) {
+ if (realPids[i] == MTD_INVALID_PID) { // 0x0000 is a valid PID (PAT)!
+ realPids[i] = RealPid;
+ uniqPids[RealPid] = (number << UNIQ_PID_SHIFT) | i;
+ DBGMTD("CAM %d/%d: mapped PID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealPid, RealPid, uniqPids[RealPid], uniqPids[RealPid]);
+ return uniqPids[RealPid];
+ }
+ }
+#endif // KEEPPIDS
+ esyslog("ERROR: MTD %d: mapper ran out of unique PIDs", number);
+ return 0;
+}
+
+uint16_t cMtdMapper::RealToUniqSid(uint16_t RealSid)
+{
+#ifdef KEEPPIDS
+ return RealSid;
+#endif // KEEPPIDS
+ int UniqSid = uniqSids.IndexOf(RealSid);
+ if (UniqSid < 0) {
+ UniqSid = uniqSids.Size();
+ uniqSids.Append(RealSid);
+ DBGMTD("CAM %d/%d: mapped SID %d (%04X) to %d (%04X)", masterCamSlotNumber, number, RealSid, RealSid, UniqSid | (number << UNIQ_PID_SHIFT), UniqSid | (number << UNIQ_PID_SHIFT));
+ }
+ UniqSid |= number << UNIQ_PID_SHIFT;
+ return UniqSid;
+}
+
+void cMtdMapper::Clear(void)
+{
+ DBGMTD("CAM %d/%d: MTD mapper cleared", masterCamSlotNumber, number);
+ memset(uniqPids, 0, sizeof(uniqPids));
+ memset(realPids, MTD_INVALID_PID, sizeof(realPids));
+ uniqSids.Clear();
+}
+
+void MtdMapSid(uchar *p, cMtdMapper *MtdMapper)
+{
+ Poke13(p, MtdMapper->RealToUniqSid(Peek13(p)));
+}
+
+void MtdMapPid(uchar *p, cMtdMapper *MtdMapper)
+{
+ Poke13(p, MtdMapper->RealToUniqPid(Peek13(p)));
+}
+
+// --- cMtdCamSlot -----------------------------------------------------------
+
+#define MTD_BUFFER_SIZE MEGABYTE(1)
+
+cMtdCamSlot::cMtdCamSlot(cCamSlot *MasterSlot, int Index)
+:cCamSlot(NULL, true, MasterSlot)
+{
+ mtdBuffer = new cRingBufferLinear(MTD_BUFFER_SIZE, TS_SIZE, true, "MTD buffer");
+ mtdMapper = new cMtdMapper(Index + 1, MasterSlot->SlotNumber());
+ delivered = false;
+ ciAdapter = MasterSlot->ciAdapter; // we don't pass the CI adapter in the constructor, to prevent this one from being inserted into CamSlots
+}
+
+cMtdCamSlot::~cMtdCamSlot()
+{
+ delete mtdMapper;
+ delete mtdBuffer;
+}
+
+const int *cMtdCamSlot::GetCaSystemIds(void)
+{
+ return MasterSlot()->GetCaSystemIds();
+}
+
+void cMtdCamSlot::SendCaPmt(uint8_t CmdId)
+{
+ cMutexLock MutexLock(&mutex);
+ cCiCaPmtList CaPmtList;
+ BuildCaPmts(CmdId, CaPmtList, mtdMapper);
+ MasterSlot()->SendCaPmts(CaPmtList);
+}
+
+bool cMtdCamSlot::RepliesToQuery(void)
+{
+ return MasterSlot()->RepliesToQuery();
+}
+
+bool cMtdCamSlot::ProvidesCa(const int *CaSystemIds)
+{
+ return MasterSlot()->ProvidesCa(CaSystemIds);
+}
+
+bool cMtdCamSlot::CanDecrypt(const cChannel *Channel)
+{
+ //TODO PID mapping?
+ return MasterSlot()->CanDecrypt(Channel);
+}
+
+void cMtdCamSlot::StartDecrypting(void)
+{
+ MasterSlot()->StartDecrypting();
+ cCamSlot::StartDecrypting();
+}
+
+void cMtdCamSlot::StopDecrypting(void)
+{
+ cCamSlot::StopDecrypting();
+ mtdMapper->Clear();
+ //XXX mtdBuffer->Clear(); //XXX would require locking?
+}
+
+bool cMtdCamSlot::IsDecrypting(void)
+{
+ return cCamSlot::IsDecrypting();
+}
+
+uchar *cMtdCamSlot::Decrypt(uchar *Data, int &Count)
+{
+ // Send data to CAM:
+ if (Count >= TS_SIZE) {
+ Count = TS_SIZE;
+ int Pid = TsPid(Data);
+ TsSetPid(Data, mtdMapper->RealToUniqPid(Pid));
+ MasterSlot()->Decrypt(Data, Count);
+ if (Count == 0)
+ TsSetPid(Data, Pid); // must restore PID for later retry
+ }
+ else
+ Count = 0;
+ // Drop delivered data from previous call:
+ if (delivered) {
+ mtdBuffer->Del(TS_SIZE);
+ delivered = false;
+ }
+ // Receive data from buffer:
+ int c = 0;
+ uchar *d = mtdBuffer->Get(c);
+ if (d) {
+ if (c >= TS_SIZE) {
+ TsSetPid(d, mtdMapper->UniqToRealPid(TsPid(d)));
+ delivered = true;
+ }
+ else
+ d = NULL;
+ }
+ return d;
+}
+
+int cMtdCamSlot::PutData(const uchar *Data, int Count)
+{
+ return mtdBuffer->Put(Data, Count);
+}
+
+int cMtdCamSlot::PutCat(const uchar *Data, int Count)
+{
+ MasterSlot()->Decrypt(const_cast<uchar *>(Data), Count);
+ return Count;
+}
diff --git a/mtd.h b/mtd.h
new file mode 100644
index 00000000..9dd016be
--- /dev/null
+++ b/mtd.h
@@ -0,0 +1,187 @@
+/*
+ * mtd.h: Multi Transponder Decryption
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: mtd.h 1.1 2017/03/17 16:06:34 kls Exp $
+ */
+
+#ifndef __MTD_H
+#define __MTD_H
+
+/*
+Multiple Transponder Decryption (MTD) is the method of sending TS packets
+from channels on different transponders to one single CAM for decryption.
+While decrypting several channels from the same transponder ("Multi Channel
+Decryption") is straightforward, because the PIDs are unique within one
+transponder, channels on different transponders might use the same PIDs
+for different streams.
+
+Here's a summary of how MTD is implemented in VDR:
+
+Identifying the relevant source code
+------------------------------------
+
+The actual code that implements the MTD handling is located in the files
+mtd.h and mtd.c. There are also a few places in ci.[hc], device.c and
+menu.c where things need to be handled differently for MTD. All functions
+and variables that have to do with MTD have the three letters "mtd" (upper-
+and/or lowercase) in their name, so that these code lines can be easily
+identified if necessary.
+
+What a plugin implementing a cCiAdapter/cCamSlot needs to do
+------------------------------------------------------------
+
+If an implementation of cCiAdapter/cCamSlot supports MTD, it needs to
+fulfill the following requirements:
+- The cCiAdapter's Assign() function needs to return true for any given
+ device.
+- The cCamSlot's constructor needs to call MtdEnable().
+- The cCamSlot's Decrypt() function shall accept the given TS packet,
+ but shall *not* return a decrypted packet. Decypted packets shall be
+ delivered through a call to MtdPutData(), one at a time.
+- The cCamSlot's Decrypt() function needs to be thread safe, because
+ it will be called from several cMtdCamSlot objects.
+
+Physical vs. virtual CAMs
+-------------------------
+
+MTD is done by having one physical CAM (accessed through a plugin's
+implementation of cCiAdapter/cCamSlot) and several "virtual" CAMs,
+implemented through cMtdCamSlot objects ("MTD CAMs"). For each device
+that requires the physical CAM, one instance of a cMtdCamSlot is created
+on the fly at runtime, and that MTD CAM is assigned to the device.
+The MTD CAM takes care of mapping the PIDs, and a cMtdHandler in the
+physical CAM object distributes the decrypted TS packets to the proper
+devices.
+
+Mapping the PIDs
+----------------
+
+The main problem with MTD is that the TS packets from different devices
+(and thus different transponders with possibly overlapping PIDs) need to
+be combined into one stream, sent to the physical CAM, and finally be sorted
+apart again and returned to the devices they came from. Both aspects are
+solved in VDR by mapping the "real" PIDs into "unique" PIDs. Real PIDs
+are in the range 0x0000-0x1FFF (13 bit). Unique PIDs use the upper 5 bit
+to indicate the number of the MTD CAM a TS packet came from, and the lower
+8 bit as individual PID values. Mapping is done with a single array lookup
+and is thus very fast. The cMtdHandler class takes care of distributing
+the TS packets to the individual cMtdCamSlot objects, while mapping the
+PIDs (in both directions) is done by the cMtdMapper class.
+
+Mapping the SIDs
+----------------
+
+Besides the PIDs there are also the "service ids" (SIDs, a.k.a. "programme
+numbers" or PNRs) that need to be taken care of. SIDs only appear in the
+CA-PMTs sent to the CAM, so they only need to be mapped from real to unique
+(not the other way) and since the are only mapped when switching channels,
+mapping doesn't need to be very fast. Mapping SIDs is also done by the
+cMtdMapper class.
+
+Handling the CAT
+----------------
+
+Each transponder carries a CAT ("Conditional Access Table") with the fixed PID 1.
+The CAT contains a list of EMM PIDs, which are necessary to convey entitlement
+messages to the smart card. Since the CAM only recognizes the CAT if it has
+its fixed PID of 1, this PID cannot be mapped and has to be sent to the CAM
+as is. However, the cCaPidReceiver also needs to see the CAM in order to
+request from the device the TS packets with the EMM PIDs. Since any receivers
+only get the TS packets after they have been sent through the CAM, we need
+to send the CAT in both ways, with mapped PID but unmapped EMM PIDs for the
+cCaPidReceiver, and with unmapped PID but mapped EMM PIDs for the CAM itself.
+Since the PID 0x0001 can always be distinguished from any mapped PID (which
+always have a non-zero upper byte), the CAT can be easily channeled in both
+ways.
+
+Handling the CA-PMTs
+--------------------
+
+The CA-PMTs that are sent to the CAM contain both SIDs and PIDs, which are
+mapped in cCiCaPmt::MtdMapPids().
+*/
+
+#include "ci.h"
+#include "remux.h"
+#include "ringbuffer.h"
+
+class cMtdHandler {
+private:
+ cVector<cMtdCamSlot *> camSlots;
+public:
+ cMtdHandler(void);
+ ///< Creates a new MTD handler that distributes TS data received through
+ ///< calls to the Put() function to the individual CAM slots that have been
+ ///< created via GetMtdCamSlot(). It also distributes several function
+ ///< calls from the physical master CAM slot to the individual MTD CAM slots.
+ ~cMtdHandler();
+ cMtdCamSlot *GetMtdCamSlot(cCamSlot *MasterSlot);
+ ///< Creates a new MTD CAM slot, or reuses an existing one that is currently
+ ///< unused.
+ int Put(const uchar *Data, int Count);
+ ///< Puts at most Count bytes of Data into the CAM slot which's index is
+ ///< derived from the PID of the TS packets.
+ ///< Returns the number of bytes actually stored.
+ int Priority(void);
+ ///< Returns the maximum priority of any of the active MTD CAM slots.
+ bool IsDecrypting(void);
+ ///< Returns true if any of the active MTD CAM slots is currently decrypting.
+ void StartDecrypting(void);
+ ///< Tells all active MTD CAM slots to start decrypting.
+ void CancelActivation(void);
+ ///< Tells all active MTD CAM slots to cancel activation.
+ bool IsActivating(void);
+ ///< Returns true if any of the active MTD CAM slots is currently activating.
+ bool Devices(cVector<int> &CardIndexes);
+ ///< Adds the card indexes of the devices of any active MTD CAM slots to
+ ///< the given CardIndexes.
+ ///< Returns true if the array is not empty.
+ };
+
+#define MTD_DONT_CALL(v) dsyslog("PROGRAMMING ERROR (%s,%d): DON'T CALL %s", __FILE__, __LINE__, __FUNCTION__); return v;
+
+class cMtdMapper;
+
+void MtdMapSid(uchar *p, cMtdMapper *MtdMapper);
+void MtdMapPid(uchar *p, cMtdMapper *MtdMapper);
+
+class cMtdCamSlot : public cCamSlot {
+private:
+ cMtdMapper *mtdMapper;
+ cRingBufferLinear *mtdBuffer;
+ bool delivered;
+protected:
+ virtual const int *GetCaSystemIds(void);
+ virtual void SendCaPmt(uint8_t CmdId);
+public:
+ cMtdCamSlot(cCamSlot *MasterSlot, int Index);
+ ///< Creates a new "Multi Transponder Decryption" CAM slot, connected to the
+ ///< given physical MasterSlot, using the given Index for mapping PIDs.
+ virtual ~cMtdCamSlot();
+ cMtdMapper *MtdMapper(void) { return mtdMapper; }
+ virtual bool RepliesToQuery(void);
+ virtual bool ProvidesCa(const int *CaSystemIds);
+ virtual bool CanDecrypt(const cChannel *Channel);
+ virtual void StartDecrypting(void);
+ virtual void StopDecrypting(void);
+ virtual bool IsDecrypting(void);
+ virtual uchar *Decrypt(uchar *Data, int &Count);
+ int PutData(const uchar *Data, int Count);
+ int PutCat(const uchar *Data, int Count);
+ // The following functions shall not be called for a cMtdCamSlot:
+ virtual cCamSlot *Spawn(void) { MTD_DONT_CALL(NULL); }
+ virtual bool Reset(void) { MTD_DONT_CALL(false); }
+ virtual eModuleStatus ModuleStatus(void) { MTD_DONT_CALL(msNone); }
+ virtual const char *GetCamName(void) { MTD_DONT_CALL(NULL); }
+ virtual bool Ready(void) { MTD_DONT_CALL(false); }
+ virtual bool HasMMI(void) { MTD_DONT_CALL(false); }
+ virtual bool HasUserIO(void) { MTD_DONT_CALL(false); }
+ virtual bool EnterMenu(void) { MTD_DONT_CALL(false); }
+ virtual cCiMenu *GetMenu(void) { MTD_DONT_CALL(NULL); }
+ virtual cCiEnquiry *GetEnquiry(void) { MTD_DONT_CALL(NULL); }
+ };
+
+#endif //__MTD_H
diff --git a/remux.h b/remux.h
index 5eab076f..28dfde4b 100644
--- a/remux.h
+++ b/remux.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: remux.h 4.1 2016/12/22 13:09:54 kls Exp $
+ * $Id: remux.h 4.2 2017/02/27 16:11:57 kls Exp $
*/
#ifndef __REMUX_H
@@ -83,6 +83,12 @@ inline int TsPid(const uchar *p)
return (p[1] & TS_PID_MASK_HI) * 256 + p[2];
}
+inline void TsSetPid(uchar *p, int Pid)
+{
+ p[1] = (p[1] & ~TS_PID_MASK_HI) | ((Pid >> 8) & TS_PID_MASK_HI);
+ p[2] = Pid & 0x00FF;
+}
+
inline bool TsIsScrambled(const uchar *p)
{
return p[3] & TS_SCRAMBLING_CONTROL;
diff --git a/tools.h b/tools.h
index 73cca5a3..d2234c39 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 4.5 2016/12/23 13:56:35 kls Exp $
+ * $Id: tools.h 4.6 2017/03/16 16:04:43 kls Exp $
*/
#ifndef __TOOLS_H
@@ -242,6 +242,17 @@ cString dtoa(double d, const char *Format = "%f");
///< the decimal point, independent of the currently selected locale.
///< If Format is given, it will be used instead of the default.
cString itoa(int n);
+inline uint16_t Peek13(const uchar *p)
+{
+ uint16_t v = uint16_t(*p++ & 0x1F) << 8;
+ return v + (*p & 0xFF);
+}
+inline void Poke13(uchar *p, uint16_t v)
+{
+ v |= uint16_t(*p & ~0x1F) << 8;
+ *p++ = v >> 8;
+ *p = v & 0xFF;
+}
cString AddDirectory(const char *DirName, const char *FileName);
bool EntriesOnSameFileSystem(const char *File1, const char *File2);
///< Checks whether the given files are on the same file system. If either of the
@@ -744,6 +755,11 @@ public:
}
};
+inline int CompareInts(const void *a, const void *b)
+{
+ return *(const int *)a > *(const int *)b;
+}
+
inline int CompareStrings(const void *a, const void *b)
{
return strcmp(*(const char **)a, *(const char **)b);