From 3a97be4fe96792f19674630edac762ae5a060b90 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 26 Nov 2005 13:39:47 +0100 Subject: [PATCH] Implemented handling of the "CA PMT Reply" for CAMs; some preparations for being able to record several encrypted channels from the same transponder --- CONTRIBUTORS | 1 + HISTORY | 9 +- ci.c | 454 +++++++++++++++++++++++++++++++++++++-------------- ci.h | 69 +++++--- device.c | 34 +++- dvbdevice.c | 54 ++---- 6 files changed, 440 insertions(+), 181 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c3b50b17..f2927b0e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1249,6 +1249,7 @@ Marco Schl for fixing a race condition in the SPU decoder for fixing initializing the day index when editing the weekday parameter of a repeating timer + for figuring out some obscure length bytes the the CA PMT Reply data of AlphaCrypt CAMs Jürgen Schmitz for reporting a bug in displaying the current channel when switching via the SVDRP diff --git a/HISTORY b/HISTORY index 8ba1bb23..255cf521 100644 --- a/HISTORY +++ b/HISTORY @@ -3934,7 +3934,7 @@ Video Disk Recorder Revision History layers of subdirectories. - Removed EPG bugfix #0, because it removed actually important data. -2005-11-11: Version 1.3.37 +2005-11-26: Version 1.3.37 - Added compiler options "-fPIC -g" to all plugins (thanks to Rolf Ahrenberg). - Fixed initializing the day index when editing the weekday parameter of a @@ -3946,3 +3946,10 @@ Video Disk Recorder Revision History to Werner Fink). Live DD mode requires a full featured DVB card and a LinuxDVB driver with firmware version 0x2622 or higher. Older versions will use Transfer Mode just like before. +- Implemented handling of the "CA PMT Reply" for CAMs (thanks to Marco + Schlüßler for figuring out some obscure length bytes in the CA PMT Reply + data of AlphaCrypt CAMs). +- Some preparations for being able to record several encrypted channels from + the same transponder at the same time (or record and view different encrypted + channels), provided the CAM in use can handle this. This is work in progress + and isn't actively used, yet. diff --git a/ci.c b/ci.c index 3fe321e6..163ad970 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 1.39 2005/11/04 14:18:52 kls Exp $ + * $Id: ci.c 1.40 2005/11/26 13:36:51 kls Exp $ */ #include "ci.h" @@ -845,10 +845,118 @@ bool cCiApplicationInformation::EnterMenu(void) return false; } +// --- cCiCaPmt -------------------------------------------------------------- + +// Ca Pmt List Management: + +#define CPLM_MORE 0x00 +#define CPLM_FIRST 0x01 +#define CPLM_LAST 0x02 +#define CPLM_ONLY 0x03 +#define CPLM_ADD 0x04 +#define CPLM_UPDATE 0x05 + +// Ca Pmt Cmd Ids: + +#define CPCI_OK_DESCRAMBLING 0x01 +#define CPCI_OK_MMI 0x02 +#define CPCI_QUERY 0x03 +#define CPCI_NOT_SELECTED 0x04 + +class cCiCaPmt : public cListObject { + friend class cCiConditionalAccessSupport; +private: + uint8_t cmdId; + int length; + int esInfoLengthPos; + uint8_t capmt[2048]; ///< XXX is there a specified maximum? + int caDescriptorsLength; + uint8_t caDescriptors[2048]; + bool streamFlag; + void AddCaDescriptors(int Length, const uint8_t *Data); +public: + cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds); + void SetListManagement(uint8_t ListManagement); + bool Valid(void); + void AddPid(int Pid, uint8_t StreamType); + }; + +cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds) +{ + cmdId = CmdId; + caDescriptorsLength = GetCaDescriptors(Source, Transponder, ProgramNumber, CaSystemIds, sizeof(caDescriptors), caDescriptors, streamFlag); + length = 0; + capmt[length++] = CPLM_ONLY; + capmt[length++] = (ProgramNumber >> 8) & 0xFF; + capmt[length++] = ProgramNumber & 0xFF; + capmt[length++] = 0x01; // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1 + esInfoLengthPos = length; + capmt[length++] = 0x00; // program_info_length H (at program level) + capmt[length++] = 0x00; // program_info_length L + if (!streamFlag) + AddCaDescriptors(caDescriptorsLength, caDescriptors); +} + +void cCiCaPmt::SetListManagement(uint8_t ListManagement) +{ + capmt[0] = ListManagement; +} + +bool cCiCaPmt::Valid(void) +{ + return caDescriptorsLength > 0; +} + +void cCiCaPmt::AddPid(int Pid, uint8_t StreamType) +{ + if (Pid) { + //XXX buffer overflow check??? + capmt[length++] = StreamType; + capmt[length++] = (Pid >> 8) & 0xFF; + capmt[length++] = Pid & 0xFF; + esInfoLengthPos = length; + capmt[length++] = 0x00; // ES_info_length H (at ES level) + capmt[length++] = 0x00; // ES_info_length L + if (streamFlag) + AddCaDescriptors(caDescriptorsLength, caDescriptors); + } +} + +void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data) +{ + if (esInfoLengthPos) { + if (length + Length < int(sizeof(capmt))) { + capmt[length++] = cmdId; + memcpy(capmt + length, Data, Length); + length += Length; + int l = length - esInfoLengthPos - 2; + capmt[esInfoLengthPos] = (l >> 8) & 0xFF; + capmt[esInfoLengthPos + 1] = l & 0xFF; + } + else + esyslog("ERROR: buffer overflow in CA descriptor"); + esInfoLengthPos = 0; + } + else + esyslog("ERROR: adding CA descriptor without Pid!"); +} + // --- cCiConditionalAccessSupport ------------------------------------------- #define MAXCASYSTEMIDS 16 +// CA Enable Ids: + +#define CAEI_POSSIBLE 0x01 +#define CAEI_POSSIBLE_COND_PURCHASE 0x02 +#define CAEI_POSSIBLE_COND_TECHNICAL 0x03 +#define CAEI_NOT_POSSIBLE_ENTITLEMENT 0x71 +#define CAEI_NOT_POSSIBLE_TECHNICAL 0x73 + +#define CA_ENABLE_FLAG 0x80 + +#define CA_ENABLE(x) (((x) & CA_ENABLE_FLAG) ? (x) & ~CA_ENABLE_FLAG : 0) + class cCiConditionalAccessSupport : public cCiSession { private: int state; @@ -858,14 +966,15 @@ public: cCiConditionalAccessSupport(int SessionId, cCiTransportConnection *Tc); virtual bool Process(int Length = 0, const uint8_t *Data = NULL); const unsigned short *GetCaSystemIds(void) { return caSystemIds; } - bool SendPMT(cCiCaPmt &CaPmt); + bool SendPMT(cCiCaPmt *CaPmt); + bool ReceivedReply(bool CanDescramble = false); }; cCiConditionalAccessSupport::cCiConditionalAccessSupport(int SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc) { dbgprotocol("New Conditional Access Support (session id %d)\n", SessionId); - state = 0; + state = 0; // inactive caSystemIds[numCaSystemIds = 0] = 0; } @@ -892,7 +1001,58 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) } dbgprotocol("\n"); } - state = 2; + state = 2; // got ca info + break; + case AOT_CA_PMT_REPLY: { + dbgprotocol("%d: <== Ca Pmt Reply", SessionId()); + state = 4; // got ca pmt reply + int l = 0; + const uint8_t *d = GetData(Data, l); + if (l > 1) { + unsigned short pnr = ((unsigned short)(*d) << 8) | *(d + 1); + dbgprotocol(" %d", pnr); + d += 2; + l -= 2; + if (l > 0) { + dbgprotocol(" %02X", *d); + d += 1; + l -= 1; + if (l > 0) { + if (l % 3 == 0 && l > 1) { + // The EN50221 standard defines that the next byte is supposed + // to be the CA_enable value at programme level. However, there are + // CAMs (for instance the AlphaCrypt with firmware <= 3.05) that + // insert a two byte length field here. + // This is a workaround to skip this length field: + unsigned short len = ((unsigned short)(*d) << 8) | *(d + 1); + if (len == l - 2) { + d += 2; + l -= 2; + } + } + unsigned char caepl = *d; + dbgprotocol(" %02X", caepl); + d += 1; + l -= 1; + bool ok = true; + if (l <= 2) + ok = CA_ENABLE(caepl) == CAEI_POSSIBLE; + while (l > 2) { + unsigned short pid = ((unsigned short)(*d) << 8) | *(d + 1); + unsigned char caees = *(d + 2); + dbgprotocol(" %d=%02X", pid, caees); + d += 3; + l -= 3; + if (CA_ENABLE(caees) != CAEI_POSSIBLE) + ok = false; + } + if (ok) + state = 5; // descrambling possible + } + } + } + dbgprotocol("\n"); + } break; default: esyslog("ERROR: CI conditional access support: unknown tag %06X", Tag); return false; @@ -901,20 +1061,27 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) else if (state == 0) { dbgprotocol("%d: ==> Ca Info Enq\n", SessionId()); SendData(AOT_CA_INFO_ENQ); - state = 1; + state = 1; // enquired ca info } return true; } -bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt &CaPmt) +bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt) { - if (state == 2) { - SendData(AOT_CA_PMT, CaPmt.length, CaPmt.capmt); + if (CaPmt && state >= 2) { + dbgprotocol("%d: ==> Ca Pmt\n", SessionId()); + SendData(AOT_CA_PMT, CaPmt->length, CaPmt->capmt); + state = 3; // sent ca pmt return true; } return false; } +bool cCiConditionalAccessSupport::ReceivedReply(bool CanDescramble) +{ + return state >= (CanDescramble ? 5 : 4); +} + // --- cCiDateTime ----------------------------------------------------------- class cCiDateTime : public cCiSession { @@ -1307,78 +1474,6 @@ bool cCiEnquiry::Abort(void) return mmi && mmi->SendCloseMMI(); } -// --- cCiCaPmt -------------------------------------------------------------- - -// Ca Pmt List Management: - -#define CPLM_MORE 0x00 -#define CPLM_FIRST 0x01 -#define CPLM_LAST 0x02 -#define CPLM_ONLY 0x03 -#define CPLM_ADD 0x04 -#define CPLM_UPDATE 0x05 - -// Ca Pmt Cmd Ids: - -#define CPCI_OK_DESCRAMBLING 0x01 -#define CPCI_OK_MMI 0x02 -#define CPCI_QUERY 0x03 -#define CPCI_NOT_SELECTED 0x04 - -cCiCaPmt::cCiCaPmt(int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds) -{ - caDescriptorsLength = GetCaDescriptors(Source, Transponder, ProgramNumber, CaSystemIds, sizeof(caDescriptors), caDescriptors, streamFlag); - length = 0; - capmt[length++] = CPLM_ONLY; - capmt[length++] = (ProgramNumber >> 8) & 0xFF; - capmt[length++] = ProgramNumber & 0xFF; - capmt[length++] = 0x01; // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1 - esInfoLengthPos = length; - capmt[length++] = 0x00; // program_info_length H (at program level) - capmt[length++] = 0x00; // program_info_length L - if (!streamFlag) - AddCaDescriptors(caDescriptorsLength, caDescriptors); -} - -bool cCiCaPmt::Valid(void) -{ - return caDescriptorsLength > 0; -} - -void cCiCaPmt::AddPid(int Pid, uint8_t StreamType) -{ - if (Pid) { - //XXX buffer overflow check??? - capmt[length++] = StreamType; - capmt[length++] = (Pid >> 8) & 0xFF; - capmt[length++] = Pid & 0xFF; - esInfoLengthPos = length; - capmt[length++] = 0x00; // ES_info_length H (at ES level) - capmt[length++] = 0x00; // ES_info_length L - if (streamFlag) - AddCaDescriptors(caDescriptorsLength, caDescriptors); - } -} - -void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data) -{ - if (esInfoLengthPos) { - if (length + Length < int(sizeof(capmt))) { - capmt[length++] = CPCI_OK_DESCRAMBLING; - memcpy(capmt + length, Data, Length); - length += Length; - int l = length - esInfoLengthPos - 2; - capmt[esInfoLengthPos] = (l >> 8) & 0xFF; - capmt[esInfoLengthPos + 1] = l & 0xFF; - } - else - esyslog("ERROR: buffer overflow in CA descriptor"); - esInfoLengthPos = 0; - } - else - esyslog("ERROR: adding CA descriptor without Pid!"); -} - // -- cCiHandler ------------------------------------------------------------- cCiHandler::cCiHandler(int Fd, int NumSlots) @@ -1393,6 +1488,7 @@ cCiHandler::cCiHandler(int Fd, int NumSlots) moduleReady[i] = false; tpl = new cCiTransportLayer(Fd, numSlots); tc = NULL; + source = transponder = 0; } cCiHandler::~cCiHandler() @@ -1556,58 +1652,98 @@ bool cCiHandler::Ready(void) return true; } -bool cCiHandler::Process(void) +bool cCiHandler::Process(int Slot) { bool result = true; cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - tc = tpl->Process(Slot); - if (tc) { - int Length; - const uint8_t *Data = tc->Data(Length); - if (Data && Length > 1) { - switch (*Data) { - case ST_SESSION_NUMBER: if (Length > 4) { - int SessionId = ntohs(get_unaligned((uint16_t *)&Data[2])); - cCiSession *Session = GetSessionBySessionId(SessionId); - if (Session) - Session->Process(Length - 4, Data + 4); - else - esyslog("ERROR: unknown session id: %d", SessionId); - } - break; - case ST_OPEN_SESSION_REQUEST: OpenSession(Length, Data); - break; - case ST_CLOSE_SESSION_REQUEST: if (Length == 4) - CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2]))); - break; - case ST_CREATE_SESSION_RESPONSE: //XXX fall through to default - case ST_CLOSE_SESSION_RESPONSE: //XXX fall through to default - default: esyslog("ERROR: unknown session tag: %02X", *Data); - } + for (int slot = 0; slot < numSlots; slot++) { + if (Slot < 0 || slot == Slot) { + tc = tpl->Process(slot); + if (tc) { + int Length; + const uint8_t *Data = tc->Data(Length); + if (Data && Length > 1) { + switch (*Data) { + case ST_SESSION_NUMBER: if (Length > 4) { + int SessionId = ntohs(get_unaligned((uint16_t *)&Data[2])); + cCiSession *Session = GetSessionBySessionId(SessionId); + if (Session) + Session->Process(Length - 4, Data + 4); + else + esyslog("ERROR: unknown session id: %d", SessionId); + } + break; + case ST_OPEN_SESSION_REQUEST: OpenSession(Length, Data); + break; + case ST_CLOSE_SESSION_REQUEST: if (Length == 4) + CloseSession(ntohs(get_unaligned((uint16_t *)&Data[2]))); + break; + case ST_CREATE_SESSION_RESPONSE: //XXX fall through to default + case ST_CLOSE_SESSION_RESPONSE: //XXX fall through to default + default: esyslog("ERROR: unknown session tag: %02X", *Data); + } + } + } + else if (CloseAllSessions(slot)) { + tpl->ResetSlot(slot); + result = false; + } + else if (tpl->ModuleReady(slot)) { + dbgprotocol("Module ready in slot %d\n", slot); + moduleReady[slot] = true; + tpl->NewConnection(slot); } } - else if (CloseAllSessions(Slot)) { - tpl->ResetSlot(Slot); - result = false; - } - else if (tpl->ModuleReady(Slot)) { - dbgprotocol("Module ready in slot %d\n", Slot); - moduleReady[Slot] = true; - tpl->NewConnection(Slot); - } } + SendCaPmt(); bool UserIO = false; for (int i = 0; i < MAX_CI_SESSION; i++) { if (sessions[i] && sessions[i]->Process()) UserIO |= sessions[i]->HasUserIO(); } hasUserIO = UserIO; - if (newCaSupport) - newCaSupport = result = false; // triggers new SetCaPmt at caller! return result; } +void cCiHandler::SendCaPmt(void) +{ + cMutexLock MutexLock(&mutex); + if (newCaSupport) { + newCaSupport = false; + for (int Slot = 0; Slot < numSlots; Slot++) { + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); + if (cas) { + // build the list of CA_PMT data: + cList CaPmtList; + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + bool Active = false; + cCiCaPmt *CaPmt = new cCiCaPmt(CPCI_OK_DESCRAMBLING, source, transponder, p->programNumber, GetCaSystemIds(Slot)); + if (CaPmt->Valid()) { + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->active) { + CaPmt->AddPid(q->pid, q->streamType); + Active = true; + } + } + } + if (Active) + CaPmtList.Add(CaPmt); + else + delete CaPmt; + } + // send the CA_PMT data: + uint8_t ListManagement = CaPmtList.Count() > 1 ? CPLM_FIRST : CPLM_ONLY; + for (cCiCaPmt *CaPmt = CaPmtList.First(); CaPmt; CaPmt = CaPmtList.Next(CaPmt)) { + CaPmt->SetListManagement(ListManagement); + if (!cas->SendPMT(CaPmt)) + newCaSupport = true; + ListManagement = CaPmt->Next() && CaPmt->Next()->Next() ? CPLM_MORE : CPLM_LAST; + } + } + } + } +} + bool cCiHandler::EnterMenu(int Slot) { cMutexLock MutexLock(&mutex); @@ -1676,11 +1812,89 @@ bool cCiHandler::ProvidesCa(const unsigned short *CaSystemIds) return false; } -bool cCiHandler::SetCaPmt(cCiCaPmt &CaPmt, int Slot) +void cCiHandler::SetSource(int Source, int Transponder) { cMutexLock MutexLock(&mutex); - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - return cas && cas->SendPMT(CaPmt); + if (source != Source || transponder != Transponder) { + //XXX if there are active entries, send an empty CA_PMT + caProgramList.Clear(); + } + source = Source; + transponder = Transponder; +} + +void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType) +{ + cMutexLock MutexLock(&mutex); + cCiCaProgramData *ProgramData = NULL; + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->programNumber == ProgramNumber) { + ProgramData = p; + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->pid == Pid) + return; + } + } + } + if (!ProgramData) + caProgramList.Add(ProgramData = new cCiCaProgramData(ProgramNumber)); + ProgramData->pidList.Add(new cCiCaPidData(Pid, StreamType)); +} + +void cCiHandler::SetPid(int Pid, bool Active) +{ + cMutexLock MutexLock(&mutex); + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->pid == Pid) { + q->active = Active; + return; + } + } + } +} + +bool cCiHandler::CanDecrypt(int ProgramNumber) +{ + cMutexLock MutexLock(&mutex); + for (int Slot = 0; Slot < numSlots; Slot++) { + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); + if (cas) { + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->programNumber == ProgramNumber) { + cCiCaPmt CaPmt(CPCI_QUERY, source, transponder, p->programNumber, GetCaSystemIds(Slot));//XXX??? + if (CaPmt.Valid()) { + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { +//XXX if (q->active) + CaPmt.AddPid(q->pid, q->streamType); + } + } + if (!cas->SendPMT(&CaPmt)) + return false;//XXX + //XXX + time_t timeout = time(NULL) + 3;//XXX + while (time(NULL) <= timeout) { + Process(Slot); + cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); + if (!cas) + return false;//XXX + if (cas->ReceivedReply(true)) + return true; + //XXX remember if a slot doesn't receive a reply + } + break; + } + } + } + } + return false; +} + +void cCiHandler::StartDecrypting(void) +{ + cMutexLock MutexLock(&mutex); + newCaSupport = true; + SendCaPmt(); } bool cCiHandler::Reset(int Slot) diff --git a/ci.h b/ci.h index ae760155..f1e289ad 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 1.18 2005/10/30 12:31:14 kls Exp $ + * $Id: ci.h 1.19 2005/11/26 13:37:42 kls Exp $ */ #ifndef __CI_H @@ -13,6 +13,7 @@ #include #include #include "thread.h" +#include "tools.h" class cCiMMI; @@ -65,25 +66,32 @@ public: bool Abort(void); }; -class cCiCaPmt { - friend class cCiConditionalAccessSupport; -private: - int length; - int esInfoLengthPos; - uint8_t capmt[2048]; ///< XXX is there a specified maximum? - int caDescriptorsLength; - uint8_t caDescriptors[2048]; - bool streamFlag; - void AddCaDescriptors(int Length, const uint8_t *Data); -public: - cCiCaPmt(int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds); - bool Valid(void); - void AddPid(int Pid, uint8_t StreamType); - }; - #define MAX_CI_SESSION 16 //XXX #define MAX_CI_SLOT 16 +class cCiCaPidData : public cListObject { +public: + bool active; + int pid; + int streamType; + cCiCaPidData(int Pid, int StreamType) + { + active = false; + pid = Pid; + streamType = StreamType; + } + }; + +class cCiCaProgramData : public cListObject { +public: + int programNumber; + cList pidList; + cCiCaProgramData(int ProgramNumber) + { + programNumber = ProgramNumber; + } + }; + class cCiSession; class cCiTransportLayer; class cCiTransportConnection; @@ -99,6 +107,9 @@ private: cCiSession *sessions[MAX_CI_SESSION]; cCiTransportLayer *tpl; cCiTransportConnection *tc; + int source; + int transponder; + cList caProgramList; int ResourceIdToInt(const uint8_t *Data); bool Send(uint8_t Tag, int SessionId, int ResourceId = 0, int Status = -1); cCiSession *GetSessionBySessionId(int SessionId); @@ -108,12 +119,15 @@ private: bool CloseSession(int SessionId); int CloseAllSessions(int Slot); cCiHandler(int Fd, int NumSlots); + void SendCaPmt(void); public: ~cCiHandler(); static cCiHandler *CreateCiHandler(const char *FileName); int NumSlots(void) { return numSlots; } bool Ready(void); - bool Process(void); + bool Process(int Slot = -1); + ///< Processes the given Slot. If Slot is -1, all slots are processed. + ///< Returns false in case of an error. bool HasUserIO(void) { return hasUserIO; } bool EnterMenu(int Slot); cCiMenu *GetMenu(void); @@ -121,7 +135,24 @@ public: const char *GetCamName(int Slot); const unsigned short *GetCaSystemIds(int Slot); bool ProvidesCa(const unsigned short *CaSystemIds); //XXX Slot??? - bool SetCaPmt(cCiCaPmt &CaPmt, int Slot); + void SetSource(int Source, int Transponder); + ///< Sets the Source and Transponder of the device this cCiHandler is + ///< currently tuned to. If Source or Transponder are different than + ///< what was given in a previous call to SetSource(), any previously + ///< added PIDs will be cleared. + void AddPid(int ProgramNumber, int Pid, int StreamType); + ///< Adds the given PID information to the list of PIDs. A later call + ///< to SetPid() will (de)activate one of these entries. + void SetPid(int Pid, bool Active); + ///< Sets the given Pid (which has previously been added through a + ///< call to AddPid()) to Active. If Active is true, a later call to + ///< StartDecrypting() will send the full list of currently active CA_PMT + ///< entries to the CAM, including this one. + bool CanDecrypt(int ProgramNumber); + ///< XXX + void StartDecrypting(void); + ///< Triggers sending all currently active CA_PMT entries to the CAM, + ///< so that it will start decrypting. bool Reset(int Slot); }; diff --git a/device.c b/device.c index 0da5a492..1b4cec45 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 1.111 2005/11/05 15:23:58 kls Exp $ + * $Id: device.c 1.112 2005/11/26 12:56:09 kls Exp $ */ #include "device.h" @@ -397,6 +397,8 @@ bool cDevice::AddPid(int Pid, ePidType PidType) DelPid(Pid, PidType); return false; } + if (ciHandler) + ciHandler->SetPid(Pid, true); } PRINTPIDS("a"); return true; @@ -424,6 +426,8 @@ bool cDevice::AddPid(int Pid, ePidType PidType) DelPid(Pid, PidType); return false; } + if (ciHandler) + ciHandler->SetPid(Pid, true); } } return true; @@ -450,6 +454,8 @@ void cDevice::DelPid(int Pid, ePidType PidType) if (pidHandles[n].used == 0) { pidHandles[n].handle = -1; pidHandles[n].pid = 0; + if (ciHandler) + ciHandler->SetPid(Pid, false); } } PRINTPIDS("E"); @@ -601,12 +607,34 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) sectionHandler->SetStatus(false); sectionHandler->SetChannel(NULL); } + // Tell the ciHandler about the channel switch and add all PIDs of this + // channel to it, for possible later decryption: + if (ciHandler) { + ciHandler->SetSource(Channel->Source(), Channel->Transponder()); +// Men at work - please stand clear! ;-) +#ifdef XXX_DO_MULTIPLE_CA_CHANNELS + if (Channel->Ca() > CACONFBASE) { +#endif + ciHandler->AddPid(Channel->Sid(), Channel->Vpid(), 2); + for (const int *Apid = Channel->Apids(); *Apid; Apid++) + ciHandler->AddPid(Channel->Sid(), *Apid, 4); + for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) + ciHandler->AddPid(Channel->Sid(), *Dpid, 0); +#ifdef XXX_DO_MULTIPLE_CA_CHANNELS + bool CanDecrypt = ciHandler->CanDecrypt(Channel->Sid());//XXX + dsyslog("CanDecrypt %d %d %d %s", CardIndex() + 1, CanDecrypt, Channel->Number(), Channel->Name());//XXX + } +#endif + } if (SetChannelDevice(Channel, LiveView)) { // Start section handling: if (sectionHandler) { sectionHandler->SetChannel(Channel); sectionHandler->SetStatus(true); } + // Start decrypting any PIDs the might have been set in SetChannelDevice(): + if (ciHandler) + ciHandler->StartDecrypting(); } else Result = scrFailed; @@ -1168,6 +1196,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) Unlock(); if (!Running()) Start(); + if (ciHandler) + ciHandler->StartDecrypting(); return true; } } @@ -1194,6 +1224,8 @@ void cDevice::Detach(cReceiver *Receiver) else if (receiver[i]) receiversLeft = true; } + if (ciHandler) + ciHandler->StartDecrypting(); if (!receiversLeft) Cancel(3); } diff --git a/dvbdevice.c b/dvbdevice.c index b59076cd..30ce1e4a 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbdevice.c 1.137 2005/11/11 14:53:52 kls Exp $ + * $Id: dvbdevice.c 1.138 2005/11/26 13:23:11 kls Exp $ */ #include "dvbdevice.h" @@ -35,6 +35,7 @@ extern "C" { #define DO_REC_AND_PLAY_ON_PRIMARY_DEVICE 1 #define DO_MULTIPLE_RECORDINGS 1 +//#define DO_MULTIPLE_CA_CHANNELS #define DEV_VIDEO "/dev/video" #define DEV_DVB_ADAPTER "/dev/dvb/adapter" @@ -69,15 +70,13 @@ static int DvbOpen(const char *Name, int n, int Mode, bool ReportError = false) class cDvbTuner : public cThread { private: - enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked, tsCam }; + enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked }; int fd_frontend; int cardIndex; fe_type_t frontendType; cCiHandler *ciHandler; cChannel channel; const char *diseqcCommands; - bool useCa; - time_t startTime; eTunerStatus tunerStatus; cMutex mutex; cCondVar locked; @@ -89,7 +88,7 @@ public: cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCiHandler *CiHandler); virtual ~cDvbTuner(); bool IsTunedTo(const cChannel *Channel) const; - void Set(const cChannel *Channel, bool Tune, bool UseCa); + void Set(const cChannel *Channel, bool Tune); bool Locked(int TimeoutMs = 0); }; @@ -100,9 +99,7 @@ cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_type_t FrontendType, cCi frontendType = FrontendType; ciHandler = CiHandler; diseqcCommands = NULL; - useCa = false; tunerStatus = tsIdle; - startTime = time(NULL); if (frontendType == FE_QPSK) CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power SetDescription("tuner on device %d", cardIndex + 1); @@ -122,16 +119,11 @@ bool cDvbTuner::IsTunedTo(const cChannel *Channel) const return tunerStatus != tsIdle && channel.Source() == Channel->Source() && channel.Transponder() == Channel->Transponder(); } -void cDvbTuner::Set(const cChannel *Channel, bool Tune, bool UseCa) +void cDvbTuner::Set(const cChannel *Channel, bool Tune) { cMutexLock MutexLock(&mutex); if (Tune) tunerStatus = tsSet; - else if (tunerStatus == tsCam) - tunerStatus = tsLocked; - useCa = UseCa; - if (Channel->Ca() && tunerStatus != tsCam) - startTime = time(NULL); channel = *Channel; newSet.Broadcast(); } @@ -309,7 +301,6 @@ void cDvbTuner::Action(void) continue; case tsTuned: case tsLocked: - case tsCam: if (hasEvent) { if (event.status & FE_REINIT) { tunerStatus = tsSet; @@ -323,30 +314,10 @@ void cDvbTuner::Action(void) } } - if (ciHandler) { - if (ciHandler->Process() && useCa) { - if (tunerStatus == tsLocked) { - for (int Slot = 0; Slot < ciHandler->NumSlots(); Slot++) { - cCiCaPmt CaPmt(channel.Source(), channel.Transponder(), channel.Sid(), ciHandler->GetCaSystemIds(Slot)); - if (CaPmt.Valid()) { - CaPmt.AddPid(channel.Vpid(), 2); - CaPmt.AddPid(channel.Apid(0), 4); - CaPmt.AddPid(channel.Apid(1), 4); - CaPmt.AddPid(channel.Dpid(0), 0); - if (ciHandler->SetCaPmt(CaPmt, Slot)) { - tunerStatus = tsCam; - startTime = 0; - } - } - } - } - } - else if (tunerStatus > tsLocked) - tunerStatus = tsLocked; - } - // in the beginning we loop more often to let the CAM connection start up fast + if (ciHandler) + ciHandler->Process(); if (tunerStatus != tsTuned) - newSet.TimedWait(mutex, (ciHandler && (time(NULL) - startTime < 20)) ? 100 : 1000); + newSet.TimedWait(mutex, 1000); } } @@ -782,9 +753,12 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne if (dvbTuner->IsTunedTo(Channel)) { if (Channel->Vpid() && !HasPid(Channel->Vpid()) || Channel->Apid(0) && !HasPid(Channel->Apid(0))) { #ifdef DO_MULTIPLE_RECORDINGS +#ifndef DO_MULTIPLE_CA_CHANNELS if (Ca() > CACONFBASE || Channel->Ca() > CACONFBASE) needsDetachReceivers = Ca() != Channel->Ca(); - else if (!IsPrimaryDevice()) + else +#endif + if (!IsPrimaryDevice()) result = true; #ifdef DO_REC_AND_PLAY_ON_PRIMARY_DEVICE else @@ -834,13 +808,13 @@ bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) // Set the tuner: - dvbTuner->Set(Channel, DoTune, !EITScanner.UsesDevice(this)); //XXX 1.3: this is an ugly hack - find a cleaner solution//XXX + dvbTuner->Set(Channel, DoTune); // If this channel switch was requested by the EITScanner we don't wait for // a lock and don't set any live PIDs (the EITScanner will wait for the lock // by itself before setting any filters): - if (EITScanner.UsesDevice(this)) + if (EITScanner.UsesDevice(this)) //XXX return true; // PID settings: