ci.c 93.2 KB
Newer Older
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1 2 3 4 5 6
/*
 * ci.c: Common Interface
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
7
 * $Id: ci.c 4.21.1.5 2019/05/28 15:55:44 kls Exp $
Klaus Schmidinger's avatar
Klaus Schmidinger committed
8 9 10 11 12 13 14 15
 */

#include "ci.h"
#include <ctype.h>
#include <linux/dvb/ca.h>
#include <malloc.h>
#include <netinet/in.h>
#include <poll.h>
16
#include <stdio.h>
Klaus Schmidinger's avatar
Klaus Schmidinger committed
17 18 19 20
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
Klaus Schmidinger's avatar
Klaus Schmidinger committed
21
#include "device.h"
22
#include "mtd.h"
Klaus Schmidinger's avatar
Klaus Schmidinger committed
23
#include "pat.h"
etobi's avatar
etobi committed
24 25 26 27
#include "receiver.h"
#include "remux.h"
#include "libsi/si.h"
#include "skins.h"
Klaus Schmidinger's avatar
Klaus Schmidinger committed
28 29 30 31 32
#include "tools.h"

// Set these to 'true' for debug output:
static bool DumpTPDUDataTransfer = false;
static bool DebugProtocol = false;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
33 34
static bool DumpPolls = false;
static bool DumpDateTime = false;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
35

Klaus Schmidinger's avatar
Klaus Schmidinger committed
36
#define dbgprotocol(a...) if (DebugProtocol) fprintf(stderr, a)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
37 38 39 40 41 42 43

// --- Helper functions ------------------------------------------------------

#define SIZE_INDICATOR 0x80

static const uint8_t *GetLength(const uint8_t *Data, int &Length)
///< Gets the length field from the beginning of Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
44
///< Returns a pointer to the first byte after the length and
Klaus Schmidinger's avatar
Klaus Schmidinger committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58
///< stores the length value in Length.
{
  Length = *Data++;
  if ((Length & SIZE_INDICATOR) != 0) {
     int l = Length & ~SIZE_INDICATOR;
     Length = 0;
     for (int i = 0; i < l; i++)
         Length = (Length << 8) | *Data++;
     }
  return Data;
}

static uint8_t *SetLength(uint8_t *Data, int Length)
///< Sets the length field at the beginning of Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
59
///< Returns a pointer to the first byte after the length.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
{
  uint8_t *p = Data;
  if (Length < 128)
     *p++ = Length;
  else {
     int n = sizeof(Length);
     for (int i = n - 1; i >= 0; i--) {
         int b = (Length >> (8 * i)) & 0xFF;
         if (p != Data || b)
            *++p = b;
         }
     *Data = (p - Data) | SIZE_INDICATOR;
     p++;
     }
  return p;
}

static char *CopyString(int Length, const uint8_t *Data)
///< Copies the string at Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
79
///< Returns a pointer to a newly allocated string.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
80
{
81 82 83 84 85 86 87 88
  char *s = MALLOC(char, Length + 1);
  char *p = s;
  while (Length > 0) {
        char c = *Data;
        if (isprint(c)) // some CAMs send funny characters in their strings, let's just skip them
           *p++ = c;
        else if (c == 0x8A) // the character 0x8A is used as newline, so let's put a real '\n' in there
           *p++ = '\n';
Klaus Schmidinger's avatar
Klaus Schmidinger committed
89 90 91
        Length--;
        Data++;
        }
92
  *p = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
93 94 95 96 97
  return s;
}

static char *GetString(int &Length, const uint8_t **Data)
///< Gets the string at Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
98
///< Returns a pointer to a newly allocated string, or NULL in case of error.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
99 100 101 102 103 104 105 106 107 108 109 110 111
///< Upon return Length and Data represent the remaining data after the string has been skipped.
{
  if (Length > 0 && Data && *Data) {
     int l = 0;
     const uint8_t *d = GetLength(*Data, l);
     char *s = CopyString(l, d);
     Length -= d - *Data + l;
     *Data = d + l;
     return s;
     }
  return NULL;
}

etobi's avatar
etobi committed
112 113 114 115 116 117 118 119 120 121 122
// --- cCaPidReceiver --------------------------------------------------------

// A receiver that is used to make the device receive the ECM pids, as well as the
// CAT and the EMM pids.

class cCaPidReceiver : public cReceiver {
private:
  int catVersion;
  cVector<int> emmPids;
  uchar buffer[2048]; // 11 bit length, max. 2048 byte
  uchar *bufp;
123
  uchar mtdCatBuffer[TS_SIZE]; // TODO: handle multi packet CATs!
etobi's avatar
etobi committed
124
  int length;
125 126
  cMutex mutex;
  bool handlingPid;
etobi's avatar
etobi committed
127 128 129 130 131
  void AddEmmPid(int Pid);
  void DelEmmPids(void);
public:
  cCaPidReceiver(void);
  virtual ~cCaPidReceiver() { Detach(); }
132 133
  virtual void Receive(const uchar *Data, int Length);
  bool HasCaPids(void) const { return NumPids() - emmPids.Size() - 1 > 0; }
etobi's avatar
etobi committed
134
  void Reset(void) { DelEmmPids(); catVersion = -1; }
135 136 137 138 139 140 141 142 143
  bool HandlingPid(void);
       ///< The cCaPidReceiver adds/deletes PIDs to/from the base class cReceiver,
       ///< which in turn does the same on the cDevice it is attached to. The cDevice
       ///< then sets the PIDs on the assigned cCamSlot, which can cause a deadlock on the
       ///< cCamSlot's mutex if a cReceiver is detached from the device at the same time.
       ///< Since these PIDs, however, are none that have to be decrypted,
       ///< it is not necessary to set them in the CAM. Therefore this function is
       ///< used in cCamSlot::SetPid() to detect this situation, and thus avoid the
       ///< deadlock.
etobi's avatar
etobi committed
144 145 146 147 148 149 150
  };

cCaPidReceiver::cCaPidReceiver(void)
{
  catVersion = -1;
  bufp = NULL;
  length = 0;
151 152 153
  handlingPid = false;
  cMutexLock MutexLock(&mutex);
  handlingPid = true;
etobi's avatar
etobi committed
154
  AddPid(CATPID);
155
  handlingPid = false;
etobi's avatar
etobi committed
156 157 158 159 160 161 162 163 164
}

void cCaPidReceiver::AddEmmPid(int Pid)
{
  for (int i = 0; i < emmPids.Size(); i++) {
      if (emmPids[i] == Pid)
         return;
      }
  emmPids.Append(Pid);
165 166
  cMutexLock MutexLock(&mutex);
  handlingPid = true;
etobi's avatar
etobi committed
167
  AddPid(Pid);
168
  handlingPid = false;
etobi's avatar
etobi committed
169 170 171 172
}

void cCaPidReceiver::DelEmmPids(void)
{
173 174
  cMutexLock MutexLock(&mutex);
  handlingPid = true;
etobi's avatar
etobi committed
175 176 177
  for (int i = 0; i < emmPids.Size(); i++)
      DelPid(emmPids[i]);
  emmPids.Clear();
178
  handlingPid = false;
etobi's avatar
etobi committed
179 180
}

