diff options
author | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2007-01-07 18:00:00 +0100 |
---|---|---|
committer | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2007-01-07 18:00:00 +0100 |
commit | 66ab78a40f5b57e20142a33484e32c785a0c4017 (patch) | |
tree | 2e351bb9df6cc1325d439729646f83b35e2c346d /ci.c | |
parent | a2096f3beb9f7bf3ba600cf66555628fc899720e (diff) | |
download | vdr-patch-lnbsharing-66ab78a40f5b57e20142a33484e32c785a0c4017.tar.gz vdr-patch-lnbsharing-66ab78a40f5b57e20142a33484e32c785a0c4017.tar.bz2 |
Version 1.5.0vdr-1.5.0
- The CAM handling has been refactored. Instead of a cCiHandler per device there
is now an abstract cCiAdapter and a cCamSlot. This allows each slot to be
accessed individually.
- The general 15 seconds workaround time before opening the CAM menu has been
removed. If the CAM menu doesn't open within a timeout, the enter menu command
is now sent again.
- If a CAM is reset or pulled and reinserted, it now automatically starts
decrypting the current channel again.
- The Setup/CAM menu now dynamically refreshes its items and displays whether
a CAM is present or ready. The 'Reset' function no longer leaves the menu.
- The CAM menu will now be openend when pressing the Ok key on a slot entry.
- The CAM menu now stays within the current menu context and doesn't close and
reopen the menu every time an option is selected.
- When an encrypted channel is switched to for the first time, VDR now checks
explicitly whether a CAM can actually decrypt that channel. If there is more
than one CAM in the system that claims to be able to decrypt the channel,
they are all tried in turn.
To make this possible, an encrypted channel needs to be received in Transfer
Mode when it is switched to for the first time, so that VDR can determine
whether the TS packets are actually decrypted. Once a channel is known to
be decrypted by a particular CAM, the next time it is switched to it will
be shown in normal live viewing mode.
- A cDevice now automatically detaches all cReceiver objects that receive PIDs
that can't be decrypted with the current CAM. A plugin that attaches a cReceiver
to a device should therefore watch the receiver's IsAttached() function to
see if it is still attached to the device.
- The cReceiver constructor no longer takes an 'int Ca' as its first parameter,
but rather a 'tChannelID ChannelID'. This is necessary for the device to be
able to determine which CAM a particular channel can be decrypted with. If the
channel is known to be unencrypted, or a plugin doesn't want to provide the
channel id for other reasons, an invalid tChannelID() can be given.
- The cThread::Start() function now waits until a previous incarnation of this
thread has actually stopped. Before this it could happen that a thread's
Cancel(-1) function was called and immediately after that it was started again,
but the Start() function still found it to be 'active'.
- The parameter NeedsDetachReceivers in cDevice::GetDevice(const cChannel *Channel, ...)
has been removed. A call to this function will automatically detach all receivers
from the device if it returns a non-NULL pointer.
- The cTimeMs class now accepts an initial timeout value in its constructor.
- A CAM is now explicitly instructed to stop decrypting when switching away from
an encrypted channel.
- If the CAM in use can decrypt several channels at the same time, VDR can
now make use if this capability. Whether or not a CAM can decrypt more
than one channel is determined by sending it an initial empty QUERY command
and testing whether it replies to it.
- Ca values in the range 0...F in channels.conf can still be used to assign a channel
to a particular device, but this will no longer work with encrypted channels because
without valid CA ids VDR can't decide which CAM slot to use. However, since VDR now
automatically determines which CAM can decrypt which channel, setting fixed
channel/device relations should no longer be necessary.
IF AN ENCRYPTED CHANNEL CAN'T BE DECRYPTED AND YOU HAVE A CA VALUE IN THE RANGE
0...F FOR THAT CHANNEL, SET IT TO 0 (FTA) AND TUNE TO THE CHANNEL AGAIN.
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/05 10: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); } |