summaryrefslogtreecommitdiff
path: root/ci.c
diff options
context:
space:
mode:
Diffstat (limited to 'ci.c')
-rw-r--r--ci.c1742
1 files changed, 975 insertions, 767 deletions
diff --git a/ci.c b/ci.c
index cce7b5d..26447cb 100644
--- a/ci.c
+++ b/ci.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: ci.c 1.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);
}