MythTV  0.26-pre
dvbci.cpp
Go to the documentation of this file.
00001 /*
00002  * ci.cc: Common Interface
00003  *
00004  * Copyright (C) 2000 Klaus Schmidinger
00005  *
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * as published by the Free Software Foundation; either version 2
00009  * of the License, or (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00019  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
00020  *
00021  * The author can be reached at kls@cadsoft.de
00022  *
00023  * The project's page is at http://www.cadsoft.de/people/kls/vdr
00024  *
00025  */
00026 
00027 #include "dvbci.h"
00028 #include <errno.h>
00029 #include <ctype.h>
00030 #include <linux/dvb/ca.h>
00031 #include <malloc.h>
00032 #include <netinet/in.h>
00033 #include <poll.h>
00034 #include <string.h>
00035 #include <sys/ioctl.h>
00036 #include <sys/time.h>
00037 #include <time.h>
00038 #include <unistd.h>
00039 #include <fcntl.h>
00040 
00041 #include <QString>
00042 
00043 #include "mythlogging.h"
00044 
00045 #ifndef MALLOC
00046 #define MALLOC(type, size)  (type *)malloc(sizeof(type) * (size))
00047 #endif
00048 
00049 #define esyslog(a...) LOG(VB_GENERAL, LOG_ERR, QString().sprintf(a))
00050 #define isyslog(a...) LOG(VB_DVBCAM, LOG_INFO, QString().sprintf(a))
00051 #define dsyslog(a...) LOG(VB_DVBCAM, LOG_DEBUG, QString().sprintf(a))
00052 
00053 #define LOG_ERROR         esyslog("ERROR (%s,%d): %m", __FILE__, __LINE__)
00054 #define LOG_ERROR_STR(s)  esyslog("ERROR: %s: %m", s)
00055 
00056 
00057 // Set these to 'true' for debug output:
00058 static bool DumpTPDUDataTransfer = false;
00059 static bool DebugProtocol = false;
00060 static bool _connected = false;
00061 
00062 #define dbgprotocol(a...) if (DebugProtocol) LOG(VB_DVBCAM, LOG_DEBUG, QString().sprintf(a))
00063 
00064 #define OK       0
00065 #define TIMEOUT -1
00066 #define ERROR   -2
00067 
00068 // --- Workarounds -----------------------------------------------------------
00069 
00070 // The Irdeto AllCAM 4.7 (and maybe others, too) does not react on AOT_ENTER_MENU
00071 // during the first few seconds of a newly established connection
00072 #define WRKRND_TIME_BEFORE_ENTER_MENU  15 // seconds
00073 
00074 // --- Helper functions ------------------------------------------------------
00075 
00076 #define SIZE_INDICATOR 0x80
00077 
00078 static ssize_t safe_read(int filedes, void *buffer, size_t size)
00079 {
00080   for (;;) {
00081       ssize_t p = read(filedes, buffer, size);
00082       if (p < 0 && (errno == EINTR || errno == EAGAIN)) {
00083          dsyslog("EINTR while reading from file handle %d - retrying", filedes);
00084          continue;
00085          }
00086       return p;
00087       }
00088 }
00089 
00090 static const uint8_t *GetLength(const uint8_t *Data, int &Length)
00094 {
00095   Length = *Data++;
00096   if ((Length & SIZE_INDICATOR) != 0) {
00097      int l = Length & ~SIZE_INDICATOR;
00098      Length = 0;
00099      for (int i = 0; i < l; i++)
00100          Length = (Length << 8) | *Data++;
00101      }
00102   return Data;
00103 }
00104 
00105 static uint8_t *SetLength(uint8_t *Data, int Length)
00108 {
00109   uint8_t *p = Data;
00110   if (Length < 128)
00111      *p++ = Length;
00112   else {
00113      int n = sizeof(Length);
00114      for (int i = n - 1; i >= 0; i--) {
00115          int b = (Length >> (8 * i)) & 0xFF;
00116          if (p != Data || b)
00117             *++p = b;
00118          }
00119      *Data = (p - Data) | SIZE_INDICATOR;
00120      p++;
00121      }
00122   return p;
00123 }
00124 
00125 static char *CopyString(int Length, const uint8_t *Data)
00128 {
00129   char *s = MALLOC(char, Length + 1);
00130   strncpy(s, (char *)Data, Length);
00131   s[Length] = 0;
00132   return s;
00133 }
00134 
00135 static char *GetString(int &Length, const uint8_t **Data)
00139 {
00140   if (Length > 0 && Data && *Data) {
00141      int l = 0;
00142      const uint8_t *d = GetLength(*Data, l);
00143      char *s = CopyString(l, d);
00144      Length -= d - *Data + l;
00145      *Data = d + l;
00146      return s;
00147      }
00148   return NULL;
00149 }
00150 
00151 
00152 
00153 // --- cMutex ----------------------------------------------------------------
00154 
00155 cMutex::cMutex(void)
00156 {
00157   lockingPid = 0;
00158   locked = 0;
00159   pthread_mutex_init(&mutex, NULL);
00160 }
00161 
00162 cMutex::~cMutex()
00163 {
00164   pthread_mutex_destroy(&mutex);
00165 }
00166 
00167 void cMutex::Lock(void)
00168 {
00169   if (getpid() != lockingPid || !locked) {
00170      pthread_mutex_lock(&mutex);
00171      lockingPid = getpid();
00172      }
00173   locked++;
00174 }
00175 
00176 void cMutex::Unlock(void)
00177 {
00178  if (--locked <= 0) {
00179     if (locked < 0) {
00180         esyslog("cMutex Lock inbalance detected");
00181         locked = 0;
00182         }
00183     lockingPid = 0;
00184     pthread_mutex_unlock(&mutex);
00185     }
00186 }
00187 // --- cMutexLock ------------------------------------------------------------
00188 
00189 cMutexLock::cMutexLock(cMutex *Mutex)
00190 {
00191   mutex = NULL;
00192   locked = false;
00193   Lock(Mutex);
00194 }
00195 
00196 cMutexLock::~cMutexLock()
00197 {
00198   if (mutex && locked)
00199      mutex->Unlock();
00200 }
00201 
00202 bool cMutexLock::Lock(cMutex *Mutex)
00203 {
00204   if (Mutex && !mutex) {
00205      mutex = Mutex;
00206      Mutex->Lock();
00207      locked = true;
00208      return true;
00209      }
00210   return false;
00211 }
00212 
00213 
00214 
00215 // --- cTPDU -----------------------------------------------------------------
00216 
00217 #define MAX_TPDU_SIZE  2048
00218 #define MAX_TPDU_DATA  (MAX_TPDU_SIZE - 4)
00219 
00220 #define DATA_INDICATOR 0x80
00221 
00222 #define T_SB           0x80
00223 #define T_RCV          0x81
00224 #define T_CREATE_TC    0x82
00225 #define T_CTC_REPLY    0x83
00226 #define T_DELETE_TC    0x84
00227 #define T_DTC_REPLY    0x85
00228 #define T_REQUEST_TC   0x86
00229 #define T_NEW_TC       0x87
00230 #define T_TC_ERROR     0x88
00231 #define T_DATA_LAST    0xA0
00232 #define T_DATA_MORE    0xA1
00233 
00234 class cTPDU {
00235 private:
00236   int size;
00237   uint8_t data[MAX_TPDU_SIZE];
00238   const uint8_t *GetData(const uint8_t *Data, int &Length);
00239 public:
00240   cTPDU(void) { size = 0; memset(data, 0, sizeof(uint8_t) * MAX_TPDU_SIZE); }
00241   cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length = 0, const uint8_t *Data = NULL);
00242   uint8_t Slot(void) { return data[0]; }
00243   uint8_t Tcid(void) { return data[1]; }
00244   uint8_t Tag(void)  { return data[2]; }
00245   const uint8_t *Data(int &Length) { return GetData(data + 3, Length); }
00246   uint8_t Status(void);
00247   int Write(int fd);
00248   int Read(int fd);
00249   void Dump(bool Outgoing);
00250   };
00251 
00252 cTPDU::cTPDU(uint8_t Slot, uint8_t Tcid, uint8_t Tag, int Length, const uint8_t *Data)
00253 {
00254   size = 0;
00255   data[0] = Slot;
00256   data[1] = Tcid;
00257   data[2] = Tag;
00258   switch (Tag) {
00259     case T_RCV:
00260     case T_CREATE_TC:
00261     case T_CTC_REPLY:
00262     case T_DELETE_TC:
00263     case T_DTC_REPLY:
00264     case T_REQUEST_TC:
00265          data[3] = 1; // length
00266          data[4] = Tcid;
00267          size = 5;
00268          break;
00269     case T_NEW_TC:
00270     case T_TC_ERROR:
00271          if (Length == 1) {
00272             data[3] = 2; // length
00273             data[4] = Tcid;
00274             data[5] = Data[0];
00275             size = 6;
00276             }
00277          else
00278             esyslog("ERROR: illegal data length for TPDU tag 0x%02X: %d", Tag, Length);
00279          break;
00280     case T_DATA_LAST:
00281     case T_DATA_MORE:
00282          if (Length <= MAX_TPDU_DATA) {
00283             uint8_t *p = data + 3;
00284             p = SetLength(p, Length + 1);
00285             *p++ = Tcid;
00286             if (Length)
00287                memcpy(p, Data, Length);
00288             size = Length + (p - data);
00289             }
00290          else
00291             esyslog("ERROR: illegal data length for TPDU tag 0x%02X: %d", Tag, Length);
00292          break;
00293     default:
00294          esyslog("ERROR: unknown TPDU tag: 0x%02X", Tag);
00295     }
00296  }
00297 
00298 int cTPDU::Write(int fd)
00299 {
00300   Dump(true);
00301   if (size)
00302      return write(fd, data, size) == size ? OK : ERROR;
00303   esyslog("ERROR: attemp to write TPDU with zero size");
00304   return ERROR;
00305 }
00306 
00307 int cTPDU::Read(int fd)
00308 {
00309   size = safe_read(fd, data, sizeof(data));
00310   if (size < 0) {
00311      esyslog("ERROR: %m");
00312      size = 0;
00313      return ERROR;
00314      }
00315   Dump(false);
00316   return OK;
00317 }
00318 
00319 void cTPDU::Dump(bool Outgoing)
00320 {
00321   if (DumpTPDUDataTransfer) {
00322 #define MAX_DUMP 256
00323      QString msg = QString("%1 ").arg(Outgoing ? "-->" : "<--");
00324      for (int i = 0; i < size && i < MAX_DUMP; i++)
00325          msg += QString("%1 ").arg((short int)data[i], 2, 16, QChar('0'));
00326      if (size >= MAX_DUMP)
00327          msg += "...";
00328      LOG(VB_DVBCAM, LOG_INFO, msg);
00329      if (!Outgoing) {
00330         msg = QString("   ");
00331         for (int i = 0; i < size && i < MAX_DUMP; i++)
00332             msg += QString("%1 ").arg(isprint(data[i]) ? data[i] : '.', 2);
00333         if (size >= MAX_DUMP)
00334             msg += "...";
00335         LOG(VB_DVBCAM, LOG_INFO, msg);
00336         }
00337      }
00338 }
00339 
00340 const uint8_t *cTPDU::GetData(const uint8_t *Data, int &Length)
00341 {
00342   if (size) {
00343      Data = GetLength(Data, Length);
00344      if (Length) {
00345         Length--; // the first byte is always the tcid
00346         return Data + 1;
00347         }
00348      }
00349   return NULL;
00350 }
00351 
00352 uint8_t cTPDU::Status(void)
00353 {
00354   if (size >= 4 && data[size - 4] == T_SB && data[size - 3] == 2) {
00355      //XXX test tcid???
00356      return data[size - 1];
00357      }
00358   return 0;
00359 }
00360 
00361 // --- cCiTransportConnection ------------------------------------------------
00362 
00363 enum eState { stIDLE, stCREATION, stACTIVE, stDELETION };
00364 
00365 class cCiTransportConnection {
00366   friend class cCiTransportLayer;
00367 private:
00368   int fd;
00369   uint8_t slot;
00370   uint8_t tcid;
00371   eState state;
00372   cTPDU *tpdu;
00373   struct timeval last_poll;
00374   int lastResponse;
00375   bool dataAvailable;
00376   void Init(int Fd, uint8_t Slot, uint8_t Tcid);
00377   int SendTPDU(uint8_t Tag, int Length = 0, const uint8_t *Data = NULL);
00378   int RecvTPDU(void);
00379   int CreateConnection(void);
00380   int Poll(void);
00381   eState State(void) { return state; }
00382   int LastResponse(void) { return lastResponse; }
00383   bool DataAvailable(void) { return dataAvailable; }
00384 public:
00385   cCiTransportConnection(void);
00386   ~cCiTransportConnection();
00387   int Slot(void) const { return slot; }
00388   int SendData(int Length, const uint8_t *Data);
00389   int RecvData(void);
00390   const uint8_t *Data(int &Length);
00391   //XXX Close()
00392   };
00393 
00394 cCiTransportConnection::cCiTransportConnection(void)
00395 {
00396   tpdu = NULL;
00397   last_poll.tv_sec = 0;
00398   last_poll.tv_usec = 0;
00399   Init(-1, 0, 0);
00400 }
00401 
00402 cCiTransportConnection::~cCiTransportConnection()
00403 {
00404   delete tpdu;
00405 }
00406 
00407 void cCiTransportConnection::Init(int Fd, uint8_t Slot, uint8_t Tcid)
00408 {
00409   fd = Fd;
00410   slot = Slot;
00411   tcid = Tcid;
00412   state = stIDLE;
00413   if (fd >= 0 && !tpdu)
00414      tpdu = new cTPDU;
00415   lastResponse = ERROR;
00416   dataAvailable = false;
00417 //XXX Clear()???
00418 }
00419 
00420 int cCiTransportConnection::SendTPDU(uint8_t Tag, int Length, const uint8_t *Data)
00421 {
00422   cTPDU TPDU(slot, tcid, Tag, Length, Data);
00423   return TPDU.Write(fd);
00424 }
00425 
00426 #define CAM_READ_TIMEOUT  5000 // ms
00427 
00428 int cCiTransportConnection::RecvTPDU(void)
00429 {
00430   struct pollfd pfd[1];
00431   pfd[0].fd = fd;
00432   pfd[0].events = POLLIN;
00433   lastResponse = ERROR;
00434 
00435   for (;;) {
00436       int ret = poll(pfd, 1, CAM_READ_TIMEOUT);
00437       if (ret == -1 && (errno == EAGAIN || errno == EINTR))
00438           continue;
00439       break;
00440   }
00441 
00442   if (
00443         (pfd[0].revents & POLLIN) &&
00444         tpdu->Read(fd) == OK &&
00445         tpdu->Tcid() == tcid
00446      )
00447   {
00448      switch (state) {
00449        case stIDLE:     break;
00450        case stCREATION: if (tpdu->Tag() == T_CTC_REPLY) {
00451                            dataAvailable = tpdu->Status() & DATA_INDICATOR;
00452                            state = stACTIVE;
00453                            lastResponse = tpdu->Tag();
00454                            }
00455                         break;
00456        case stACTIVE:   switch (tpdu->Tag()) {
00457                           case T_SB:
00458                           case T_DATA_LAST:
00459                           case T_DATA_MORE:
00460                           case T_REQUEST_TC: break;
00461                           case T_DELETE_TC:  if (SendTPDU(T_DTC_REPLY) != OK)
00462                                                 return ERROR;
00463                                              Init(fd, slot, tcid);
00464                                              break;
00465                           default: return ERROR;
00466                           }
00467                         dataAvailable = tpdu->Status() & DATA_INDICATOR;
00468                         lastResponse = tpdu->Tag();
00469                         break;
00470        case stDELETION: if (tpdu->Tag() == T_DTC_REPLY) {
00471                            Init(fd, slot, tcid);
00472                            //XXX Status()???
00473                            lastResponse = tpdu->Tag();
00474                            }
00475                         break;
00476        }
00477      }
00478   else {
00479      esyslog("ERROR: CAM: Read failed: slot %d, tcid %d\n", slot, tcid);
00480      Init(-1, slot, tcid);
00481      }
00482   return lastResponse;
00483 }
00484 
00485 int cCiTransportConnection::SendData(int Length, const uint8_t *Data)
00486 {
00487   while (state == stACTIVE && Length > 0) {
00488         uint8_t Tag = T_DATA_LAST;
00489         int l = Length;
00490         if (l > MAX_TPDU_DATA) {
00491            Tag = T_DATA_MORE;
00492            l = MAX_TPDU_DATA;
00493            }
00494         if (SendTPDU(Tag, l, Data) != OK || RecvTPDU() != T_SB)
00495            break;
00496         Length -= l;
00497         Data += l;
00498         }
00499   return Length ? ERROR : OK;
00500 }
00501 
00502 int cCiTransportConnection::RecvData(void)
00503 {
00504   if (SendTPDU(T_RCV) == OK)
00505      return RecvTPDU();
00506   return ERROR;
00507 }
00508 
00509 const uint8_t *cCiTransportConnection::Data(int &Length)
00510 {
00511   return tpdu->Data(Length);
00512 }
00513 
00514 #define MAX_CONNECT_RETRIES  25
00515 
00516 int cCiTransportConnection::CreateConnection(void)
00517 {
00518   if (state == stIDLE) {
00519      if (SendTPDU(T_CREATE_TC) == OK) {
00520         state = stCREATION;
00521         if (RecvTPDU() == T_CTC_REPLY) {
00522            _connected=true;
00523            return OK;
00524         // the following is a workaround for CAMs that don't quite follow the specs...
00525         } else {
00526            for (int i = 0; i < MAX_CONNECT_RETRIES; i++) {
00527                dsyslog("CAM: retrying to establish connection");
00528                if (RecvTPDU() == T_CTC_REPLY) {
00529                   dsyslog("CAM: connection established");
00530                   _connected=true;
00531                   return OK;
00532                   }
00533                }
00534            return ERROR;
00535            }
00536         }
00537      }
00538   return ERROR;
00539 }
00540 
00541 // Polls can be done with a 100ms interval (EN50221 - A.4.1.12)
00542 #define POLL_INTERVAL 100
00543 
00544 int cCiTransportConnection::Poll(void)
00545 {
00546   struct timeval curr_time;
00547 
00548   if (state != stACTIVE)
00549     return ERROR;
00550 
00551   gettimeofday(&curr_time, 0);
00552   uint64_t msdiff = (curr_time.tv_sec * 1000) + (curr_time.tv_usec / 1000) -
00553                     (last_poll.tv_sec * 1000) - (last_poll.tv_usec / 1000);
00554 
00555   if (msdiff < POLL_INTERVAL)
00556     return OK;
00557 
00558   last_poll.tv_sec = curr_time.tv_sec;
00559   last_poll.tv_usec = curr_time.tv_usec;
00560 
00561   if (SendTPDU(T_DATA_LAST) != OK)
00562     return ERROR;
00563 
00564   return RecvTPDU();
00565 }
00566 
00567 // --- cCiTransportLayer -----------------------------------------------------
00568 
00569 #define MAX_CI_CONNECT  16 // maximum possible value is 254
00570 
00571 class cCiTransportLayer {
00572 private:
00573   int fd;
00574   int numSlots;
00575   cCiTransportConnection tc[MAX_CI_CONNECT];
00576 public:
00577   cCiTransportLayer(int Fd, int NumSlots);
00578   cCiTransportConnection *NewConnection(int Slot);
00579   bool ResetSlot(int Slot);
00580   bool ModuleReady(int Slot);
00581   cCiTransportConnection *Process(int Slot);
00582   };
00583 
00584 cCiTransportLayer::cCiTransportLayer(int Fd, int NumSlots)
00585 {
00586   fd = Fd;
00587   numSlots = NumSlots;
00588   for (int s = 0; s < numSlots; s++)
00589       ResetSlot(s);
00590 }
00591 
00592 cCiTransportConnection *cCiTransportLayer::NewConnection(int Slot)
00593 {
00594   for (int i = 0; i < MAX_CI_CONNECT; i++) {
00595       if (tc[i].State() == stIDLE) {
00596          dbgprotocol("Creating connection: slot %d, tcid %d\n", Slot, i + 1);
00597          tc[i].Init(fd, Slot, i + 1);
00598          if (tc[i].CreateConnection() == OK)
00599             return &tc[i];
00600          break;
00601          }
00602       }
00603   return NULL;
00604 }
00605 
00606 bool cCiTransportLayer::ResetSlot(int Slot)
00607 {
00608   dbgprotocol("Resetting slot %d...", Slot);
00609   if (ioctl(fd, CA_RESET, 1 << Slot) != -1) {
00610      dbgprotocol("ok.\n");
00611      return true;
00612      }
00613   else
00614      esyslog("ERROR: can't reset CAM slot %d: %m", Slot);
00615   dbgprotocol("failed!\n");
00616   return false;
00617 }
00618 
00619 bool cCiTransportLayer::ModuleReady(int Slot)
00620 {
00621   ca_slot_info_t sinfo;
00622   sinfo.num = Slot;
00623   if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1)
00624      return sinfo.flags & CA_CI_MODULE_READY;
00625   else
00626      esyslog("ERROR: can't get info on CAM slot %d: %m", Slot);
00627   return false;
00628 }
00629 
00630 cCiTransportConnection *cCiTransportLayer::Process(int Slot)
00631 {
00632   for (int i = 0; i < MAX_CI_CONNECT; i++) {
00633       cCiTransportConnection *Tc = &tc[i];
00634       if (Tc->Slot() == Slot) {
00635          switch (Tc->State()) {
00636            case stCREATION:
00637            case stACTIVE:
00638                 if (!Tc->DataAvailable()) {
00639                    if (Tc->Poll() != OK)
00640                       ;//XXX continue;
00641                    }
00642                 switch (Tc->LastResponse()) {
00643                   case T_REQUEST_TC:
00644                        //XXX
00645                        break;
00646                   case T_DATA_MORE:
00647                   case T_DATA_LAST:
00648                   case T_CTC_REPLY:
00649                   case T_SB:
00650                        if (Tc->DataAvailable())
00651                           Tc->RecvData();
00652                        break;
00653                   case TIMEOUT:
00654                   case ERROR:
00655                   default:
00656                        //XXX Tc->state = stIDLE;//XXX Init()???
00657                        return NULL;
00658                        break;
00659                   }
00660                 //XXX this will only work with _one_ transport connection per slot!
00661                 return Tc;
00662                 break;
00663            default: ;
00664            }
00665          }
00666       }
00667   return NULL;
00668 }
00669 
00670 // -- cCiSession -------------------------------------------------------------
00671 
00672 // Session Tags:
00673 
00674 #define ST_SESSION_NUMBER           0x90
00675 #define ST_OPEN_SESSION_REQUEST     0x91
00676 #define ST_OPEN_SESSION_RESPONSE    0x92
00677 #define ST_CREATE_SESSION           0x93
00678 #define ST_CREATE_SESSION_RESPONSE  0x94
00679 #define ST_CLOSE_SESSION_REQUEST    0x95
00680 #define ST_CLOSE_SESSION_RESPONSE   0x96
00681 
00682 // Session Status:
00683 
00684 #define SS_OK             0x00
00685 #define SS_NOT_ALLOCATED  0xF0
00686 
00687 // Resource Identifiers:
00688 
00689 #define RI_RESOURCE_MANAGER            0x00010041
00690 #define RI_APPLICATION_INFORMATION     0x00020041
00691 #define RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
00692 #define RI_HOST_CONTROL                0x00200041
00693 #define RI_DATE_TIME                   0x00240041
00694 #define RI_MMI                         0x00400041
00695 
00696 // Application Object Tags:
00697 
00698 #define AOT_NONE                    0x000000
00699 #define AOT_PROFILE_ENQ             0x9F8010
00700 #define AOT_PROFILE                 0x9F8011
00701 #define AOT_PROFILE_CHANGE          0x9F8012
00702 #define AOT_APPLICATION_INFO_ENQ    0x9F8020
00703 #define AOT_APPLICATION_INFO        0x9F8021
00704 #define AOT_ENTER_MENU              0x9F8022
00705 #define AOT_CA_INFO_ENQ             0x9F8030
00706 #define AOT_CA_INFO                 0x9F8031
00707 #define AOT_CA_PMT                  0x9F8032
00708 #define AOT_CA_PMT_REPLY            0x9F8033
00709 #define AOT_TUNE                    0x9F8400
00710 #define AOT_REPLACE                 0x9F8401
00711 #define AOT_CLEAR_REPLACE           0x9F8402
00712 #define AOT_ASK_RELEASE             0x9F8403
00713 #define AOT_DATE_TIME_ENQ           0x9F8440
00714 #define AOT_DATE_TIME               0x9F8441
00715 #define AOT_CLOSE_MMI               0x9F8800
00716 #define AOT_DISPLAY_CONTROL         0x9F8801
00717 #define AOT_DISPLAY_REPLY           0x9F8802
00718 #define AOT_TEXT_LAST               0x9F8803
00719 #define AOT_TEXT_MORE               0x9F8804
00720 #define AOT_KEYPAD_CONTROL          0x9F8805
00721 #define AOT_KEYPRESS                0x9F8806
00722 #define AOT_ENQ                     0x9F8807
00723 #define AOT_ANSW                    0x9F8808
00724 #define AOT_MENU_LAST               0x9F8809
00725 #define AOT_MENU_MORE               0x9F880A
00726 #define AOT_MENU_ANSW               0x9F880B
00727 #define AOT_LIST_LAST               0x9F880C
00728 #define AOT_LIST_MORE               0x9F880D
00729 #define AOT_SUBTITLE_SEGMENT_LAST   0x9F880E
00730 #define AOT_SUBTITLE_SEGMENT_MORE   0x9F880F
00731 #define AOT_DISPLAY_MESSAGE         0x9F8810
00732 #define AOT_SCENE_END_MARK          0x9F8811
00733 #define AOT_SCENE_DONE              0x9F8812
00734 #define AOT_SCENE_CONTROL           0x9F8813
00735 #define AOT_SUBTITLE_DOWNLOAD_LAST  0x9F8814
00736 #define AOT_SUBTITLE_DOWNLOAD_MORE  0x9F8815
00737 #define AOT_FLUSH_DOWNLOAD          0x9F8816
00738 #define AOT_DOWNLOAD_REPLY          0x9F8817
00739 #define AOT_COMMS_CMD               0x9F8C00
00740 #define AOT_CONNECTION_DESCRIPTOR   0x9F8C01
00741 #define AOT_COMMS_REPLY             0x9F8C02
00742 #define AOT_COMMS_SEND_LAST         0x9F8C03
00743 #define AOT_COMMS_SEND_MORE         0x9F8C04
00744 #define AOT_COMMS_RCV_LAST          0x9F8C05
00745 #define AOT_COMMS_RCV_MORE          0x9F8C06
00746 
00747 class cCiSession {
00748 private:
00749   int sessionId;
00750   int resourceId;
00751   cCiTransportConnection *tc;
00752 protected:
00753   int GetTag(int &Length, const uint8_t **Data);
00754   const uint8_t *GetData(const uint8_t *Data, int &Length);
00755   int SendData(int Tag, int Length = 0, const uint8_t *Data = NULL);
00756 public:
00757   cCiSession(int SessionId, int ResourceId, cCiTransportConnection *Tc);
00758   virtual ~cCiSession();
00759   const cCiTransportConnection *Tc(void) { return tc; }
00760   int SessionId(void) { return sessionId; }
00761   int ResourceId(void) { return resourceId; }
00762   virtual bool HasUserIO(void) { return false; }
00763   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
00764   };
00765 
00766 cCiSession::cCiSession(int SessionId, int ResourceId, cCiTransportConnection *Tc)
00767 {
00768   sessionId = SessionId;
00769   resourceId = ResourceId;
00770   tc = Tc;
00771 }
00772 
00773 cCiSession::~cCiSession()
00774 {
00775 }
00776 
00777 int cCiSession::GetTag(int &Length, const uint8_t **Data)
00781 {
00782   if (Length >= 3 && Data && *Data) {
00783      int t = 0;
00784      for (int i = 0; i < 3; i++)
00785          t = (t << 8) | *(*Data)++;
00786      Length -= 3;
00787      return t;
00788      }
00789   return AOT_NONE;
00790 }
00791 
00792 const uint8_t *cCiSession::GetData(const uint8_t *Data, int &Length)
00793 {
00794   Data = GetLength(Data, Length);
00795   return Length ? Data : NULL;
00796 }
00797 
00798 int cCiSession::SendData(int Tag, int Length, const uint8_t *Data)
00799 {
00800   uint8_t buffer[2048];
00801   uint8_t *p = buffer;
00802   *p++ = ST_SESSION_NUMBER;
00803   *p++ = 0x02;
00804   *p++ = (sessionId >> 8) & 0xFF;
00805   *p++ =  sessionId       & 0xFF;
00806   *p++ = (Tag >> 16) & 0xFF;
00807   *p++ = (Tag >>  8) & 0xFF;
00808   *p++ =  Tag        & 0xFF;
00809   p = SetLength(p, Length);
00810   if (p - buffer + Length < int(sizeof(buffer))) {
00811      memcpy(p, Data, Length);
00812      p += Length;
00813      return tc->SendData(p - buffer, buffer);
00814      }
00815   esyslog("ERROR: CAM: data length (%d) exceeds buffer size", Length);
00816   return ERROR;
00817 }
00818 
00819 bool cCiSession::Process(int Length, const uint8_t *Data)
00820 {
00821   (void)Length;
00822   (void)Data;
00823   return true;
00824 }
00825 
00826 // -- cCiResourceManager -----------------------------------------------------
00827 
00828 class cCiResourceManager : public cCiSession {
00829 private:
00830   int state;
00831 public:
00832   cCiResourceManager(int SessionId, cCiTransportConnection *Tc);
00833   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
00834   };
00835 
00836 cCiResourceManager::cCiResourceManager(int SessionId, cCiTransportConnection *Tc)
00837 :cCiSession(SessionId, RI_RESOURCE_MANAGER, Tc)
00838 {
00839   dbgprotocol("New Resource Manager (session id %d)\n", SessionId);
00840   state = 0;
00841 }
00842 
00843 bool cCiResourceManager::Process(int Length, const uint8_t *Data)
00844 {
00845   if (Data) {
00846      int Tag = GetTag(Length, &Data);
00847      switch (Tag) {
00848        case AOT_PROFILE_ENQ: {
00849             dbgprotocol("%d: <== Profile Enquiry\n", SessionId());
00850             int resources[] = { htonl(RI_RESOURCE_MANAGER),
00851                                 htonl(RI_APPLICATION_INFORMATION),
00852                                 htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
00853                                 htonl(RI_DATE_TIME),
00854                                 htonl(RI_MMI)
00855                               };
00856             dbgprotocol("%d: ==> Profile\n", SessionId());
00857             SendData(AOT_PROFILE, sizeof(resources), (uint8_t*)resources);
00858             state = 3;
00859             }
00860             break;
00861        case AOT_PROFILE: {
00862             dbgprotocol("%d: <== Profile\n", SessionId());
00863             if (state == 1) {
00864                int l = 0;
00865                const uint8_t *d = GetData(Data, l);
00866                if (l > 0 && d)
00867                   esyslog("CI resource manager: unexpected data");
00868                dbgprotocol("%d: ==> Profile Change\n", SessionId());
00869                SendData(AOT_PROFILE_CHANGE);
00870                state = 2;
00871                }
00872             else {
00873                esyslog("ERROR: CI resource manager: unexpected tag %06X in state %d", Tag, state);
00874                }
00875             }
00876             break;
00877        default: esyslog("ERROR: CI resource manager: unknown tag %06X", Tag);
00878                 return false;
00879        }
00880      }
00881   else if (state == 0) {
00882      dbgprotocol("%d: ==> Profile Enq\n", SessionId());
00883      SendData(AOT_PROFILE_ENQ);
00884      state = 1;
00885      }
00886   return true;
00887 }
00888 
00889 // --- cCiApplicationInformation ---------------------------------------------
00890 
00891 class cCiApplicationInformation : public cCiSession {
00892 private:
00893   int state;
00894   time_t creationTime;
00895   uint8_t applicationType;
00896   uint16_t applicationManufacturer;
00897   uint16_t manufacturerCode;
00898   char *menuString;
00899 public:
00900   cCiApplicationInformation(int SessionId, cCiTransportConnection *Tc);
00901   virtual ~cCiApplicationInformation();
00902   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
00903   bool EnterMenu(void);
00904   char *GetApplicationString() { return strdup(menuString); };
00905   uint16_t GetApplicationManufacturer() { return applicationManufacturer; };
00906   uint16_t GetManufacturerCode()        { return manufacturerCode; };
00907   };
00908 
00909 cCiApplicationInformation::cCiApplicationInformation(int SessionId, cCiTransportConnection *Tc)
00910 :cCiSession(SessionId, RI_APPLICATION_INFORMATION, Tc)
00911 {
00912   dbgprotocol("New Application Information (session id %d)\n", SessionId);
00913   state = 0;
00914   creationTime = time(NULL);
00915   applicationType = 0;
00916   applicationManufacturer = 0;
00917   manufacturerCode = 0;
00918   menuString = NULL;
00919 }
00920 
00921 cCiApplicationInformation::~cCiApplicationInformation()
00922 {
00923   free(menuString);
00924 }
00925 
00926 bool cCiApplicationInformation::Process(int Length, const uint8_t *Data)
00927 {
00928   if (Data) {
00929      int Tag = GetTag(Length, &Data);
00930      switch (Tag) {
00931        case AOT_APPLICATION_INFO: {
00932             dbgprotocol("%d: <== Application Info\n", SessionId());
00933             int l = 0;
00934             const uint8_t *d = GetData(Data, l);
00935             if ((l -= 1) < 0) break;
00936             applicationType = *d++;
00937             if ((l -= 2) < 0) break;
00938             applicationManufacturer = ntohs(*(uint16_t *)d);
00939             d += 2;
00940             if ((l -= 2) < 0) break;
00941             manufacturerCode = ntohs(*(uint16_t *)d);
00942             d += 2;
00943             free(menuString);
00944             menuString = GetString(l, &d);
00945             isyslog("CAM: %s, %02X, %04X, %04X", menuString, applicationType,
00946                             applicationManufacturer, manufacturerCode);
00947             }
00948             state = 2;
00949             break;
00950        default: esyslog("ERROR: CI application information: unknown tag %06X", Tag);
00951                 return false;
00952        }
00953      }
00954   else if (state == 0) {
00955      dbgprotocol("%d: ==> Application Info Enq\n", SessionId());
00956      SendData(AOT_APPLICATION_INFO_ENQ);
00957      state = 1;
00958      }
00959   return true;
00960 }
00961 
00962 bool cCiApplicationInformation::EnterMenu(void)
00963 {
00964   if (state == 2 && time(NULL) - creationTime > WRKRND_TIME_BEFORE_ENTER_MENU) {
00965      dbgprotocol("%d: ==> Enter Menu\n", SessionId());
00966      SendData(AOT_ENTER_MENU);
00967      return true;//XXX
00968      }
00969   return false;
00970 }
00971 
00972 // --- cCiConditionalAccessSupport -------------------------------------------
00973 
00974 class cCiConditionalAccessSupport : public cCiSession {
00975 private:
00976   int state;
00977   int numCaSystemIds;
00978   unsigned short caSystemIds[MAXCASYSTEMIDS + 1]; // list is zero terminated!
00979   bool needCaPmt;
00980 public:
00981   cCiConditionalAccessSupport(int SessionId, cCiTransportConnection *Tc);
00982   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
00983   const unsigned short *GetCaSystemIds(void) { return caSystemIds; }
00984   bool SendPMT(cCiCaPmt &CaPmt);
00985   bool NeedCaPmt(void) { return needCaPmt; }
00986   };
00987 
00988 cCiConditionalAccessSupport::cCiConditionalAccessSupport(
00989     int SessionId, cCiTransportConnection *Tc) :
00990     cCiSession(SessionId, RI_CONDITIONAL_ACCESS_SUPPORT, Tc),
00991     state(0), numCaSystemIds(0), needCaPmt(false)
00992 {
00993   dbgprotocol("New Conditional Access Support (session id %d)\n", SessionId);
00994   memset(caSystemIds, 0, sizeof(caSystemIds));
00995 }
00996 
00997 bool cCiConditionalAccessSupport::Process(int Length, const uint8_t *Data)
00998 {
00999   if (Data) {
01000      int Tag = GetTag(Length, &Data);
01001      switch (Tag) {
01002        case AOT_CA_INFO: {
01003             dbgprotocol("%d: <== Ca Info", SessionId());
01004             int l = 0;
01005             const uint8_t *d = GetData(Data, l);
01006             while (l > 1) {
01007                   unsigned short id = ((unsigned short)(*d) << 8) | *(d + 1);
01008                   dbgprotocol(" %04X", id);
01009                   d += 2;
01010                   l -= 2;
01011                   if (numCaSystemIds < MAXCASYSTEMIDS) {
01012                      int i = 0;
01013                      // Make sure the id is not already present
01014                      for (; i < numCaSystemIds; i++)
01015                         if (caSystemIds[i] == id)
01016                            break;
01017 
01018                      if (i < numCaSystemIds)
01019                          continue;
01020 
01021                      caSystemIds[numCaSystemIds++] = id;
01022                      caSystemIds[numCaSystemIds] = 0;
01023                      }
01024                   else
01025                      esyslog("ERROR: too many CA system IDs!");
01026                     }
01027             dbgprotocol("\n");
01028             }
01029             state = 2;
01030             needCaPmt = true;
01031             break;
01032        default: esyslog("ERROR: CI conditional access support: unknown tag %06X", Tag);
01033                 return false;
01034        }
01035      }
01036   else if (state == 0) {
01037      dbgprotocol("%d: ==> Ca Info Enq\n", SessionId());
01038      SendData(AOT_CA_INFO_ENQ);
01039      state = 1;
01040      }
01041   return true;
01042 }
01043 
01044 bool cCiConditionalAccessSupport::SendPMT(cCiCaPmt &CaPmt)
01045 {
01046   if (state == 2) {
01047      SendData(AOT_CA_PMT, CaPmt.length, CaPmt.capmt);
01048      needCaPmt = false;
01049      return true;
01050      }
01051   return false;
01052 }
01053 
01054 // --- cCiDateTime -----------------------------------------------------------
01055 
01056 class cCiDateTime : public cCiSession {
01057 private:
01058   int interval;
01059   time_t lastTime;
01060   int timeOffset;
01061   bool SendDateTime(void);
01062 public:
01063   cCiDateTime(int SessionId, cCiTransportConnection *Tc);
01064   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
01065   void SetTimeOffset(double offset);
01066   };
01067 
01068 cCiDateTime::cCiDateTime(int SessionId, cCiTransportConnection *Tc)
01069 :cCiSession(SessionId, RI_DATE_TIME, Tc)
01070 {
01071   interval = 0;
01072   lastTime = 0;
01073   timeOffset = 0;
01074   dbgprotocol("New Date Time (session id %d)\n", SessionId);
01075 }
01076 
01077 void cCiDateTime::SetTimeOffset(double offset)
01078 {
01079     timeOffset = (int) offset;
01080     dbgprotocol("New Time Offset: %i secs\n", timeOffset);
01081 }
01082 
01083 bool cCiDateTime::SendDateTime(void)
01084 {
01085   time_t t = time(NULL);
01086   struct tm tm_gmt;
01087   struct tm tm_loc;
01088 
01089   // Avoid using signed time_t types
01090   if (timeOffset < 0)
01091       t -= (time_t)(-timeOffset);
01092   else
01093       t += (time_t)(timeOffset);
01094 
01095   if (gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc)) {
01096      int Y = tm_gmt.tm_year;
01097      int M = tm_gmt.tm_mon + 1;
01098      int D = tm_gmt.tm_mday;
01099      int L = (M == 1 || M == 2) ? 1 : 0;
01100      int MJD = 14956 + D + int((Y - L) * 365.25) + int((M + 1 + L * 12) * 30.6001);
01101 #define DEC2BCD(d) (((d / 10) << 4) + (d % 10))
01102      struct tTime { unsigned short mjd; uint8_t h, m, s; short offset; };
01103      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) };
01104      dbgprotocol("%d: ==> Date Time\n", SessionId());
01105      SendData(AOT_DATE_TIME, 7, (uint8_t*)&T);
01106      //XXX return value of all SendData() calls???
01107      return true;
01108      }
01109   return false;
01110 }
01111 
01112 bool cCiDateTime::Process(int Length, const uint8_t *Data)
01113 {
01114   if (Data) {
01115      int Tag = GetTag(Length, &Data);
01116      switch (Tag) {
01117        case AOT_DATE_TIME_ENQ: {
01118             interval = 0;
01119             int l = 0;
01120             const uint8_t *d = GetData(Data, l);
01121             if (l > 0)
01122                interval = *d;
01123             dbgprotocol("%d: <== Date Time Enq, interval = %d\n", SessionId(), interval);
01124             lastTime = time(NULL);
01125             return SendDateTime();
01126             }
01127             break;
01128        default: esyslog("ERROR: CI date time: unknown tag %06X", Tag);
01129                 return false;
01130        }
01131      }
01132   else if (interval && time(NULL) - lastTime > interval) {
01133      lastTime = time(NULL);
01134      return SendDateTime();
01135      }
01136   return true;
01137 }
01138 
01139 // --- cCiMMI ----------------------------------------------------------------
01140 
01141 // Close MMI Commands:
01142 
01143 #define CLOSE_MMI_IMMEDIATE                0x00
01144 #define CLOSE_MMI_DELAY                    0x01
01145 
01146 // Display Control Commands:
01147 
01148 #define DCC_SET_MMI_MODE                          0x01
01149 #define DCC_DISPLAY_CHARACTER_TABLE_LIST          0x02
01150 #define DCC_INPUT_CHARACTER_TABLE_LIST            0x03
01151 #define DCC_OVERLAY_GRAPHICS_CHARACTERISTICS      0x04
01152 #define DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS  0x05
01153 
01154 // MMI Modes:
01155 
01156 #define MM_HIGH_LEVEL                      0x01
01157 #define MM_LOW_LEVEL_OVERLAY_GRAPHICS      0x02
01158 #define MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS  0x03
01159 
01160 // Display Reply IDs:
01161 
01162 #define DRI_MMI_MODE_ACK                              0x01
01163 #define DRI_LIST_DISPLAY_CHARACTER_TABLES             0x02
01164 #define DRI_LIST_INPUT_CHARACTER_TABLES               0x03
01165 #define DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS      0x04
01166 #define DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS  0x05
01167 #define DRI_UNKNOWN_DISPLAY_CONTROL_CMD               0xF0
01168 #define DRI_UNKNOWN_MMI_MODE                          0xF1
01169 #define DRI_UNKNOWN_CHARACTER_TABLE                   0xF2
01170 
01171 // Enquiry Flags:
01172 
01173 #define EF_BLIND  0x01
01174 
01175 // Answer IDs:
01176 
01177 #define AI_CANCEL  0x00
01178 #define AI_ANSWER  0x01
01179 
01180 class cCiMMI : public cCiSession {
01181 private:
01182   char *GetText(int &Length, const uint8_t **Data);
01183   cCiMenu *menu;
01184   cCiEnquiry *enquiry;
01185 public:
01186   cCiMMI(int SessionId, cCiTransportConnection *Tc);
01187   virtual ~cCiMMI();
01188   virtual bool Process(int Length = 0, const uint8_t *Data = NULL);
01189   virtual bool HasUserIO(void) { return menu || enquiry; }
01190   cCiMenu *Menu(void);
01191   cCiEnquiry *Enquiry(void);
01192   bool SendMenuAnswer(uint8_t Selection);
01193   bool SendAnswer(const char *Text);
01194   };
01195 
01196 cCiMMI::cCiMMI(int SessionId, cCiTransportConnection *Tc)
01197 :cCiSession(SessionId, RI_MMI, Tc)
01198 {
01199   dbgprotocol("New MMI (session id %d)\n", SessionId);
01200   menu = NULL;
01201   enquiry = NULL;
01202 }
01203 
01204 cCiMMI::~cCiMMI()
01205 {
01206   delete menu;
01207   delete enquiry;
01208 }
01209 
01210 char *cCiMMI::GetText(int &Length, const uint8_t **Data)
01214 {
01215   int Tag = GetTag(Length, Data);
01216   if (Tag == AOT_TEXT_LAST) {
01217      char *s = GetString(Length, Data);
01218      dbgprotocol("%d: <== Text Last '%s'\n", SessionId(), s);
01219      return s;
01220      }
01221   else
01222      esyslog("CI MMI: unexpected text tag: %06X", Tag);
01223   return NULL;
01224 }
01225 
01226 bool cCiMMI::Process(int Length, const uint8_t *Data)
01227 {
01228   if (Data) {
01229      int Tag = GetTag(Length, &Data);
01230      switch (Tag) {
01231        case AOT_DISPLAY_CONTROL: {
01232             dbgprotocol("%d: <== Display Control\n", SessionId());
01233             int l = 0;
01234             const uint8_t *d = GetData(Data, l);
01235             if (l > 0) {
01236                switch (*d) {
01237                  case DCC_SET_MMI_MODE:
01238                       if (l == 2 && *++d == MM_HIGH_LEVEL) {
01239                          struct tDisplayReply { uint8_t id; uint8_t mode; };
01240                          tDisplayReply dr = { id : DRI_MMI_MODE_ACK, mode : MM_HIGH_LEVEL };
01241                          dbgprotocol("%d: ==> Display Reply\n", SessionId());
01242                          SendData(AOT_DISPLAY_REPLY, 2, (uint8_t *)&dr);
01243                          }
01244                       break;
01245                  default: esyslog("CI MMI: unsupported display control command %02X", *d);
01246                           return false;
01247                  }
01248                }
01249             }
01250             break;
01251        case AOT_LIST_LAST:
01252        case AOT_MENU_LAST: {
01253             dbgprotocol("%d: <== Menu Last\n", SessionId());
01254             delete menu;
01255             menu = new cCiMenu(this, Tag == AOT_MENU_LAST);
01256             int l = 0;
01257             const uint8_t *d = GetData(Data, l);
01258             if (l > 0) {
01259                // since the specification allows choiceNb to be undefined it is useless, so let's just skip it:
01260                d++;
01261                l--;
01262                if (l > 0) menu->titleText = GetText(l, &d);
01263                if (l > 0) menu->subTitleText = GetText(l, &d);
01264                if (l > 0) menu->bottomText = GetText(l, &d);
01265                while (l > 0) {
01266                      char *s = GetText(l, &d);
01267                      if (s) {
01268                         if (!menu->AddEntry(s))
01269                            free(s);
01270                         }
01271                      else
01272                         break;
01273                      }
01274                }
01275             }
01276             break;
01277        case AOT_ENQ: {
01278             dbgprotocol("%d: <== Enq\n", SessionId());
01279             delete enquiry;
01280             enquiry = new cCiEnquiry(this);
01281             int l = 0;
01282             const uint8_t *d = GetData(Data, l);
01283             if (l > 0) {
01284                uint8_t blind = *d++;
01285                //XXX GetByte()???
01286                l--;
01287                enquiry->blind = blind & EF_BLIND;
01288                enquiry->expectedLength = *d++;
01289                l--;
01290                // I really wonder why there is no text length field here...
01291                enquiry->text = CopyString(l, d);
01292                }
01293             }
01294             break;
01295        case AOT_CLOSE_MMI: {
01296             int l = 0;
01297             const uint8_t *d = GetData(Data, l);
01298 
01299             if(l > 0){
01300                 switch(*d){
01301                 case CLOSE_MMI_IMMEDIATE:
01302                     dbgprotocol("%d <== Menu Close: immediate\n", SessionId());
01303                     break;
01304                 case CLOSE_MMI_DELAY:
01305                     dbgprotocol("%d <== Menu Close: delay\n", SessionId());
01306                     break;
01307                 default: esyslog("ERROR: CI MMI: unknown close_mmi_cmd_id %02X", *d);
01308                     return false;
01309                 }
01310             }
01311 
01312             break;
01313        }
01314        default: esyslog("ERROR: CI MMI: unknown tag %06X", Tag);
01315                 return false;
01316        }
01317      }
01318   return true;
01319 }
01320 
01321 cCiMenu *cCiMMI::Menu(void)
01322 {
01323   cCiMenu *m = menu;
01324   menu = NULL;
01325   return m;
01326 }
01327 
01328 cCiEnquiry *cCiMMI::Enquiry(void)
01329 {
01330   cCiEnquiry *e = enquiry;
01331   enquiry = NULL;
01332   return e;
01333 }
01334 
01335 bool cCiMMI::SendMenuAnswer(uint8_t Selection)
01336 {
01337   dbgprotocol("%d: ==> Menu Answ\n", SessionId());
01338   SendData(AOT_MENU_ANSW, 1, &Selection);
01339   //XXX return value of all SendData() calls???
01340   return true;
01341 }
01342 
01343 bool cCiMMI::SendAnswer(const char *Text)
01344 {
01345   dbgprotocol("%d: ==> Answ\n", SessionId());
01346   struct tAnswer { uint8_t id; char text[256]; };//XXX
01347   tAnswer answer;
01348   answer.id = Text ? AI_ANSWER : AI_CANCEL;
01349   if (Text) {
01350      strncpy(answer.text, Text, sizeof(answer.text) - 1);
01351      answer.text[255] = '\0';
01352   }
01353   SendData(AOT_ANSW, Text ? strlen(Text) + 1 : 1, (uint8_t *)&answer);
01354   //XXX return value of all SendData() calls???
01355   return true;
01356 }
01357 
01358 // --- cCiMenu ---------------------------------------------------------------
01359 
01360 cCiMenu::cCiMenu(cCiMMI *MMI, bool Selectable)
01361 {
01362   mmi = MMI;
01363   selectable = Selectable;
01364   titleText = subTitleText = bottomText = NULL;
01365   numEntries = 0;
01366   for (int i = 0; i < MAX_CIMENU_ENTRIES; i++)
01367       entries[i] = NULL;
01368 }
01369 
01370 cCiMenu::~cCiMenu()
01371 {
01372   free(titleText);
01373   free(subTitleText);
01374   free(bottomText);
01375   for (int i = 0; i < numEntries; i++)
01376       free(entries[i]);
01377 }
01378 
01379 bool cCiMenu::AddEntry(char *s)
01380 {
01381   if (numEntries < MAX_CIMENU_ENTRIES) {
01382      entries[numEntries++] = s;
01383      return true;
01384      }
01385   return false;
01386 }
01387 
01388 bool cCiMenu::Select(int Index)
01389 {
01390   if (mmi && -1 <= Index && Index < numEntries)
01391      return mmi->SendMenuAnswer(Index + 1);
01392   return false;
01393 }
01394 
01395 bool cCiMenu::Cancel(void)
01396 {
01397   return Select(-1);
01398 }
01399 
01400 // --- cCiEnquiry ------------------------------------------------------------
01401 
01402 cCiEnquiry::cCiEnquiry(cCiMMI *MMI)
01403 {
01404   mmi = MMI;
01405   text = NULL;
01406   blind = false;;
01407   expectedLength = 0;;
01408 }
01409 
01410 cCiEnquiry::~cCiEnquiry()
01411 {
01412   free(text);
01413 }
01414 
01415 bool cCiEnquiry::Reply(const char *s)
01416 {
01417   return mmi ? mmi->SendAnswer(s) : false;
01418 }
01419 
01420 bool cCiEnquiry::Cancel(void)
01421 {
01422   return Reply(NULL);
01423 }
01424 
01425 // --- cCiCaPmt --------------------------------------------------------------
01426 
01427 // Ca Pmt Cmd Ids:
01428 
01429 #define CPCI_OK_DESCRAMBLING  0x01
01430 #define CPCI_OK_MMI           0x02
01431 #define CPCI_QUERY            0x03
01432 #define CPCI_NOT_SELECTED     0x04
01433 
01434 cCiCaPmt::cCiCaPmt(int ProgramNumber, uint8_t cplm)
01435 {
01436   length = 0;
01437   capmt[length++] = cplm; // ca_pmt_list_management
01438   capmt[length++] = (ProgramNumber >> 8) & 0xFF;
01439   capmt[length++] =  ProgramNumber       & 0xFF;
01440   capmt[length++] = 0x01; // version_number, current_next_indicator - apparently vn doesn't matter, but cni must be 1
01441 
01442   // program_info_length
01443   infoLengthPos = length;
01444   capmt[length++] = 0x00;
01445   capmt[length++] = 0x00;
01446 }
01447 
01448 void cCiCaPmt::AddElementaryStream(int type, int pid)
01449 {
01450   if (length + 5 > int(sizeof(capmt)))
01451   {
01452     esyslog("ERROR: buffer overflow in CA_PMT");
01453     return;
01454   }
01455 
01456   capmt[length++] = type & 0xFF;
01457   capmt[length++] = (pid >> 8) & 0xFF;
01458   capmt[length++] =  pid       & 0xFF;
01459 
01460   // ES_info_length
01461   infoLengthPos = length;
01462   capmt[length++] = 0x00;
01463   capmt[length++] = 0x00;
01464 }
01465 
01483 void cCiCaPmt::AddCaDescriptor(int ca_system_id, int ca_pid, int data_len,
01484                                const uint8_t *data)
01485 {
01486   if (!infoLengthPos)
01487   {
01488     esyslog("ERROR: adding CA descriptor without program/stream!");
01489     return;
01490   }
01491 
01492   if (length + data_len + 7 > int(sizeof(capmt)))
01493   {
01494     esyslog("ERROR: buffer overflow in CA_PMT");
01495     return;
01496   }
01497 
01498   // We are either at start of program descriptors or stream descriptors.
01499   if (infoLengthPos + 2 == length)
01500     capmt[length++] = CPCI_OK_DESCRAMBLING; // ca_pmt_cmd_id
01501 
01502   capmt[length++] = 0x09;           // CA descriptor tag
01503   capmt[length++] = 4 + data_len;   // descriptor length
01504 
01505   capmt[length++] = (ca_system_id >> 8) & 0xFF;
01506   capmt[length++] = ca_system_id & 0xFF;
01507   capmt[length++] = (ca_pid >> 8) & 0xFF;
01508   capmt[length++] = ca_pid & 0xFF;
01509 
01510   if (data_len > 0)
01511   {
01512     memcpy(&capmt[length], data, data_len);
01513     length += data_len;
01514   }
01515 
01516   // update program_info_length/ES_info_length
01517   int l = length - infoLengthPos - 2;
01518   capmt[infoLengthPos] = (l >> 8) & 0xFF;
01519   capmt[infoLengthPos + 1] = l & 0xFF;
01520 }
01521 
01522 // -- cLlCiHandler -------------------------------------------------------------
01523 
01524 cLlCiHandler::cLlCiHandler(int Fd, int NumSlots)
01525 {
01526   numSlots = NumSlots;
01527   newCaSupport = false;
01528   hasUserIO = false;
01529   for (int i = 0; i < MAX_CI_SESSION; i++)
01530       sessions[i] = NULL;
01531   tpl = new cCiTransportLayer(Fd, numSlots);
01532   tc = NULL;
01533   fdCa = Fd;
01534   needCaPmt = false;
01535 }
01536 
01537 cLlCiHandler::~cLlCiHandler()
01538 {
01539   cMutexLock MutexLock(&mutex);
01540   for (int i = 0; i < MAX_CI_SESSION; i++)
01541     if (sessions[i] != NULL)
01542       delete sessions[i];
01543   delete tpl;
01544   close(fdCa);
01545 }
01546 
01547 cCiHandler *cCiHandler::CreateCiHandler(const char *FileName)
01548 {
01549     int fd_ca = open(FileName, O_RDWR);
01550     if (fd_ca >= 0)
01551     {
01552         ca_caps_t Caps;
01553         if (ioctl(fd_ca, CA_GET_CAP, &Caps) == 0)
01554         {
01555             int NumSlots = Caps.slot_num;
01556             if (NumSlots > 0)
01557             {
01558                 if (Caps.slot_type & CA_CI_LINK)
01559                     return new cLlCiHandler(fd_ca, NumSlots);
01560                 else if (Caps.slot_type & CA_CI)
01561                     return new cHlCiHandler(fd_ca, NumSlots);
01562                 else
01563                     isyslog("CAM doesn't support either high or low level CI,"
01564                             " Caps.slot_type=%i", Caps.slot_type);
01565             }
01566             else
01567                 esyslog("ERROR: no CAM slots found");
01568         }
01569         else
01570             LOG_ERROR_STR(FileName);
01571         close(fd_ca);
01572     }
01573     return NULL;
01574 }
01575 
01576 int cLlCiHandler::ResourceIdToInt(const uint8_t *Data)
01577 {
01578   return (ntohl(*(int *)Data));
01579 }
01580 
01581 bool cLlCiHandler::Send(uint8_t Tag, int SessionId, int ResourceId, int Status)
01582 {
01583   uint8_t buffer[16];
01584   uint8_t *p = buffer;
01585   *p++ = Tag;
01586   *p++ = 0x00; // will contain length
01587   if (Status >= 0)
01588      *p++ = Status;
01589   if (ResourceId) {
01590      *(int *)p = htonl(ResourceId);
01591      p += 4;
01592      }
01593   *(short *)p = htons(SessionId);
01594   p += 2;
01595   buffer[1] = p - buffer - 2; // length
01596   return tc && tc->SendData(p - buffer, buffer) == OK;
01597 }
01598 
01599 cCiSession *cLlCiHandler::GetSessionBySessionId(int SessionId)
01600 {
01601   for (int i = 0; i < MAX_CI_SESSION; i++) {
01602       if (sessions[i] && sessions[i]->SessionId() == SessionId)
01603          return sessions[i];
01604       }
01605   return NULL;
01606 }
01607 
01608 cCiSession *cLlCiHandler::GetSessionByResourceId(int ResourceId, int Slot)
01609 {
01610   for (int i = 0; i < MAX_CI_SESSION; i++) {
01611       if (sessions[i] && sessions[i]->Tc()->Slot() == Slot && sessions[i]->ResourceId() == ResourceId)
01612          return sessions[i];
01613       }
01614   return NULL;
01615 }
01616 
01617 cCiSession *cLlCiHandler::CreateSession(int ResourceId)
01618 {
01619   if (!GetSessionByResourceId(ResourceId, tc->Slot())) {
01620      for (int i = 0; i < MAX_CI_SESSION; i++) {
01621          if (!sessions[i]) {
01622             switch (ResourceId) {
01623               case RI_RESOURCE_MANAGER:           return sessions[i] = new cCiResourceManager(i + 1, tc);
01624               case RI_APPLICATION_INFORMATION:    return sessions[i] = new cCiApplicationInformation(i + 1, tc);
01625               case RI_CONDITIONAL_ACCESS_SUPPORT: newCaSupport = true;
01626                                                   return sessions[i] = new cCiConditionalAccessSupport(i + 1, tc);
01627               case RI_HOST_CONTROL:               break; //XXX
01628               case RI_DATE_TIME:                  return sessions[i] = new cCiDateTime(i + 1, tc);
01629               case RI_MMI:                        return sessions[i] = new cCiMMI(i + 1, tc);
01630               }
01631             }
01632          }
01633      }
01634   return NULL;
01635 }
01636 
01637 bool cLlCiHandler::OpenSession(int Length, const uint8_t *Data)
01638 {
01639   if (Length == 6 && *(Data + 1) == 0x04) {
01640      int ResourceId = ResourceIdToInt(Data + 2);
01641      dbgprotocol("OpenSession %08X\n", ResourceId);
01642      switch (ResourceId) {
01643        case RI_RESOURCE_MANAGER:
01644        case RI_APPLICATION_INFORMATION:
01645        case RI_CONDITIONAL_ACCESS_SUPPORT:
01646        case RI_HOST_CONTROL:
01647        case RI_DATE_TIME:
01648        case RI_MMI:
01649        {
01650            cCiSession *Session = CreateSession(ResourceId);
01651            if (Session)
01652            {
01653                Send(ST_OPEN_SESSION_RESPONSE, Session->SessionId(),
01654                     Session->ResourceId(), SS_OK);
01655                return true;
01656            }
01657            esyslog("ERROR: can't create session for resource identifier: %08X",
01658                    ResourceId);
01659            break;
01660        }
01661        default: esyslog("ERROR: unknown resource identifier: %08X", ResourceId);
01662        }
01663      }
01664   return false;
01665 }
01666 
01667 bool cLlCiHandler::CloseSession(int SessionId)
01668 {
01669   dbgprotocol("CloseSession %08X\n", SessionId);
01670   cCiSession *Session = GetSessionBySessionId(SessionId);
01671   if (Session && sessions[SessionId - 1] == Session) {
01672      delete Session;
01673      sessions[SessionId - 1] = NULL;
01674      Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_OK);
01675      return true;
01676      }
01677   else {
01678      esyslog("ERROR: unknown session id: %d", SessionId);
01679      Send(ST_CLOSE_SESSION_RESPONSE, SessionId, 0, SS_NOT_ALLOCATED);
01680      }
01681   return false;
01682 }
01683 
01684 int cLlCiHandler::CloseAllSessions(int Slot)
01685 {
01686   int result = 0;
01687   for (int i = 0; i < MAX_CI_SESSION; i++) {
01688       if (sessions[i] && sessions[i]->Tc()->Slot() == Slot) {
01689          CloseSession(sessions[i]->SessionId());
01690          result++;
01691          }
01692       }
01693   return result;
01694 }
01695 
01696 bool cLlCiHandler::Process(void)
01697 {
01698     bool result = true;
01699     cMutexLock MutexLock(&mutex);
01700 
01701     for (int Slot = 0; Slot < numSlots; Slot++)
01702     {
01703         tc = tpl->Process(Slot);
01704         if (tc)
01705         {
01706             int Length;
01707             const uint8_t *Data = tc->Data(Length);
01708             if (Data && Length > 1)
01709             {
01710                 switch (*Data)
01711                 {
01712                     case ST_SESSION_NUMBER:
01713                         if (Length > 4)
01714                         {
01715                             int SessionId = ntohs(*(short *)&Data[2]);
01716                             cCiSession *Session = GetSessionBySessionId(SessionId);
01717                             if (Session)
01718                             {
01719                                 Session->Process(Length - 4, Data + 4);
01720                                 if (Session->ResourceId() == RI_APPLICATION_INFORMATION)
01721                                 {
01722 #if 0
01723                                     esyslog("Test: %x",
01724                                             ((cCiApplicationInformation*)Session)->GetApplicationManufacturer());
01725 #endif
01726                                 }
01727                             }
01728                             else
01729                                 esyslog("ERROR: unknown session id: %d", SessionId);
01730                         }
01731                         break;
01732 
01733                     case ST_OPEN_SESSION_REQUEST:
01734                         OpenSession(Length, Data);
01735                         break;
01736 
01737                     case ST_CLOSE_SESSION_REQUEST:
01738                         if (Length == 4)
01739                             CloseSession(ntohs(*(short *)&Data[2]));
01740                         break;
01741 
01742                     case ST_CREATE_SESSION_RESPONSE: //XXX fall through to default
01743                     case ST_CLOSE_SESSION_RESPONSE:  //XXX fall through to default
01744                     default:
01745                         esyslog("ERROR: unknown session tag: %02X", *Data);
01746                 }
01747             }
01748         }
01749         else if (CloseAllSessions(Slot))
01750         {
01751             tpl->ResetSlot(Slot);
01752             result = false;
01753         }
01754         else if (tpl->ModuleReady(Slot))
01755         {
01756             dbgprotocol("Module ready in slot %d\n", Slot);
01757             tpl->NewConnection(Slot);
01758         }
01759     }
01760 
01761     bool UserIO = false;
01762     needCaPmt = false;
01763     for (int i = 0; i < MAX_CI_SESSION; i++)
01764     {
01765         if (sessions[i] && sessions[i]->Process())
01766         {
01767             UserIO |= sessions[i]->HasUserIO();
01768             if (sessions[i]->ResourceId() == RI_CONDITIONAL_ACCESS_SUPPORT)
01769             {
01770                 cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *) sessions[i];
01771                 needCaPmt |= cas->NeedCaPmt();
01772             }
01773         }
01774     }
01775     hasUserIO = UserIO;
01776 
01777     if (newCaSupport)
01778         newCaSupport = result = false; // triggers new SetCaPmt at caller!
01779     return result;
01780 }
01781 
01782 bool cLlCiHandler::EnterMenu(int Slot)
01783 {
01784   cMutexLock MutexLock(&mutex);
01785   cCiApplicationInformation *api = (cCiApplicationInformation *)GetSessionByResourceId(RI_APPLICATION_INFORMATION, Slot);
01786   return api ? api->EnterMenu() : false;
01787 }
01788 
01789 cCiMenu *cLlCiHandler::GetMenu(void)
01790 {
01791   cMutexLock MutexLock(&mutex);
01792   for (int Slot = 0; Slot < numSlots; Slot++) {
01793       cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot);
01794       if (mmi)
01795          return mmi->Menu();
01796       }
01797   return NULL;
01798 }
01799 
01800 cCiEnquiry *cLlCiHandler::GetEnquiry(void)
01801 {
01802   cMutexLock MutexLock(&mutex);
01803   for (int Slot = 0; Slot < numSlots; Slot++) {
01804       cCiMMI *mmi = (cCiMMI *)GetSessionByResourceId(RI_MMI, Slot);
01805       if (mmi)
01806          return mmi->Enquiry();
01807       }
01808   return NULL;
01809 }
01810 
01811 const unsigned short *cLlCiHandler::GetCaSystemIds(int Slot)
01812  {
01813   cMutexLock MutexLock(&mutex);
01814   cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
01815   return cas ? cas->GetCaSystemIds() : NULL;
01816 }
01817 
01818 bool cLlCiHandler::SetCaPmt(cCiCaPmt &CaPmt, int Slot)
01819 {
01820   cMutexLock MutexLock(&mutex);
01821   cCiConditionalAccessSupport *cas = (cCiConditionalAccessSupport *)GetSessionByResourceId(RI_CONDITIONAL_ACCESS_SUPPORT, Slot);
01822   return cas && cas->SendPMT(CaPmt);
01823 }
01824 
01825 void cLlCiHandler::SetTimeOffset(double offset_in_seconds)
01826 {
01827     cMutexLock MutexLock(&mutex);
01828     cCiDateTime *dt = NULL;
01829 
01830     for (uint i = 0; i < (uint) NumSlots(); i++)
01831     {
01832         dt = (cCiDateTime*) GetSessionByResourceId(RI_DATE_TIME, i);
01833         if (dt)
01834             dt->SetTimeOffset(offset_in_seconds);
01835     }
01836 }
01837 
01838 bool cLlCiHandler::Reset(int Slot)
01839 {
01840   cMutexLock MutexLock(&mutex);
01841   CloseAllSessions(Slot);
01842   return tpl->ResetSlot(Slot);
01843 }
01844 
01845 bool cLlCiHandler::connected() const
01846 {
01847   return _connected;
01848 }
01849 
01850 // -- cHlCiHandler -------------------------------------------------------------
01851 
01852 cHlCiHandler::cHlCiHandler(int Fd, int NumSlots)
01853 {
01854     numSlots = NumSlots;
01855     numCaSystemIds = 0;
01856     caSystemIds[0] = 0;
01857     fdCa = Fd;
01858     state = 0;
01859     esyslog("New High level CI handler");
01860 }
01861 
01862 cHlCiHandler::~cHlCiHandler()
01863 {
01864     cMutexLock MutexLock(&mutex);
01865     close(fdCa);
01866 }
01867 
01868 int cHlCiHandler::CommHL(unsigned tag, unsigned function, struct ca_msg *msg)
01869 {
01870     if (tag) {
01871         msg->msg[2] = tag & 0xff;
01872         msg->msg[1] = (tag & 0xff00) >> 8;
01873         msg->msg[0] = (tag & 0xff0000) >> 16;
01874         esyslog("Sending message=[%02x %02x %02x ]",
01875                        msg->msg[0], msg->msg[1], msg->msg[2]);
01876     }
01877 
01878     return ioctl(fdCa, function, msg);
01879 }
01880 
01881 int cHlCiHandler::GetData(unsigned tag, struct ca_msg *msg)
01882 {
01883     return CommHL(tag, CA_GET_MSG, msg);
01884 }
01885 
01886 int cHlCiHandler::SendData(unsigned tag, struct ca_msg *msg)
01887 {
01888     return CommHL(tag, CA_SEND_MSG, msg);
01889 }
01890 
01891 bool cHlCiHandler::Process(void)
01892 {
01893     cMutexLock MutexLock(&mutex);
01894 
01895     struct ca_msg msg;
01896     switch(state) {
01897     case 0:
01898         // Get CA_system_ids
01899         /*      Enquire         */
01900         if ((SendData(AOT_CA_INFO_ENQ, &msg)) < 0) {
01901             esyslog("HLCI communication failed");
01902         } else {
01903             dbgprotocol("==> Ca Info Enquiry");
01904             /*  Receive         */
01905             if ((GetData(AOT_CA_INFO, &msg)) < 0) {
01906                 esyslog("HLCI communication failed");
01907             } else {
01908                 QString message("Debug: ");
01909                 for(int i = 0; i < 20; i++) {
01910                     message += QString("%1 ").arg(msg.msg[i]);
01911                 }
01912                 LOG(VB_GENERAL, LOG_DEBUG, message);
01913                 dbgprotocol("<== Ca Info");
01914                 int l = msg.msg[3];
01915                 const uint8_t *d = &msg.msg[4];
01916                 while (l > 1) {
01917                     unsigned short id = ((unsigned short)(*d) << 8) | *(d + 1);
01918                     dbgprotocol(" %04X", id);
01919                     d += 2;
01920                     l -= 2;
01921                     if (numCaSystemIds < MAXCASYSTEMIDS) {
01922                         caSystemIds[numCaSystemIds++] = id;
01923                         caSystemIds[numCaSystemIds] = 0;
01924                     }
01925                     else
01926                         esyslog("ERROR: too many CA system IDs!");
01927                 }
01928                 dbgprotocol("\n");
01929             }
01930             state = 1;
01931             break;
01932         }
01933     }
01934 
01935     bool result = true;
01936 
01937     return result;
01938 }
01939 
01940 bool cHlCiHandler::EnterMenu(int)
01941 {
01942     return false;
01943 }
01944 
01945 cCiMenu *cHlCiHandler::GetMenu(void)
01946 {
01947     return NULL;
01948 }
01949 
01950 cCiEnquiry *cHlCiHandler::GetEnquiry(void)
01951 {
01952     return NULL;
01953 }
01954 
01955 const unsigned short *cHlCiHandler::GetCaSystemIds(int)
01956 {
01957     return caSystemIds;
01958 }
01959 
01960 bool cHlCiHandler::SetCaPmt(cCiCaPmt &CaPmt, int)
01961 {
01962     cMutexLock MutexLock(&mutex);
01963     struct ca_msg msg;
01964 
01965     esyslog("Setting CA PMT.");
01966     state = 2;
01967 
01968     msg.msg[3] = CaPmt.length;
01969 
01970     if (CaPmt.length > (256 - 4))
01971     {
01972         esyslog("CA message too long");
01973         return false;
01974     }
01975 
01976     memcpy(&msg.msg[4], CaPmt.capmt, CaPmt.length);
01977 
01978     if ((SendData(AOT_CA_PMT, &msg)) < 0) {
01979         esyslog("HLCI communication failed");
01980         return false;
01981     }
01982 
01983     return true;
01984 }
01985 
01986 bool cHlCiHandler::Reset(int)
01987 {
01988     if ((ioctl(fdCa, CA_RESET)) < 0) {
01989         esyslog("ioctl CA_RESET failed.");
01990         return false;
01991     }
01992     return true;
01993 }
01994 
01995 bool cHlCiHandler::NeedCaPmt(void)
01996 {
01997     if(state == 1)
01998         return true;
01999 
02000     return false;
02001 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends