1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00

Implemented support for MTD

This commit is contained in:
Klaus Schmidinger 2017-03-18 16:33:59 +01:00
parent 3f9cdea1c1
commit 2cc25e65f4
10 changed files with 969 additions and 104 deletions

View File

@ -8882,7 +8882,7 @@ Video Disk Recorder Revision History
- Added a short sleep to cTSBuffer::Action() to avoid high CPU usage (thanks to - Added a short sleep to cTSBuffer::Action() to avoid high CPU usage (thanks to
Sergey Chernyavskiy). 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). - Added 'S3W ABS-3A' to sources.conf (thanks to Frank Richter).
- Fixed a possible deadlock in the recordings handler thread. - Fixed a possible deadlock in the recordings handler thread.
@ -8914,3 +8914,7 @@ Video Disk Recorder Revision History
contained an empty string). contained an empty string).
- PIDs can now be added to and deleted from a cReceiver while it is attached to - 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. 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.

View File

@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and # See the main source file 'vdr.c' for copyright information and
# how to reach the author. # 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: .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\ 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\ 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\ 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\ 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 timers.o tools.o transfer.o vdr.o videodir.o

346
ci.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "ci.h"
@ -19,6 +19,7 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include "device.h" #include "device.h"
#include "mtd.h"
#include "pat.h" #include "pat.h"
#include "receiver.h" #include "receiver.h"
#include "remux.h" #include "remux.h"
@ -118,6 +119,7 @@ private:
cVector<int> emmPids; cVector<int> emmPids;
uchar buffer[2048]; // 11 bit length, max. 2048 byte uchar buffer[2048]; // 11 bit length, max. 2048 byte
uchar *bufp; uchar *bufp;
uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs!
int length; int length;
void AddEmmPid(int Pid); void AddEmmPid(int Pid);
void DelEmmPids(void); void DelEmmPids(void);
@ -157,6 +159,7 @@ void cCaPidReceiver::DelEmmPids(void)
void cCaPidReceiver::Receive(const uchar *Data, int Length) void cCaPidReceiver::Receive(const uchar *Data, int Length)
{ {
if (TsPid(Data) == CATPID) { if (TsPid(Data) == CATPID) {
cMtdCamSlot *MtdCamSlot = dynamic_cast<cMtdCamSlot *>(Device()->CamSlot());
const uchar *p = NULL; const uchar *p = NULL;
if (TsPayloadStart(Data)) { if (TsPayloadStart(Data)) {
if (Data[5] == SI::TableIdCAT) { if (Data[5] == SI::TableIdCAT) {
@ -166,6 +169,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
if (v != catVersion) { if (v != catVersion) {
if (Data[11] == 0 && Data[12] == 0) { // section number, last section number if (Data[11] == 0 && Data[12] == 0) { // section number, last section number
if (length > TS_SIZE - 8) { if (length > TS_SIZE - 8) {
if (MtdCamSlot)
esyslog("ERROR: need to implement multi packet CAT handling for MTD!");
int n = TS_SIZE - 13; int n = TS_SIZE - 13;
memcpy(buffer, Data + 13, n); memcpy(buffer, Data + 13, n);
bufp = buffer + n; bufp = buffer + n;
@ -180,6 +185,8 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
dsyslog("multi table CAT section - unhandled!"); dsyslog("multi table CAT section - unhandled!");
catVersion = v; 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 for (int i = 0; i < length - 4; i++) { // -4 = checksum
if (p[i] == 0x09) { if (p[i] == 0x09) {
int CaId = int(p[i + 2] << 8) | p[i + 3]; 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); AddEmmPid(EmmPid);
if (MtdCamSlot)
MtdMapPid(const_cast<uchar *>(p + i + 4), MtdCamSlot->MtdMapper());
switch (CaId >> 8) { switch (CaId >> 8) {
case 0x01: for (int j = i + 7; j < p[i + 1] + 2; j += 4) { 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); AddEmmPid(EmmPid);
if (MtdCamSlot)
MtdMapPid(const_cast<uchar *>(p + j), MtdCamSlot->MtdMapper());
} }
break; break;
} }
@ -220,6 +231,9 @@ void cCaPidReceiver::Receive(const uchar *Data, int Length)
p = NULL; p = NULL;
bufp = 0; bufp = 0;
length = 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) { else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) {
dsyslog("CAM %d: activated!", camSlot->SlotNumber()); dsyslog("CAM %d: activated!", camSlot->SlotNumber());
Skins.QueueMessage(mtInfo, tr("CAM activated!")); Skins.QueueMessage(mtInfo, tr("CAM activated!"));
cDevice *d = Device();
Detach(); 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); void SetListManagement(uint8_t ListManagement);
uint8_t ListManagement(void) { return capmt.Get(0); } uint8_t ListManagement(void) { return capmt.Get(0); }
void AddPid(int Pid, uint8_t StreamType); 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) 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!"); 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 ------------------------------------------- // --- cCiConditionalAccessSupport -------------------------------------------
// CA Enable Ids: // CA Enable Ids:
@ -897,8 +995,12 @@ void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
case AOT_CA_PMT_REPLY: { case AOT_CA_PMT_REPLY: {
dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId()); dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", Tc()->CamSlot()->SlotNumber(), SessionId());
if (!repliesToQuery) { 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; 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 state = 5; // got ca pmt reply
int l = 0; int l = 0;
@ -1650,6 +1752,14 @@ public:
programNumber = ProgramNumber; programNumber = ProgramNumber;
modified = false; modified = false;
} }
bool Active(void)
{
for (cCiCaPidData *p = pidList.First(); p; p = pidList.Next(p)) {
if (p->active)
return true;
}
return false;
}
}; };
// --- cCiAdapter ------------------------------------------------------------ // --- cCiAdapter ------------------------------------------------------------
@ -1725,15 +1835,18 @@ cCamSlot::cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData, cCamSlot *MasterSlot
caPidReceiver = WantsTsData ? new cCaPidReceiver : NULL; caPidReceiver = WantsTsData ? new cCaPidReceiver : NULL;
caActivationReceiver = NULL; caActivationReceiver = NULL;
slotIndex = -1; slotIndex = -1;
mtdAvailable = false;
mtdHandler = NULL;
lastModuleStatus = msReset; // avoids initial reset log message lastModuleStatus = msReset; // avoids initial reset log message
resetTime = 0; resetTime = 0;
resendPmt = false; resendPmt = false;
source = transponder = 0;
for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway for (int i = 0; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) // tc[0] is not used, but initialized anyway
tc[i] = NULL; tc[i] = NULL;
CamSlots.Add(this); if (MasterSlot)
slotNumber = Index() + 1; slotNumber = MasterSlot->SlotNumber();
if (ciAdapter) { if (ciAdapter) {
CamSlots.Add(this);
slotNumber = Index() + 1;
ciAdapter->AddCamSlot(this); ciAdapter->AddCamSlot(this);
Reset(); Reset();
} }
@ -1747,26 +1860,38 @@ cCamSlot::~cCamSlot()
delete caActivationReceiver; delete caActivationReceiver;
CamSlots.Del(this, false); CamSlots.Del(this, false);
DeleteAllConnections(); 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) bool cCamSlot::Assign(cDevice *Device, bool Query)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (Device == assignedDevice)
return true;
if (ciAdapter) { 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 (ciAdapter->Assign(Device, true)) {
if (!Device && assignedDevice) { if (!Query) {
if (caPidReceiver)
assignedDevice->Detach(caPidReceiver);
assignedDevice->SetCamSlot(NULL);
}
if (!Query || !Device) {
StopDecrypting(); StopDecrypting();
source = transponder = 0;
if (ciAdapter->Assign(Device)) { if (ciAdapter->Assign(Device)) {
int OldDeviceNumber = assignedDevice ? assignedDevice->DeviceNumber() + 1 : 0;
assignedDevice = Device;
if (Device) { if (Device) {
Device->SetCamSlot(this); Device->SetCamSlot(this);
assignedDevice = Device;
if (caPidReceiver) { if (caPidReceiver) {
caPidReceiver->Reset(); caPidReceiver->Reset();
Device->AttachReceiver(caPidReceiver); Device->AttachReceiver(caPidReceiver);
@ -1787,6 +1912,16 @@ bool cCamSlot::Assign(cDevice *Device, bool Query)
return false; 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) void cCamSlot::NewConnection(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
@ -1812,8 +1947,6 @@ void cCamSlot::DeleteAllConnections(void)
void cCamSlot::Process(cTPDU *TPDU) void cCamSlot::Process(cTPDU *TPDU)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (caActivationReceiver && !caActivationReceiver->IsAttached())
CancelActivation();
if (TPDU) { if (TPDU) {
int n = TPDU->Tcid(); int n = TPDU->Tcid();
if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) { 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++) { for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) {
if (tc[i]) { if (tc[i]) {
if (!tc[i]->Process()) { if (!tc[i]->Process()) {
Reset(); Reset();
return; return;
} }
} }
} }
if (moduleCheckTimer.TimedOut()) { if (moduleCheckTimer.TimedOut()) {
@ -1836,6 +1969,7 @@ void cCamSlot::Process(cTPDU *TPDU)
case msNone: case msNone:
dbgprotocol("Slot %d: no module present\n", slotNumber); dbgprotocol("Slot %d: no module present\n", slotNumber);
isyslog("CAM %d: no module present", slotNumber); isyslog("CAM %d: no module present", slotNumber);
MtdActivate(false);
DeleteAllConnections(); DeleteAllConnections();
CancelActivation(); CancelActivation();
break; break;
@ -1852,7 +1986,7 @@ void cCamSlot::Process(cTPDU *TPDU)
dbgprotocol("Slot %d: module ready\n", slotNumber); dbgprotocol("Slot %d: module ready\n", slotNumber);
isyslog("CAM %d: module ready", slotNumber); isyslog("CAM %d: module ready", slotNumber);
NewConnection(); NewConnection();
resendPmt = caProgramList.Count() > 0; resendPmt = true;
break; break;
default: default:
esyslog("ERROR: unknown module status %d (%s)", ms, __FUNCTION__); esyslog("ERROR: unknown module status %d (%s)", ms, __FUNCTION__);
@ -1861,8 +1995,14 @@ void cCamSlot::Process(cTPDU *TPDU)
} }
moduleCheckTimer.Set(MODULE_CHECK_INTERVAL); moduleCheckTimer.Set(MODULE_CHECK_INTERVAL);
} }
if (resendPmt) if (resendPmt && Ready()) {
SendCaPmt(CPCI_OK_DESCRAMBLING); if (mtdHandler) {
mtdHandler->StartDecrypting();
resendPmt = false;
}
else if (caProgramList.Count())
StartDecrypting();
}
processed.Broadcast(); processed.Broadcast();
} }
@ -1922,12 +2062,18 @@ void cCamSlot::StartActivation(void)
void cCamSlot::CancelActivation(void) void cCamSlot::CancelActivation(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
delete caActivationReceiver; if (mtdHandler)
caActivationReceiver = NULL; mtdHandler->CancelActivation();
else {
delete caActivationReceiver;
caActivationReceiver = NULL;
}
} }
bool cCamSlot::IsActivating(void) bool cCamSlot::IsActivating(void)
{ {
if (mtdHandler)
return mtdHandler->IsActivating();
return caActivationReceiver; return caActivationReceiver;
} }
@ -2001,54 +2147,113 @@ cCiEnquiry *cCamSlot::GetEnquiry(void)
return NULL; 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);
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;
}
}
}
}
}
void cCamSlot::SendCaPmts(cCiCaPmtList &CaPmtList)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT);
if (cas) { if (cas) {
const int *CaSystemIds = cas->GetCaSystemIds(); for (int i = 0; i < CaPmtList.caPmts.Size(); i++)
if (CaSystemIds && *CaSystemIds) { cas->SendPMT(CaPmtList.caPmts[i]);
if (caProgramList.Count()) { }
for (int Loop = 1; Loop <= 2; Loop++) { resendPmt = false;
for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { }
if (p->modified || resendPmt) {
bool Active = false; void cCamSlot::SendCaPmt(uint8_t CmdId)
cCiCaPmt CaPmt(CmdId, source, transponder, p->programNumber, CaSystemIds); {
for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { cMutexLock MutexLock(&mutex);
if (q->active) { cCiCaPmtList CaPmtList;
CaPmt.AddPid(q->pid, q->streamType); BuildCaPmts(CmdId, CaPmtList);
Active = true; SendCaPmts(CaPmtList);
} }
}
if ((Loop == 1) != Active) { // first remove, then add void cCamSlot::MtdEnable(void)
if (caPidReceiver) { {
int CaPids[MAXRECEIVEPIDS + 1]; mtdAvailable = true;
if (GetCaPids(source, transponder, p->programNumber, CaSystemIds, MAXRECEIVEPIDS + 1, CaPids) > 0) { }
if (Loop == 1)
caPidReceiver->DelPids(CaPids); void cCamSlot::MtdActivate(bool On)
else {
caPidReceiver->AddPids(CaPids); if (McdAvailable() && MtdAvailable()) {
} if (On) {
} if (!mtdHandler) {
if (cas->RepliesToQuery()) dsyslog("CAM %d: activating MTD support", SlotNumber());
CaPmt.SetListManagement(Active ? CPLM_ADD : CPLM_UPDATE); mtdHandler = new cMtdHandler;
if (Active || cas->RepliesToQuery())
cas->SendPMT(&CaPmt);
p->modified = false;
}
}
}
}
resendPmt = false;
}
else {
cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL);
cas->SendPMT(&CaPmt);
} }
} }
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) const int *cCamSlot::GetCaSystemIds(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
@ -2058,6 +2263,8 @@ const int *cCamSlot::GetCaSystemIds(void)
int cCamSlot::Priority(void) int cCamSlot::Priority(void)
{ {
if (mtdHandler)
return mtdHandler->Priority();
cDevice *d = Device(); cDevice *d = Device();
return d ? d->Priority() : IDLEPRIORITY; return d ? d->Priority() : IDLEPRIORITY;
} }
@ -2107,7 +2314,7 @@ void cCamSlot::SetPid(int Pid, bool Active)
} }
return; return;
} }
} }
} }
} }
@ -2178,15 +2385,14 @@ void cCamSlot::StartDecrypting(void)
void cCamSlot::StopDecrypting(void) void cCamSlot::StopDecrypting(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (caProgramList.Count()) { caProgramList.Clear();
caProgramList.Clear();
SendCaPmt(CPCI_NOT_SELECTED);
}
} }
bool cCamSlot::IsDecrypting(void) bool cCamSlot::IsDecrypting(void)
{ {
cMutexLock MutexLock(&mutex); cMutexLock MutexLock(&mutex);
if (mtdHandler)
return mtdHandler->IsDecrypting();
if (caProgramList.Count()) { if (caProgramList.Count()) {
for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) {
if (p->modified) if (p->modified)

61
ci.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __CI_H
@ -13,6 +13,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include "channels.h" #include "channels.h"
#include "ringbuffer.h"
#include "thread.h" #include "thread.h"
#include "tools.h" #include "tools.h"
@ -124,10 +125,23 @@ class cCiSession;
class cCiCaProgramData; class cCiCaProgramData;
class cCaPidReceiver; class cCaPidReceiver;
class cCaActivationReceiver; 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 { class cCamSlot : public cListObject {
friend class cCiAdapter; friend class cCiAdapter;
friend class cCiTransportConnection; friend class cCiTransportConnection;
friend class cCiConditionalAccessSupport;
friend class cMtdCamSlot;
private: private:
cMutex mutex; cMutex mutex;
cCondVar processed; cCondVar processed;
@ -146,13 +160,40 @@ private:
int source; int source;
int transponder; int transponder;
cList<cCiCaProgramData> caProgramList; cList<cCiCaProgramData> caProgramList;
const int *GetCaSystemIds(void); bool mtdAvailable;
void SendCaPmt(uint8_t CmdId); cMtdHandler *mtdHandler;
void NewConnection(void); void NewConnection(void);
void DeleteAllConnections(void); void DeleteAllConnections(void);
void Process(cTPDU *TPDU = NULL); void Process(cTPDU *TPDU = NULL);
void Write(cTPDU *TPDU); void Write(cTPDU *TPDU);
cCiSession *GetSessionByResourceId(uint32_t ResourceId); 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: public:
cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false, cCamSlot *MasterSlot = NULL); cCamSlot(cCiAdapter *CiAdapter, bool WantsTsData = false, cCamSlot *MasterSlot = NULL);
///< Creates a new CAM slot for the given CiAdapter. ///< Creates a new CAM slot for the given CiAdapter.
@ -175,6 +216,13 @@ public:
cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; } cCamSlot *MasterSlot(void) { return masterSlot ? masterSlot : this; }
///< Returns this CAM slot's master slot, or a pointer to itself if it is a ///< Returns this CAM slot's master slot, or a pointer to itself if it is a
///< master slot. ///< 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); virtual bool Assign(cDevice *Device, bool Query = false);
///< Assigns this CAM slot to the given Device, if this is possible. ///< 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 ///< If Query is 'true', the CI adapter of this slot only checks whether
@ -190,6 +238,10 @@ public:
///< class function. ///< class function.
cDevice *Device(void) { return assignedDevice; } cDevice *Device(void) { return assignedDevice; }
///< Returns the device this CAM slot is currently assigned to. ///< 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; } bool WantsTsData(void) const { return caPidReceiver != NULL; }
///< Returns true if this CAM slot wants to receive the TS data through ///< Returns true if this CAM slot wants to receive the TS data through
///< its Decrypt() function. ///< its Decrypt() function.
@ -308,7 +360,8 @@ public:
///< the CAM's control). If no decrypted TS packet is currently available, NULL ///< 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 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 ///< 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 ///< A derived class that implements this function will also need
///< to set the WantsTsData parameter in the call to the base class ///< to set the WantsTsData parameter in the call to the base class
///< constructor to true in order to receive the TS data. ///< constructor to true in order to receive the TS data.

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #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 continue; // CAM slot can't be used with this device
bool ndr; bool ndr;
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basically able to do the job 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)) if (NumUsableSlots && !HasInternalCam) {
ndr = true; // using a different CAM slot requires detaching receivers 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 // Put together an integer number that reflects the "impact" using
// this device would have on the overall system. Each condition is represented // 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 // 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) if (NeedsDetachReceivers)
d->DetachAllReceivers(); d->DetachAllReceivers();
if (s) { if (s) {
if (s->Device() != d) { // Some of the following statements could probably be combined, but let's keep them
if (s->Device()) // explicit so we can clearly see every single aspect of the decisions made here.
s->Device()->DetachAllReceivers(); if (d->CamSlot()) {
if (d->CamSlot()) if (s->MtdActive()) {
d->CamSlot()->Assign(NULL); if (s == d->CamSlot()->MasterSlot()) {
s->Assign(d); // 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()) else if (d->CamSlot() && !d->CamSlot()->IsDecrypting())

34
menu.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "menu.h"
@ -3770,15 +3770,18 @@ bool cMenuSetupCAMItem::Changed(void)
else if (camSlot->IsActivating()) else if (camSlot->IsActivating())
// TRANSLATORS: note the leading blank! // TRANSLATORS: note the leading blank!
Activating = tr(" (activating)"); Activating = tr(" (activating)");
cVector<int> CardIndexes;
for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) {
if (CamSlot == camSlot || CamSlot->MasterSlot() == camSlot) { if (CamSlot == camSlot || CamSlot->MasterSlot() == camSlot)
if (cDevice *Device = CamSlot->Device()) { CamSlot->Devices(CardIndexes);
if (!**AssignedDevice)
AssignedDevice = cString::sprintf(" %s", tr("@ device"));
AssignedDevice = cString::sprintf("%s %d", *AssignedDevice, Device->CardIndex() + 1);
}
}
} }
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); cString buffer = cString::sprintf(" %d %s%s%s", camSlot->SlotNumber(), CamName, *AssignedDevice, Activating);
if (strcmp(buffer, Text()) != 0) { if (strcmp(buffer, Text()) != 0) {
SetText(buffer); SetText(buffer);
@ -3874,14 +3877,13 @@ eOSState cMenuSetupCAM::Activate(void)
if (cDevice *Device = cDevice::GetDevice(i)) { if (cDevice *Device = cDevice::GetDevice(i)) {
if (Device->ProvidesChannel(Channel)) { if (Device->ProvidesChannel(Channel)) {
if (Device->Priority() < LIVEPRIORITY) { // don't interrupt recordings if (Device->Priority() < LIVEPRIORITY) { // don't interrupt recordings
if (CamSlot->CanActivate()) { if (CamSlot->Assign(Device, true)) { // query
if (CamSlot->Assign(Device, true)) { // query cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again
cControl::Shutdown(); // must end transfer mode before assigning CAM, otherwise it might be unassigned again CamSlot = CamSlot->MtdSpawn();
if (CamSlot->Assign(Device)) { if (CamSlot->Assign(Device)) {
if (Device->SwitchChannel(Channel, true)) { if (Device->SwitchChannel(Channel, true)) {
CamSlot->StartActivation(); CamSlot->StartActivation();
return osContinue; return osContinue;
}
} }
} }
} }

323
mtd.c Normal file
View File

@ -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;
}

187
mtd.h Normal file
View File

@ -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

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __REMUX_H
@ -83,6 +83,12 @@ inline int TsPid(const uchar *p)
return (p[1] & TS_PID_MASK_HI) * 256 + p[2]; 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) inline bool TsIsScrambled(const uchar *p)
{ {
return p[3] & TS_SCRAMBLING_CONTROL; return p[3] & TS_SCRAMBLING_CONTROL;

18
tools.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __TOOLS_H
@ -242,6 +242,17 @@ cString dtoa(double d, const char *Format = "%f");
///< the decimal point, independent of the currently selected locale. ///< the decimal point, independent of the currently selected locale.
///< If Format is given, it will be used instead of the default. ///< If Format is given, it will be used instead of the default.
cString itoa(int n); 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); cString AddDirectory(const char *DirName, const char *FileName);
bool EntriesOnSameFileSystem(const char *File1, const char *File2); bool EntriesOnSameFileSystem(const char *File1, const char *File2);
///< Checks whether the given files are on the same file system. If either of the ///< 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) inline int CompareStrings(const void *a, const void *b)
{ {
return strcmp(*(const char **)a, *(const char **)b); return strcmp(*(const char **)a, *(const char **)b);