diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2007-01-07 14:46:14 +0100 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2007-01-07 14:46:14 +0100 |
commit | 87dd5139ff6666d64e7e343bcff632b342c4c814 (patch) | |
tree | c2b8f2f437a09e1ad2f740adc574f3f1833d8fe3 /ci.c | |
parent | b4cab10eca558f6d90fa25a2a6e7fc3d90fac508 (diff) | |
download | vdr-87dd5139ff6666d64e7e343bcff632b342c4c814.tar.gz vdr-87dd5139ff6666d64e7e343bcff632b342c4c814.tar.bz2 |
CAM handling refactored; multiple recordings with one CAM; automatic CAM selection1.5.0
Diffstat (limited to 'ci.c')
-rw-r--r-- | ci.c | 1742 |
1 files changed, 975 insertions, 767 deletions
@@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ci.c 1.46 2007/01/05 10:08:46 kls Exp $ + * $Id: ci.c 1.47 2007/01/07 14:38:03 kls Exp $ */ #include "ci.h" @@ -17,38 +17,18 @@ #include <sys/ioctl.h> #include <time.h> #include <unistd.h> +#include "device.h" #include "pat.h" #include "tools.h" -/* these might come in handy in case you want to use this code without VDR's other files: -#ifndef MALLOC -#define MALLOC(type, size) (type *)malloc(sizeof(type) * (size)) -#endif - -#ifndef esyslog -static int SysLogLevel = 3; -#define esyslog(a...) void( (SysLogLevel > 0) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#define isyslog(a...) void( (SysLogLevel > 1) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#define dsyslog(a...) void( (SysLogLevel > 2) ? void(fprintf(stderr, a)), void(fprintf(stderr, "\n")) : void() ) -#endif -*/ - // Set these to 'true' for debug output: static bool DumpTPDUDataTransfer = false; static bool DebugProtocol = false; +static bool DumpPolls = false; +static bool DumpDateTime = false; #define dbgprotocol(a...) if (DebugProtocol) fprintf(stderr, a) -#define OK 0 -#define TIMEOUT -1 -#define ERROR -2 - -// --- Workarounds ----------------------------------------------------------- - -// The Irdeto AllCAM 4.7 (and maybe others, too) does not react on AOT_ENTER_MENU -// during the first few seconds of a newly established connection -#define WRKRND_TIME_BEFORE_ENTER_MENU 15 // seconds - // --- Helper functions ------------------------------------------------------ #define SIZE_INDICATOR 0x80 @@ -144,27 +124,29 @@ static char *GetString(int &Length, const uint8_t **Data) class cTPDU { private: int size; - uint8_t data[MAX_TPDU_SIZE]; + uint8_t buffer[MAX_TPDU_SIZE]; const uint8_t *GetData(const uint8_t *Data, int &Length); public: cTPDU(void) { size = 0; } cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); - uint8_t Slot(void) { return data[0]; } - uint8_t Tcid(void) { return data[1]; } - uint8_t Tag(void) { return data[2]; } - const uint8_t *Data(int &Length) { return GetData(data + 3, Length); } + uint8_t Slot(void) { return buffer[0]; } + uint8_t Tcid(void) { return buffer[1]; } + uint8_t Tag(void) { return buffer[2]; } + const uint8_t *Data(int &Length) { return GetData(buffer + 3, Length); } uint8_t Status(void); - int Write(int fd); - int Read(int fd); - void Dump(int fd, bool Outgoing); + uint8_t *Buffer(void) { return buffer; } + int Size(void) { return size; } + void SetSize(int Size) { size = Size; } + int MaxSize(void) { return sizeof(buffer); } + void Dump(int SlotNumber, bool Outgoing); }; cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t *Data) { size = 0; - data[0] = Slot; - data[1] = Tcid; - data[2] = Tag; + buffer[0] = Slot; + buffer[1] = Tcid; + buffer[2] = Tag; switch (Tag) { case T_RCV: case T_CREATE_TC: @@ -172,72 +154,51 @@ cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t case T_DELETE_TC: case T_DTC_REPLY: case T_REQUEST_TC: - data[3] = 1; // length - data[4] = Tcid; + buffer[3] = 1; // length + buffer[4] = Tcid; size = 5; break; case T_NEW_TC: case T_TC_ERROR: if (Length == 1) { - data[3] = 2; // length - data[4] = Tcid; - data[5] = Data[0]; + buffer[3] = 2; // length + buffer[4] = Tcid; + buffer[5] = Data[0]; size = 6; } else - esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length); + esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid); break; case T_DATA_LAST: case T_DATA_MORE: if (Length <= MAX_TPDU_DATA) { - uint8_t *p = data + 3; + uint8_t *p = buffer + 3; p = SetLength(p, Length + 1); *p++ = Tcid; if (Length) memcpy(p, Data, Length); - size = Length + (p - data); + size = Length + (p - buffer); } else - esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d", Tag, Length); + esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid); break; default: - esyslog("ERROR: unknown TPDU tag: 0x%02X", Tag); + esyslog("ERROR: unknown TPDU tag: 0x%02X (%d/%d)", Tag, Slot, Tcid); } } -int cTPDU::Write(int fd) -{ - Dump(fd, true); - if (size) - return safe_write(fd, data, size) == size ? OK : ERROR; - esyslog("ERROR: attemp to write TPDU with zero size"); - return ERROR; -} - -int cTPDU::Read(int fd) +void cTPDU::Dump(int SlotNumber, bool Outgoing) { - size = safe_read(fd, data, sizeof(data)); - if (size < 0) { - esyslog("ERROR: %m"); - size = 0; - return ERROR; - } - Dump(fd, false); - return OK; -} - -void cTPDU::Dump(int fd, bool Outgoing) -{ - if (DumpTPDUDataTransfer) { + if (DumpTPDUDataTransfer && (DumpPolls || Tag() != T_SB)) { #define MAX_DUMP 256 - fprintf(stderr, "%2d %s ", fd, Outgoing ? "-->" : "<--"); + fprintf(stderr, " %d: %s ", SlotNumber, Outgoing ? "-->" : "<--"); for (int i = 0; i < size && i < MAX_DUMP; i++) - fprintf(stderr, "%02X ", data[i]); + fprintf(stderr, "%02X ", buffer[i]); fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : ""); if (!Outgoing) { - fprintf(stderr, " "); + fprintf(stderr, " "); for (int i = 0; i < size && i < MAX_DUMP; i++) - fprintf(stderr, "%2c ", isprint(data[i]) ? data[i] : '.'); + fprintf(stderr, "%2c ", isprint(buffer[i]) ? buffer[i] : '.'); fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : ""); } } @@ -257,301 +218,51 @@ const uint8_t *cTPDU::GetData(const uint8_t *Data, int &Length) uint8_t cTPDU::Status(void) { - if (size >= 4 && data[size - 4] == T_SB && data[size - 3] == 2) { - //XXX test tcid??? - return data[size - 1]; - } + if (size >= 4 && buffer[size - 4] == T_SB && buffer[size - 3] == 2) + return buffer[size - 1]; return 0; } // --- cCiTransportConnection ------------------------------------------------ -enum eState { stIDLE, stCREATION, stACTIVE, stDELETION }; +#define MAX_SESSIONS_PER_TC 16 class cCiTransportConnection { - friend class cCiTransportLayer; private: - int fd; - uint8_t slot; + enum eState { stIDLE, stCREATION, stACTIVE, stDELETION }; + cCamSlot *camSlot; uint8_t tcid; eState state; - cTPDU *tpdu; - int lastResponse; - bool dataAvailable; - void Init(int Fd, uint8_t Slot, uint8_t Tcid); - int SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); - int RecvTPDU(void); - int CreateConnection(void); - int Poll(void); - eState State(void) { return state; } - int LastResponse(void) { return lastResponse; } - bool DataAvailable(void) { return dataAvailable; } -public: - cCiTransportConnection(void); - ~cCiTransportConnection(); - int Slot(void) const { return slot; } - int SendData(int Length, const uint8_t *Data); - int RecvData(void); - const uint8_t *Data(int &Length); - //XXX Close() - void Reset(void); - }; - -cCiTransportConnection::cCiTransportConnection(void) -{ - tpdu = NULL; - Init(-1, 0, 0); -} - -cCiTransportConnection::~cCiTransportConnection() -{ - delete tpdu; -} - -void cCiTransportConnection::Init(int Fd, uint8_t Slot, uint8_t Tcid) -{ - fd = Fd; - slot = Slot; - tcid = Tcid; - state = stIDLE; - if (fd >= 0 && !tpdu) - tpdu = new cTPDU; - lastResponse = ERROR; - dataAvailable = false; -//XXX Clear()??? -} - -int cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data) -{ - cTPDU TPDU(slot, tcid, Tag, Length, Data); - return TPDU.Write(fd); -} - -#define CAM_READ_TIMEOUT 3500 // ms - -int cCiTransportConnection::RecvTPDU(void) -{ - struct pollfd pfd[1]; - pfd[0].fd = fd; - pfd[0].events = POLLIN; - lastResponse = ERROR; - if (poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN) && tpdu->Read(fd) == OK && tpdu->Tcid() == tcid) { - switch (state) { - case stIDLE: break; - case stCREATION: if (tpdu->Tag() == T_CTC_REPLY) { - dataAvailable = tpdu->Status() & DATA_INDICATOR; - state = stACTIVE; - lastResponse = tpdu->Tag(); - } - break; - case stACTIVE: switch (tpdu->Tag()) { - case T_SB: - case T_DATA_LAST: - case T_DATA_MORE: - case T_REQUEST_TC: break; - case T_DELETE_TC: if (SendTPDU(T_DTC_REPLY) != OK) - return ERROR; - Init(fd, slot, tcid); - break; - default: return ERROR; - } - dataAvailable = tpdu->Status() & DATA_INDICATOR; - lastResponse = tpdu->Tag(); - break; - case stDELETION: if (tpdu->Tag() == T_DTC_REPLY) { - Init(fd, slot, tcid); - //XXX Status()??? - lastResponse = tpdu->Tag(); - } - break; - } - } - else if (FATALERRNO) { - esyslog("ERROR: CAM: Read failed: slot %d, tcid %d - %m", slot, tcid); - Init(-1, slot, tcid); - } - return lastResponse; -} - -int cCiTransportConnection::SendData(int Length, const uint8_t *Data) -{ - while (state == stACTIVE && Length > 0) { - uint8_t Tag = T_DATA_LAST; - int l = Length; - if (l > MAX_TPDU_DATA) { - Tag = T_DATA_MORE; - l = MAX_TPDU_DATA; - } - if (SendTPDU(Tag, l, Data) != OK || RecvTPDU() != T_SB) - break; - Length -= l; - Data += l; - } - return Length ? ERROR : OK; -} - -int cCiTransportConnection::RecvData(void) -{ - if (SendTPDU(T_RCV) == OK) - return RecvTPDU(); - return ERROR; -} - -const uint8_t *cCiTransportConnection::Data(int &Length) -{ - return tpdu->Data(Length); -} - -#define MAX_CONNECT_RETRIES 2 - -int cCiTransportConnection::CreateConnection(void) -{ - if (state == stIDLE) { - if (SendTPDU(T_CREATE_TC) == OK) { - state = stCREATION; - if (RecvTPDU() == T_CTC_REPLY) - return OK; - // the following is a workaround for CAMs that don't quite follow the specs... - else { - for (int i = 0; i < MAX_CONNECT_RETRIES; i++) { - dsyslog("CAM: retrying to establish connection"); - if (RecvTPDU() == T_CTC_REPLY) { - dsyslog("CAM: connection established"); - return OK; - } - } - return ERROR; - } - } - } - return ERROR; -} - -int cCiTransportConnection::Poll(void) -{ - if (state == stACTIVE) { - if (SendTPDU(T_DATA_LAST) == OK) - return RecvTPDU(); - } - return ERROR; -} - -void cCiTransportConnection::Reset(void) -{ - Init(-1, 0, 0); -} - -// --- cCiTransportLayer ----------------------------------------------------- - -#define MAX_CI_CONNECT 16 // maximum possible value is 254 - -class cCiTransportLayer { -private: - int fd; - int numSlots; - cCiTransportConnection tc[MAX_CI_CONNECT]; + bool createConnectionRequested; + bool deleteConnectionRequested; + bool hasUserIO; + cTimeMs alive; + cTimeMs timer; + cCiSession *sessions[MAX_SESSIONS_PER_TC + 1]; // session numbering starts with 1 + void SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL); + void SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId = 0, int Status = -1); + void Poll(void); + uint32_t ResourceIdToInt(const uint8_t *Data); + cCiSession *GetSessionBySessionId(uint16_t SessionId); + void OpenSession(int Length, const uint8_t *Data); + void CloseSession(uint16_t SessionId); + void HandleSessions(cTPDU *TPDU); public: - cCiTransportLayer(int Fd, int NumSlots); - cCiTransportConnection *NewConnection(int Slot); - bool ResetSlot(int Slot, bool Wait = false); - bool ModuleReady(int Slot); - cCiTransportConnection *Process(int Slot); + cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid); + virtual ~cCiTransportConnection(); + cCamSlot *CamSlot(void) { return camSlot; } + uint8_t Tcid(void) const { return tcid; } + void CreateConnection(void) { createConnectionRequested = true; } + void DeleteConnection(void) { deleteConnectionRequested = true; } + const char *GetCamName(void); + bool Ready(void); + bool HasUserIO(void) { return hasUserIO; } + void SendData(int Length, const uint8_t *Data); + bool Process(cTPDU *TPDU = NULL); + cCiSession *GetSessionByResourceId(uint32_t ResourceId); }; -cCiTransportLayer::cCiTransportLayer(int Fd, int NumSlots) -{ - fd = Fd; - numSlots = NumSlots; - for (int s = 0; s < numSlots; s++) - ResetSlot(s); - cCondWait::SleepMs(2000); -} - -cCiTransportConnection *cCiTransportLayer::NewConnection(int Slot) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - if (tc[i].State() == stIDLE) { - dbgprotocol("Creating connection: slot %d, tcid %d\n", Slot, i + 1); - tc[i].Init(fd, Slot, i + 1); - if (tc[i].CreateConnection() == OK) - return &tc[i]; - break; - } - } - return NULL; -} - -bool cCiTransportLayer::ResetSlot(int Slot, bool Wait) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - if (tc[i].State() != stIDLE && tc[i].Slot() == Slot) - tc[i].Reset(); - } - dbgprotocol("Resetting slot %d...", Slot); - if (ioctl(fd, CA_RESET, 1 << Slot) != -1) { - if (Wait) - cCondWait::SleepMs(2000); - dbgprotocol("ok.\n"); - return true; - } - else - esyslog("ERROR: can't reset CAM slot %d: %m", Slot); - dbgprotocol("failed!\n"); - return false; -} - -bool cCiTransportLayer::ModuleReady(int Slot) -{ - ca_slot_info_t sinfo; - sinfo.num = Slot; - if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) - return sinfo.flags & CA_CI_MODULE_READY; - else - esyslog("ERROR: can't get info on CAM slot %d: %m", Slot); - return false; -} - -cCiTransportConnection *cCiTransportLayer::Process(int Slot) -{ - for (int i = 0; i < MAX_CI_CONNECT; i++) { - cCiTransportConnection *Tc = &tc[i]; - if (Tc->Slot() == Slot) { - switch (Tc->State()) { - case stCREATION: - case stACTIVE: - if (!Tc->DataAvailable()) { - if (Tc->Poll() != OK) - ;//XXX continue; - } - switch (Tc->LastResponse()) { - case T_REQUEST_TC: - //XXX - break; - case T_DATA_MORE: - case T_DATA_LAST: - case T_CTC_REPLY: - case T_SB: - if (Tc->DataAvailable()) - Tc->RecvData(); - break; - case TIMEOUT: - case ERROR: - default: - //XXX Tc->state = stIDLE;//XXX Init()??? - return NULL; - break; - } - //XXX this will only work with _one_ transport connection per slot! - return Tc; - break; - default: ; - } - } - } - return NULL; -} - -// -- cCiSession ------------------------------------------------------------- +// --- cCiSession ------------------------------------------------------------ // Session Tags: @@ -636,15 +347,15 @@ private: protected: int GetTag(int &Length, const uint8_t **Data); const uint8_t *GetData(const uint8_t *Data, int &Length); - int SendData(int Tag, int Length = 0, const uint8_t *Data = NULL); + void SendData(int Tag, int Length = 0, const uint8_t *Data = NULL); + cCiTransportConnection *Tc(void) { return tc; } public: cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc); virtual ~cCiSession(); - const cCiTransportConnection *Tc(void) { return tc; } uint16_t SessionId(void) { return sessionId; } uint32_t ResourceId(void) { return resourceId; } virtual bool HasUserIO(void) { return false; } - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiSession::cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc) @@ -679,7 +390,7 @@ const uint8_t *cCiSession::GetData(const uint8_t *Data, int &Length) return Length ? Data : NULL; } -int cCiSession::SendData(int Tag, int Length, const uint8_t *Data) +void cCiSession::SendData(int Tag, int Length, const uint8_t *Data) { uint8_t buffer[2048]; uint8_t *p = buffer; @@ -694,78 +405,75 @@ int cCiSession::SendData(int Tag, int Length, const uint8_t *Data) if (p - buffer + Length < int(sizeof(buffer))) { memcpy(p, Data, Length); p += Length; - return tc->SendData(p - buffer, buffer); + tc->SendData(p - buffer, buffer); } - esyslog("ERROR: CAM: data length (%d) exceeds buffer size", Length); - return ERROR; + else + esyslog("ERROR: CAM %d: data length (%d) exceeds buffer size", Tc()->CamSlot()->SlotNumber(), Length); } -bool cCiSession::Process(int Length, const uint8_t *Data) +void cCiSession::Process(int Length, const uint8_t *Data) { - return true; } -// -- cCiResourceManager ----------------------------------------------------- +// --- cCiResourceManager ---------------------------------------------------- class cCiResourceManager : public cCiSession { private: int state; public: cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiResourceManager::cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_RESOURCE_MANAGER, Tc) { - dbgprotocol("New Resource Manager (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Resource Manager (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; } -bool cCiResourceManager::Process(int Length, const uint8_t *Data) +void cCiResourceManager::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_PROFILE_ENQ: { - dbgprotocol("%d: <== Profile Enquiry\n", SessionId()); + dbgprotocol("Slot %d: <== Profile Enquiry (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); uint32_t resources[] = { htonl(RI_RESOURCE_MANAGER), htonl(RI_APPLICATION_INFORMATION), htonl(RI_CONDITIONAL_ACCESS_SUPPORT), htonl(RI_DATE_TIME), htonl(RI_MMI) }; - dbgprotocol("%d: ==> Profile\n", SessionId()); + dbgprotocol("Slot %d: ==> Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE, sizeof(resources), (uint8_t*)resources); state = 3; } break; case AOT_PROFILE: { - dbgprotocol("%d: <== Profile\n", SessionId()); + dbgprotocol("Slot %d: <== Profile (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); if (state == 1) { int l = 0; const uint8_t *d = GetData(Data, l); if (l > 0 && d) - esyslog("CI resource manager: unexpected data"); - dbgprotocol("%d: ==> Profile Change\n", SessionId()); + esyslog("ERROR: CAM %d: resource manager: unexpected data", Tc()->CamSlot()->SlotNumber()); + dbgprotocol("Slot %d: ==> Profile Change (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE_CHANGE); state = 2; } else { - esyslog("ERROR: CI resource manager: unexpected tag %06X in state %d", Tag, state); + esyslog("ERROR: CAM %d: resource manager: unexpected tag %06X in state %d", Tc()->CamSlot()->SlotNumber(), Tag, state); } } break; - default: esyslog("ERROR: CI resource manager: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: resource manager: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Profile Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Profile Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_PROFILE_ENQ); state = 1; } - return true; } // --- cCiApplicationInformation --------------------------------------------- @@ -773,7 +481,6 @@ bool cCiResourceManager::Process(int Length, const uint8_t *Data) class cCiApplicationInformation : public cCiSession { private: int state; - time_t creationTime; uint8_t applicationType; uint16_t applicationManufacturer; uint16_t manufacturerCode; @@ -781,7 +488,7 @@ private: public: cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc); virtual ~cCiApplicationInformation(); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); bool EnterMenu(void); const char *GetMenuString(void) { return menuString; } }; @@ -789,9 +496,8 @@ public: cCiApplicationInformation::cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_APPLICATION_INFORMATION, Tc) { - dbgprotocol("New Application Information (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Application Information (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; - creationTime = time(NULL); menuString = NULL; } @@ -800,13 +506,13 @@ cCiApplicationInformation::~cCiApplicationInformation() free(menuString); } -bool cCiApplicationInformation::Process(int Length, const uint8_t *Data) +void cCiApplicationInformation::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_APPLICATION_INFO: { - dbgprotocol("%d: <== Application Info\n", SessionId()); + dbgprotocol("Slot %d: <== Application Info (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); int l = 0; const uint8_t *d = GetData(Data, l); if ((l -= 1) < 0) break; @@ -819,28 +525,26 @@ bool cCiApplicationInformation::Process(int Length, const uint8_t *Data) d += 2; free(menuString); menuString = GetString(l, &d); - isyslog("CAM: %s, %02X, %04X, %04X", menuString, applicationType, applicationManufacturer, manufacturerCode);//XXX make externally accessible! - } + isyslog("CAM %d: %s, %02X, %04X, %04X", Tc()->CamSlot()->SlotNumber(), menuString, applicationType, applicationManufacturer, manufacturerCode); state = 2; + } break; - default: esyslog("ERROR: CI application information: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: application information: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Application Info Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Application Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_APPLICATION_INFO_ENQ); state = 1; } - return true; } bool cCiApplicationInformation::EnterMenu(void) { - if (state == 2 && time(NULL) - creationTime > WRKRND_TIME_BEFORE_ENTER_MENU) { - dbgprotocol("%d: ==> Enter Menu\n", SessionId()); + if (state == 2) { + dbgprotocol("Slot %d: ==> Enter Menu (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_ENTER_MENU); - return true;//XXX + return true; } return false; } @@ -875,13 +579,14 @@ private: 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); + cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds); + uint8_t CmdId(void) { return cmdId; } void SetListManagement(uint8_t ListManagement); - bool Valid(void); + uint8_t ListManagement(void) { return capmt[0]; } void AddPid(int Pid, uint8_t StreamType); }; -cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const unsigned short *CaSystemIds) +cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds) { cmdId = CmdId; caDescriptorsLength = GetCaDescriptors(Source, Transponder, ProgramNumber, CaSystemIds, sizeof(caDescriptors), caDescriptors, streamFlag); @@ -902,11 +607,6 @@ 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) { @@ -957,61 +657,77 @@ void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data) #define CA_ENABLE(x) (((x) & CA_ENABLE_FLAG) ? (x) & ~CA_ENABLE_FLAG : 0) +#define QUERY_WAIT_TIME 1000 // ms to wait before sending a query +#define QUERY_REPLY_TIMEOUT 2000 // ms to wait for a reply to a query + class cCiConditionalAccessSupport : public cCiSession { private: int state; int numCaSystemIds; - unsigned short caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated! + int caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated! + bool repliesToQuery; + cTimeMs timer; public: cCiConditionalAccessSupport(uint16_t 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 ReceivedReply(bool CanDescramble = false); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); + const int *GetCaSystemIds(void) { return caSystemIds; } + void SendPMT(cCiCaPmt *CaPmt); + bool RepliesToQuery(void) { return repliesToQuery; } + bool Ready(void) { return state >= 4; } + bool ReceivedReply(void) { return state >= 5; } + bool CanDecrypt(void) { return state == 6; } }; cCiConditionalAccessSupport::cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc) { - dbgprotocol("New Conditional Access Support (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Conditional Access Support (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); state = 0; // inactive caSystemIds[numCaSystemIds = 0] = 0; + repliesToQuery = false; } -bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) +void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_CA_INFO: { - dbgprotocol("%d: <== Ca Info", SessionId()); + dbgprotocol("Slot %d: <== Ca Info (%d)", Tc()->CamSlot()->SlotNumber(), SessionId()); numCaSystemIds = 0; int l = 0; const uint8_t *d = GetData(Data, l); while (l > 1) { - unsigned short id = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t id = ((uint16_t)(*d) << 8) | *(d + 1); dbgprotocol(" %04X", id); d += 2; l -= 2; if (numCaSystemIds < MAXCASYSTEMIDS) caSystemIds[numCaSystemIds++] = id; else { - esyslog("ERROR: too many CA system IDs!"); + esyslog("ERROR: CAM %d: too many CA system IDs!", Tc()->CamSlot()->SlotNumber()); break; } } caSystemIds[numCaSystemIds] = 0; dbgprotocol("\n"); + if (state == 1) { + timer.Set(QUERY_WAIT_TIME); // WORKAROUND: Alphacrypt 3.09 doesn't reply to QUERY immediately after reset + state = 2; // got ca info + } } - state = 2; // got ca info break; case AOT_CA_PMT_REPLY: { - dbgprotocol("%d: <== Ca Pmt Reply", SessionId()); - state = 4; // got 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()); + repliesToQuery = true; + } + state = 5; // 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); + uint16_t pnr = ((uint16_t)(*d) << 8) | *(d + 1); dbgprotocol(" %d", pnr); d += 2; l -= 2; @@ -1026,7 +742,7 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) // 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); + uint16_t len = ((uint16_t)(*d) << 8) | *(d + 1); if (len == l - 2) { d += 2; l -= 2; @@ -1040,7 +756,7 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) if (l <= 2) ok = CA_ENABLE(caepl) == CAEI_POSSIBLE; while (l > 2) { - unsigned short pid = ((unsigned short)(*d) << 8) | *(d + 1); + uint16_t pid = ((uint16_t)(*d) << 8) | *(d + 1); unsigned char caees = *(d + 2); dbgprotocol(" %d=%02X", pid, caees); d += 3; @@ -1049,39 +765,40 @@ bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data) ok = false; } if (ok) - state = 5; // descrambling possible + state = 6; // descrambling possible } } } dbgprotocol("\n"); } break; - default: esyslog("ERROR: CI conditional access support: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: conditional access support: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (state == 0) { - dbgprotocol("%d: ==> Ca Info Enq\n", SessionId()); + dbgprotocol("Slot %d: ==> Ca Info Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_CA_INFO_ENQ); state = 1; // enquired ca info } - return true; + else if (state == 2 && timer.TimedOut()) { + cCiCaPmt CaPmt(CPCI_QUERY, 0, 0, 0, NULL); + SendPMT(&CaPmt); + timer.Set(QUERY_REPLY_TIMEOUT); + state = 3; // waiting for reply + } + else if (state == 3 && timer.TimedOut()) { + dsyslog("CAM %d: doesn't reply to QUERY - only a single channel can be decrypted", Tc()->CamSlot()->SlotNumber()); + state = 4; // normal operation + } } -bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt) +void cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt) { if (CaPmt && state >= 2) { - dbgprotocol("%d: ==> Ca Pmt\n", SessionId()); + dbgprotocol("Slot %d: ==> Ca Pmt (%d) %d %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), CaPmt->ListManagement(), CaPmt->CmdId()); SendData(AOT_CA_PMT, CaPmt->length, CaPmt->capmt); - state = 3; // sent ca pmt - return true; + state = 4; // sent ca pmt } - return false; -} - -bool cCiConditionalAccessSupport::ReceivedReply(bool CanDescramble) -{ - return state >= (CanDescramble ? 5 : 4); } // --- cCiDateTime ----------------------------------------------------------- @@ -1090,10 +807,10 @@ class cCiDateTime : public cCiSession { private: int interval; time_t lastTime; - bool SendDateTime(void); + void SendDateTime(void); public: cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); }; cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc) @@ -1101,10 +818,10 @@ cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc) { interval = 0; lastTime = 0; - dbgprotocol("New Date Time (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new Date Time (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); } -bool cCiDateTime::SendDateTime(void) +void cCiDateTime::SendDateTime(void) { time_t t = time(NULL); struct tm tm_gmt; @@ -1116,17 +833,18 @@ bool cCiDateTime::SendDateTime(void) int L = (M == 1 || M == 2) ? 1 : 0; int MJD = 14956 + D + int((Y - L) * 365.25) + int((M + 1 + L * 12) * 30.6001); #define DEC2BCD(d) (((d / 10) << 4) + (d % 10)) - struct tTime { unsigned short mjd; uint8_t h, m, s; short offset; }; + struct tTime { uint16_t mjd; uint8_t h, m, s; short offset; }; tTime T = { mjd : htons(MJD), h : DEC2BCD(tm_gmt.tm_hour), m : DEC2BCD(tm_gmt.tm_min), s : DEC2BCD(tm_gmt.tm_sec), offset : htons(tm_loc.tm_gmtoff / 60) }; - dbgprotocol("%d: ==> Date Time\n", SessionId()); + bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer; + DumpTPDUDataTransfer &= DumpDateTime; + if (DumpDateTime) + dbgprotocol("Slot %d: ==> Date Time (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_DATE_TIME, 7, (uint8_t*)&T); - //XXX return value of all SendData() calls??? - return true; + DumpTPDUDataTransfer = OldDumpTPDUDataTransfer; } - return false; } -bool cCiDateTime::Process(int Length, const uint8_t *Data) +void cCiDateTime::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); @@ -1137,20 +855,18 @@ bool cCiDateTime::Process(int Length, const uint8_t *Data) const uint8_t *d = GetData(Data, l); if (l > 0) interval = *d; - dbgprotocol("%d: <== Date Time Enq, interval = %d\n", SessionId(), interval); + dbgprotocol("Slot %d: <== Date Time Enq (%d), interval = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), interval); lastTime = time(NULL); - return SendDateTime(); + SendDateTime(); } break; - default: esyslog("ERROR: CI date time: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: date time: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } else if (interval && time(NULL) - lastTime > interval) { lastTime = time(NULL); - return SendDateTime(); + SendDateTime(); } - return true; } // --- cCiMMI ---------------------------------------------------------------- @@ -1197,11 +913,11 @@ private: public: cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc); virtual ~cCiMMI(); - virtual bool Process(int Length = 0, const uint8_t *Data = NULL); + virtual void Process(int Length = 0, const uint8_t *Data = NULL); virtual bool HasUserIO(void) { return menu || enquiry; } cCiMenu *Menu(bool Clear = false); cCiEnquiry *Enquiry(bool Clear = false); - bool SendMenuAnswer(uint8_t Selection); + void SendMenuAnswer(uint8_t Selection); bool SendAnswer(const char *Text); bool SendCloseMMI(void); }; @@ -1209,7 +925,7 @@ public: cCiMMI::cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc) :cCiSession(SessionId, RI_MMI, Tc) { - dbgprotocol("New MMI (session id %d)\n", SessionId); + dbgprotocol("Slot %d: new MMI (session id %d)\n", Tc->CamSlot()->SlotNumber(), SessionId); menu = fetchedMenu = NULL; enquiry = fetchedEnquiry = NULL; } @@ -1236,21 +952,21 @@ char *cCiMMI::GetText(int &Length, const uint8_t **Data) int Tag = GetTag(Length, Data); if (Tag == AOT_TEXT_LAST) { char *s = GetString(Length, Data); - dbgprotocol("%d: <== Text Last '%s'\n", SessionId(), s); + dbgprotocol("Slot %d: <== Text Last (%d) '%s'\n", Tc()->CamSlot()->SlotNumber(), SessionId(), s); return s; } else - esyslog("CI MMI: unexpected text tag: %06X", Tag); + esyslog("ERROR: CAM %d: MMI: unexpected text tag: %06X", Tc()->CamSlot()->SlotNumber(), Tag); return NULL; } -bool cCiMMI::Process(int Length, const uint8_t *Data) +void cCiMMI::Process(int Length, const uint8_t *Data) { if (Data) { int Tag = GetTag(Length, &Data); switch (Tag) { case AOT_DISPLAY_CONTROL: { - dbgprotocol("%d: <== Display Control\n", SessionId()); + dbgprotocol("Slot %d: <== Display Control (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); int l = 0; const uint8_t *d = GetData(Data, l); if (l > 0) { @@ -1259,19 +975,18 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) if (l == 2 && *++d == MM_HIGH_LEVEL) { struct tDisplayReply { uint8_t id; uint8_t mode; }; tDisplayReply dr = { id : DRI_MMI_MODE_ACK, mode : MM_HIGH_LEVEL }; - dbgprotocol("%d: ==> Display Reply\n", SessionId()); + dbgprotocol("Slot %d: ==> Display Reply (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_DISPLAY_REPLY, 2, (uint8_t *)&dr); } break; - default: esyslog("CI MMI: unsupported display control command %02X", *d); - return false; + default: esyslog("ERROR: CAM %d: MMI: unsupported display control command %02X", Tc()->CamSlot()->SlotNumber(), *d); } } } break; case AOT_LIST_LAST: case AOT_MENU_LAST: { - dbgprotocol("%d: <== Menu Last\n", SessionId()); + dbgprotocol("Slot %d: <== Menu Last (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); delete menu; menu = new cCiMenu(this, Tag == AOT_MENU_LAST); int l = 0; @@ -1296,7 +1011,7 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) } break; case AOT_ENQ: { - dbgprotocol("%d: <== Enq\n", SessionId()); + dbgprotocol("Slot %d: <== Enq (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); delete enquiry; enquiry = new cCiEnquiry(this); int l = 0; @@ -1323,14 +1038,12 @@ bool cCiMMI::Process(int Length, const uint8_t *Data) if (l > 1) delay = *d; } - dbgprotocol("%d: <== Close MMI id = %02X delay = %d\n", SessionId(), id, delay); + dbgprotocol("Slot %d: <== Close MMI (%d) id = %02X delay = %d\n", Tc()->CamSlot()->SlotNumber(), SessionId(), id, delay); } break; - default: esyslog("ERROR: CI MMI: unknown tag %06X", Tag); - return false; + default: esyslog("ERROR: CAM %d: MMI: unknown tag %06X", Tc()->CamSlot()->SlotNumber(), Tag); } } - return true; } cCiMenu *cCiMMI::Menu(bool Clear) @@ -1355,32 +1068,28 @@ cCiEnquiry *cCiMMI::Enquiry(bool Clear) return fetchedEnquiry; } -bool cCiMMI::SendMenuAnswer(uint8_t Selection) +void cCiMMI::SendMenuAnswer(uint8_t Selection) { - dbgprotocol("%d: ==> Menu Answ\n", SessionId()); + dbgprotocol("Slot %d: ==> Menu Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_MENU_ANSW, 1, &Selection); - //XXX return value of all SendData() calls??? - return true; } bool cCiMMI::SendAnswer(const char *Text) { - dbgprotocol("%d: ==> Answ\n", SessionId()); + dbgprotocol("Slot %d: ==> Answ (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); struct tAnswer { uint8_t id; char text[256]; };//XXX tAnswer answer; answer.id = Text ? AI_ANSWER : AI_CANCEL; if (Text) strncpy(answer.text, Text, sizeof(answer.text)); SendData(AOT_ANSW, Text ? strlen(Text) + 1 : 1, (uint8_t *)&answer); - //XXX return value of all SendData() calls??? return true; } bool cCiMMI::SendCloseMMI(void) { - dbgprotocol("%d: ==> Close MMI\n", SessionId()); + dbgprotocol("Slot %d: ==> Close MMI (%d)\n", Tc()->CamSlot()->SlotNumber(), SessionId()); SendData(AOT_CLOSE_MMI, 0); - //XXX return value of all SendData() calls??? return true; } @@ -1422,24 +1131,23 @@ bool cCiMenu::HasUpdate(void) return !mmi || mmi->HasUserIO(); } -bool cCiMenu::Select(int Index) +void cCiMenu::Select(int Index) { cMutexLock MutexLock(mutex); - dbgprotocol("%d: ==> Select %d\n", mmi ? mmi->SessionId() : -1, Index); if (mmi && -1 <= Index && Index < numEntries) - return mmi->SendMenuAnswer(Index + 1); - return false; + mmi->SendMenuAnswer(Index + 1); } -bool cCiMenu::Cancel(void) +void cCiMenu::Cancel(void) { - return Select(-1); + Select(-1); } -bool cCiMenu::Abort(void) +void cCiMenu::Abort(void) { cMutexLock MutexLock(mutex); - return mmi && mmi->SendCloseMMI(); + if (mmi) + mmi->SendCloseMMI(); } // --- cCiEnquiry ------------------------------------------------------------ @@ -1460,78 +1168,77 @@ cCiEnquiry::~cCiEnquiry() free(text); } -bool cCiEnquiry::Reply(const char *s) +void cCiEnquiry::Reply(const char *s) { cMutexLock MutexLock(mutex); - return mmi ? mmi->SendAnswer(s) : false; + if (mmi) + mmi->SendAnswer(s); } -bool cCiEnquiry::Cancel(void) +void cCiEnquiry::Cancel(void) { - return Reply(NULL); + Reply(NULL); } -bool cCiEnquiry::Abort(void) +void cCiEnquiry::Abort(void) { cMutexLock MutexLock(mutex); - return mmi && mmi->SendCloseMMI(); + if (mmi) + mmi->SendCloseMMI(); } -// -- cCiHandler ------------------------------------------------------------- +// --- cCiTransportConnection (cont'd) --------------------------------------- -cCiHandler::cCiHandler(int Fd, int NumSlots) +#define TC_POLL_TIMEOUT 300 // ms WORKAROUND: TC_POLL_TIMEOUT < 300ms doesn't work with DragonCAM +#define TC_ALIVE_TIMEOUT 2000 // ms after which a transport connection is assumed dead + +cCiTransportConnection::cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid) { - fd = Fd; - numSlots = NumSlots; - newCaSupport = false; + dbgprotocol("Slot %d: creating connection %d/%d\n", CamSlot->SlotNumber(), CamSlot->SlotIndex(), Tcid); + camSlot = CamSlot; + tcid = Tcid; + state = stIDLE; + createConnectionRequested = false; + deleteConnectionRequested = false; hasUserIO = false; - for (int i = 0; i < MAX_CI_SESSION; i++) + alive.Set(TC_ALIVE_TIMEOUT); + for (int i = 0; i <= MAX_SESSIONS_PER_TC; i++) // sessions[0] is not used, but initialized anyway sessions[i] = NULL; - for (int i = 0; i < MAX_CI_SLOT; i++) - moduleReady[i] = false; - tpl = new cCiTransportLayer(Fd, numSlots); - tc = NULL; - source = transponder = 0; } -cCiHandler::~cCiHandler() +cCiTransportConnection::~cCiTransportConnection() { - for (int i = 0; i < MAX_CI_SESSION; i++) + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) delete sessions[i]; - delete tpl; - close(fd); } -cCiHandler *cCiHandler::CreateCiHandler(const char *FileName) +bool cCiTransportConnection::Ready(void) { - int fd_ca = open(FileName, O_RDWR); - if (fd_ca >= 0) { - ca_caps_t Caps; - if (ioctl(fd_ca, CA_GET_CAP, &Caps) == 0) { - int NumSlots = Caps.slot_num; - if (NumSlots > 0) { - //XXX dsyslog("CAM: found %d CAM slots", NumSlots); // TODO let's do this only once we can be sure that there _really_ is a CAM adapter! - if ((Caps.slot_type & CA_CI_LINK) != 0) - return new cCiHandler(fd_ca, NumSlots); - else - isyslog("CAM doesn't support link layer interface"); - } - else - esyslog("ERROR: no CAM slots found"); - } - else - LOG_ERROR_STR(FileName); - close(fd_ca); - } - return NULL; + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + return cas && cas->Ready(); } -uint32_t cCiHandler::ResourceIdToInt(const uint8_t *Data) +const char *cCiTransportConnection::GetCamName(void) { - return (ntohl(get_unaligned((uint32_t *)Data))); + cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION); + return ai ? ai->GetMenuString() : NULL; } -bool cCiHandler::Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status) +void cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data) +{ + cTPDU TPDU(camSlot->SlotIndex(), tcid, Tag, Length, Data); + camSlot->Write(&TPDU); + timer.Set(TC_POLL_TIMEOUT); +} + +void cCiTransportConnection::SendData(int Length, const uint8_t *Data) +{ + // if Length ever exceeds MAX_TPDU_DATA this needs to be handled differently + if (state == stACTIVE && Length > 0) + SendTPDU(T_DATA_LAST, Length, Data); +} + +void cCiTransportConnection::SendTag(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int Status) { uint8_t buffer[16]; uint8_t *p = buffer; @@ -1546,297 +1253,587 @@ bool cCiHandler::Send(uint8_t Tag, uint16_t SessionId, uint32_t ResourceId, int put_unaligned(htons(SessionId), (uint16_t *)p); p += 2; buffer[1] = p - buffer - 2; // length - return tc && tc->SendData(p - buffer, buffer) == OK; + SendData(p - buffer, buffer); } -cCiSession *cCiHandler::GetSessionBySessionId(uint16_t SessionId) +void cCiTransportConnection::Poll(void) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->SessionId() == SessionId) - return sessions[i]; - } - return NULL; + bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer; + DumpTPDUDataTransfer &= DumpPolls; + if (DumpPolls) + dbgprotocol("Slot %d: ==> Poll\n", camSlot->SlotNumber()); + SendTPDU(T_DATA_LAST); + DumpTPDUDataTransfer = OldDumpTPDUDataTransfer; } -cCiSession *cCiHandler::GetSessionByResourceId(uint32_t ResourceId, int Slot) +uint32_t cCiTransportConnection::ResourceIdToInt(const uint8_t *Data) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Tc()->Slot() == Slot && sessions[i]->ResourceId() == ResourceId) - return sessions[i]; - } - return NULL; + return (ntohl(get_unaligned((uint32_t *)Data))); } -cCiSession *cCiHandler::CreateSession(uint32_t ResourceId) +cCiSession *cCiTransportConnection::GetSessionBySessionId(uint16_t SessionId) { - if (!GetSessionByResourceId(ResourceId, tc->Slot())) { - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (!sessions[i]) { - switch (ResourceId) { - case RI_RESOURCE_MANAGER: return sessions[i] = new cCiResourceManager(i + 1, tc); - case RI_APPLICATION_INFORMATION: return sessions[i] = new cCiApplicationInformation(i + 1, tc); - case RI_CONDITIONAL_ACCESS_SUPPORT: newCaSupport = true; - return sessions[i] = new cCiConditionalAccessSupport(i + 1, tc); - case RI_HOST_CONTROL: break; //XXX - case RI_DATE_TIME: return sessions[i] = new cCiDateTime(i + 1, tc); - case RI_MMI: return sessions[i] = new cCiMMI(i + 1, tc); - } - } - } - } + return (SessionId <= MAX_SESSIONS_PER_TC) ? sessions[SessionId] : NULL; +} + +cCiSession *cCiTransportConnection::GetSessionByResourceId(uint32_t ResourceId) +{ + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (sessions[i] && sessions[i]->ResourceId() == ResourceId) + return sessions[i]; + } return NULL; } -bool cCiHandler::OpenSession(int Length, const uint8_t *Data) +void cCiTransportConnection::OpenSession(int Length, const uint8_t *Data) { if (Length == 6 && *(Data + 1) == 0x04) { uint32_t ResourceId = ResourceIdToInt(Data + 2); - dbgprotocol("OpenSession %08X\n", ResourceId); - switch (ResourceId) { - case RI_RESOURCE_MANAGER: - case RI_APPLICATION_INFORMATION: - case RI_CONDITIONAL_ACCESS_SUPPORT: - case RI_HOST_CONTROL: - case RI_DATE_TIME: - case RI_MMI: - { - cCiSession *Session = CreateSession(ResourceId); - if (Session) { - Send(ST_OPEN_SESSION_RESPONSE, Session->SessionId(), Session->ResourceId(), SS_OK); - return true; + dbgprotocol("Slot %d: open session %08X\n", camSlot->SlotNumber(), ResourceId); + if (!GetSessionByResourceId(ResourceId)) { + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (!sessions[i]) { + switch (ResourceId) { + case RI_RESOURCE_MANAGER: sessions[i] = new cCiResourceManager(i, this); break; + case RI_APPLICATION_INFORMATION: sessions[i] = new cCiApplicationInformation(i, this); break; + case RI_CONDITIONAL_ACCESS_SUPPORT: sessions[i] = new cCiConditionalAccessSupport(i, this); break; + case RI_DATE_TIME: sessions[i] = new cCiDateTime(i, this); break; + case RI_MMI: sessions[i] = new cCiMMI(i, this); break; + case RI_HOST_CONTROL: // not implemented + default: esyslog("ERROR: CAM %d: unknown resource identifier: %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); } - esyslog("ERROR: can't create session for resource identifier: %08X", ResourceId); + if (sessions[i]) + SendTag(ST_OPEN_SESSION_RESPONSE, sessions[i]->SessionId(), sessions[i]->ResourceId(), SS_OK); + return; + } } - break; - default: esyslog("ERROR: unknown resource identifier: %08X", ResourceId); - } + esyslog("ERROR: CAM %d: no free session slot for resource identifier %08X (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); + } + else + esyslog("ERROR: CAM %d: session for resource identifier %08X already exists (%d/%d)", camSlot->SlotNumber(), ResourceId, camSlot->SlotIndex(), tcid); } - return false; } -bool cCiHandler::CloseSession(uint16_t SessionId) +void cCiTransportConnection::CloseSession(uint16_t SessionId) { - dbgprotocol("CloseSession %d\n", SessionId); + dbgprotocol("Slot %d: close session %d\n", camSlot->SlotNumber(), SessionId); cCiSession *Session = GetSessionBySessionId(SessionId); - if (Session && sessions[SessionId - 1] == Session) { + if (Session && sessions[SessionId] == Session) { delete Session; - sessions[SessionId - 1] = NULL; - Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK); - return true; + sessions[SessionId] = NULL; + SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK); } else { - esyslog("ERROR: unknown session id: %d", SessionId); - Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED); + esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid); + SendTag(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED); + } +} + +void cCiTransportConnection::HandleSessions(cTPDU *TPDU) +{ + int Length; + const uint8_t *Data = TPDU->Data(Length); + if (Data && Length > 1) { + switch (*Data) { + case ST_SESSION_NUMBER: if (Length > 4) { + uint16_t SessionId = ntohs(get_unaligned((uint16_t *)&Data[2])); + cCiSession *Session = GetSessionBySessionId(SessionId); + if (Session) + Session->Process(Length - 4, Data + 4); + else + esyslog("ERROR: CAM %d: unknown session id: %d (%d/%d)", camSlot->SlotNumber(), SessionId, camSlot->SlotIndex(), tcid); + } + 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: // not implemented + case ST_CLOSE_SESSION_RESPONSE: // not implemented + default: esyslog("ERROR: CAM %d: unknown session tag: %02X (%d/%d)", camSlot->SlotNumber(), *Data, camSlot->SlotIndex(), tcid); + } } - return false; } -int cCiHandler::CloseAllSessions(int Slot) +bool cCiTransportConnection::Process(cTPDU *TPDU) { - int result = 0; - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Tc()->Slot() == Slot) { - CloseSession(sessions[i]->SessionId()); - result++; - } - } - return result; + if (TPDU) + alive.Set(TC_ALIVE_TIMEOUT); + else if (alive.TimedOut()) + return false; + switch (state) { + case stIDLE: + if (createConnectionRequested) { + dbgprotocol("Slot %d: create connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + createConnectionRequested = false; + SendTPDU(T_CREATE_TC); + state = stCREATION; + } + return true; + case stCREATION: + if (TPDU && TPDU->Tag() == T_CTC_REPLY) { + dbgprotocol("Slot %d: connection created %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + Poll(); + state = stACTIVE; + } + else if (timer.TimedOut()) { + dbgprotocol("Slot %d: timeout while creating connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + state = stIDLE; + } + return true; + case stACTIVE: + if (deleteConnectionRequested) { + dbgprotocol("Slot %d: delete connection requested %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + deleteConnectionRequested = false; + SendTPDU(T_DELETE_TC); + state = stDELETION; + return true; + } + if (TPDU) { + switch (TPDU->Tag()) { + case T_REQUEST_TC: + esyslog("ERROR: CAM %d: T_REQUEST_TC not implemented (%d/%d)", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + break; + case T_DATA_MORE: + case T_DATA_LAST: + HandleSessions(TPDU); + // continue with T_SB + case T_SB: + if ((TPDU->Status() & DATA_INDICATOR) != 0) { + dbgprotocol("Slot %d: receive data %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + SendTPDU(T_RCV); + } + break; + case T_DELETE_TC: + dbgprotocol("Slot %d: delete connection %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + SendTPDU(T_DTC_REPLY); + state = stIDLE; + return true; + } + } + else if (timer.TimedOut()) + Poll(); + hasUserIO = false; + for (int i = 1; i <= MAX_SESSIONS_PER_TC; i++) { + if (sessions[i]) { + sessions[i]->Process(); + if (sessions[i]->HasUserIO()) + hasUserIO = true; + } + } + break; + case stDELETION: + if (TPDU && TPDU->Tag() == T_DTC_REPLY || timer.TimedOut()) { + dbgprotocol("Slot %d: connection deleted %d/%d\n", camSlot->SlotNumber(), camSlot->SlotIndex(), tcid); + state = stIDLE; + } + return true; + } + return true; } -int cCiHandler::NumCams(void) +// --- cCiCaPidData ---------------------------------------------------------- + +class cCiCaPidData : public cListObject { +public: + bool active; + int pid; + int streamType; + cCiCaPidData(int Pid, int StreamType) + { + active = false; + pid = Pid; + streamType = StreamType; + } + }; + +// --- cCiCaProgramData ------------------------------------------------------ + +class cCiCaProgramData : public cListObject { +public: + int programNumber; + bool modified; + cList<cCiCaPidData> pidList; + cCiCaProgramData(int ProgramNumber) + { + programNumber = ProgramNumber; + modified = false; + } + }; + +// --- cCiAdapter ------------------------------------------------------------ + +cCiAdapter::cCiAdapter(void) +:cThread("CI adapter") { - int result = 0; - for (int i = 0; i < MAX_CI_SLOT; i++) - if (moduleReady[i]) - result++; - return result; + assignedDevice = NULL; + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) + camSlots[i] = NULL; } -bool cCiHandler::Ready(void) +cCiAdapter::~cCiAdapter() { - cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - if (moduleReady[Slot]) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (!cas || !*cas->GetCaSystemIds()) - return false; - } + Cancel(3); + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) + delete camSlots[i]; +} + +void cCiAdapter::AddCamSlot(cCamSlot *CamSlot) +{ + if (CamSlot) { + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (!camSlots[i]) { + CamSlot->slotIndex = i; + camSlots[i] = CamSlot; + return; + } + } + esyslog("ERROR: no free CAM slot in CI adapter"); + } +} + +bool cCiAdapter::Ready(void) +{ + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (camSlots[i] && !camSlots[i]->Ready()) + return false; } return true; } -bool cCiHandler::Process(int Slot) +void cCiAdapter::Action(void) +{ + cTPDU TPDU; + while (Running()) { + int n = Read(TPDU.Buffer(), TPDU.MaxSize()); + if (n > 0 && TPDU.Slot() < MAX_CAM_SLOTS_PER_ADAPTER) { + TPDU.SetSize(n); + cCamSlot *cs = camSlots[TPDU.Slot()]; + TPDU.Dump(cs ? cs->SlotNumber() : 0, false); + if (cs) + cs->Process(&TPDU); + } + for (int i = 0; i < MAX_CAM_SLOTS_PER_ADAPTER; i++) { + if (camSlots[i]) + camSlots[i]->Process(); + } + } +} + +// --- cCamSlot -------------------------------------------------------------- + +cCamSlots CamSlots; + +#define MODULE_CHECK_INTERVAL 100 // ms +#define MODULE_RESET_TIMEOUT 2 // s + +cCamSlot::cCamSlot(cCiAdapter *CiAdapter) +{ + ciAdapter = CiAdapter; + slotIndex = -1; + 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 (ciAdapter) + ciAdapter->AddCamSlot(this); + Reset(); +} + +cCamSlot::~cCamSlot() +{ + CamSlots.Del(this, false); + DeleteAllConnections(); +} + +bool cCamSlot::Assign(cDevice *Device, bool Query) { - bool result = true; cMutexLock MutexLock(&mutex); - 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) { - uint16_t 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); + if (ciAdapter) { + if (ciAdapter->Assign(Device, true)) { + if (!Device && ciAdapter->assignedDevice) + ciAdapter->assignedDevice->SetCamSlot(NULL); + if (!Query) { + StopDecrypting(); + source = transponder = 0; + if (ciAdapter->Assign(Device)) { + ciAdapter->assignedDevice = Device; + if (Device) { + Device->SetCamSlot(this); + dsyslog("CAM %d: assigned to device %d", slotNumber, Device->DeviceNumber() + 1); } - } - } - 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 + dsyslog("CAM %d: unassigned", slotNumber); + } + else + return false; + } + return true; + } + } + return false; +} + +cDevice *cCamSlot::Device(void) +{ + cMutexLock MutexLock(&mutex); + if (ciAdapter) { + cDevice *d = ciAdapter->assignedDevice; + if (d && d->CamSlot() == this) + return d; + } + return NULL; +} + +void cCamSlot::NewConnection(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + if (!tc[i]) { + tc[i] = new cCiTransportConnection(this, i); + tc[i]->CreateConnection(); + return; } } - SendCaPmt(); - bool UserIO = false; - for (int i = 0; i < MAX_CI_SESSION; i++) { - if (sessions[i] && sessions[i]->Process()) - UserIO |= sessions[i]->HasUserIO(); + esyslog("ERROR: CAM %d: can't create new transport connection!", slotNumber); +} + +void cCamSlot::DeleteAllConnections(void) +{ + cMutexLock MutexLock(&mutex); + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + delete tc[i]; + tc[i] = NULL; } - hasUserIO = UserIO; - return result; } -void cCiHandler::SendCaPmt(void) +void cCamSlot::Process(cTPDU *TPDU) { 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<cCiCaPmt> 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; - } - } + if (TPDU) { + int n = TPDU->Tcid(); + if (1 <= n && n <= MAX_CONNECTIONS_PER_CAM_SLOT) { + if (tc[n]) + tc[n]->Process(TPDU); + } + } + for (int i = 1; i <= MAX_CONNECTIONS_PER_CAM_SLOT; i++) { + if (tc[i]) { + if (!tc[i]->Process()) { + Reset(); + return; + } } + } + if (moduleCheckTimer.TimedOut()) { + eModuleStatus ms = ModuleStatus(); + if (ms != lastModuleStatus) { + switch (ms) { + case msNone: + dbgprotocol("Slot %d: no module present\n", slotNumber); + isyslog("CAM %d: no module present", slotNumber); + DeleteAllConnections(); + break; + case msReset: + dbgprotocol("Slot %d: module reset\n", slotNumber); + isyslog("CAM %d: module reset", slotNumber); + DeleteAllConnections(); + break; + case msPresent: + dbgprotocol("Slot %d: module present\n", slotNumber); + isyslog("CAM %d: module present", slotNumber); + break; + case msReady: + dbgprotocol("Slot %d: module ready\n", slotNumber); + isyslog("CAM %d: module ready", slotNumber); + NewConnection(); + resendPmt = caProgramList.Count() > 0; + break; + } + lastModuleStatus = ms; + } + moduleCheckTimer.Set(MODULE_CHECK_INTERVAL); + } + if (resendPmt) + SendCaPmt(CPCI_OK_DESCRAMBLING); + processed.Broadcast(); +} + +cCiSession *cCamSlot::GetSessionByResourceId(uint32_t ResourceId) +{ + cMutexLock MutexLock(&mutex); + return tc[1] ? tc[1]->GetSessionByResourceId(ResourceId) : NULL; +} + +void cCamSlot::Write(cTPDU *TPDU) +{ + cMutexLock MutexLock(&mutex); + if (ciAdapter && TPDU->Size()) { + TPDU->Dump(SlotNumber(), true); + ciAdapter->Write(TPDU->Buffer(), TPDU->Size()); + } +} + +bool cCamSlot::Reset(void) +{ + cMutexLock MutexLock(&mutex); + ChannelCamRelations.Reset(slotNumber); + DeleteAllConnections(); + if (ciAdapter) { + dbgprotocol("Slot %d: reset...", slotNumber); + if (ciAdapter->Reset(slotIndex)) { + resetTime = time(NULL); + dbgprotocol("ok.\n"); + return true; + } + dbgprotocol("failed!\n"); + } + return false; +} + +eModuleStatus cCamSlot::ModuleStatus(void) +{ + cMutexLock MutexLock(&mutex); + eModuleStatus ms = ciAdapter ? ciAdapter->ModuleStatus(slotIndex) : msNone; + if (resetTime) { + if (ms <= msReset) { + if (time(NULL) - resetTime < MODULE_RESET_TIMEOUT) + return msReset; + } + resetTime = 0; } + return ms; +} + +const char *cCamSlot::GetCamName(void) +{ + cMutexLock MutexLock(&mutex); + return tc[1] ? tc[1]->GetCamName() : NULL; } -bool cCiHandler::EnterMenu(int Slot) +bool cCamSlot::Ready(void) { cMutexLock MutexLock(&mutex); - cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot); + return ModuleStatus() == msNone || tc[1] && tc[1]->Ready(); +} + +bool cCamSlot::HasMMI(void) +{ + return GetSessionByResourceId(RI_MMI); +} + +bool cCamSlot::HasUserIO(void) +{ + cMutexLock MutexLock(&mutex); + return tc[1] && tc[1]->HasUserIO(); +} + +bool cCamSlot::EnterMenu(void) +{ + cMutexLock MutexLock(&mutex); + cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION); return api ? api->EnterMenu() : false; } -cCiMenu *cCiHandler::GetMenu(void) +cCiMenu *cCamSlot::GetMenu(void) { cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot); - if (mmi) { - cCiMenu *Menu = mmi->Menu(); - if (Menu) - Menu->mutex = &mutex; - return Menu; - } - } + cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI); + if (mmi) { + cCiMenu *Menu = mmi->Menu(); + if (Menu) + Menu->mutex = &mutex; + return Menu; + } return NULL; } -cCiEnquiry *cCiHandler::GetEnquiry(void) +cCiEnquiry *cCamSlot::GetEnquiry(void) { cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot); - if (mmi) { - cCiEnquiry *Enquiry = mmi->Enquiry(); - if (Enquiry) - Enquiry->mutex = &mutex; - return Enquiry; - } - } + cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI); + if (mmi) { + cCiEnquiry *Enquiry = mmi->Enquiry(); + if (Enquiry) + Enquiry->mutex = &mutex; + return Enquiry; + } return NULL; } -const char *cCiHandler::GetCamName(int Slot) +void cCamSlot::SendCaPmt(uint8_t CmdId) { cMutexLock MutexLock(&mutex); - cCiApplicationInformation *ai = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot); - return ai ? ai->GetMenuString() : NULL; + 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 (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); + } + } + } } -const unsigned short *cCiHandler::GetCaSystemIds(int Slot) +const int *cCamSlot::GetCaSystemIds(void) { cMutexLock MutexLock(&mutex); - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); return cas ? cas->GetCaSystemIds() : NULL; } -bool cCiHandler::ProvidesCa(const unsigned short *CaSystemIds) +int cCamSlot::Priority(void) { - cMutexLock MutexLock(&mutex); - for (int Slot = 0; Slot < numSlots; Slot++) { - cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot); - if (cas) { - for (const unsigned short *ids = cas->GetCaSystemIds(); ids && *ids; ids++) { - for (const unsigned short *id = CaSystemIds; *id; id++) { - if (*id == *ids) - return true; - } - } - } - } - return false; + cDevice *d = Device(); + return d ? d->Priority() : -1; } -void cCiHandler::SetSource(int Source, int Transponder) +bool cCamSlot::ProvidesCa(const int *CaSystemIds) { cMutexLock MutexLock(&mutex); - if (source != Source || transponder != Transponder) { - //XXX if there are active entries, send an empty CA_PMT - caProgramList.Clear(); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas) { + for (const int *ids = cas->GetCaSystemIds(); ids && *ids; ids++) { + for (const int *id = CaSystemIds; *id; id++) { + if (*id == *ids) + return true; + } + } } - source = Source; - transponder = Transponder; + return false; } -void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType) +void cCamSlot::AddPid(int ProgramNumber, int Pid, int StreamType) { cMutexLock MutexLock(&mutex); cCiCaProgramData *ProgramData = NULL; @@ -1854,65 +1851,276 @@ void cCiHandler::AddPid(int ProgramNumber, int Pid, int StreamType) ProgramData->pidList.Add(new cCiCaPidData(Pid, StreamType)); } -void cCiHandler::SetPid(int Pid, bool Active) +void cCamSlot::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; + if (q->active != Active) { + q->active = Active; + p->modified = true; + } return; } } } } -bool cCiHandler::CanDecrypt(int ProgramNumber) +// see ISO/IEC 13818-1 +#define STREAM_TYPE_VIDEO 0x02 +#define STREAM_TYPE_AUDIO 0x04 +#define STREAM_TYPE_DOLBY 0x06 + +void cCamSlot::AddChannel(const cChannel *Channel) { 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; - } + if (source != Channel->Source() || transponder != Channel->Transponder()) + StopDecrypting(); + source = Channel->Source(); + transponder = Channel->Transponder(); + if (Channel->Ca() >= CA_ENCRYPTED_MIN) { + AddPid(Channel->Sid(), Channel->Vpid(), STREAM_TYPE_VIDEO); + for (const int *Apid = Channel->Apids(); *Apid; Apid++) + AddPid(Channel->Sid(), *Apid, STREAM_TYPE_AUDIO); + for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) + AddPid(Channel->Sid(), *Dpid, STREAM_TYPE_DOLBY); + } +} + +#define QUERY_REPLY_WAIT 100 // ms to wait between checks for a reply + +bool cCamSlot::CanDecrypt(const cChannel *Channel) +{ + if (Channel->Ca() < CA_ENCRYPTED_MIN) + return true; // channel not encrypted + if (!IsDecrypting()) + return true; // any CAM can decrypt at least one channel + cMutexLock MutexLock(&mutex); + cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT); + if (cas && cas->RepliesToQuery()) { + cCiCaPmt CaPmt(CPCI_QUERY, Channel->Source(), Channel->Transponder(), Channel->Sid(), GetCaSystemIds()); + CaPmt.SetListManagement(CPLM_ADD); // WORKAROUND: CPLM_ONLY doesn't work with Alphacrypt 3.09 (deletes existing CA_PMTs) + CaPmt.AddPid(Channel->Vpid(), STREAM_TYPE_VIDEO); + for (const int *Apid = Channel->Apids(); *Apid; Apid++) + CaPmt.AddPid(*Apid, STREAM_TYPE_AUDIO); + for (const int *Dpid = Channel->Dpids(); *Dpid; Dpid++) + CaPmt.AddPid(*Dpid, STREAM_TYPE_DOLBY); + cas->SendPMT(&CaPmt); + cTimeMs Timeout(QUERY_REPLY_TIMEOUT); + do { + processed.TimedWait(mutex, QUERY_REPLY_WAIT); + if ((cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT)) != NULL) { // must re-fetch it, there might have been a reset + if (cas->ReceivedReply()) + return cas->CanDecrypt(); + } + else + return false; + } while (!Timeout.TimedOut()); + dsyslog("CAM %d: didn't reply to QUERY", SlotNumber()); + } + return false; +} + +void cCamSlot::StartDecrypting(void) +{ + SendCaPmt(CPCI_OK_DESCRAMBLING); +} + +void cCamSlot::StopDecrypting(void) +{ + cMutexLock MutexLock(&mutex); + if (caProgramList.Count()) { + caProgramList.Clear(); + SendCaPmt(CPCI_NOT_SELECTED); + } +} + +bool cCamSlot::IsDecrypting(void) +{ + cMutexLock MutexLock(&mutex); + if (caProgramList.Count()) { + for (cCiCaProgramData *p = caProgramList.First(); p; p = caProgramList.Next(p)) { + if (p->modified) + return true; // any modifications need to be processed before we can assume it's no longer decrypting + for (cCiCaPidData *q = p->pidList.First(); q; q = p->pidList.Next(q)) { + if (q->active) + return true; } } - } + } return false; } -void cCiHandler::StartDecrypting(void) +// --- cChannelCamRelation --------------------------------------------------- + +#define CAM_CHECKED_TIMEOUT 15 // seconds before a CAM that has been checked for a particular channel will be checked again + +class cChannelCamRelation : public cListObject { +private: + tChannelID channelID; + uint32_t camSlotsChecked; + uint32_t camSlotsDecrypt; + time_t lastChecked; +public: + cChannelCamRelation(tChannelID ChannelID); + bool TimedOut(void); + tChannelID ChannelID(void) { return channelID; } + bool CamChecked(int CamSlotNumber); + bool CamDecrypt(int CamSlotNumber); + void SetChecked(int CamSlotNumber); + void SetDecrypt(int CamSlotNumber); + void ClrChecked(int CamSlotNumber); + void ClrDecrypt(int CamSlotNumber); + }; + +cChannelCamRelation::cChannelCamRelation(tChannelID ChannelID) +{ + channelID = ChannelID; + camSlotsChecked = 0; + camSlotsDecrypt = 0; + lastChecked = 0; +} + +bool cChannelCamRelation::TimedOut(void) +{ + return !camSlotsDecrypt && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT; +} + +bool cChannelCamRelation::CamChecked(int CamSlotNumber) +{ + if (lastChecked && time(NULL) - lastChecked > CAM_CHECKED_TIMEOUT) { + lastChecked = 0; + camSlotsChecked = 0; + } + return camSlotsChecked & (1 << (CamSlotNumber - 1)); +} + +bool cChannelCamRelation::CamDecrypt(int CamSlotNumber) +{ + return camSlotsDecrypt & (1 << (CamSlotNumber - 1)); +} + +void cChannelCamRelation::SetChecked(int CamSlotNumber) +{ + camSlotsChecked |= (1 << (CamSlotNumber - 1)); + lastChecked = time(NULL); + ClrDecrypt(CamSlotNumber); +} + +void cChannelCamRelation::SetDecrypt(int CamSlotNumber) +{ + camSlotsDecrypt |= (1 << (CamSlotNumber - 1)); + ClrChecked(CamSlotNumber); +} + +void cChannelCamRelation::ClrChecked(int CamSlotNumber) +{ + camSlotsChecked &= ~(1 << (CamSlotNumber - 1)); + lastChecked = 0; +} + +void cChannelCamRelation::ClrDecrypt(int CamSlotNumber) +{ + camSlotsDecrypt &= ~(1 << (CamSlotNumber - 1)); +} + +// --- cChannelCamRelations -------------------------------------------------- + +#define CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL 3600 // seconds between cleanups + +cChannelCamRelations ChannelCamRelations; + +cChannelCamRelations::cChannelCamRelations(void) +{ + lastCleanup = time(NULL); +} + +void cChannelCamRelations::Cleanup(void) +{ + cMutexLock MutexLock(&mutex); + if (time(NULL) - lastCleanup > CHANNEL_CAM_RELATIONS_CLEANUP_INTERVAL) { + for (cChannelCamRelation *ccr = First(); ccr; ) { + cChannelCamRelation *c = ccr; + ccr = Next(ccr); + if (c->TimedOut()) + Del(c); + } + lastCleanup = time(NULL); + } +} + +cChannelCamRelation *cChannelCamRelations::GetEntry(tChannelID ChannelID) +{ + cMutexLock MutexLock(&mutex); + Cleanup(); + for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) { + if (ccr->ChannelID() == ChannelID) + return ccr; + } + return NULL; +} + +cChannelCamRelation *cChannelCamRelations::AddEntry(tChannelID ChannelID) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (!ccr) + Add(ccr = new cChannelCamRelation(ChannelID)); + return ccr; +} + +void cChannelCamRelations::Reset(int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + for (cChannelCamRelation *ccr = First(); ccr; ccr = Next(ccr)) { + ccr->ClrChecked(CamSlotNumber); + ccr->ClrDecrypt(CamSlotNumber); + } +} + +bool cChannelCamRelations::CamChecked(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + return ccr ? ccr->CamChecked(CamSlotNumber) : false; +} + +bool cChannelCamRelations::CamDecrypt(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = GetEntry(ChannelID); + return ccr ? ccr->CamDecrypt(CamSlotNumber) : false; +} + +void cChannelCamRelations::SetChecked(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = AddEntry(ChannelID); + if (ccr) + ccr->SetChecked(CamSlotNumber); +} + +void cChannelCamRelations::SetDecrypt(tChannelID ChannelID, int CamSlotNumber) +{ + cMutexLock MutexLock(&mutex); + cChannelCamRelation *ccr = AddEntry(ChannelID); + if (ccr) + ccr->SetDecrypt(CamSlotNumber); +} + +void cChannelCamRelations::ClrChecked(tChannelID ChannelID, int CamSlotNumber) { cMutexLock MutexLock(&mutex); - newCaSupport = true; - SendCaPmt(); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (ccr) + ccr->ClrChecked(CamSlotNumber); } -bool cCiHandler::Reset(int Slot) +void cChannelCamRelations::ClrDecrypt(tChannelID ChannelID, int CamSlotNumber) { cMutexLock MutexLock(&mutex); - CloseAllSessions(Slot); - return tpl->ResetSlot(Slot, true); + cChannelCamRelation *ccr = GetEntry(ChannelID); + if (ccr) + ccr->ClrDecrypt(CamSlotNumber); } |