From 2cc25e65f445162fad126f4e4b29bc380e002c0f Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 18 Mar 2017 16:33:59 +0100 Subject: [PATCH] Implemented support for MTD --- HISTORY | 6 +- Makefile | 4 +- ci.c | 346 ++++++++++++++++++++++++++++++++++++++++++++----------- ci.h | 61 +++++++++- device.c | 86 ++++++++++++-- menu.c | 34 +++--- mtd.c | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++ mtd.h | 187 ++++++++++++++++++++++++++++++ remux.h | 8 +- tools.h | 18 ++- 10 files changed, 969 insertions(+), 104 deletions(-) create mode 100644 mtd.c create mode 100644 mtd.h 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 #include #include "device.h" +#include "mtd.h" #include "pat.h" #include "receiver.h" #include "remux.h" @@ -118,6 +119,7 @@ private: cVector 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(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(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(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 &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); + 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); 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; - } - } - } - } - resendPmt = false; - } - else { - cCiCaPmt CaPmt(CmdId, 0, 0, 0, NULL); - cas->SendPMT(&CaPmt); + 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 #include #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 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 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 &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 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 &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 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(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 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 &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);