File indexing completed on 2024-05-12 04:52:08

0001 /*
0002  * dvbcam_linux.cpp
0003  *
0004  * Copyright (C) 2010-2011 Christoph Pfister <christophpfister@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License along
0017  * with this program; if not, write to the Free Software Foundation, Inc.,
0018  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0019  */
0020 
0021 #include "../log.h"
0022 
0023 #include <errno.h>
0024 #include <fcntl.h>
0025 #include <linux/dvb/ca.h>
0026 #include <QCoreApplication>
0027 #include <QFile>
0028 #include <QSocketNotifier>
0029 #include <sys/ioctl.h>
0030 #include <unistd.h>
0031 
0032 #include "dvbcam_linux.h"
0033 #include "dvbsi.h"
0034 
0035 // krazy:excludeall=syscalls
0036 
0037 class DvbLinuxCamService
0038 {
0039 public:
0040     DvbLinuxCamService() : pendingAction(Add) { }
0041     ~DvbLinuxCamService() { }
0042 
0043     enum PendingActions {
0044         Nothing,
0045         Add,
0046         Update,
0047         Remove
0048     };
0049 
0050     PendingActions pendingAction;
0051     QByteArray pmtSectionData;
0052 };
0053 
0054 DvbLinuxCam::DvbLinuxCam(QObject *parent) : QObject(parent), caFd(-1), socketNotifier(NULL), ready(false), eventPosted(false)
0055 {
0056     connect(&pollTimer, &QTimer::timeout, this, &DvbLinuxCam::pollModule);
0057 }
0058 
0059 DvbLinuxCam::~DvbLinuxCam()
0060 {
0061 }
0062 
0063 void DvbLinuxCam::startCa(const QString &path)
0064 {
0065     Q_ASSERT(caFd < 0);
0066     caFd = open(QFile::encodeName(path).constData(), O_RDWR | O_NONBLOCK);
0067 
0068     if (caFd < 0) {
0069         qCWarning(logCam, "Cannot open CA device node %s", qPrintable(path));
0070         return;
0071     }
0072 
0073     slot = -1;
0074     pollTimer.start(5000);
0075     pendingCommands = ResetCa;
0076 
0077     if (!detectSlot()) {
0078         pendingCommands = Nothing;
0079     } else {
0080         pendingCommands = ResetCa;
0081     }
0082 }
0083 
0084 void DvbLinuxCam::startDescrambling(const QByteArray &pmtSectionData)
0085 {
0086     DvbPmtSection pmtSection(pmtSectionData);
0087 
0088     if (!pmtSection.isValid()) {
0089         qCWarning(logCam, "PMT section is invalid while descrambling");
0090         return;
0091     }
0092 
0093     int serviceId = pmtSection.programNumber();
0094     QMap<int, DvbLinuxCamService>::iterator it = services.find(serviceId);
0095 
0096     if (it == services.end()) {
0097         it = services.insert(serviceId, DvbLinuxCamService());
0098     }
0099 
0100     if (it->pendingAction != DvbLinuxCamService::Add) {
0101         it->pendingAction = DvbLinuxCamService::Update;
0102     }
0103 
0104     it->pmtSectionData = pmtSectionData;
0105 
0106     if (ready && !eventPosted) {
0107         eventPosted = true;
0108         QCoreApplication::postEvent(this, new QEvent(QEvent::User));
0109     }
0110 }
0111 
0112 void DvbLinuxCam::stopDescrambling(int serviceId)
0113 {
0114     QMap<int, DvbLinuxCamService>::iterator it = services.find(serviceId);
0115 
0116     if (it == services.end()) {
0117         qCWarning(logCam, "Cannot find service id %d while stopping CAM", serviceId);
0118         return;
0119     }
0120 
0121     switch (it->pendingAction) {
0122     case DvbLinuxCamService::Nothing:
0123     case DvbLinuxCamService::Update:
0124         it->pendingAction = DvbLinuxCamService::Remove;
0125         break;
0126     case DvbLinuxCamService::Add:
0127         services.erase(it);
0128         return;
0129     case DvbLinuxCamService::Remove:
0130         qCWarning(logCam, "CAM Service was already removed");
0131         services.erase(it);
0132         return;
0133     }
0134 
0135     if (ready && !eventPosted) {
0136         eventPosted = true;
0137         QCoreApplication::postEvent(this, new QEvent(QEvent::User));
0138     }
0139 }
0140 
0141 void DvbLinuxCam::stopCa()
0142 {
0143     services.clear();
0144     ready = false;
0145     eventPosted = false;
0146 
0147     delete socketNotifier;
0148     socketNotifier = NULL;
0149 
0150     pollTimer.stop();
0151 
0152     if (caFd >= 0) {
0153         close(caFd);
0154         caFd = -1;
0155     }
0156 }
0157 
0158 void DvbLinuxCam::pollModule()
0159 {
0160     if (slot < 0) {
0161         detectSlot();
0162     } else {
0163         if ((pendingCommands & ExpectingReply) != 0) {
0164             pendingCommands &= ~ExpectingReply;
0165             qCDebug(logCam, "CAM: request timed out");
0166         }
0167 
0168         if (pendingCommands == 0) {
0169             pendingCommands |= SendPoll;
0170         }
0171 
0172         handlePendingCommands();
0173     }
0174 }
0175 
0176 void DvbLinuxCam::readyRead()
0177 {
0178     QByteArray buffer;
0179     buffer.resize(256);
0180     int size = 0;
0181 
0182     while (true) {
0183         int bytesRead = int(read(caFd, buffer.data() + size, buffer.size() - size));
0184 
0185         if ((bytesRead < 0) && (errno == EINTR)) {
0186             continue;
0187         }
0188 
0189         if (bytesRead == (buffer.size() - size)) {
0190             size += bytesRead;
0191             buffer.resize(4 * buffer.size());
0192             continue;
0193         }
0194 
0195         if (bytesRead > 0) {
0196             size += bytesRead;
0197         }
0198 
0199         break;
0200     }
0201 
0202     const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer.constData());
0203 
0204     if ((size >= 2) && (data[0] == slot) && (data[1] == ConnectionId)) {
0205         pendingCommands &= ~ExpectingReply;
0206         handleTransportLayer(data + 2, size - 2);
0207         handlePendingCommands();
0208     } else {
0209         qCWarning(logCam, "CAM: unknown recipient");
0210     }
0211 }
0212 
0213 bool DvbLinuxCam::detectSlot()
0214 {
0215     Q_ASSERT((caFd >= 0) && (slot < 0));
0216     ca_caps_t caInfo;
0217     memset(&caInfo, 0, sizeof(caInfo));
0218 
0219     if (ioctl(caFd, CA_GET_CAP, &caInfo) != 0) {
0220         qCWarning(logCam, "Cannot perform ioctl CA_GET_CAP");
0221         return false;
0222     }
0223 
0224     for (uint i = 0; i < caInfo.slot_num; ++i) {
0225         ca_slot_info_t slotInfo;
0226         memset(&slotInfo, 0, sizeof(slotInfo));
0227         slotInfo.num = i;
0228 
0229         if (ioctl(caFd, CA_GET_SLOT_INFO, &slotInfo) != 0) {
0230             qCWarning(logCam, "Cannot perform ioctl CA_GET_SLOT_INFO for slot %d", slot);
0231             continue;
0232         }
0233 
0234         if ((slotInfo.type & CA_CI_LINK) == 0) {
0235             qCWarning(logCam, "Unknown CAM CI link type %d", slotInfo.type);
0236             continue;
0237         }
0238 
0239         if ((slotInfo.flags & CA_CI_MODULE_READY) != 0) {
0240             slot = i;
0241             break;
0242         }
0243     }
0244 
0245     if (slot < 0) {
0246         return false;
0247     }
0248 
0249     if (socketNotifier == NULL) {
0250         socketNotifier = new QSocketNotifier(caFd, QSocketNotifier::Read, this);
0251         connect(socketNotifier, &QSocketNotifier::activated, this, &DvbLinuxCam::readyRead);
0252     }
0253 
0254     if (pendingCommands == 0) {
0255         pendingCommands |= SendCreateTransportConnection;
0256     }
0257 
0258     message.resize(64);
0259     messageData = message.data() + HeaderSize;
0260     handlePendingCommands();
0261     return true;
0262 }
0263 
0264 int DvbLinuxCam::decodeLength(const unsigned char *&data, int &size)
0265 {
0266     int length = 0;
0267 
0268     if (size > 0) {
0269         length = data[0];
0270     }
0271 
0272     if ((length & 0x80) == 0) {
0273         ++data;
0274         --size;
0275     } else {
0276         int temp = (length & 0x7f) + 1;
0277         length = 0;
0278 
0279         if (temp <= size) {
0280             for (int i = 1; i < temp; ++i) {
0281                 length = ((length << 8) | data[i]);
0282             }
0283         }
0284 
0285         data += temp;
0286         size -= temp;
0287     }
0288 
0289     if (length > size) {
0290         length = size;
0291     }
0292 
0293     return length;
0294 }
0295 
0296 void DvbLinuxCam::resize(int messageSize)
0297 {
0298     if (message.size() < (messageSize + HeaderSize)) {
0299         message.resize(2 * (messageSize + HeaderSize));
0300         messageData = message.data() + HeaderSize;
0301     }
0302 }
0303 
0304 void DvbLinuxCam::handleTransportLayer(const unsigned char *data, int size)
0305 {
0306     while (size > 0) {
0307         int tag = data[0];
0308         ++data;
0309         --size;
0310         int length = decodeLength(data, size);
0311 
0312         switch (tag) {
0313         case StatusByte:
0314             if ((length < 2) || (data[0] != ConnectionId)) {
0315                 size = 0;
0316                 qCWarning(logCam, "CAM: invalid StatusByte object");
0317                 break;
0318             }
0319 
0320             if ((data[1] & 0x80) != 0) {
0321                 pendingCommands |= SendReceiveData;
0322             }
0323 
0324             break;
0325         case CreateTransportConnectionReply:
0326             if ((length < 1) || (data[0] != ConnectionId)) {
0327                 size = 0;
0328                 qCWarning(logCam, "CAM: invalid CreateTransportConnectionReply object");
0329                 break;
0330             }
0331 
0332             pendingCommands &= ~(SendCreateTransportConnection);
0333             break;
0334         case DataLast:
0335             if ((length < 1) || (data[0] != ConnectionId)) {
0336                 size = 0;
0337                 qCWarning(logCam, "CAM: invalid DataLast object");
0338                 break;
0339             }
0340 
0341             handleSessionLayer(data + 1, length - 1);
0342             break;
0343         default:
0344             qCWarning(logCam, "CAM: unknown tag %d", tag);
0345             break;
0346         }
0347 
0348         data += length;
0349         size -= length;
0350     }
0351 }
0352 
0353 void DvbLinuxCam::handleSessionLayer(const unsigned char *data, int size)
0354 {
0355     if (size > 0) {
0356         int tag = data[0];
0357         ++data;
0358         --size;
0359         int length = decodeLength(data, size);
0360 
0361         switch (tag) {
0362         case OpenSessionRequest: {
0363             if (length < 4) {
0364                 qCWarning(logCam, "CAM: invalid OpenSessionRequest object");
0365                 break;
0366             }
0367 
0368             unsigned int resource = ((data[0] << 24) | (data[1] << 16) |
0369                 (data[2] << 8) | data[3]);
0370 
0371             messageData[0] = 0x00;
0372             messageData[1] = (resource >> 24);
0373             messageData[2] = (resource >> 16) & 0xff;
0374             messageData[3] = (resource >> 8) & 0xff;
0375             messageData[4] = resource & 0xff;
0376             messageData[5] = 0x00;
0377             messageData[6] = 0x00;
0378 
0379             switch (resource) {
0380             case ResourceManager:
0381                 messageData[6] = ResourceManagerSession;
0382                 pendingCommands |= SendProfileEnquiry;
0383                 break;
0384             case ApplicationInformation:
0385                 messageData[6] = ApplicationInformationSession;
0386                 pendingCommands |= SendApplicationInfoEnquiry;
0387                 break;
0388             case ConditionalAccess:
0389                 messageData[6] = ConditionalAccessSession;
0390                 pendingCommands |= SendCaInfoEnquiry;
0391                 break;
0392             default:
0393                 messageData[0] = quint8(0xf0);
0394                 break;
0395             }
0396 
0397             sendSessionLayerMessage(OpenSessionResponse, messageData, messageData + 7);
0398             break;
0399             }
0400         case SessionNumber:
0401             if (length < 2) {
0402                 qCWarning(logCam, "CAM: invalid SessionNumber object");
0403                 break;
0404             }
0405 
0406             handleApplicationLayer(data + length, size - length);
0407             break;
0408         default:
0409             qCWarning(logCam, "CAM: unknown tag %d", tag);
0410             break;
0411         }
0412     }
0413 }
0414 
0415 void DvbLinuxCam::handleApplicationLayer(const unsigned char *data, int size)
0416 {
0417     while (size >= 3) {
0418         int tag = ((data[0] << 16) | (data[1] << 8) | data[2]);
0419         data += 3;
0420         size -= 3;
0421         int length = decodeLength(data, size);
0422 
0423         switch (tag) {
0424         case ProfileEnquiry:
0425             messageData[0] = ((ResourceManager >> 24) & 0xff);
0426             messageData[1] = ((ResourceManager >> 16) & 0xff);
0427             messageData[2] = ((ResourceManager >> 8) & 0xff);
0428             messageData[3] = (ResourceManager & 0xff);
0429             messageData[4] = ((ApplicationInformation >> 24) & 0xff);
0430             messageData[5] = ((ApplicationInformation >> 16) & 0xff);
0431             messageData[6] = ((ApplicationInformation >> 8) & 0xff);
0432             messageData[7] = (ApplicationInformation & 0xff);
0433             messageData[8] = ((ConditionalAccess >> 24) & 0xff);
0434             messageData[9] = ((ConditionalAccess >> 16) & 0xff);
0435             messageData[10] = ((ConditionalAccess >> 8) & 0xff);
0436             messageData[11] = (ConditionalAccess & 0xff);
0437             sendApplicationLayerMessage(ProfileReply, messageData, messageData + 12);
0438             break;
0439         case ProfileReply:
0440             pendingCommands |= SendProfileChange;
0441             break;
0442         case ApplicationInfo:
0443             break;
0444         case CaInfo:
0445             ready = true;
0446             eventPosted = true;
0447             QCoreApplication::postEvent(this, new QEvent(QEvent::User));
0448             break;
0449         default:
0450             qCWarning(logCam, "CAM: unknown tag %d", tag);
0451             break;
0452         }
0453 
0454         data += length;
0455         size -= length;
0456     }
0457 }
0458 
0459 void DvbLinuxCam::handlePendingCommands()
0460 {
0461     if ((pendingCommands & ExpectingReply) == 0) {
0462         int pendingCommand = pendingCommands & (~pendingCommands + 1);
0463         pendingCommands &= ~pendingCommand;
0464 
0465         switch (pendingCommand) {
0466         case 0:
0467             break;
0468         case ResetCa:
0469             if (ioctl(caFd, CA_RESET, 0xff) != 0) {
0470                 qCWarning(logCam, "Cannot perform ioctl CA_RESET");
0471             }
0472 
0473             qCDebug(logCam, "--> CAM reset");
0474             slot = -1;
0475             pollTimer.start(100);
0476             pendingCommands = Nothing;
0477             break;
0478         case SendCreateTransportConnection:
0479             messageData[0] = ConnectionId;
0480             sendTransportLayerMessage(CreateTransportConnection, messageData,
0481                 messageData + 1);
0482             pendingCommands |= SendCreateTransportConnection;
0483             break;
0484         case SendPoll:
0485             messageData[0] = ConnectionId;
0486             sendTransportLayerMessage(DataLast, messageData, messageData + 1);
0487             break;
0488         case SendReceiveData:
0489             messageData[0] = ConnectionId;
0490             sendTransportLayerMessage(ReceiveData, messageData, messageData + 1);
0491             break;
0492         case SendProfileEnquiry:
0493             sendApplicationLayerMessage(ProfileEnquiry, messageData, messageData);
0494             break;
0495         case SendProfileChange:
0496             sendApplicationLayerMessage(ProfileChange, messageData, messageData);
0497             break;
0498         case SendApplicationInfoEnquiry:
0499             sendApplicationLayerMessage(ApplicationInfoEnquiry, messageData,
0500                 messageData);
0501             break;
0502         case SendCaInfoEnquiry:
0503             sendApplicationLayerMessage(CaInfoEnquiry, messageData, messageData);
0504             break;
0505         default:
0506             qCWarning(logCam, "CAM: unknown pending command %d", pendingCommand);
0507             break;
0508         }
0509     }
0510 }
0511 
0512 void DvbLinuxCam::customEvent(QEvent *event)
0513 {
0514     Q_UNUSED(event)
0515 
0516     if (!ready) {
0517         return;
0518     }
0519 
0520     int activeCaPmts = 0;
0521 
0522     for (QMap<int, DvbLinuxCamService>::iterator it = services.begin();
0523          it != services.end();) {
0524         if (it->pendingAction == DvbLinuxCamService::Remove) {
0525             DvbPmtSection pmtSection(it->pmtSectionData);
0526             sendCaPmt(pmtSection, Update, StopDescrambling);
0527             it = services.erase(it);
0528         } else {
0529             ++activeCaPmts;
0530             ++it;
0531         }
0532     }
0533 
0534     for (QMap<int, DvbLinuxCamService>::iterator it = services.begin();
0535          it != services.end(); ++it) {
0536         switch (it->pendingAction) {
0537         case DvbLinuxCamService::Nothing:
0538             continue;
0539         case DvbLinuxCamService::Add:
0540             if (activeCaPmts == 1) {
0541                 DvbPmtSection pmtSection(it->pmtSectionData);
0542                 sendCaPmt(pmtSection, Only, Descramble);
0543             } else {
0544                 DvbPmtSection pmtSection(it->pmtSectionData);
0545                 sendCaPmt(pmtSection, Add, Descramble);
0546             }
0547 
0548             break;
0549         case DvbLinuxCamService::Update: {
0550             DvbPmtSection pmtSection(it->pmtSectionData);
0551             sendCaPmt(pmtSection, Update, Descramble);
0552             break;
0553             }
0554         case DvbLinuxCamService::Remove:
0555             qCWarning(logCam, "CAM: impossible to remove custom event");
0556             break;
0557         }
0558 
0559         it->pendingAction = DvbLinuxCamService::Nothing;
0560     }
0561 
0562     eventPosted = false;
0563 }
0564 
0565 void DvbLinuxCam::sendCaPmt(const DvbPmtSection &pmtSection, CaPmtListManagement listManagement,
0566     CaPmtCommand command)
0567 {
0568     messageData[0] = listManagement;
0569     messageData[1] = ((pmtSection.programNumber() >> 8) & 0xff);
0570     messageData[2] = (pmtSection.programNumber() & 0xff);
0571     messageData[3] = (((pmtSection.versionNumber() << 1) & 0xff) |
0572         (pmtSection.currentNextIndicator() ? 0x01 : 0x00));
0573 
0574     int index = 6;
0575     int lengthIndex = 4;
0576     int length = 0;
0577 
0578     for (DvbDescriptor descriptor = pmtSection.descriptors(); descriptor.isValid();
0579          descriptor.advance()) {
0580         if (descriptor.descriptorTag() == 0x09) {
0581             resize(index + 1 + descriptor.getLength());
0582 
0583             if (length == 0) {
0584                 messageData[index++] = command;
0585                 ++length;
0586             }
0587 
0588             memcpy(messageData + index, descriptor.getData(), descriptor.getLength());
0589             index += descriptor.getLength();
0590             length += descriptor.getLength();
0591         }
0592     }
0593 
0594     messageData[lengthIndex] = ((length >> 8) & 0xff);
0595     messageData[lengthIndex + 1] = (length & 0xff);
0596 
0597     for (DvbPmtSectionEntry entry = pmtSection.entries(); entry.isValid(); entry.advance()) {
0598         resize(index + 5);
0599         messageData[index++] = (entry.streamType() & 0xff);
0600         messageData[index++] = ((entry.pid() >> 8) & 0xff);
0601         messageData[index++] = (entry.pid() & 0xff);
0602         lengthIndex = index;
0603         index += 2;
0604         length = 0;
0605 
0606         for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid();
0607              descriptor.advance()) {
0608             if (descriptor.descriptorTag() == 0x09) {
0609                 resize(index + 1 + descriptor.getLength());
0610 
0611                 if (length == 0) {
0612                     messageData[index++] = command;
0613                     ++length;
0614                 }
0615 
0616                 memcpy(messageData + index, descriptor.getData(),
0617                     descriptor.getLength());
0618                 index += descriptor.getLength();
0619                 length += descriptor.getLength();
0620             }
0621         }
0622 
0623         messageData[lengthIndex] = ((length >> 8) & 0xff);
0624         messageData[lengthIndex + 1] = (length & 0xff);
0625     }
0626 
0627     sendApplicationLayerMessage(CaPmt, messageData, messageData + index);
0628 }
0629 
0630 void DvbLinuxCam::sendTransportLayerMessage(TransportLayerTag tag, char *data, char *end)
0631 {
0632     uint length = uint(end - data);
0633     Q_ASSERT(length < 0x10000);
0634 
0635     if (length < 0x80) {
0636         *(--data) = (length & 0xff);
0637     } else {
0638         *(--data) = (length & 0xff);
0639         *(--data) = ((length >> 8) & 0xff);
0640         *(--data) = quint8(0x82);
0641     }
0642 
0643     *(--data) = tag;
0644     *(--data) = ConnectionId;
0645     *(--data) = (slot & 0xff);
0646     length = uint(end - data);
0647 
0648     if (write(caFd, data, length) != length) {
0649         qCWarning(logCam, "CAM: cannot send message of length %d", length);
0650     }
0651 
0652     pendingCommands |= ExpectingReply;
0653     pollTimer.start(400);
0654 }
0655 
0656 void DvbLinuxCam::sendSessionLayerMessage(SessionLayerTag tag, char *data, char *end)
0657 {
0658     switch (tag) {
0659     case OpenSessionResponse:
0660         Q_ASSERT((end - data) == 0x07);
0661         *(--data) = 0x07;
0662         break;
0663     case SessionNumber:
0664         Q_ASSERT((end - data) >= 0x02);
0665         *(--data) = 0x02;
0666         break;
0667     default:
0668         Q_ASSERT(false);
0669         return;
0670     }
0671 
0672     *(--data) = tag;
0673     *(--data) = ConnectionId;
0674     sendTransportLayerMessage(DataLast, data, end);
0675 }
0676 
0677 void DvbLinuxCam::sendApplicationLayerMessage(ApplicationLayerTag tag, char *data, char *end)
0678 {
0679     uint length = uint(end - data);
0680     Q_ASSERT(length < 0x10000);
0681 
0682     if (length < 0x80) {
0683         *(--data) = (length & 0xff);
0684     } else {
0685         *(--data) = (length & 0xff);
0686         *(--data) = ((length >> 8) & 0xff);
0687         *(--data) = quint8(0x82);
0688     }
0689 
0690     *(--data) = (tag & 0xff);
0691     *(--data) = ((tag >> 8) & 0xff);
0692     *(--data) = (tag >> 16);
0693 
0694     switch (tag) {
0695     case ProfileEnquiry:
0696     case ProfileReply:
0697     case ProfileChange:
0698         *(--data) = ResourceManagerSession;
0699         *(--data) = (ResourceManagerSession >> 8);
0700         break;
0701     case ApplicationInfoEnquiry:
0702         *(--data) = ApplicationInformationSession;
0703         *(--data) = (ApplicationInformationSession >> 8);
0704         break;
0705     case CaInfoEnquiry:
0706     case CaPmt:
0707         *(--data) = ConditionalAccessSession;
0708         *(--data) = (ConditionalAccessSession >> 8);
0709         break;
0710     default:
0711         Q_ASSERT(false);
0712         return;
0713     }
0714 
0715     sendSessionLayerMessage(SessionNumber, data, end);
0716 }
0717 
0718 #include "moc_dvbcam_linux.cpp"