181
void cCaPidReceiver::Receive(const uchar *Data, int Length)
etobi's avatar
etobi committed
182 183
{
  if (TsPid(Data) == CATPID) {
184
     cMtdCamSlot *MtdCamSlot = dynamic_cast<cMtdCamSlot *>(Device()->CamSlot());
185
     const uchar *p = NULL;
etobi's avatar
etobi committed
186 187 188 189 190 191 192 193
     if (TsPayloadStart(Data)) {
        if (Data[5] == SI::TableIdCAT) {
           length = (int(Data[6] & 0x03) << 8) | Data[7]; // section length
           if (length > 5) {
              int v = (Data[10] & 0x3E) >> 1; // version number
              if (v != catVersion) {
                 if (Data[11] == 0 && Data[12] == 0) { // section number, last section number
                    if (length > TS_SIZE - 8) {
194 195
                       if (MtdCamSlot)
                          esyslog("ERROR: need to implement multi packet CAT handling for MTD!");
etobi's avatar
etobi committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209
                       int n = TS_SIZE - 13;
                       memcpy(buffer, Data + 13, n);
                       bufp = buffer + n;
                       length -= n + 5; // 5 = header
                       }
                    else {
                       p = Data + 13; // no need to copy the data
                       length -= 5; // header
                       }
                    }
                 else
                    dsyslog("multi table CAT section - unhandled!");
                 catVersion = v;
                 }
210 211
              else if (MtdCamSlot)
                 MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
etobi's avatar
etobi committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
              }
           }
        }
     else if (bufp && length > 0) {
        int n = min(length, TS_SIZE - 4);
        if (bufp + n - buffer <= int(sizeof(buffer))) {
           memcpy(bufp, Data + 4, n);
           bufp += n;
           length -= n;
           if (length <= 0) {
              p = buffer;
              length = bufp - buffer;
              }
           }
        else {
           esyslog("ERROR: buffer overflow in cCaPidReceiver::Receive()");
228
           bufp = NULL;
etobi's avatar
etobi committed
229 230 231 232 233 234 235 236
           length = 0;
           }
        }
     if (p) {
        DelEmmPids();
        for (int i = 0; i < length - 4; i++) { // -4 = checksum
            if (p[i] == 0x09) {
               int CaId = int(p[i + 2] << 8) | p[i + 3];
237
               int EmmPid = Peek13(p + i + 4);
etobi's avatar
etobi committed
238
               AddEmmPid(EmmPid);
239 240
               if (MtdCamSlot)
                  MtdMapPid(const_cast<uchar *>(p + i + 4), MtdCamSlot->MtdMapper());
etobi's avatar
etobi committed
241 242
               switch (CaId >> 8) {
                 case 0x01: for (int j = i + 7; j < p[i + 1] + 2; j += 4) {
243
                                EmmPid = Peek13(p + j);
etobi's avatar
etobi committed
244
                                AddEmmPid(EmmPid);
245 246
                                if (MtdCamSlot)
                                   MtdMapPid(const_cast<uchar *>(p + j), MtdCamSlot->MtdMapper());
etobi's avatar
etobi committed
247 248 249 250 251 252
                                }
                            break;
                 }
               i += p[i + 1] + 2 - 1; // -1 to compensate for the loop increment
               }
            }
253 254 255 256 257 258 259 260 261 262 263 264 265
        if (MtdCamSlot) {
           if (!bufp && length) {
              // update crc32 - but only single packet CAT is handled for now:
              uint32_t crc = SI::CRC32::crc32((const char *)p - 8, length + 8 - 4, 0xFFFFFFFF); // <TableIdCAT....>[crc32]
              uchar *c = const_cast<uchar *>(p + length - 4);
              *c++ = crc >> 24;
              *c++ = crc >> 16;
              *c++ = crc >> 8;
              *c++ = crc;
              }
           memcpy(mtdCatBuffer, Data, TS_SIZE);
           MtdCamSlot->PutCat(mtdCatBuffer, TS_SIZE);
           }
etobi's avatar
etobi committed
266
        p = NULL;
267
        bufp = NULL;
etobi's avatar
etobi committed
268 269 270 271 272
        length = 0;
        }
     }
}

273 274 275 276 277 278
bool cCaPidReceiver::HandlingPid(void)
{
  cMutexLock MutexLock(&mutex);
  return handlingPid;
}

etobi's avatar
etobi committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292
// --- cCaActivationReceiver -------------------------------------------------

// A receiver that is used to make the device stay on a given channel and
// keep the CAM slot assigned.

#define UNSCRAMBLE_TIME     5 // seconds of receiving purely unscrambled data before considering the smart card "activated"
#define TS_PACKET_FACTOR 1024 // only process every TS_PACKET_FACTORth packet to keep the load down

class cCaActivationReceiver : public cReceiver {
private:
  cCamSlot *camSlot;
  time_t lastScrambledTime;
  int numTsPackets;
protected:
293
  virtual void Receive(const uchar *Data, int Length);
etobi's avatar
etobi committed
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
public:
  cCaActivationReceiver(const cChannel *Channel, cCamSlot *CamSlot);
  virtual ~cCaActivationReceiver();
  };

cCaActivationReceiver::cCaActivationReceiver(const cChannel *Channel, cCamSlot *CamSlot)
:cReceiver(Channel, MINPRIORITY + 1)
{
  camSlot = CamSlot;
  lastScrambledTime = time(NULL);
  numTsPackets = 0;
}

cCaActivationReceiver::~cCaActivationReceiver()
{
  Detach();
}

312
void cCaActivationReceiver::Receive(const uchar *Data, int Length)
etobi's avatar
etobi committed
313 314 315 316 317 318
{
  if (numTsPackets++ % TS_PACKET_FACTOR == 0) {
     time_t Now = time(NULL);
     if (TsIsScrambled(Data))
        lastScrambledTime = Now;
     else if (Now - lastScrambledTime > UNSCRAMBLE_TIME) {
319
        dsyslog("CAM %d: activated!", camSlot->MasterSlotNumber());
etobi's avatar
etobi committed
320
        Skins.QueueMessage(mtInfo, tr("CAM activated!"));
321
        cDevice *d = Device();
etobi's avatar
etobi committed
322
        Detach();
323 324 325 326
        if (d) {
           if (cCamSlot *s = d->CamSlot())
              s->CancelActivation(); // this will delete *this* object, so no more code referencing *this* after this call!
           }
etobi's avatar
etobi committed
327 328 329 330
        }
     }
}

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
// --- cCamResponse ----------------------------------------------------------

// CAM Response Actions:

#define CRA_NONE      0
#define CRA_DISCARD  -1
#define CRA_CONFIRM  -2
#define CRA_SELECT   -3

class cCamResponse : public cListObject {
private:
  int camNumber;
  char *text;
  int action;
public:
  cCamResponse(void);
  ~cCamResponse();
  bool Parse(const char *s);
  int Matches(int CamNumber, const char *Text) const;
  };

cCamResponse::cCamResponse(void)
{
  camNumber = -1;
  text = NULL;
  action = CRA_NONE;
}

cCamResponse::~cCamResponse()
{
  free(text);
}

bool cCamResponse::Parse(const char *s)
{
  // Number:
  s = skipspace(s);
  if (*s == '*') {
     camNumber = 0; // all CAMs
     s++;
     }
  else {
     char *e;
     camNumber = strtol(s, &e, 10);
     if (e == s || camNumber <= 0)
        return false;
     s = e;
     }
  // Text:
  s = skipspace(s);
  char *t = const_cast<char *>(s); // might have to modify it
  char *q = NULL; // holds a copy in case of backslashes
  bool InQuotes = false;
  while (*t) {
        if (*t == '"') {
           if (t == s) { // opening quotes
              InQuotes = true;
              s++;
              }
           else if (InQuotes) // closing quotes
              break;
           }
        else if (*t == '\\') {
           if (!q) { // need to make a copy in order to strip backslashes
              q = strdup(s);
              t = q + (t - s);
              s = q;
              }
           memmove(t, t + 1, strlen(t));
           }
        else if (*t == ' ') {
           if (!InQuotes)
              break;
           }
        t++;
        }
  free(text); // just for safety
  text = NULL;
  if (t != s) {
     text = strndup(s, t - s);
     s = t + 1;
     }
  free(q);
  if (!text)
     return false;
  // Action:
  s = skipspace(s);
  if      (strcasecmp(s, "DISCARD") == 0) action = CRA_DISCARD;
  else if (strcasecmp(s, "CONFIRM") == 0) action = CRA_CONFIRM;
  else if (strcasecmp(s, "SELECT")  == 0) action = CRA_SELECT;
  else if (isnumber(s))                   action = atoi(s);
  else
     return false;
  return true;
}

