mirror of
https://github.com/rofafor/vdr-plugin-iptv.git
synced 2023-10-10 13:37:03 +02:00
428 lines
12 KiB
C
428 lines
12 KiB
C
/*
|
|
* sectionfilter.c: IPTV plugin for the Video Disk Recorder
|
|
*
|
|
* See the README file for copyright information and how to reach the author.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "log.h"
|
|
#include "sectionfilter.h"
|
|
|
|
cIptvSectionFilter::cIptvSectionFilter(int deviceIndexP, uint16_t pidP, uint8_t tidP, uint8_t maskP)
|
|
: pusiSeenM(0),
|
|
feedCcM(0),
|
|
doneqM(0),
|
|
secBufM(NULL),
|
|
secBufpM(0),
|
|
secLenM(0),
|
|
tsFeedpM(0),
|
|
pidM(pidP),
|
|
ringBufferM(new cRingBufferFrame(eDmxMaxSectionCount * eDmxMaxSectionSize)),
|
|
deviceIndexM(deviceIndexP)
|
|
{
|
|
debug16("%s (%d, %d)", __PRETTY_FUNCTION__, deviceIndexM, pidM);
|
|
int i;
|
|
|
|
memset(secBufBaseM, 0, sizeof(secBufBaseM));
|
|
memset(filterValueM, 0, sizeof(filterValueM));
|
|
memset(filterMaskM, 0, sizeof(filterMaskM));
|
|
memset(filterModeM, 0, sizeof(filterModeM));
|
|
memset(maskAndModeM, 0, sizeof(maskAndModeM));
|
|
memset(maskAndNotModeM, 0, sizeof(maskAndNotModeM));
|
|
|
|
filterValueM[0] = tidP;
|
|
filterMaskM[0] = maskP;
|
|
|
|
// Invert the filter
|
|
for (i = 0; i < eDmxMaxFilterSize; ++i)
|
|
filterValueM[i] ^= 0xFF;
|
|
|
|
uint8_t mask, mode, doneq = 0;
|
|
for (i = 0; i < eDmxMaxFilterSize; ++i) {
|
|
mode = filterModeM[i];
|
|
mask = filterMaskM[i];
|
|
maskAndModeM[i] = (uint8_t)(mask & mode);
|
|
maskAndNotModeM[i] = (uint8_t)(mask & ~mode);
|
|
doneq |= maskAndNotModeM[i];
|
|
}
|
|
doneqM = doneq ? 1 : 0;
|
|
|
|
// Create sockets
|
|
socketM[0] = socketM[1] = -1;
|
|
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socketM) != 0) {
|
|
char tmp[64];
|
|
error("Opening section filter sockets failed (device=%d pid=%d): %s", deviceIndexM, pidM, strerror_r(errno, tmp, sizeof(tmp)));
|
|
}
|
|
else if ((fcntl(socketM[0], F_SETFL, O_NONBLOCK) != 0) || (fcntl(socketM[1], F_SETFL, O_NONBLOCK) != 0)) {
|
|
char tmp[64];
|
|
error("Setting section filter socket to non-blocking mode failed (device=%d pid=%d): %s", deviceIndexM, pidM, strerror_r(errno, tmp, sizeof(tmp)));
|
|
}
|
|
}
|
|
|
|
cIptvSectionFilter::~cIptvSectionFilter()
|
|
{
|
|
debug16("%s (%d, %d)", __PRETTY_FUNCTION__, deviceIndexM, pidM);
|
|
int tmp = socketM[1];
|
|
socketM[1] = -1;
|
|
if (tmp >= 0)
|
|
close(tmp);
|
|
tmp = socketM[0];
|
|
socketM[0] = -1;
|
|
if (tmp >= 0)
|
|
close(tmp);
|
|
secBufM = NULL;
|
|
DELETENULL(ringBufferM);
|
|
}
|
|
|
|
inline uint16_t cIptvSectionFilter::GetLength(const uint8_t *dataP)
|
|
{
|
|
return (uint16_t)(3 + ((dataP[1] & 0x0f) << 8) + dataP[2]);
|
|
}
|
|
|
|
void cIptvSectionFilter::New(void)
|
|
{
|
|
tsFeedpM = secBufpM = secLenM = 0;
|
|
secBufM = secBufBaseM;
|
|
}
|
|
|
|
int cIptvSectionFilter::Filter(void)
|
|
{
|
|
if (secBufM) {
|
|
int i;
|
|
uint8_t neq = 0;
|
|
|
|
for (i = 0; i < eDmxMaxFilterSize; ++i) {
|
|
uint8_t calcxor = (uint8_t)(filterValueM[i] ^ secBufM[i]);
|
|
if (maskAndModeM[i] & calcxor)
|
|
return 0;
|
|
neq |= (maskAndNotModeM[i] & calcxor);
|
|
}
|
|
|
|
if (doneqM && !neq)
|
|
return 0;
|
|
|
|
if (ringBufferM && (secLenM > 0))
|
|
ringBufferM->Put(new cFrame(secBufM, secLenM));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline int cIptvSectionFilter::Feed(void)
|
|
{
|
|
if (Filter() < 0)
|
|
return -1;
|
|
secLenM = 0;
|
|
return 0;
|
|
}
|
|
|
|
int cIptvSectionFilter::CopyDump(const uint8_t *bufP, uint8_t lenP)
|
|
{
|
|
uint16_t limit, seclen, n;
|
|
|
|
if (tsFeedpM >= eDmxMaxSectionFeedSize)
|
|
return 0;
|
|
|
|
if (tsFeedpM + lenP > eDmxMaxSectionFeedSize)
|
|
lenP = (uint8_t)(eDmxMaxSectionFeedSize - tsFeedpM);
|
|
|
|
if (lenP <= 0)
|
|
return 0;
|
|
|
|
memcpy(secBufBaseM + tsFeedpM, bufP, lenP);
|
|
tsFeedpM = uint16_t(tsFeedpM + lenP);
|
|
|
|
limit = tsFeedpM;
|
|
if (limit > eDmxMaxSectionFeedSize)
|
|
return -1; // internal error should never happen
|
|
|
|
// Always set secbuf
|
|
secBufM = secBufBaseM + secBufpM;
|
|
|
|
for (n = 0; secBufpM + 2 < limit; ++n) {
|
|
seclen = GetLength(secBufM);
|
|
if ((seclen <= 0) || (seclen > eDmxMaxSectionSize) || ((seclen + secBufpM) > limit))
|
|
return 0;
|
|
secLenM = seclen;
|
|
if (pusiSeenM)
|
|
Feed();
|
|
secBufpM = uint16_t(secBufpM + seclen);
|
|
secBufM += seclen;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cIptvSectionFilter::Process(const uint8_t* dataP)
|
|
{
|
|
if (dataP[0] != TS_SYNC_BYTE)
|
|
return;
|
|
|
|
// Stop if not the PID this filter is looking for
|
|
if (ts_pid(dataP) != pidM)
|
|
return;
|
|
|
|
uint8_t count = payload(dataP);
|
|
|
|
// Check if no payload or out of range
|
|
if (count == 0)
|
|
return;
|
|
|
|
// Payload start
|
|
uint8_t p = (uint8_t)(TS_SIZE - count);
|
|
|
|
uint8_t cc = (uint8_t)(dataP[3] & 0x0f);
|
|
int ccok = ((feedCcM + 1) & 0x0f) == cc;
|
|
feedCcM = cc;
|
|
|
|
int dc_i = 0;
|
|
if (dataP[3] & 0x20) {
|
|
// Adaption field present, check for discontinuity_indicator
|
|
if ((dataP[4] > 0) && (dataP[5] & 0x80))
|
|
dc_i = 1;
|
|
}
|
|
|
|
if (!ccok || dc_i) {
|
|
// Discontinuity detected. Reset pusiSeenM = 0 to
|
|
// stop feeding of suspicious data until next PUSI=1 arrives
|
|
pusiSeenM = 0;
|
|
New();
|
|
}
|
|
|
|
if (dataP[1] & 0x40) {
|
|
// PUSI=1 (is set), section boundary is here
|
|
if (count > 1 && dataP[p] < count) {
|
|
const uint8_t *before = &dataP[p + 1];
|
|
uint8_t before_len = dataP[p];
|
|
const uint8_t *after = &before[before_len];
|
|
uint8_t after_len = (uint8_t)(count - 1 - before_len);
|
|
CopyDump(before, before_len);
|
|
|
|
// Before start of new section, set pusiSeenM = 1
|
|
pusiSeenM = 1;
|
|
New();
|
|
CopyDump(after, after_len);
|
|
}
|
|
}
|
|
else {
|
|
// PUSI=0 (is not set), no section boundary
|
|
CopyDump(&dataP[p], count);
|
|
}
|
|
}
|
|
|
|
bool cIptvSectionFilter::Send(void)
|
|
{
|
|
bool result = false;
|
|
cFrame *section = ringBufferM->Get();
|
|
if (section) {
|
|
uchar *data = section->Data();
|
|
int count = section->Count();
|
|
if (data && (count > 0) && (socketM[1] >= 0) && (socketM[0] >= 0)) {
|
|
ssize_t len = send(socketM[1], data, count, MSG_EOR);
|
|
ERROR_IF(len < 0 && errno != EAGAIN, "send()");
|
|
if (len > 0) {
|
|
ringBufferM->Drop(section);
|
|
result = !!ringBufferM->Available();
|
|
// Update statistics
|
|
AddSectionStatistic(len, 1);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
cIptvSectionFilterHandler::cIptvSectionFilterHandler(int deviceIndexP, unsigned int bufferLenP)
|
|
: cThread("IPTV section handler"),
|
|
ringBufferM(new cRingBufferLinear(bufferLenP, TS_SIZE, false, *cString::sprintf("IPTV SECTION HANDLER %d", deviceIndexP))),
|
|
mutexM(),
|
|
deviceIndexM(deviceIndexP)
|
|
{
|
|
debug1("%s (%d, %d) [device %d]", __PRETTY_FUNCTION__, deviceIndexP, bufferLenP, deviceIndexM);
|
|
|
|
// Initialize filter pointers
|
|
memset(filtersM, 0, sizeof(filtersM));
|
|
|
|
// Create input buffer
|
|
if (ringBufferM) {
|
|
ringBufferM->SetTimeouts(100, 100);
|
|
ringBufferM->SetIoThrottle();
|
|
}
|
|
else
|
|
error("Failed to allocate buffer for section filter handler (device=%d): ", deviceIndexM);
|
|
|
|
Start();
|
|
}
|
|
|
|
cIptvSectionFilterHandler::~cIptvSectionFilterHandler()
|
|
{
|
|
debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
|
|
// Stop thread
|
|
if (Running())
|
|
Cancel(3);
|
|
DELETE_POINTER(ringBufferM);
|
|
|
|
// Destroy all filters
|
|
cMutexLock MutexLock(&mutexM);
|
|
for (int i = 0; i < eMaxSecFilterCount; ++i)
|
|
Delete(i);
|
|
}
|
|
|
|
void cIptvSectionFilterHandler::Action(void)
|
|
{
|
|
debug1("%s Entering [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
|
|
bool processed = false;
|
|
// Do the thread loop
|
|
while (Running()) {
|
|
// Send demuxed section packets through all filters
|
|
bool retry = false;
|
|
mutexM.Lock();
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (filtersM[i] && filtersM[i]->Send())
|
|
retry = true;
|
|
}
|
|
mutexM.Unlock();
|
|
if (retry)
|
|
continue;
|
|
// Read one TS packet
|
|
if (ringBufferM) {
|
|
int len = 0;
|
|
if (processed) {
|
|
ringBufferM->Del(TS_SIZE);
|
|
processed = false;
|
|
}
|
|
uchar *p = ringBufferM->Get(len);
|
|
if (p && (len >= TS_SIZE)) {
|
|
if (*p != TS_SYNC_BYTE) {
|
|
for (int i = 1; i < len; ++i) {
|
|
if (p[i] == TS_SYNC_BYTE) {
|
|
len = i;
|
|
break;
|
|
}
|
|
}
|
|
ringBufferM->Del(len);
|
|
debug1("%s Skipped %d bytes to sync on TS packet [device %d]]", __PRETTY_FUNCTION__, len, deviceIndexM);
|
|
continue;
|
|
}
|
|
// Process TS packet through all filters
|
|
mutexM.Lock();
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (filtersM[i])
|
|
filtersM[i]->Process(p);
|
|
}
|
|
mutexM.Unlock();
|
|
processed = true;
|
|
continue;
|
|
}
|
|
}
|
|
cCondWait::SleepMs(10); // to avoid busy loop and reduce cpu load
|
|
}
|
|
debug1("%s Exiting [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
|
|
}
|
|
|
|
cString cIptvSectionFilterHandler::GetInformation(void)
|
|
{
|
|
debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
|
|
// loop through active section filters
|
|
cMutexLock MutexLock(&mutexM);
|
|
cString s = "";
|
|
unsigned int count = 0;
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (filtersM[i]) {
|
|
s = cString::sprintf("%sFilter %d: %s Pid=0x%02X (%s)\n", *s, i,
|
|
*filtersM[i]->GetSectionStatistic(), filtersM[i]->GetPid(),
|
|
id_pid(filtersM[i]->GetPid()));
|
|
if (++count > IPTV_STATS_ACTIVE_FILTERS_COUNT)
|
|
break;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
bool cIptvSectionFilterHandler::Delete(unsigned int indexP)
|
|
{
|
|
debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, indexP, deviceIndexM);
|
|
if ((indexP < eMaxSecFilterCount) && filtersM[indexP]) {
|
|
debug16("%s (%d) Found [device %d]", __PRETTY_FUNCTION__, indexP, deviceIndexM);
|
|
cIptvSectionFilter *tmp = filtersM[indexP];
|
|
filtersM[indexP] = NULL;
|
|
delete tmp;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cIptvSectionFilterHandler::IsBlackListed(u_short pidP, u_char tidP, u_char maskP) const
|
|
{
|
|
debug16("%s (%d, %02X, %02X) [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, deviceIndexM);
|
|
// loop through section filter table
|
|
for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
|
|
int index = IptvConfig.GetDisabledFilters(i);
|
|
// Check if matches
|
|
if ((index >= 0) && (index < SECTION_FILTER_TABLE_SIZE) &&
|
|
(section_filter_table[index].pid == pidP) && (section_filter_table[index].tid == tidP) &&
|
|
(section_filter_table[index].mask == maskP)) {
|
|
debug16("%s (%d, %02X, %02X) Found %s [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, section_filter_table[index].description, deviceIndexM);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int cIptvSectionFilterHandler::Open(u_short pidP, u_char tidP, u_char maskP)
|
|
{
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// Blacklist check, refuse certain filters
|
|
if (IsBlackListed(pidP, tidP, maskP))
|
|
return -1;
|
|
// Search the next free filter slot
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (!filtersM[i]) {
|
|
filtersM[i] = new cIptvSectionFilter(deviceIndexM, pidP, tidP, maskP);
|
|
debug16("%s (%d, %02X, %02X) handle=%d index=%u [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, filtersM[i]->GetFd(), i, deviceIndexM);
|
|
return filtersM[i]->GetFd();
|
|
}
|
|
}
|
|
// No free filter slot found
|
|
return -1;
|
|
}
|
|
|
|
void cIptvSectionFilterHandler::Close(int handleP)
|
|
{
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// Search the filter for deletion
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (filtersM[i] && (handleP == filtersM[i]->GetFd())) {
|
|
debug1("%s (%d) pid=%d handle=%d index=%d [device %d]", __PRETTY_FUNCTION__, handleP, filtersM[i]->GetPid(), filtersM[i]->GetFd(), i, deviceIndexM);
|
|
Delete(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int cIptvSectionFilterHandler::GetPid(int handleP)
|
|
{
|
|
// Lock
|
|
cMutexLock MutexLock(&mutexM);
|
|
// Search the filter for data
|
|
for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
|
|
if (filtersM[i] && (handleP == filtersM[i]->GetFd())) {
|
|
debug1("%s (%d) pid=%d handle=%d index=%d [device %d]", __PRETTY_FUNCTION__, handleP, filtersM[i]->GetPid(), filtersM[i]->GetFd(), i, deviceIndexM);
|
|
return filtersM[i]->GetPid();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cIptvSectionFilterHandler::Write(uchar *bufferP, int lengthP)
|
|
{
|
|
debug16("%s (, %d) [device %d]", __PRETTY_FUNCTION__, lengthP, deviceIndexM);
|
|
// Fill up the buffer
|
|
if (ringBufferM) {
|
|
int len = ringBufferM->Put(bufferP, lengthP);
|
|
if (len != lengthP)
|
|
ringBufferM->ReportOverflow(lengthP - len);
|
|
}
|
|
}
|