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"