int cCamResponse::Matches(int CamNumber, const char *Text) const
{
  if (!camNumber || camNumber == CamNumber) {
     if (strcmp(text, Text) == 0)
        return action;
     }
  return CRA_NONE;
}

// --- cCamResponses  --------------------------------------------------------

class cCamResponses : public cConfig<cCamResponse> {
public:
  int GetMatch(int CamNumber, const char *Text) const;
  };

int cCamResponses::GetMatch(int CamNumber, const char *Text) const
{
  for (const cCamResponse *cr = First(); cr; cr = Next(cr)) {
      int Action = cr->Matches(CamNumber, Text);
      if (Action != CRA_NONE) {
         dsyslog("CAM %d: auto response %4d to '%s'\n", CamNumber, Action, Text);
         return Action;
         }
      }
  return CRA_NONE;
}

cCamResponses CamResponses;

bool CamResponsesLoad(const char *FileName, bool AllowComments, bool MustExist)
{
  return CamResponses.Load(FileName, AllowComments, MustExist);
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
462 463
// --- cTPDU -----------------------------------------------------------------

464
#define MAX_TPDU_SIZE  4096
Klaus Schmidinger's avatar
Klaus Schmidinger committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
#define MAX_TPDU_DATA  (MAX_TPDU_SIZE - 4)

#define DATA_INDICATOR 0x80

#define T_SB           0x80
#define T_RCV          0x81
#define T_CREATE_TC    0x82
#define T_CTC_REPLY    0x83
#define T_DELETE_TC    0x84
#define T_DTC_REPLY    0x85
#define T_REQUEST_TC   0x86
#define T_NEW_TC       0x87
#define T_TC_ERROR     0x88
#define T_DATA_LAST    0xA0
#define T_DATA_MORE    0xA1

class cTPDU {
private:
  int size;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
484
  uint8_t buffer[MAX_TPDU_SIZE];
Klaus Schmidinger's avatar
Klaus Schmidinger committed
485 486 487 488
  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);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
489 490 491 492
  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); }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
493
  uint8_t Status(void);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
494 495 496 497 498
  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);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
499 500 501 502 503
  };

cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t *Data)
{
  size = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
504 505 506
  buffer[0] = Slot;
  buffer[1] = Tcid;
  buffer[2] = Tag;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
507 508 509 510 511 512 513
  switch (Tag) {
    case T_RCV:
    case T_CREATE_TC:
    case T_CTC_REPLY:
    case T_DELETE_TC:
    case T_DTC_REPLY:
    case T_REQUEST_TC:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
514 515
         buffer[3] = 1; // length
         buffer[4] = Tcid;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
516 517 518 519 520
         size = 5;
         break;
    case T_NEW_TC:
    case T_TC_ERROR:
         if (Length == 1) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
521 522 523
            buffer[3] = 2; // length
            buffer[4] = Tcid;
            buffer[5] = Data[0];
Klaus Schmidinger's avatar
Klaus Schmidinger committed
524 525 526
            size = 6;
            }
         else
Klaus Schmidinger's avatar
Klaus Schmidinger committed
527
            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
528 529 530 531
         break;
    case T_DATA_LAST:
    case T_DATA_MORE:
         if (Length <= MAX_TPDU_DATA) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
532
            uint8_t *p = buffer + 3;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
533 534 535 536
            p = SetLength(p, Length + 1);
            *p++ = Tcid;
            if (Length)
               memcpy(p, Data, Length);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
537
            size = Length + (p - buffer);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
538 539
            }
         else
Klaus Schmidinger's avatar
Klaus Schmidinger committed
540
            esyslog("ERROR: invalid data length for TPDU tag 0x%02X: %d (%d/%d)", Tag, Length, Slot, Tcid);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
541 542
         break;
    default:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
543
         esyslog("ERROR: unknown TPDU tag: 0x%02X (%d/%d)", Tag, Slot, Tcid);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
544 545 546
    }
 }

Klaus Schmidinger's avatar
Klaus Schmidinger committed
547
void cTPDU::Dump(int SlotNumber, bool Outgoing)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
548
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
549
  if (DumpTPDUDataTransfer && (DumpPolls || Tag() != T_SB)) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
550
#define MAX_DUMP 256
Klaus Schmidinger's avatar
Klaus Schmidinger committed
551
     fprintf(stderr, "     %d: %s ", SlotNumber, Outgoing ? "-->" : "<--");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
552
     for (int i = 0; i < size && i < MAX_DUMP; i++)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
553
         fprintf(stderr, "%02X ", buffer[i]);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
554
     fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : "");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
555
     if (!Outgoing) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
556
        fprintf(stderr, "           ");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
557
        for (int i = 0; i < size && i < MAX_DUMP; i++)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
558
            fprintf(stderr, "%2c ", isprint(buffer[i]) ? buffer[i] : '.');
Klaus Schmidinger's avatar
Klaus Schmidinger committed
559
        fprintf(stderr, "%s\n", size >= MAX_DUMP ? "..." : "");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
        }
     }
}

const uint8_t *cTPDU::GetData(const uint8_t *Data, int &Length)
{
  if (size) {
     Data = GetLength(Data, Length);
     if (Length) {
        Length--; // the first byte is always the tcid
        return Data + 1;
        }
     }
  return NULL;
}

uint8_t cTPDU::Status(void)
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
578 579
  if (size >= 4 && buffer[size - 4] == T_SB && buffer[size - 3] == 2)
     return buffer[size - 1];
Klaus Schmidinger's avatar
Klaus Schmidinger committed
580 581 582 583 584
  return 0;
}

// --- cCiTransportConnection ------------------------------------------------

Klaus Schmidinger's avatar
Klaus Schmidinger committed
585
#define MAX_SESSIONS_PER_TC  16
Klaus Schmidinger's avatar
Klaus Schmidinger committed
586 587 588

class cCiTransportConnection {
private:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
589 590
  enum eState { stIDLE, stCREATION, stACTIVE, stDELETION };
  cCamSlot *camSlot;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
591 592
  uint8_t tcid;
  eState state;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
593 594 595 596 597 598
  bool createConnectionRequested;
  bool deleteConnectionRequested;
  bool hasUserIO;
  cTimeMs alive;
  cTimeMs timer;
  cCiSession *sessions[MAX_SESSIONS_PER_TC + 1]; // session numbering starts with 1
599
  cCiSession *tsPostProcessor;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
600 601 602 603 604 605 606 607
  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);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
608
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
609 610
  cCiTransportConnection(cCamSlot *CamSlot, uint8_t Tcid);
  virtual ~cCiTransportConnection();
611 612
  void SetTsPostProcessor(cCiSession *CiSession);
  bool TsPostProcess(uint8_t *TsPacket);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
613 614 615 616 617 618 619 620 621 622
  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);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
623 624
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
625
// --- cCiSession ------------------------------------------------------------
Klaus Schmidinger's avatar
Klaus Schmidinger committed
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701

// Session Tags:

#define ST_SESSION_NUMBER           0x90
#define ST_OPEN_SESSION_REQUEST     0x91
#define ST_OPEN_SESSION_RESPONSE    0x92
#define ST_CREATE_SESSION           0x93
#define ST_CREATE_SESSION_RESPONSE  0x94
#define ST_CLOSE_SESSION_REQUEST    0x95
#define ST_CLOSE_SESSION_RESPONSE   0x96

// Session Status:

#define SS_OK             0x00
#define SS_NOT_ALLOCATED  0xF0

// Resource Identifiers:

#define RI_RESOURCE_MANAGER            0x00010041
#define RI_APPLICATION_INFORMATION     0x00020041
#define RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
#define RI_HOST_CONTROL                0x00200041
#define RI_DATE_TIME                   0x00240041
#define RI_MMI                         0x00400041

// Application Object Tags:

#define AOT_NONE                    0x000000
#define AOT_PROFILE_ENQ             0x9F8010
#define AOT_PROFILE                 0x9F8011
#define AOT_PROFILE_CHANGE          0x9F8012
#define AOT_APPLICATION_INFO_ENQ    0x9F8020
#define AOT_APPLICATION_INFO        0x9F8021
#define AOT_ENTER_MENU              0x9F8022
#define AOT_CA_INFO_ENQ             0x9F8030
#define AOT_CA_INFO                 0x9F8031
#define AOT_CA_PMT                  0x9F8032
#define AOT_CA_PMT_REPLY            0x9F8033
#define AOT_TUNE                    0x9F8400
#define AOT_REPLACE                 0x9F8401
#define AOT_CLEAR_REPLACE           0x9F8402
#define AOT_ASK_RELEASE             0x9F8403
#define AOT_DATE_TIME_ENQ           0x9F8440
#define AOT_DATE_TIME               0x9F8441
#define AOT_CLOSE_MMI               0x9F8800
#define AOT_DISPLAY_CONTROL         0x9F8801
#define AOT_DISPLAY_REPLY           0x9F8802
#define AOT_TEXT_LAST               0x9F8803
#define AOT_TEXT_MORE               0x9F8804
#define AOT_KEYPAD_CONTROL          0x9F8805
#define AOT_KEYPRESS                0x9F8806
#define AOT_ENQ                     0x9F8807
#define AOT_ANSW                    0x9F8808
#define AOT_MENU_LAST               0x9F8809
#define AOT_MENU_MORE               0x9F880A
#define AOT_MENU_ANSW               0x9F880B
#define AOT_LIST_LAST               0x9F880C
#define AOT_LIST_MORE               0x9F880D
#define AOT_SUBTITLE_SEGMENT_LAST   0x9F880E
#define AOT_SUBTITLE_SEGMENT_MORE   0x9F880F
#define AOT_DISPLAY_MESSAGE         0x9F8810
#define AOT_SCENE_END_MARK          0x9F8811
#define AOT_SCENE_DONE              0x9F8812
#define AOT_SCENE_CONTROL           0x9F8813
#define AOT_SUBTITLE_DOWNLOAD_LAST  0x9F8814
#define AOT_SUBTITLE_DOWNLOAD_MORE  0x9F8815
#define AOT_FLUSH_DOWNLOAD          0x9F8816
#define AOT_DOWNLOAD_REPLY          0x9F8817
#define AOT_COMMS_CMD               0x9F8C00
#define AOT_CONNECTION_DESCRIPTOR   0x9F8C01
#define AOT_COMMS_REPLY             0x9F8C02
#define AOT_COMMS_SEND_LAST         0x9F8C03
#define AOT_COMMS_SEND_MORE         0x9F8C04
#define AOT_COMMS_RCV_LAST          0x9F8C05
#define AOT_COMMS_RCV_MORE          0x9F8C06

702
#define RESOURCE_CLASS_MASK         0xFFFF0000
Klaus Schmidinger's avatar
Klaus Schmidinger committed
703

Klaus Schmidinger's avatar
Klaus Schmidinger committed
704
cCiSession::cCiSession(uint16_t SessionId, uint32_t ResourceId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
705 706 707 708 709 710 711 712 713 714
{
  sessionId = SessionId;
  resourceId = ResourceId;
  tc = Tc;
}

cCiSession::~cCiSession()
{
}

715 716 717 718 719 720 721 722 723 724
void cCiSession::SetResourceId(uint32_t Id)
{
  resourceId = Id;
}

void cCiSession::SetTsPostProcessor(void)
{
  tc->SetTsPostProcessor(this);
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
725 726
int cCiSession::GetTag(int &Length, const uint8_t **Data)
///< Gets the tag at Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
727
///< Returns the actual tag, or AOT_NONE in case of error.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
///< Upon return Length and Data represent the remaining data after the tag has been skipped.
{
  if (Length >= 3 && Data && *Data) {
     int t = 0;
     for (int i = 0; i < 3; i++)
         t = (t << 8) | *(*Data)++;
     Length -= 3;
     return t;
     }
  return AOT_NONE;
}

const uint8_t *cCiSession::GetData(const uint8_t *Data, int &Length)
{
  Data = GetLength(Data, Length);
  return Length ? Data : NULL;
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
746
void cCiSession::SendData(int Tag, int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
747
{
748
  uint8_t buffer[MAX_TPDU_SIZE];
Klaus Schmidinger's avatar
Klaus Schmidinger committed
749 750 751 752 753 754 755 756 757
  uint8_t *p = buffer;
  *p++ = ST_SESSION_NUMBER;
  *p++ = 0x02;
  *p++ = (sessionId >> 8) & 0xFF;
  *p++ =  sessionId       & 0xFF;
  *p++ = (Tag >> 16) & 0xFF;
  *p++ = (Tag >>  8) & 0xFF;
  *p++ =  Tag        & 0xFF;
  p = SetLength(p, Length);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
758
  if (p - buffer + Length < int(sizeof(buffer))) {
etobi's avatar
etobi committed
759 760
     if (Data)
        memcpy(p, Data, Length);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
761
     p += Length;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
762
     tc->SendData(p - buffer, buffer);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
763
     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
764
  else
765 766 767 768 769 770
     esyslog("ERROR: CAM %d: data length (%d) exceeds buffer size", CamSlot()->SlotNumber(), Length);
}

cCamSlot *cCiSession::CamSlot(void)
{
  return Tc()->CamSlot();
Klaus Schmidinger's avatar
Klaus Schmidinger committed
771 772
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
773
void cCiSession::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
774 775 776
{
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
777
// --- cCiResourceManager ----------------------------------------------------
Klaus Schmidinger's avatar
Klaus Schmidinger committed
778 779 780 781 782

class cCiResourceManager : public cCiSession {
private:
  int state;
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
783
  cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
784
  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
785 786
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
787
cCiResourceManager::cCiResourceManager(uint16_t SessionId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
788 789
:cCiSession(SessionId, RI_RESOURCE_MANAGER, Tc)
{
790
  dbgprotocol("Slot %d: new Resource Manager (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
791 792 793
  state = 0;
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
794
void cCiResourceManager::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
795 796 797 798 799
{
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_PROFILE_ENQ: {
800 801 802
            dbgprotocol("Slot %d: <== Profile Enquiry (%d)\n", CamSlot()->SlotNumber(), SessionId());
            dbgprotocol("Slot %d: ==> Profile (%d)\n", CamSlot()->SlotNumber(), SessionId());
            SendData(AOT_PROFILE, CiResourceHandlers.NumIds() * sizeof(uint32_t), (uint8_t*)CiResourceHandlers.Ids());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
803 804 805 806
            state = 3;
            }
            break;
       case AOT_PROFILE: {
807
            dbgprotocol("Slot %d: <== Profile (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
808 809 810 811
            if (state == 1) {
               int l = 0;
               const uint8_t *d = GetData(Data, l);
               if (l > 0 && d)
812 813
                  esyslog("ERROR: CAM %d: resource manager: unexpected data", CamSlot()->SlotNumber());
               dbgprotocol("Slot %d: ==> Profile Change (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
814 815 816 817
               SendData(AOT_PROFILE_CHANGE);
               state = 2;
               }
            else {
818
               esyslog("ERROR: CAM %d: resource manager: unexpected tag %06X in state %d", CamSlot()->SlotNumber(), Tag, state);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
819 820 821
               }
            }
            break;
822
       default: esyslog("ERROR: CAM %d: resource manager: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
823 824 825
       }
     }
  else if (state == 0) {
826
     dbgprotocol("Slot %d: ==> Profile Enq (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
827 828 829 830 831 832 833
     SendData(AOT_PROFILE_ENQ);
     state = 1;
     }
}

// --- cCiApplicationInformation ---------------------------------------------

Klaus Schmidinger's avatar
Klaus Schmidinger committed
834
cCiApplicationInformation::cCiApplicationInformation(uint16_t SessionId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
835 836
:cCiSession(SessionId, RI_APPLICATION_INFORMATION, Tc)
{
837
  dbgprotocol("Slot %d: new Application Information (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
838 839 840 841 842 843 844 845 846
  state = 0;
  menuString = NULL;
}

cCiApplicationInformation::~cCiApplicationInformation()
{
  free(menuString);
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
847
void cCiApplicationInformation::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
848 849 850 851 852
{
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_APPLICATION_INFO: {
853
            dbgprotocol("Slot %d: <== Application Info (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
854 855 856 857 858
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if ((l -= 1) < 0) break;
            applicationType = *d++;
            if ((l -= 2) < 0) break;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
859
            applicationManufacturer = ntohs(get_unaligned((uint16_t *)d));
Klaus Schmidinger's avatar
Klaus Schmidinger committed
860 861
            d += 2;
            if ((l -= 2) < 0) break;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
862
            manufacturerCode = ntohs(get_unaligned((uint16_t *)d));
Klaus Schmidinger's avatar
Klaus Schmidinger committed
863 864 865
            d += 2;
            free(menuString);
            menuString = GetString(l, &d);
866
            isyslog("CAM %d: %s, %02X, %04X, %04X", CamSlot()->SlotNumber(), menuString, applicationType, applicationManufacturer, manufacturerCode);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
867
            state = 2;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
868
            }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
869
            break;
870
       default: esyslog("ERROR: CAM %d: application information: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
871 872 873
       }
     }
  else if (state == 0) {
874
     dbgprotocol("Slot %d: ==> Application Info Enq (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
875 876 877 878 879 880 881
     SendData(AOT_APPLICATION_INFO_ENQ);
     state = 1;
     }
}

bool cCiApplicationInformation::EnterMenu(void)
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
882
  if (state == 2) {
883
     dbgprotocol("Slot %d: ==> Enter Menu (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
884
     SendData(AOT_ENTER_MENU);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
885
     return true;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
886 887 888 889
     }
  return false;
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
890 891
// --- cCiCaPmt --------------------------------------------------------------

Klaus Schmidinger's avatar
Klaus Schmidinger committed
892 893
#define MAXCASYSTEMIDS 64

Klaus Schmidinger's avatar
Klaus Schmidinger committed
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
// Ca Pmt List Management:

#define CPLM_MORE    0x00
#define CPLM_FIRST   0x01
#define CPLM_LAST    0x02
#define CPLM_ONLY    0x03
#define CPLM_ADD     0x04
#define CPLM_UPDATE  0x05

// Ca Pmt Cmd Ids:

#define CPCI_OK_DESCRAMBLING  0x01
#define CPCI_OK_MMI           0x02
#define CPCI_QUERY            0x03
#define CPCI_NOT_SELECTED     0x04

etobi's avatar
etobi committed
910
class cCiCaPmt {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
911 912 913 914
  friend class cCiConditionalAccessSupport;
private:
  uint8_t cmdId;
  int esInfoLengthPos;
915 916
  cDynamicBuffer caDescriptors;
  cDynamicBuffer capmt;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
917 918 919 920
  int source;
  int transponder;
  int programNumber;
  int caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated!
Klaus Schmidinger's avatar
Klaus Schmidinger committed
921 922
  void AddCaDescriptors(int Length, const uint8_t *Data);
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
923 924
  cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds);
  uint8_t CmdId(void) { return cmdId; }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
925
  void SetListManagement(uint8_t ListManagement);
926
  uint8_t ListManagement(void) { return capmt.Get(0); }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
927
  void AddPid(int Pid, uint8_t StreamType);
928
  void MtdMapPids(cMtdMapper *MtdMapper);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
929 930
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
931
cCiCaPmt::cCiCaPmt(uint8_t CmdId, int Source, int Transponder, int ProgramNumber, const int *CaSystemIds)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
932 933
{
  cmdId = CmdId;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
934 935 936 937 938 939 940 941 942
  source = Source;
  transponder = Transponder;
  programNumber = ProgramNumber;
  int i = 0;
  if (CaSystemIds) {
     for (; CaSystemIds[i]; i++)
         caSystemIds[i] = CaSystemIds[i];
     }
  caSystemIds[i] = 0;
943 944 945 946 947 948 949 950 951
  GetCaDescriptors(source, transponder, programNumber, caSystemIds, caDescriptors, 0);
  capmt.Append(CPLM_ONLY);
  capmt.Append((ProgramNumber >> 8) & 0xFF);
  capmt.Append( ProgramNumber       & 0xFF);
  capmt.Append(0x01); // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1
  esInfoLengthPos = capmt.Length();
  capmt.Append(0x00); // program_info_length H (at program level)
  capmt.Append(0x00); // program_info_length L
  AddCaDescriptors(caDescriptors.Length(), caDescriptors.Data());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
952 953 954 955
}

void cCiCaPmt::SetListManagement(uint8_t ListManagement)
{
956
  capmt.Set(0, ListManagement);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
957 958 959 960 961
}

void cCiCaPmt::AddPid(int Pid, uint8_t StreamType)
{
  if (Pid) {
962 963 964 965 966 967 968 969
     GetCaDescriptors(source, transponder, programNumber, caSystemIds, caDescriptors, Pid);
     capmt.Append(StreamType);
     capmt.Append((Pid >> 8) & 0xFF);
     capmt.Append( Pid       & 0xFF);
     esInfoLengthPos = capmt.Length();
     capmt.Append(0x00); // ES_info_length H (at ES level)
     capmt.Append(0x00); // ES_info_length L
     AddCaDescriptors(caDescriptors.Length(), caDescriptors.Data());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
970 971 972 973 974 975
     }
}

void cCiCaPmt::AddCaDescriptors(int Length, const uint8_t *Data)
{
  if (esInfoLengthPos) {
976 977 978 979 980 981
     if (Length || cmdId == CPCI_QUERY) {
        capmt.Append(cmdId);
        capmt.Append(Data, Length);
        int l = capmt.Length() - esInfoLengthPos - 2;
        capmt.Set(esInfoLengthPos,     (l >> 8) & 0xFF);
        capmt.Set(esInfoLengthPos + 1,  l       & 0xFF);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
982 983 984 985 986 987 988
        }
     esInfoLengthPos = 0;
     }
  else
     esyslog("ERROR: adding CA descriptor without Pid!");
}

989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
static int MtdMapCaDescriptor(uchar *p, cMtdMapper *MtdMapper)
{
  // See pat.c: cCaDescriptor::cCaDescriptor() for the layout of the data!
  if (*p == SI::CaDescriptorTag) {
     int l = *++p;
     if (l >= 4) {
        MtdMapPid(p + 3, MtdMapper);
        return l + 2;
        }
     else
        esyslog("ERROR: wrong length (%d) in MtdMapCaDescriptor()", l);
     }
  else
     esyslog("ERROR: wrong tag (%d) in MtdMapCaDescriptor()", *p);
  return -1;
}

static int MtdMapCaDescriptors(uchar *p, cMtdMapper *MtdMapper)
{
  int Length = p[0] * 256 + p[1];
  if (Length >= 3) {
     p += 3;
     int m = Length - 1;
     while (m > 0) {
           int l = MtdMapCaDescriptor(p, MtdMapper);
           if (l > 0) {
              p += l;
              m -= l;
              }
           }
     }
  return Length + 2;
}

static int MtdMapStream(uchar *p, cMtdMapper *MtdMapper)
{
  // See ci.c: cCiCaPmt::AddPid() for the layout of the data!
  MtdMapPid(p + 1, MtdMapper);
  int l = MtdMapCaDescriptors(p + 3, MtdMapper);
  if (l > 0)
     return l + 3;
  return -1;
}

static int MtdMapStreams(uchar *p, cMtdMapper *MtdMapper, int Length)
{
  int m = Length;
  while (m >= 5) {
        int l = MtdMapStream(p, MtdMapper);
        if (l > 0) {
           p += l;
           m -= l;
           }
        else
           break;
        }
  return Length;
}

void cCiCaPmt::MtdMapPids(cMtdMapper *MtdMapper)
{
  uchar *p = capmt.Data();
  int m = capmt.Length();
  if (m >= 3) {
     MtdMapSid(p + 1, MtdMapper);
     p += 4;
     m -= 4;
     if (m >= 2) {
        int l = MtdMapCaDescriptors(p, MtdMapper);
        if (l >= 0) {
           p += l;
           m -= l;
           MtdMapStreams(p, MtdMapper, m);
           }
        }
     }
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1067 1068
// --- cCiConditionalAccessSupport -------------------------------------------

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
// CA Enable Ids:

#define CAEI_POSSIBLE                  0x01
#define CAEI_POSSIBLE_COND_PURCHASE    0x02
#define CAEI_POSSIBLE_COND_TECHNICAL   0x03
#define CAEI_NOT_POSSIBLE_ENTITLEMENT  0x71
#define CAEI_NOT_POSSIBLE_TECHNICAL    0x73

#define CA_ENABLE_FLAG                 0x80

#define CA_ENABLE(x) (((x) & CA_ENABLE_FLAG) ? (x) & ~CA_ENABLE_FLAG : 0)

1081
#define QUERY_WAIT_TIME       500 // ms to wait before sending a query
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1082
#define QUERY_REPLY_TIMEOUT  2000 // ms to wait for a reply to a query
1083
#define QUERY_RETRIES           6 // max. number of retries to check if there is a reply to a query
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1084

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1085 1086 1087
class cCiConditionalAccessSupport : public cCiSession {
private:
  int state;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1088
  int numCaSystemIds;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1089 1090 1091
  int caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated!
  bool repliesToQuery;
  cTimeMs timer;
1092
  int numRetries;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1093
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1094
  cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1095 1096 1097 1098 1099 1100 1101
  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; }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1102 1103
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1104
cCiConditionalAccessSupport::cCiConditionalAccessSupport(uint16_t SessionId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1105 1106
:cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc)
{
1107
  dbgprotocol("Slot %d: new Conditional Access Support (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1108
  state = 0; // inactive
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1109
  caSystemIds[numCaSystemIds = 0] = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1110
  repliesToQuery = false;
1111
  numRetries = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1112 1113
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1114
void cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1115
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1116 1117 1118 1119
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_CA_INFO: {
1120
            dbgprotocol("Slot %d: <== Ca Info (%d)", CamSlot()->SlotNumber(), SessionId());
etobi's avatar
etobi committed
1121
            cString Ids;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1122
            numCaSystemIds = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1123 1124 1125
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            while (l > 1) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1126
                  uint16_t id = ((uint16_t)(*d) << 8) | *(d + 1);
etobi's avatar
etobi committed
1127
                  Ids = cString::sprintf("%s %04X", *Ids ? *Ids : "", id);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1128
                  dbgprotocol(" %04X", id);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1129 1130
                  d += 2;
                  l -= 2;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1131
                  if (numCaSystemIds < MAXCASYSTEMIDS)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1132
                     caSystemIds[numCaSystemIds++] = id;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1133
                  else {
1134
                     esyslog("ERROR: CAM %d: too many CA system IDs!", CamSlot()->SlotNumber());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1135 1136
                     break;
                     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1137
                  }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1138
            caSystemIds[numCaSystemIds] = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1139
            dbgprotocol("\n");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1140
            if (state == 1) {
1141 1142
               timer.Set(0);
               numRetries = QUERY_RETRIES;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1143 1144
               state = 2; // got ca info
               }
1145
            dsyslog("CAM %d: system ids:%s", CamSlot()->SlotNumber(), *Ids ? *Ids : " none");
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1146
            }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1147 1148
            break;
       case AOT_CA_PMT_REPLY: {
1149
            dbgprotocol("Slot %d: <== Ca Pmt Reply (%d)", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1150
            if (!repliesToQuery) {
1151 1152
               if (CamSlot()->IsMasterSlot())
                  dsyslog("CAM %d: replies to QUERY - multi channel decryption (MCD) possible", CamSlot()->SlotNumber());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1153
               repliesToQuery = true;
1154
               if (CamSlot()->MtdAvailable()) {
1155 1156
                  if (CamSlot()->IsMasterSlot())
                     dsyslog("CAM %d: supports multi transponder decryption (MTD)", CamSlot()->SlotNumber());
1157 1158
                  CamSlot()->MtdActivate(true);
                  }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1159 1160
               }
            state = 5; // got ca pmt reply
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1161 1162 1163
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 1) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1164
               uint16_t pnr = ((uint16_t)(*d) << 8) | *(d + 1);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178
               dbgprotocol(" %d", pnr);
               d += 2;
               l -= 2;
               if (l > 0) {
                  dbgprotocol(" %02X", *d);
                  d += 1;
                  l -= 1;
                  if (l > 0) {
                     if (l % 3 == 0 && l > 1) {
                        // The EN50221 standard defines that the next byte is supposed
                        // to be the CA_enable value at programme level. However, there are
                        // CAMs (for instance the AlphaCrypt with firmware <= 3.05) that
                        // insert a two byte length field here.
                        // This is a workaround to skip this length field:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1179
                        uint16_t len = ((uint16_t)(*d) << 8) | *(d + 1);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
                        if (len == l - 2) {
                           d += 2;
                           l -= 2;
                           }
                        }
                     unsigned char caepl = *d;
                     dbgprotocol(" %02X", caepl);
                     d += 1;
                     l -= 1;
                     bool ok = true;
                     if (l <= 2)
                        ok = CA_ENABLE(caepl) == CAEI_POSSIBLE;
                     while (l > 2) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1193
                           uint16_t pid = ((uint16_t)(*d) << 8) | *(d + 1);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1194 1195 1196 1197 1198 1199 1200 1201
                           unsigned char caees = *(d + 2);
                           dbgprotocol(" %d=%02X", pid, caees);
                           d += 3;
                           l -= 3;
                           if (CA_ENABLE(caees) != CAEI_POSSIBLE)
                              ok = false;
                           }
                     if (ok)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1202
                        state = 6; // descrambling possible
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1203 1204 1205 1206 1207
                     }
                  }
               }
            dbgprotocol("\n");
            }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1208
            break;
1209
       default: esyslog("ERROR: CAM %d: conditional access support: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1210 1211 1212
       }
     }
  else if (state == 0) {
1213
     dbgprotocol("Slot %d: ==> Ca Info Enq (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1214
     SendData(AOT_CA_INFO_ENQ);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1215
     state = 1; // enquired ca info
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1216
     }
1217 1218 1219 1220 1221 1222 1223 1224 1225
  else if ((state == 2 || state == 3) && timer.TimedOut()) {
     if (numRetries-- > 0) {
        cCiCaPmt CaPmt(CPCI_QUERY, 0, 0, 0, NULL);
        SendPMT(&CaPmt);
        timer.Set(QUERY_WAIT_TIME);
        state = 3; // waiting for reply
        }
     else {
        dsyslog("CAM %d: doesn't reply to QUERY - only a single channel can be decrypted", CamSlot()->SlotNumber());
1226
        CamSlot()->MtdActivate(false);
1227 1228
        state = 4; // normal operation
        }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1229
     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1230 1231
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1232
void cCiConditionalAccessSupport::SendPMT(cCiCaPmt *CaPmt)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1233
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1234
  if (CaPmt && state >= 2) {
1235
     dbgprotocol("Slot %d: ==> Ca Pmt (%d) %d %d\n", CamSlot()->SlotNumber(), SessionId(), CaPmt->ListManagement(), CaPmt->CmdId());
1236
     SendData(AOT_CA_PMT, CaPmt->capmt.Length(), CaPmt->capmt.Data());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1237
     state = 4; // sent ca pmt
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1238
     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1239 1240
}

1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
// --- cCiHostControl --------------------------------------------------------

class cCiHostControl : public cCiSession {
public:
  cCiHostControl(uint16_t SessionId, cCiTransportConnection *Tc);
  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
  };

cCiHostControl::cCiHostControl(uint16_t SessionId, cCiTransportConnection* Tc)
:cCiSession(SessionId, RI_HOST_CONTROL, Tc)
{
  dbgprotocol("Slot %d: new Host Control (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
}

void cCiHostControl::Process(int Length, const uint8_t* Data)
{
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_TUNE:
            dbgprotocol("Slot %d: <== Host Control Tune (%d)\n", CamSlot()->SlotNumber(), SessionId());
            break;
       case AOT_REPLACE:
            dbgprotocol("Slot %d: <== Host Control Replace (%d)\n", CamSlot()->SlotNumber(), SessionId());
            break;
       case AOT_CLEAR_REPLACE:
            dbgprotocol("Slot %d: <== Host Control Clear Replace (%d)\n", CamSlot()->SlotNumber(), SessionId());
            break;
       default: esyslog("ERROR: CAM %d: Host Control: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
       }
     }
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1274 1275 1276 1277 1278 1279
// --- cCiDateTime -----------------------------------------------------------

class cCiDateTime : public cCiSession {
private:
  int interval;
  time_t lastTime;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1280
  void SendDateTime(void);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1281
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1282
  cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1283
  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1284 1285
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1286
cCiDateTime::cCiDateTime(uint16_t SessionId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1287 1288 1289 1290
:cCiSession(SessionId, RI_DATE_TIME, Tc)
{
  interval = 0;
  lastTime = 0;
1291
  dbgprotocol("Slot %d: new Date Time (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1292 1293
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1294
void cCiDateTime::SendDateTime(void)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
{
  time_t t = time(NULL);
  struct tm tm_gmt;
  struct tm tm_loc;
  if (gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc)) {
     int Y = tm_gmt.tm_year;
     int M = tm_gmt.tm_mon + 1;
     int D = tm_gmt.tm_mday;
     int L = (M == 1 || M == 2) ? 1 : 0;
     int MJD = 14956 + D + int((Y - L) * 365.25) + int((M + 1 + L * 12) * 30.6001);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1305
#define DEC2BCD(d) uint8_t(((d / 10) << 4) + (d % 10))
etobi's avatar
etobi committed
1306
#pragma pack(1)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1307
     struct tTime { uint16_t mjd; uint8_t h, m, s; short offset; };
etobi's avatar
etobi committed
1308
#pragma pack()
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1309
     tTime T = { mjd : htons(MJD), h : DEC2BCD(tm_gmt.tm_hour), m : DEC2BCD(tm_gmt.tm_min), s : DEC2BCD(tm_gmt.tm_sec), offset : short(htons(tm_loc.tm_gmtoff / 60)) };
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1310 1311 1312
     bool OldDumpTPDUDataTransfer = DumpTPDUDataTransfer;
     DumpTPDUDataTransfer &= DumpDateTime;
     if (DumpDateTime)
1313
        dbgprotocol("Slot %d: ==> Date Time (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1314
     SendData(AOT_DATE_TIME, 7, (uint8_t*)&T);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1315
     DumpTPDUDataTransfer = OldDumpTPDUDataTransfer;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1316 1317 1318
     }
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1319
void cCiDateTime::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329
{
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_DATE_TIME_ENQ: {
            interval = 0;
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 0)
               interval = *d;
1330
            dbgprotocol("Slot %d: <== Date Time Enq (%d), interval = %d\n", CamSlot()->SlotNumber(), SessionId(), interval);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1331
            lastTime = time(NULL);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1332
            SendDateTime();
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1333 1334
            }
            break;
1335
       default: esyslog("ERROR: CAM %d: date time: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1336 1337 1338 1339
       }
     }
  else if (interval && time(NULL) - lastTime > interval) {
     lastTime = time(NULL);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1340
     SendDateTime();
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382
     }
}

// --- cCiMMI ----------------------------------------------------------------

// Display Control Commands:

#define DCC_SET_MMI_MODE                          0x01
#define DCC_DISPLAY_CHARACTER_TABLE_LIST          0x02
#define DCC_INPUT_CHARACTER_TABLE_LIST            0x03
#define DCC_OVERLAY_GRAPHICS_CHARACTERISTICS      0x04
#define DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS  0x05

// MMI Modes:

#define MM_HIGH_LEVEL                      0x01
#define MM_LOW_LEVEL_OVERLAY_GRAPHICS      0x02
#define MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS  0x03

// Display Reply IDs:

#define DRI_MMI_MODE_ACK                              0x01
#define DRI_LIST_DISPLAY_CHARACTER_TABLES             0x02
#define DRI_LIST_INPUT_CHARACTER_TABLES               0x03
#define DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS      0x04
#define DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS  0x05
#define DRI_UNKNOWN_DISPLAY_CONTROL_CMD               0xF0
#define DRI_UNKNOWN_MMI_MODE                          0xF1
#define DRI_UNKNOWN_CHARACTER_TABLE                   0xF2

// Enquiry Flags:

#define EF_BLIND  0x01

// Answer IDs:

#define AI_CANCEL  0x00
#define AI_ANSWER  0x01

class cCiMMI : public cCiSession {
private:
  char *GetText(int &Length, const uint8_t **Data);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1383 1384
  cCiMenu *menu, *fetchedMenu;
  cCiEnquiry *enquiry, *fetchedEnquiry;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1385
public:
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1386
  cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1387
  virtual ~cCiMMI();
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1388
  virtual void Process(int Length = 0, const uint8_t *Data = NULL);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1389
  virtual bool HasUserIO(void) { return menu || enquiry; }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1390 1391
  cCiMenu *Menu(bool Clear = false);
  cCiEnquiry *Enquiry(bool Clear = false);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1392
  void SendMenuAnswer(uint8_t Selection);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1393
  bool SendAnswer(const char *Text);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1394
  bool SendCloseMMI(void);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1395 1396
  };

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1397
cCiMMI::cCiMMI(uint16_t SessionId, cCiTransportConnection *Tc)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1398 1399
:cCiSession(SessionId, RI_MMI, Tc)
{
1400
  dbgprotocol("Slot %d: new MMI (session id %d)\n", CamSlot()->SlotNumber(), SessionId);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1401 1402
  menu = fetchedMenu = NULL;
  enquiry = fetchedEnquiry = NULL;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1403 1404 1405 1406
}

cCiMMI::~cCiMMI()
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1407
  if (fetchedMenu) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1408
     cMutexLock MutexLock(fetchedMenu->mutex);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1409 1410
     fetchedMenu->mmi = NULL;
     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1411
  delete menu;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1412
  if (fetchedEnquiry) {
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1413
     cMutexLock MutexLock(fetchedEnquiry->mutex);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1414 1415
     fetchedEnquiry->mmi = NULL;
     }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1416 1417 1418 1419 1420
  delete enquiry;
}

char *cCiMMI::GetText(int &Length, const uint8_t **Data)
///< Gets the text at Data.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1421
///< Returns a pointer to a newly allocated string, or NULL in case of error.
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1422 1423 1424 1425 1426
///< Upon return Length and Data represent the remaining data after the text has been skipped.
{
  int Tag = GetTag(Length, Data);
  if (Tag == AOT_TEXT_LAST) {
     char *s = GetString(Length, Data);
1427
     dbgprotocol("Slot %d: <== Text Last (%d) '%s'\n", CamSlot()->SlotNumber(), SessionId(), s);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1428 1429 1430
     return s;
     }
  else
1431
     esyslog("ERROR: CAM %d: MMI: unexpected text tag: %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1432 1433 1434
  return NULL;
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1435
void cCiMMI::Process(int Length, const uint8_t *Data)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1436 1437 1438 1439 1440
{
  if (Data) {
     int Tag = GetTag(Length, &Data);
     switch (Tag) {
       case AOT_DISPLAY_CONTROL: {
1441
            dbgprotocol("Slot %d: <== Display Control (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1442 1443 1444 1445 1446 1447 1448 1449
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 0) {
               switch (*d) {
                 case DCC_SET_MMI_MODE:
                      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 };
1450
                         dbgprotocol("Slot %d: ==> Display Reply (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1451 1452 1453
                         SendData(AOT_DISPLAY_REPLY, 2, (uint8_t *)&dr);
                         }
                      break;
1454
                 default: esyslog("ERROR: CAM %d: MMI: unsupported display control command %02X", CamSlot()->SlotNumber(), *d);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1455 1456 1457 1458 1459 1460
                 }
               }
            }
            break;
       case AOT_LIST_LAST:
       case AOT_MENU_LAST: {
1461
            dbgprotocol("Slot %d: <== Menu Last (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472
            delete menu;
            menu = new cCiMenu(this, Tag == AOT_MENU_LAST);
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 0) {
               // since the specification allows choiceNb to be undefined it is useless, so let's just skip it:
               d++;
               l--;
               if (l > 0) menu->titleText = GetText(l, &d);
               if (l > 0) menu->subTitleText = GetText(l, &d);
               if (l > 0) menu->bottomText = GetText(l, &d);
1473 1474 1475
               int Action = CRA_NONE;
               int Select = -1;
               int Item = 0;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1476 1477 1478 1479 1480
               while (l > 0) {
                     char *s = GetText(l, &d);
                     if (s) {
                        if (!menu->AddEntry(s))
                           free(s);
1481 1482 1483 1484 1485
                        else if (Action == CRA_NONE) {
                           Action = CamResponses.GetMatch(CamSlot()->SlotNumber(), s);
                           if (Action == CRA_SELECT)
                              Select = Item;
                           }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1486 1487 1488
                        }
                     else
                        break;
1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
                     Item++;
                     }
               if (Action != CRA_NONE) {
                  delete menu;
                  menu = NULL;
                  cCondWait::SleepMs(100);
                  if (Action == CRA_DISCARD) {
                     SendCloseMMI();
                     dsyslog("CAM %d: DISCARD", CamSlot()->SlotNumber());
                     }
                  else if (Action == CRA_CONFIRM) {
                     SendMenuAnswer(1);
                     dsyslog("CAM %d: CONFIRM", CamSlot()->SlotNumber());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1502
                     }
1503 1504 1505 1506 1507
                  else if (Action == CRA_SELECT) {
                     SendMenuAnswer(Select + 1);
                     dsyslog("CAM %d: SELECT %d", CamSlot()->SlotNumber(), Select + 1);
                     }
                  }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1508 1509 1510 1511
               }
            }
            break;
       case AOT_ENQ: {
1512
            dbgprotocol("Slot %d: <== Enq (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
            delete enquiry;
            enquiry = new cCiEnquiry(this);
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 0) {
               uint8_t blind = *d++;
               //XXX GetByte()???
               l--;
               enquiry->blind = blind & EF_BLIND;
               enquiry->expectedLength = *d++;
               l--;
               // I really wonder why there is no text length field here...
               enquiry->text = CopyString(l, d);
1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538
               int Action = CamResponses.GetMatch(CamSlot()->SlotNumber(), enquiry->text);
               if (Action > CRA_NONE) {
                  char s[enquiry->expectedLength * 2];
                  snprintf(s, sizeof(s), "%d", Action);
                  if (int(strlen(s)) == enquiry->expectedLength) {
                     delete enquiry;
                     enquiry = NULL;
                     SendAnswer(s);
                     dsyslog("CAM %d: PIN", CamSlot()->SlotNumber());
                     }
                  else
                     esyslog("CAM %d: ERROR: unexpected PIN length %d, expected %d", CamSlot()->SlotNumber(), int(strlen(s)), enquiry->expectedLength);
                  }
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1539 1540 1541
               }
            }
            break;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551
       case AOT_CLOSE_MMI: {
            int id = -1;
            int delay = -1;
            int l = 0;
            const uint8_t *d = GetData(Data, l);
            if (l > 0) {
               id = *d++;
               if (l > 1)
                  delay = *d;
               }
1552
            dbgprotocol("Slot %d: <== Close MMI (%d)  id = %02X  delay = %d\n", CamSlot()->SlotNumber(), SessionId(), id, delay);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1553 1554
            }
            break;
1555
       default: esyslog("ERROR: CAM %d: MMI: unknown tag %06X", CamSlot()->SlotNumber(), Tag);
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1556 1557 1558 1559
       }
     }
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1560
cCiMenu *cCiMMI::Menu(bool Clear)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1561
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1562 1563 1564 1565 1566 1567 1568
  if (Clear)
     fetchedMenu = NULL;
  else if (menu) {
     fetchedMenu = menu;
     menu = NULL;
     }
  return fetchedMenu;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1569 1570
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1571
cCiEnquiry *cCiMMI::Enquiry(bool Clear)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1572
{
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1573 1574 1575 1576 1577 1578 1579
  if (Clear)
     fetchedEnquiry = NULL;
  else if (enquiry) {
     fetchedEnquiry = enquiry;
     enquiry = NULL;
     }
  return fetchedEnquiry;
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1580 1581
}

Klaus Schmidinger's avatar
Klaus Schmidinger committed
1582
void cCiMMI::SendMenuAnswer(uint8_t Selection)
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1583
{
1584
  dbgprotocol("Slot %d: ==> Menu Answ (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1585 1586 1587 1588 1589
  SendData(AOT_MENU_ANSW, 1, &Selection);
}

bool cCiMMI::SendAnswer(const char *Text)
{
1590
  dbgprotocol("Slot %d: ==> Answ (%d)\n", CamSlot()->SlotNumber(), SessionId());
Klaus Schmidinger's avatar
Klaus Schmidinger committed
1591 1592 1593
  struct tAnswer { uint8_t id; char text[256]; };//XXX
  tAnswer answer;
  answer.id = Text ? AI_ANSWER : AI_CANCEL;
1594 1595 1596 1597 1598 1599
  int len = 0;
  if (Text) {
     len = min(sizeof(answer.text), strlen(Text));
     memcpy(answer.text, Text, len);
     }
  SendData(AOT_ANSW, len + 1, (uint8_t *)&answer);