diff --git a/Makefile b/Makefile index 466d6405..609a5a0b 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ SRCS = src/version.c \ src/access.c \ src/dtable.c \ src/tcp.c \ + src/udp.c \ src/url.c \ src/http.c \ src/notify.c \ @@ -112,9 +113,14 @@ SRCS = src/version.c \ src/descrambler/descrambler.c \ src/service_mapper.c \ src/input.c \ - src/http/http_client.c \ + src/httpc.c \ + src/rtsp.c \ src/fsmonitor.c \ src/cron.c \ + src/esfilter.c + +SRCS-${CONFIG_UPNP} += \ + src/upnp.c SRCS += \ src/api.c \ @@ -126,7 +132,8 @@ SRCS += \ src/api/api_mpegts.c \ src/api/api_epg.c \ src/api/api_epggrab.c \ - src/api/api_imagecache.c + src/api/api_imagecache.c \ + src/api/api_esfilter.c SRCS += \ src/parsers/parsers.c \ @@ -205,6 +212,13 @@ SRCS-${CONFIG_LINUXDVB} += \ src/input/mpegts/linuxdvb/linuxdvb_rotor.c \ src/input/mpegts/linuxdvb/linuxdvb_en50494.c +# SATIP +SRCS-${CONFIG_SATIP_CLIENT} += \ + src/input/mpegts/satip/satip.c \ + src/input/mpegts/satip/satip_frontend.c \ + src/input/mpegts/satip/satip_satconf.c \ + src/input/mpegts/satip/satip_rtsp.c + # IPTV SRCS-${CONFIG_IPTV} += \ src/input/mpegts/iptv/iptv.c \ diff --git a/configure b/configure index 3cd02ea3..8d0e47cc 100755 --- a/configure +++ b/configure @@ -19,6 +19,7 @@ OPTIONS=( "cwc:yes" "v4l:no" "linuxdvb:yes" + "satip_client:yes" "iptv:yes" "tsfile:yes" "dvbscan:yes" @@ -29,7 +30,6 @@ OPTIONS=( "zlib:auto" "libav:auto" "inotify:auto" - "curl:auto" "epoll:auto" "uriparser:auto" "ccache:auto" @@ -113,6 +113,18 @@ int test(void) return 0; } ' + +check_cc_snippet recvmmsg ' +#define _GNU_SOURCE +#include +#include +#define TEST test +int test(void) +{ + recvmmsg(0, NULL, 0, 0, NULL); + return 0; +} +' # # Python # @@ -161,14 +173,10 @@ if enabled_or_auto zlib; then fi # -# CURL +# SAT>IP client # -if enabled_or_auto curl; then - if check_pkg libcurl; then - enable curl - elif enabled curl; then - die "Curl development support not foun (use --disable-curl)" - fi +if enabled_or_auto satip_client; then + enable upnp fi # @@ -264,11 +272,7 @@ fi # Icon caching # if enabled_or_auto imagecache; then - if enabled curl; then - enable imagecache - elif enabled imagecache; then - die "Libcurl support not found (use --disable-imagecache)" - fi + enable imagecache fi # diff --git a/docs/html/config_esfilter.html b/docs/html/config_esfilter.html new file mode 100644 index 00000000..75a03de4 --- /dev/null +++ b/docs/html/config_esfilter.html @@ -0,0 +1,109 @@ +
+ +This table defines rules to filter and order the elementary streams +like video or audio from the input feed. + +

+The execution order of commands is granted. It means that first rule +is executed for all available streams then second and so on. + +

+If any elementary stream is not marked as ignored or exclusive, it is +used. If you like to ignore unknown elementary streams, add a rule +to the end of grid with the any (not defined) comparisons and +with the action ignore. + +

+The rules for different elementary stream groups (video, audio, +teletext, subtitle, CA, other) are executed separately (as visually edited). + +

+For the visual verification of the filtering, there is a service info +dialog in the Configuration / DVB Inputs / Services window . This dialog +shows the received PIDs and filtered PIDs in one window. + +

+The rules are listed / edited in a grid. + +

    +
  • To edit a cell, double click on it. After a cell is changed it + will flags one of its corner to red to indicated that it has been + changed. To commit these changes back to Tvheadend press the + 'Save changes' button. In order to change a Checkbox cell you only + have to click once in it. + +
  • To add a new entry, press the 'Add entry' button. The new (empty) entry + will be created on the server but will not be in its enabled state. + You can now change all the cells to the desired values, check the + 'enable' box and then press 'Save changes' to activate the new entry. + +
  • To delete one or more entries, select the lines (by clicking once on + them), and press the 'Delete selected' button. A pop up + will ask you to confirm your request. + +
  • To move up or down one or more entries, select the lines (by clicking + once on them), and press the 'Move up' or 'Move down' button. +
+ +

+The columns have the following functions: + +

+
Enabled +
If selected, the rule will be enabled. + +
Stream Type +
Select the elementary stream type to compare. Empty field means any. + +
Language +
Select the language to compare. Empty field means any. + +
Service +
The service to compare. Empty field means any. + +
CA Identification +
The CAID to compare. Empty field means any. + +
CA Provider +
The CA provider to compare. Empty field means any. + +
PID +
Program identification (PID) number to compare. Zero means any. + This comparison is processed only when service comparison is active. + +
Action +
The rule action defines the operation when all comparisons succeeds. + +
+ +
NONE +
No action, may be used for the logging and a comparison verification. + +
USE +
Use this elementary stream. + +
ONCE +
Use this elementary stream only once per selected language. + The first successfully compared rule wins. + +
EXCLUSIVE +
Use only this elementary stream. No other elementary streams + will be used. + +
EMPTY +
Add this elementary stream only when no elementary streams are + used from previous rules. + +
IGNORE +
Ignore this elementary stream. This stream is not used. Another + successfully compared rule with different action may override it. + +
+ +
Log +
Write a short message to log identifying the matched parameters. + It is useful for debugging your setup or structure of incoming + streams. + +
+
diff --git a/docs/html/config_tvadapters.html b/docs/html/config_tvadapters.html new file mode 100644 index 00000000..a4d9a36f --- /dev/null +++ b/docs/html/config_tvadapters.html @@ -0,0 +1,75 @@ +
+ +

The adapters and tuners are listed / edited in a tree

+ +
    +
  • To edit an item, click on it. To commit edited changes back to + Tvheadend press the 'Save' button. In order to change a Checkbox cell + you only have to click once in it. +
+

+ +

The rows have the following functions

+ +
+
Enabled
+
If selected, the IPTV service will be enabled an use for channel + subscriptions.
+ +
Name
+
The name of this tuner.
+
+

+ +

LinuxDVB specific rows

+ +
+
Keep FE open
+
Enable to not close the LinuxDVB frontend device in the idle state.
+
+ +

SAT>IP specific rows

+ +
+
Full Mux Rx mode supported
+
Enable, if the SAT>IP box supports the full mux rx mode (pids=all) + parameter.
+ +
Signal scale (240 or 100)
+
Some SAT>IP boxes has only 0-100 scale. If your signal is too low, try + value 100 here.
+ +
Maximum PIDs
+
Maximum supported PIDs in the filter of the SAT>IP box.
+ +
Maximum length of PIDs
+
Maximum length in characters for the command setting PIDs to the + SAT>IP box.
+ +
addpids/delpids supported
+
Enable, if the SAT>IP box supports the addpids/delpids command. + +
PIDs in setup
+
Enable, if the SAT>IP box requires pids=0 parameter in the SETUP RTSP command.
+ +
UDP RTP Port Number (2 ports)
+
Force the local UDP Port number here. The number should be even (RTP port). + The next odd number (+1) will be used as the RTCP port.
+ +
Satellite Positions
+
Select the number of satellite positions supported by the SAT>IP + hardware and your coaxial cable wiring.
+ +
Master Tuner
+
Select the master tuner. +

The signal from the standard universal LNB can be split using + a simple coaxial splitter (no multiswitch) to several outputs. + In this case, the position, the polarization and low-high + band settings must be equal.

+

If you set other tuner as master, then this tuner will act like + a slave one and tvheadend will assure that this tuner will not + use incompatible parameters (position, polarization, lo-hi).

+
+
+ +
diff --git a/src/api.c b/src/api.c index 97546982..83392622 100644 --- a/src/api.c +++ b/src/api.c @@ -125,6 +125,7 @@ void api_init ( void ) api_epggrab_init(); api_status_init(); api_imagecache_init(); + api_esfilter_init(); } void api_done ( void ) diff --git a/src/api.h b/src/api.h index 629330e8..f51f7ebe 100644 --- a/src/api.h +++ b/src/api.h @@ -59,6 +59,7 @@ void api_init ( void ); void api_done ( void ); void api_idnode_init ( void ); void api_input_init ( void ); +void api_input_satip_init ( void ); void api_service_init ( void ); void api_channel_init ( void ); void api_mpegts_init ( void ); @@ -66,12 +67,14 @@ void api_epg_init ( void ); void api_epggrab_init ( void ); void api_status_init ( void ); void api_imagecache_init ( void ); +void api_esfilter_init ( void ); /* * IDnode */ typedef struct api_idnode_grid_conf { + int tindex; int start; int limit; idnode_filter_t filter; diff --git a/src/api/api_esfilter.c b/src/api/api_esfilter.c new file mode 100644 index 00000000..2769f8c3 --- /dev/null +++ b/src/api/api_esfilter.c @@ -0,0 +1,101 @@ +/* + * API - elementary stream filter related calls + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "esfilter.h" +#include "lang_codes.h" +#include "access.h" +#include "api.h" + +static void +api_esfilter_grid + ( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args, + esfilter_class_t cls ) +{ + esfilter_t *esf; + + TAILQ_FOREACH(esf, &esfilters[cls], esf_link) { + idnode_set_add(ins, (idnode_t*)esf, &conf->filter); + } +} + +static int +api_esfilter_create + ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp, + esfilter_class_t cls ) +{ + htsmsg_t *conf; + + if (!(conf = htsmsg_get_map(args, "conf"))) + return EINVAL; + + pthread_mutex_lock(&global_lock); + esfilter_create(cls, NULL, conf, 1); + pthread_mutex_unlock(&global_lock); + + return 0; +} + +#define ESFILTER(func, t) \ +static void api_esfilter_grid_##func \ + ( idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args ) \ +{ return api_esfilter_grid(ins, conf, args, (t)); } \ +static int api_esfilter_create_##func \ + ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) \ +{ return api_esfilter_create(opaque, op, args, resp, (t)); } + +ESFILTER(video, ESF_CLASS_VIDEO); +ESFILTER(audio, ESF_CLASS_AUDIO); +ESFILTER(teletext, ESF_CLASS_TELETEXT); +ESFILTER(subtit, ESF_CLASS_SUBTIT); +ESFILTER(ca, ESF_CLASS_CA); +ESFILTER(other, ESF_CLASS_OTHER); + +void api_esfilter_init ( void ) +{ + static api_hook_t ah[] = { + { "esfilter/video/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_video }, + { "esfilter/video/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_video }, + { "esfilter/video/create", ACCESS_ADMIN, api_esfilter_create_video, NULL }, + + { "esfilter/audio/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_audio }, + { "esfilter/audio/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_audio }, + { "esfilter/audio/create", ACCESS_ADMIN, api_esfilter_create_audio, NULL }, + + { "esfilter/teletext/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_teletext }, + { "esfilter/teletext/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_teletext }, + { "esfilter/teletext/create",ACCESS_ADMIN, api_esfilter_create_teletext, NULL }, + + { "esfilter/subtit/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_subtit }, + { "esfilter/subtit/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_subtit }, + { "esfilter/subtit/create", ACCESS_ADMIN, api_esfilter_create_subtit, NULL }, + + { "esfilter/ca/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_ca }, + { "esfilter/ca/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_ca }, + { "esfilter/ca/create", ACCESS_ADMIN, api_esfilter_create_ca, NULL }, + + { "esfilter/other/class", ACCESS_ANONYMOUS, api_idnode_class, (void*)&esfilter_class_other }, + { "esfilter/other/grid", ACCESS_ANONYMOUS, api_idnode_grid, api_esfilter_grid_other }, + { "esfilter/other/create", ACCESS_ADMIN, api_esfilter_create_other, NULL }, + + { NULL }, + }; + + api_register_all(ah); +} diff --git a/src/api/api_idnode.c b/src/api/api_idnode.c index 3e951ea1..689a51b5 100644 --- a/src/api/api_idnode.c +++ b/src/api/api_idnode.c @@ -377,8 +377,8 @@ exit: } static int -api_idnode_delete - ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +api_idnode_handler + ( htsmsg_t *args, htsmsg_t **resp, void (*handler)(idnode_t *in) ) { int err = 0; idnode_t *in; @@ -400,7 +400,7 @@ api_idnode_delete HTSMSG_FOREACH(f, uuids) { if (!(uuid = htsmsg_field_get_string(f))) continue; if (!(in = idnode_find(uuid, NULL))) continue; - idnode_delete(in); + handler(in); } /* Single */ @@ -409,7 +409,7 @@ api_idnode_delete if (!(in = idnode_find(uuid, NULL))) err = ENOENT; else - idnode_delete(in); + handler(in); } pthread_mutex_unlock(&global_lock); @@ -417,14 +417,37 @@ api_idnode_delete return err; } +static int +api_idnode_delete + ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + return api_idnode_handler(args, resp, idnode_delete); +} + +static int +api_idnode_moveup + ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + return api_idnode_handler(args, resp, idnode_moveup); +} + +static int +api_idnode_movedown + ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) +{ + return api_idnode_handler(args, resp, idnode_movedown); +} + void api_idnode_init ( void ) { static api_hook_t ah[] = { - { "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL }, - { "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL }, - { "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL }, - { "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL }, - { "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL }, + { "idnode/load", ACCESS_ANONYMOUS, api_idnode_load, NULL }, + { "idnode/save", ACCESS_ADMIN, api_idnode_save, NULL }, + { "idnode/tree", ACCESS_ANONYMOUS, api_idnode_tree, NULL }, + { "idnode/class", ACCESS_ANONYMOUS, api_idnode_class, NULL }, + { "idnode/delete", ACCESS_ADMIN, api_idnode_delete, NULL }, + { "idnode/moveup", ACCESS_ADMIN, api_idnode_moveup, NULL }, + { "idnode/movedown", ACCESS_ADMIN, api_idnode_movedown, NULL }, { NULL }, }; diff --git a/src/api/api_service.c b/src/api/api_service.c index 211d12ed..e8b847e2 100644 --- a/src/api/api_service.c +++ b/src/api/api_service.c @@ -92,12 +92,45 @@ api_service_mapper_notify ( void ) notify_by_msg("servicemapper", api_mapper_status_msg()); } +static htsmsg_t * +api_service_streams_get_one ( elementary_stream_t *es ) +{ + htsmsg_t *e = htsmsg_create_map(); + htsmsg_add_u32(e, "index", es->es_index); + htsmsg_add_u32(e, "pid", es->es_pid); + htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type)); + htsmsg_add_str(e, "language", es->es_lang); + if (SCT_ISSUBTITLE(es->es_type)) { + htsmsg_add_u32(e, "composition_id", es->es_composition_id); + htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id); + } else if (SCT_ISAUDIO(es->es_type)) { + htsmsg_add_u32(e, "audio_type", es->es_audio_type); + } else if (SCT_ISVIDEO(es->es_type)) { + htsmsg_add_u32(e, "width", es->es_width); + htsmsg_add_u32(e, "height", es->es_height); + htsmsg_add_u32(e, "duration", es->es_frame_duration); + htsmsg_add_u32(e, "aspect_num", es->es_aspect_num); + htsmsg_add_u32(e, "aspect_den", es->es_aspect_den); + } else if (es->es_type == SCT_CA) { + caid_t *ca; + htsmsg_t *e2, *l2 = htsmsg_create_list(); + LIST_FOREACH(ca, &es->es_caids, link) { + e2 = htsmsg_create_map(); + htsmsg_add_u32(e2, "caid", ca->caid); + htsmsg_add_u32(e2, "provider", ca->providerid); + htsmsg_add_msg(l2, NULL, e2); + } + htsmsg_add_msg(e, "caids", l2); + } + return e; +} + static int api_service_streams ( void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp ) { const char *uuid; - htsmsg_t *e, *st; + htsmsg_t *e, *st, *stf; service_t *s; elementary_stream_t *es; @@ -116,6 +149,7 @@ api_service_streams /* Build response */ pthread_mutex_lock(&s->s_stream_mutex); st = htsmsg_create_list(); + stf = htsmsg_create_list(); if (s->s_pcr_pid) { e = htsmsg_create_map(); htsmsg_add_u32(e, "pid", s->s_pcr_pid); @@ -128,39 +162,17 @@ api_service_streams htsmsg_add_str(e, "type", "PMT"); htsmsg_add_msg(st, NULL, e); } - TAILQ_FOREACH(es, &s->s_components, es_link) { - htsmsg_t *e = htsmsg_create_map(); - htsmsg_add_u32(e, "index", es->es_index); - htsmsg_add_u32(e, "pid", es->es_pid); - htsmsg_add_str(e, "type", streaming_component_type2txt(es->es_type)); - htsmsg_add_str(e, "language", es->es_lang); - if (SCT_ISSUBTITLE(es->es_type)) { - htsmsg_add_u32(e, "composition_id", es->es_composition_id); - htsmsg_add_u32(e, "ancillary_id", es->es_ancillary_id); - } else if (SCT_ISAUDIO(es->es_type)) { - htsmsg_add_u32(e, "audio_type", es->es_audio_type); - } else if (SCT_ISVIDEO(es->es_type)) { - htsmsg_add_u32(e, "width", es->es_width); - htsmsg_add_u32(e, "height", es->es_height); - htsmsg_add_u32(e, "duration", es->es_frame_duration); - htsmsg_add_u32(e, "aspect_num", es->es_aspect_num); - htsmsg_add_u32(e, "aspect_den", es->es_aspect_den); - } else if (es->es_type == SCT_CA) { - caid_t *ca; - htsmsg_t *e2, *l2 = htsmsg_create_list(); - LIST_FOREACH(ca, &es->es_caids, link) { - e2 = htsmsg_create_map(); - htsmsg_add_u32(e2, "caid", ca->caid); - htsmsg_add_u32(e2, "provider", ca->providerid); - htsmsg_add_msg(l2, NULL, e2); - } - htsmsg_add_msg(e, "caids", l2); - } - htsmsg_add_msg(st, NULL, e); - } + TAILQ_FOREACH(es, &s->s_components, es_link) + htsmsg_add_msg(st, NULL, api_service_streams_get_one(es)); + if (TAILQ_FIRST(&s->s_filt_components) == NULL || + s->s_status == SERVICE_IDLE) + service_build_filter(s); + TAILQ_FOREACH(es, &s->s_filt_components, es_filt_link) + htsmsg_add_msg(stf, NULL, api_service_streams_get_one(es)); *resp = htsmsg_create_map(); htsmsg_add_str(*resp, "name", s->s_nicename); htsmsg_add_msg(*resp, "streams", st); + htsmsg_add_msg(*resp, "fstreams", stf); pthread_mutex_unlock(&s->s_stream_mutex); /* Done */ diff --git a/src/esfilter.c b/src/esfilter.c new file mode 100644 index 00000000..d5de70a6 --- /dev/null +++ b/src/esfilter.c @@ -0,0 +1,1023 @@ +/* + * tvheadend, Elementary Stream Filter + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "settings.h" +#include "lang_codes.h" +#include "service.h" +#include "esfilter.h" + +struct esfilter_entry_queue esfilters[ESF_CLASS_LAST + 1]; + +static void esfilter_class_save(idnode_t *self); + +/* + * Class masks + */ +uint32_t esfilterclsmask[ESF_CLASS_LAST+1] = { + 0, + ESF_MASK_VIDEO, + ESF_MASK_AUDIO, + ESF_MASK_TELETEXT, + ESF_MASK_SUBTIT, + ESF_MASK_CA, + ESF_MASK_OTHER +}; + +static const idclass_t *esfilter_classes[ESF_CLASS_LAST+1] = { + NULL, + &esfilter_class_video, + &esfilter_class_audio, + &esfilter_class_teletext, + &esfilter_class_subtit, + &esfilter_class_ca, + &esfilter_class_other +}; + +/* + * Class types + */ + +static struct strtab esfilterclasstab[] = { + { "NONE", ESF_CLASS_NONE }, + { "VIDEO", ESF_CLASS_VIDEO }, + { "AUDIO", ESF_CLASS_AUDIO }, + { "TELETEXT", ESF_CLASS_TELETEXT }, + { "SUBTIT", ESF_CLASS_SUBTIT }, + { "CA", ESF_CLASS_CA }, + { "OTHER", ESF_CLASS_OTHER }, +}; + +const char * +esfilter_class2txt(int cls) +{ + return val2str(cls, esfilterclasstab) ?: "INVALID"; +} + +#if 0 +static int +esfilter_txt2class(const char *s) +{ + return s ? str2val(s, esfilterclasstab) : ESF_CLASS_NONE; +} +#endif + +/* + * Action types + */ + +static struct strtab esfilteractiontab[] = { + { "NONE", ESFA_NONE }, + { "USE", ESFA_USE }, + { "ONCE", ESFA_ONCE }, + { "EXCLUSIVE", ESFA_EXCLUSIVE }, + { "EMPTY", ESFA_EMPTY }, + { "IGNORE", ESFA_IGNORE } +}; + +const char * +esfilter_action2txt(esfilter_action_t a) +{ + return val2str(a, esfilteractiontab) ?: "INVALID"; +} + +#if 0 +static esfilter_action_t +esfilter_txt2action(const char *s) +{ + return s ? str2val(s, esfilteractiontab) : ESFA_NONE; +} +#endif + +/* + * Create / delete + */ + +static void +esfilter_reindex(esfilter_class_t cls) +{ + esfilter_t *esf; + int i = 1; + + TAILQ_FOREACH(esf, &esfilters[cls], esf_link) + esf->esf_save = 0; + TAILQ_FOREACH(esf, &esfilters[cls], esf_link) { + if (esf->esf_index != i) { + esf->esf_index = i; + esf->esf_save = 1; + } + i++; + } + TAILQ_FOREACH(esf, &esfilters[cls], esf_link) + if (esf->esf_save) { + esf->esf_save = 0; + esfilter_class_save((idnode_t *)esf); + } +} + +static int +esfilter_cmp(esfilter_t *a, esfilter_t *b) +{ + return a->esf_index - b->esf_index; +} + +esfilter_t * +esfilter_create + (esfilter_class_t cls, const char *uuid, htsmsg_t *conf, int save) +{ + esfilter_t *esf = calloc(1, sizeof(*esf)); + const idclass_t *c = NULL; + uint32_t ct; + + esf->esf_caid = -1; + esf->esf_caprovider = -1; + if (ESF_CLASS_IS_VALID(cls)) { + c = esfilter_classes[cls]; + } else { + if (!htsmsg_get_u32(conf, "class", &ct)) { + cls = ct; + if (ESF_CLASS_IS_VALID(cls)) + c = esfilter_classes[cls]; + } + } + if (!c) { + tvherror("esfilter", "wrong class %d!", cls); + abort(); + } + lock_assert(&global_lock); + idnode_insert(&esf->esf_id, uuid, c); + if (conf) + idnode_load(&esf->esf_id, conf); + if (ESF_CLASS_IS_VALID(cls)) + esf->esf_class = cls; + else if (!ESF_CLASS_IS_VALID(esf->esf_class)) { + tvherror("esfilter", "wrong class %d!", esf->esf_class); + abort(); + } + if (esf->esf_index) { + TAILQ_INSERT_SORTED(&esfilters[esf->esf_class], esf, esf_link, esfilter_cmp); + } else { + TAILQ_INSERT_TAIL(&esfilters[esf->esf_class], esf, esf_link); + esfilter_reindex(esf->esf_class); + } + if (save) + esfilter_class_save((idnode_t *)esf); + return esf; +} + +static void +esfilter_delete(esfilter_t *esf, int delconf) +{ + if (delconf) + hts_settings_remove("esfilter/%s", idnode_uuid_as_str(&esf->esf_id)); + TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link); + idnode_unlink(&esf->esf_id); + free(esf->esf_comment); + free(esf); +} + +/* + * Class functions + */ + +static void +esfilter_class_save(idnode_t *self) +{ + htsmsg_t *c = htsmsg_create_map(); + idnode_save(self, c); + hts_settings_save(c, "esfilter/%s", idnode_uuid_as_str(self)); + htsmsg_destroy(c); +} + +static const char * +esfilter_class_get_title(idnode_t *self) +{ + esfilter_t *esf = (esfilter_t *)self; + return idnode_uuid_as_str(&esf->esf_id); +} + +static void +esfilter_class_delete(idnode_t *self) +{ + esfilter_t *esf = (esfilter_t *)self; + esfilter_delete(esf, 1); +} + +static void +esfilter_class_moveup(idnode_t *self) +{ + esfilter_t *esf = (esfilter_t *)self; + esfilter_t *prev = TAILQ_PREV(esf, esfilter_entry_queue, esf_link); + if (prev) { + TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link); + TAILQ_INSERT_BEFORE(prev, esf, esf_link); + esfilter_reindex(esf->esf_class); + } +} + +static void +esfilter_class_movedown(idnode_t *self) +{ + esfilter_t *esf = (esfilter_t *)self; + esfilter_t *next = TAILQ_NEXT(esf, esf_link); + if (next) { + TAILQ_REMOVE(&esfilters[esf->esf_class], esf, esf_link); + TAILQ_INSERT_AFTER(&esfilters[esf->esf_class], next, esf, esf_link); + esfilter_reindex(esf->esf_class); + } +} + +static const void * +esfilter_class_type_get(void *o) +{ + esfilter_t *esf = o; + htsmsg_t *l = htsmsg_create_list(); + int i; + + for (i = SCT_UNKNOWN; i <= SCT_LAST; i++) + if ((esf->esf_type & SCT_MASK(i)) != 0) + htsmsg_add_u32(l, NULL, i); + return l; +} + +static char * +esfilter_class_type_rend (void *o) +{ + char *str; + htsmsg_t *l = htsmsg_create_list(); + esfilter_t *esf = o; + int i; + + for (i = SCT_UNKNOWN; i <= SCT_LAST; i++) { + if (SCT_MASK(i) & esf->esf_type) + htsmsg_add_str(l, NULL, streaming_component_type2txt(i)); + } + + str = htsmsg_list_2_csv(l); + htsmsg_destroy(l); + return str; +} + +static int +esfilter_class_type_set_(void *o, const void *v, esfilter_class_t cls) +{ + esfilter_t *esf = o; + htsmsg_t *types = (htsmsg_t*)v; + htsmsg_field_t *f; + uint32_t mask = 0, u32; + uint32_t vmask = esfilterclsmask[cls]; + int save; + + HTSMSG_FOREACH(f, types) { + if (!htsmsg_field_get_u32(f, &u32)) { + if (SCT_MASK(u32) & vmask) + mask |= SCT_MASK(u32); + } else { + return 0; + } + } + save = esf->esf_type != mask; + esf->esf_type = mask; + return save; +} + +static htsmsg_t * +esfilter_class_type_enum_(void *o, esfilter_class_t cls) +{ + uint32_t mask = esfilterclsmask[cls]; + htsmsg_t *l = htsmsg_create_list(); + int i; + + for (i = SCT_UNKNOWN; i <= SCT_LAST; i++) { + if (mask & SCT_MASK(i)) { + htsmsg_t *e = htsmsg_create_map(); + htsmsg_add_u32(e, "key", i); + htsmsg_add_str(e, "val", + i == SCT_UNKNOWN ? "ANY" : streaming_component_type2txt(i)); + htsmsg_add_msg(l, NULL, e); + } + } + return l; +} + +#define ESFILTER_CLS(func, type) \ +static int esfilter_class_type_set_##func(void *o, const void *v) \ + { return esfilter_class_type_set_(o, v, type); } \ +static htsmsg_t * esfilter_class_type_enum_##func(void *o) \ + { return esfilter_class_type_enum_(o, type); } + +ESFILTER_CLS(video, ESF_CLASS_VIDEO); +ESFILTER_CLS(audio, ESF_CLASS_AUDIO); +ESFILTER_CLS(teletext, ESF_CLASS_TELETEXT); +ESFILTER_CLS(subtit, ESF_CLASS_SUBTIT); +ESFILTER_CLS(ca, ESF_CLASS_CA); +ESFILTER_CLS(other, ESF_CLASS_OTHER); + +static const void * +esfilter_class_language_get(void *o) +{ + static __thread char *ret; + esfilter_t *esf = o; + ret = esf->esf_language; + return &ret; +} + +static int +esfilter_class_language_set(void *o, const void *v) +{ + esfilter_t *esf = o; + const char *s = v; + char n[4]; + int save; + strncpy(n, s && s[0] ? lang_code_get(s) : "", 4); + n[3] = 0; + save = strcmp(esf->esf_language, n); + strcpy(esf->esf_language, n); + return save; +} + +static htsmsg_t * +esfilter_class_language_enum(void *o) +{ + htsmsg_t *l = htsmsg_create_list(); + const lang_code_t *lc = lang_codes; + char buf[128]; + + while (lc->code2b) { + htsmsg_t *e = htsmsg_create_map(); + if (!strcmp(lc->code2b, "und")) { + htsmsg_add_str(e, "key", ""); + htsmsg_add_str(e, "val", "ANY"); + } else { + htsmsg_add_str(e, "key", lc->code2b); + snprintf(buf, sizeof(buf), "%s (%s)", lc->desc, lc->code2b); + buf[sizeof(buf)-1] = '\0'; + htsmsg_add_str(e, "val", buf); + } + htsmsg_add_msg(l, NULL, e); + lc++; + } + return l; +} + +static const void * +esfilter_class_service_get(void *o) +{ + static __thread char *ret; + esfilter_t *esf = o; + ret = esf->esf_service; + return &ret; +} + +static int +esfilter_class_service_set(void *o, const void *v) +{ + esfilter_t *esf = o; + const char *s = v; + int save = 0; + if (strncmp(esf->esf_service, s, UUID_HEX_SIZE)) { + strncpy(esf->esf_service, s, UUID_HEX_SIZE); + esf->esf_service[UUID_HEX_SIZE-1] = '\0'; + save = 1; + } + return save; +} + +static htsmsg_t * +esfilter_class_service_enum(void *o) +{ + htsmsg_t *e, *m = htsmsg_create_map(); + htsmsg_add_str(m, "type", "api"); + htsmsg_add_str(m, "uri", "service/list"); + htsmsg_add_str(m, "event", "service"); + e = htsmsg_create_map(); + htsmsg_add_bool(e, "enum", 1); + htsmsg_add_msg(m, "params", e); + return m; +} + +#define MAX_ITEMS 256 + +static int +esfilter_build_ca_cmp(const void *_a, const void *_b) +{ + uint32_t a = *(uint32_t *)_a; + uint32_t b = *(uint32_t *)_b; + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +static htsmsg_t * +esfilter_build_ca_enum(int provider) +{ + htsmsg_t *e, *l; + uint32_t *a = alloca(sizeof(uint32_t) * MAX_ITEMS); + char buf[16], buf2[128]; + service_t *s; + elementary_stream_t *es; + caid_t *ca; + uint32_t v; + int i, count = 0; + + lock_assert(&global_lock); + TAILQ_FOREACH(s, &service_all, s_all_link) { + pthread_mutex_lock(&s->s_stream_mutex); + TAILQ_FOREACH(es, &s->s_components, es_link) { + LIST_FOREACH(ca, &es->es_caids, link) { + v = provider ? ca->providerid : ca->caid; + for (i = 0; i < count; i++) + if (a[i] == v) + break; + if (i >= count) + a[count++] = v; + } + } + pthread_mutex_unlock(&s->s_stream_mutex); + } + qsort(a, count, sizeof(uint32_t), esfilter_build_ca_cmp); + + l = htsmsg_create_list(); + + e = htsmsg_create_map(); + htsmsg_add_str(e, "key", provider ? "ffffff" : "ffff"); + htsmsg_add_str(e, "val", "ANY"); + htsmsg_add_msg(l, NULL, e); + + for (i = 0; i < count; i++) { + e = htsmsg_create_map(); + snprintf(buf, sizeof(buf), provider ? "%06x" : "%04x", a[i]); + if (!provider) + snprintf(buf2, sizeof(buf2), provider ? "%06x %s" : "%04x - %s", + a[i], descrambler_caid2name(a[i])); + htsmsg_add_str(e, "key", buf); + htsmsg_add_str(e, "val", provider ? buf : buf2); + htsmsg_add_msg(l, NULL, e); + } + return l; + +} + +static const void * +esfilter_class_caid_get(void *o) +{ + static __thread char *ret; + static __thread char buf[16]; + esfilter_t *esf = o; + snprintf(buf, sizeof(buf), "%04x", esf->esf_caid); + ret = buf; + return &ret; +} + +static int +esfilter_class_caid_set(void *o, const void *v) +{ + esfilter_t *esf = o; + uint16_t u; + int save = 0; + u = strtol(v, NULL, 16); + if (u != esf->esf_caid) { + esf->esf_caid = u; + save = 1; + } + return save; +} + +static htsmsg_t * +esfilter_class_caid_enum(void *o) +{ + return esfilter_build_ca_enum(0); +} + +static const void * +esfilter_class_caprovider_get(void *o) +{ + static __thread char *ret; + static __thread char buf[16]; + esfilter_t *esf = o; + if (esf->esf_caprovider == -1) + strcpy(buf, "ffffff"); + else + snprintf(buf, sizeof(buf), "%06x", esf->esf_caprovider); + ret = buf; + return &ret; +} + +static int +esfilter_class_caprovider_set(void *o, const void *v) +{ + esfilter_t *esf = o; + uint32_t u; + int save = 0; + if (strcmp(v, "ffffff") == 0) + u = -1; + else + u = strtol(v, NULL, 16); + if (u != esf->esf_caprovider) { + esf->esf_caprovider = u; + save = 1; + } + return save; +} + +static htsmsg_t * +esfilter_class_caprovider_enum(void *o) +{ + return esfilter_build_ca_enum(1); +} + +static const void * +esfilter_class_action_get(void *o) +{ + esfilter_t *esf = o; + return &esf->esf_action; +} + +static int +esfilter_class_action_set(void *o, const void *v) +{ + esfilter_t *esf = o; + int n = *(int *)v; + int save = 0; + if (n >= ESFA_USE && n <= ESFA_LAST) { + save = esf->esf_action != n; + esf->esf_action = n; + } + return save; +} + +static htsmsg_t * +esfilter_class_action_enum(void *o) +{ + htsmsg_t *l = htsmsg_create_list(); + int i; + + for (i = ESFA_NONE; i <= ESFA_LAST; i++) { + htsmsg_t *e = htsmsg_create_map(); + htsmsg_add_u32(e, "key", i); + htsmsg_add_str(e, "val", esfilter_action2txt(i)); + htsmsg_add_msg(l, NULL, e); + } + return l; +} + +const idclass_t esfilter_class = { + .ic_class = "esfilter", + .ic_caption = "Elementary Stream Filter", + .ic_save = esfilter_class_save, + .ic_get_title = esfilter_class_get_title, + .ic_delete = esfilter_class_delete, + .ic_moveup = esfilter_class_moveup, + .ic_movedown = esfilter_class_movedown, + .ic_properties = (const property_t[]){ + { + .type = PT_INT, + .id = "class", + .name = "Class", + .opts = PO_RDONLY | PO_HIDDEN, + .off = offsetof(esfilter_t, esf_class), + }, + { + .type = PT_INT, + .id = "index", + .name = "Index", + .opts = PO_RDONLY | PO_HIDDEN, + .off = offsetof(esfilter_t, esf_index), + }, + { + .type = PT_BOOL, + .id = "enabled", + .name = "Enabled", + .off = offsetof(esfilter_t, esf_enabled), + }, + {} + } +}; + +const idclass_t esfilter_class_video = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_video", + .ic_caption = "Video Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_video, + .list = esfilter_class_type_enum_video, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "language", + .name = "Language", + .get = esfilter_class_language_get, + .set = esfilter_class_language_set, + .list = esfilter_class_language_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +const idclass_t esfilter_class_audio = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_audio", + .ic_caption = "Audio Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_audio, + .list = esfilter_class_type_enum_audio, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "language", + .name = "Language", + .get = esfilter_class_language_get, + .set = esfilter_class_language_set, + .list = esfilter_class_language_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +const idclass_t esfilter_class_teletext = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_teletext", + .ic_caption = "Teletext Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_teletext, + .list = esfilter_class_type_enum_teletext, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "language", + .name = "Language", + .get = esfilter_class_language_get, + .set = esfilter_class_language_set, + .list = esfilter_class_language_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +const idclass_t esfilter_class_subtit = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_subtit", + .ic_caption = "Subtitle Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_subtit, + .list = esfilter_class_type_enum_subtit, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "language", + .name = "Language", + .get = esfilter_class_language_get, + .set = esfilter_class_language_set, + .list = esfilter_class_language_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +const idclass_t esfilter_class_ca = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_ca", + .ic_caption = "CA Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_ca, + .list = esfilter_class_type_enum_ca, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "CAid", + .name = "CA Identification", + .get = esfilter_class_caid_get, + .set = esfilter_class_caid_set, + .list = esfilter_class_caid_enum, + }, + { + .type = PT_STR, + .id = "CAprovider", + .name = "CA Provider", + .get = esfilter_class_caprovider_get, + .set = esfilter_class_caprovider_set, + .list = esfilter_class_caprovider_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +const idclass_t esfilter_class_other = { + .ic_super = &esfilter_class, + .ic_class = "esfilter_other", + .ic_caption = "Other Stream Filter", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .islist = 1, + .id = "type", + .name = "Stream Type", + .get = esfilter_class_type_get, + .set = esfilter_class_type_set_other, + .list = esfilter_class_type_enum_other, + .rend = esfilter_class_type_rend, + }, + { + .type = PT_STR, + .id = "language", + .name = "Language", + .get = esfilter_class_language_get, + .set = esfilter_class_language_set, + .list = esfilter_class_language_enum, + }, + { + .type = PT_STR, + .id = "service", + .name = "Service", + .get = esfilter_class_service_get, + .set = esfilter_class_service_set, + .list = esfilter_class_service_enum, + }, + { + .type = PT_INT, + .id = "pid", + .name = "PID", + .off = offsetof(esfilter_t, esf_pid), + }, + { + .type = PT_INT, + .id = "action", + .name = "Action", + .get = esfilter_class_action_get, + .set = esfilter_class_action_set, + .list = esfilter_class_action_enum, + }, + { + .type = PT_BOOL, + .id = "log", + .name = "Log", + .off = offsetof(esfilter_t, esf_log), + }, + { + .type = PT_STR, + .id = "comment", + .name = "Comment", + .off = offsetof(esfilter_t, esf_comment), + }, + {} + } +}; + +/** + * Initialize + */ +void +esfilter_init(void) +{ + htsmsg_t *c, *e; + htsmsg_field_t *f; + int i; + + for (i = 0; i <= ESF_CLASS_LAST; i++) + TAILQ_INIT(&esfilters[i]); + + if (!(c = hts_settings_load_r(1, "esfilter"))) + return; + HTSMSG_FOREACH(f, c) { + if (!(e = htsmsg_field_get_map(f))) + continue; + esfilter_create(-1, f->hmf_name, e, 0); + } + htsmsg_destroy(c); +} + +void +esfilter_done(void) +{ + esfilter_t *esf; + int i; + + pthread_mutex_lock(&global_lock); + for (i = 0; i <= ESF_CLASS_LAST; i++) { + while ((esf = TAILQ_FIRST(&esfilters[i])) != NULL) + esfilter_delete(esf, 0); + } + pthread_mutex_unlock(&global_lock); +} diff --git a/src/esfilter.h b/src/esfilter.h new file mode 100644 index 00000000..e65ae9c6 --- /dev/null +++ b/src/esfilter.h @@ -0,0 +1,110 @@ +/* + * tvheadend, Elementary Stream Filter + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_ESFILTER_H__ +#define __TVH_ESFILTER_H__ + +#include "tvheadend.h" +#include "idnode.h" + +typedef enum { + ESF_CLASS_NONE = 0, + ESF_CLASS_VIDEO, + ESF_CLASS_AUDIO, + ESF_CLASS_TELETEXT, + ESF_CLASS_SUBTIT, + ESF_CLASS_CA, + ESF_CLASS_OTHER, + ESF_CLASS_LAST = ESF_CLASS_OTHER +} esfilter_class_t; + +#define ESF_CLASS_IS_VALID(i) \ + ((i) >= ESF_CLASS_VIDEO && (i) <= ESF_CLASS_LAST) + +extern const idclass_t esfilter_class; +extern const idclass_t esfilter_class_video; +extern const idclass_t esfilter_class_audio; +extern const idclass_t esfilter_class_teletext; +extern const idclass_t esfilter_class_subtit; +extern const idclass_t esfilter_class_ca; +extern const idclass_t esfilter_class_other; + +#define ESF_MASK_VIDEO \ + (SCT_MASK(SCT_MPEG2VIDEO) | SCT_MASK(SCT_H264) | SCT_MASK(SCT_VP8)) + +#define ESF_MASK_AUDIO \ + (SCT_MASK(SCT_MPEG2AUDIO) | SCT_MASK(SCT_AC3) | SCT_MASK(SCT_AAC) | \ + SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS)) + +#define ESF_MASK_TELETEXT \ + SCT_MASK(SCT_TELETEXT) + +#define ESF_MASK_SUBTIT \ + (SCT_MASK(SCT_DVBSUB) | SCT_MASK(SCT_TEXTSUB)) + +#define ESF_MASK_CA \ + SCT_MASK(SCT_CA) + +#define ESF_MASK_OTHER \ + SCT_MASK(SCT_MPEGTS) + +extern uint32_t esfilterclsmask[]; + +TAILQ_HEAD(esfilter_entry_queue, esfilter); + +extern struct esfilter_entry_queue esfilters[]; + +typedef enum { + ESFA_NONE = 0, + ESFA_USE, /* use this stream */ + ESFA_ONCE, /* use this stream once per language */ + ESFA_EXCLUSIVE, /* use this stream exclusively */ + ESFA_EMPTY, /* use this stream when no streams were added */ + ESFA_IGNORE, + ESFA_LAST = ESFA_IGNORE +} esfilter_action_t; + +typedef struct esfilter { + idnode_t esf_id; + TAILQ_ENTRY(esfilter) esf_link; + + int esf_class; + int esf_save; + int esf_index; + int esf_enabled; + uint32_t esf_type; + char esf_language[4]; + char esf_service[UUID_HEX_SIZE]; + int esf_pid; + uint16_t esf_caid; + uint32_t esf_caprovider; + int esf_action; + int esf_log; + char *esf_comment; +} esfilter_t; + +esfilter_t *esfilter_create + (esfilter_class_t esf_class, const char *uuid, htsmsg_t *conf, int save); + +const char * esfilter_class2txt(int cls); +const char * esfilter_action2txt(esfilter_action_t a); + +void esfilter_init(void); +void esfilter_done(void); + +#endif /* __TVH_ESFILTER_H__ */ diff --git a/src/http.c b/src/http.c index ddf70213..5932d8a9 100644 --- a/src/http.c +++ b/src/http.c @@ -62,6 +62,31 @@ static struct strtab HTTP_versiontab[] = { static void http_parse_get_args(http_connection_t *hc, char *args); +/** + * + */ +const char * +http_cmd2str(int val) +{ + return val2str(val, HTTP_cmdtab); +} + +int http_str2cmd(const char *str) +{ + return str2val(str, HTTP_cmdtab); +} + +const char * +http_ver2str(int val) +{ + return val2str(val, HTTP_versiontab); +} + +int http_str2ver(const char *str) +{ + return str2val(str, HTTP_versiontab); +} + /** * */ @@ -547,7 +572,6 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill) - /* * Delete all arguments associated with a connection */ @@ -582,7 +606,7 @@ http_arg_get(struct http_arg_list *list, const char *name) * Set an argument associated with a connection */ void -http_arg_set(struct http_arg_list *list, char *key, char *val) +http_arg_set(struct http_arg_list *list, const char *key, const char *val) { http_arg_t *ra; @@ -814,8 +838,8 @@ http_serve(int fd, void **opaque, struct sockaddr_storage *peer, memset(&hc, 0, sizeof(http_connection_t)); *opaque = &hc; - TAILQ_INIT(&hc.hc_args); - TAILQ_INIT(&hc.hc_req_args); + http_arg_init(&hc.hc_args); + http_arg_init(&hc.hc_req_args); hc.hc_fd = fd; hc.hc_peer = peer; diff --git a/src/http.h b/src/http.h index 120dda8f..5ba944bf 100644 --- a/src/http.h +++ b/src/http.h @@ -21,8 +21,9 @@ #include "htsbuf.h" #include "url.h" +#include "tvhpoll.h" -TAILQ_HEAD(http_arg_list, http_arg); +typedef TAILQ_HEAD(http_arg_list, http_arg) http_arg_list_t; typedef RB_HEAD(,http_arg) http_arg_tree_t; @@ -33,13 +34,77 @@ typedef struct http_arg { char *val; } http_arg_t; -#define HTTP_STATUS_OK 200 +#define HTTP_STATUS_CONTINUE 100 +#define HTTP_STATUS_PSWITCH 101 +#define HTTP_STATUS_OK 200 +#define HTTP_STATUS_CREATED 201 +#define HTTP_STATUS_ACCEPTED 202 +#define HTTP_STATUS_NON_AUTH_INFO 203 +#define HTTP_STATUS_NO_CONTENT 204 +#define HTTP_STATUS_RESET_CONTENT 205 #define HTTP_STATUS_PARTIAL_CONTENT 206 -#define HTTP_STATUS_FOUND 302 -#define HTTP_STATUS_BAD_REQUEST 400 -#define HTTP_STATUS_UNAUTHORIZED 401 -#define HTTP_STATUS_NOT_FOUND 404 +#define HTTP_STATUS_MULTIPLE 300 +#define HTTP_STATUS_MOVED 301 +#define HTTP_STATUS_FOUND 302 +#define HTTP_STATUS_SEE_OTHER 303 +#define HTTP_STATUS_NOT_MODIFIED 304 +#define HTTP_STATUS_USE_PROXY 305 +#define HTTP_STATUS_TMP_REDIR 307 +#define HTTP_STATUS_BAD_REQUEST 400 +#define HTTP_STATUS_UNAUTHORIZED 401 +#define HTTP_STATUS_PAYMENT 402 +#define HTTP_STATUS_FORBIDDEN 403 +#define HTTP_STATUS_NOT_FOUND 404 +#define HTTP_STATUS_NOT_ALLOWED 405 +#define HTTP_STATUS_NOT_ACCEPTABLE 406 +#define HTTP_STATUS_PROXY_AUTH 407 +#define HTTP_STATUS_TIMEOUT 408 +#define HTTP_STATUS_CONFLICT 409 +#define HTTP_STATUS_GONE 410 +#define HTTP_STATUS_LENGTH 411 +#define HTTP_STATUS_PRECONDITION 412 +#define HTTP_STATUS_ENTITY_OVER 413 +#define HTTP_STATUS_URI_TOO_LONG 414 +#define HTTP_STATUS_UNSUPPORTED 415 +#define HTTP_STATUS_BAD_RANGE 417 +#define HTTP_STATUS_EXPECTATION 418 +#define HTTP_STATUS_INTERNAL 500 +#define HTTP_STATUS_NOT_IMPLEMENTED 501 +#define HTTP_STATUS_BAD_GATEWAY 502 +#define HTTP_STATUS_SERVICE 503 +#define HTTP_STATUS_GATEWAY_TIMEOUT 504 +#define HTTP_STATUS_HTTP_VERSION 505 +typedef enum http_state { + HTTP_CON_WAIT_REQUEST, + HTTP_CON_READ_HEADER, + HTTP_CON_END, + HTTP_CON_POST_DATA, + HTTP_CON_SENDING, + HTTP_CON_SENT, + HTTP_CON_RECEIVING, + HTTP_CON_DONE, + HTTP_CON_IDLE, + HTTP_CON_OK +} http_state_t; + +typedef enum http_cmd { + HTTP_CMD_GET, + HTTP_CMD_HEAD, + HTTP_CMD_POST, + RTSP_CMD_DESCRIBE, + RTSP_CMD_OPTIONS, + RTSP_CMD_SETUP, + RTSP_CMD_TEARDOWN, + RTSP_CMD_PLAY, + RTSP_CMD_PAUSE, +} http_cmd_t; + +typedef enum http_ver { + HTTP_VERSION_1_0, + HTTP_VERSION_1_1, + RTSP_VERSION_1_0, +} http_ver_t; typedef struct http_connection { int hc_fd; @@ -51,36 +116,15 @@ typedef struct http_connection { char *hc_url_orig; int hc_keep_alive; - htsbuf_queue_t hc_reply; + htsbuf_queue_t hc_reply; - struct http_arg_list hc_args; + http_arg_list_t hc_args; - struct http_arg_list hc_req_args; /* Argumets from GET or POST request */ + http_arg_list_t hc_req_args; /* Argumets from GET or POST request */ - enum { - HTTP_CON_WAIT_REQUEST, - HTTP_CON_READ_HEADER, - HTTP_CON_END, - HTTP_CON_POST_DATA, - } hc_state; - - enum { - HTTP_CMD_GET, - HTTP_CMD_HEAD, - HTTP_CMD_POST, - RTSP_CMD_DESCRIBE, - RTSP_CMD_OPTIONS, - RTSP_CMD_SETUP, - RTSP_CMD_TEARDOWN, - RTSP_CMD_PLAY, - RTSP_CMD_PAUSE, - } hc_cmd; - - enum { - HTTP_VERSION_1_0, - HTTP_VERSION_1_1, - RTSP_VERSION_1_0, - } hc_version; + http_state_t hc_state; + http_cmd_t hc_cmd; + http_ver_t hc_version; char *hc_username; char *hc_password; @@ -99,11 +143,21 @@ typedef struct http_connection { } http_connection_t; +const char *http_cmd2str(int val); +int http_str2cmd(const char *str); +const char *http_ver2str(int val); +int http_str2ver(const char *str); + +static inline void http_arg_init(struct http_arg_list *list) +{ + TAILQ_INIT(list); +} + void http_arg_flush(struct http_arg_list *list); char *http_arg_get(struct http_arg_list *list, const char *name); -void http_arg_set(struct http_arg_list *list, char *key, char *val); +void http_arg_set(struct http_arg_list *list, const char *key, const char *val); int http_tokenize(char *buf, char **vec, int vecsize, int delimiter); @@ -142,20 +196,144 @@ int http_access_verify(http_connection_t *hc, int mask); void http_deescape(char *s); +/* + * HTTP/RTSP Client + */ + typedef struct http_client http_client_t; -typedef void (http_client_conn_cb) (void *p); -typedef size_t (http_client_data_cb) (void *p, void *buf, size_t len); -typedef void (http_client_fail_cb) (void *p); +typedef struct http_client_wcmd { + + TAILQ_ENTRY(http_client_wcmd) link; + + enum http_cmd wcmd; + int wcseq; + + void *wbuf; + size_t wpos; + size_t wsize; +} http_client_wcmd_t; + +struct http_client { + + TAILQ_ENTRY(http_client) hc_link; + + int hc_fd; + char *hc_scheme; + char *hc_host; + int hc_port; + tvhpoll_t *hc_efd; + int hc_pevents; + + int hc_code; + http_ver_t hc_version; + http_cmd_t hc_cmd; + + struct http_arg_list hc_args; /* header */ + + void *hc_aux; + size_t hc_data_limit; + size_t hc_io_size; + char *hc_data; /* data body */ + size_t hc_data_size; /* data body size - result for caller */ + + time_t hc_ping_time; /* last issued command */ + + char *hc_rbuf; /* read buffer */ + size_t hc_rsize; /* read buffer size */ + size_t hc_rpos; /* read buffer position */ + size_t hc_hsize; /* header size in bytes */ + size_t hc_csize; /* contents size in bytes */ + char *hc_chunk; + size_t hc_chunk_size; + size_t hc_chunk_csize; + size_t hc_chunk_alloc; + size_t hc_chunk_pos; + char *hc_location; + int hc_redirects; + int hc_result; + int hc_shutdown:1; + int hc_sending:1; + int hc_reconnected:1; + int hc_keepalive:1; + int hc_in_data:1; + int hc_chunked:1; + int hc_chunk_trails:1; + int hc_handle_location:1; /* handle the redirection (location) requests */ + + http_client_wcmd_t *hc_wcmd; + TAILQ_HEAD(,http_client_wcmd) hc_wqueue; + + int hc_verify_peer; /* SSL - verify peer */ + + int hc_cseq; /* RTSP */ + int hc_rcseq; /* RTSP - expected cseq */ + char *hc_rtsp_session; + char *hc_rtp_dest; + int hc_rtp_port; + int hc_rtpc_port; + int hc_rtp_multicast:1; + long hc_rtsp_stream_id; + int hc_rtp_timeout; + + struct http_client_ssl *hc_ssl; /* ssl internals */ + + /* callbacks */ + int (*hc_hdr_received) (http_client_t *hc); + int (*hc_data_received)(http_client_t *hc, void *buf, size_t len); + int (*hc_data_complete)(http_client_t *hc); + void (*hc_conn_closed) (http_client_t *hc, int err); +}; void http_client_init ( void ); void http_client_done ( void ); + http_client_t* -http_connect ( const url_t *url, - http_client_conn_cb conn_cb, - http_client_data_cb data_cb, - http_client_fail_cb fail_cb, - void *p ); -void http_close ( http_client_t *hc ); +http_client_connect ( void *aux, http_ver_t ver, + const char *scheme, const char *host, int port ); +void http_client_register ( http_client_t *hc ); +void http_client_close ( http_client_t *hc ); + +int http_client_send( http_client_t *hc, http_cmd_t cmd, + const char *path, const char *query, + http_arg_list_t *header, void *body, size_t body_size ); +int http_client_simple( http_client_t *hc, const url_t *url); +int http_client_clear_state( http_client_t *hc ); +int http_client_run( http_client_t *hc ); +void http_client_ssl_peer_verify( http_client_t *hc, int verify ); + +/* + * RTSP helpers + */ + +int rtsp_send( http_client_t *hc, http_cmd_t cmd, const char *path, + const char *query, http_arg_list_t *hdr ); + +void rtsp_clear_session( http_client_t *hc ); + +int rtsp_options_decode( http_client_t *hc ); +static inline int rtsp_options( http_client_t *hc ) { + return rtsp_send(hc, RTSP_CMD_OPTIONS, NULL, NULL, NULL); +} + +int rtsp_setup_decode( http_client_t *hc, int satip ); +int rtsp_setup( http_client_t *hc, const char *path, const char *query, + const char *multicast_addr, int rtp_port, int rtpc_port ); + +static inline int +rtsp_play( http_client_t *hc, const char *path, const char *query ) { + return rtsp_send(hc, RTSP_CMD_PLAY, path, query, NULL); +} + +static inline int +rtsp_teardown( http_client_t *hc, const char *path, const char *query ) { + return rtsp_send(hc, RTSP_CMD_TEARDOWN, path, query, NULL); +} + +int rtsp_describe_decode( http_client_t *hc ); +static inline int +rtsp_describe( http_client_t *hc, const char *path, const char *query ) { + return rtsp_send(hc, RTSP_CMD_DESCRIBE, path, query, NULL); +} #endif /* HTTP_H_ */ diff --git a/src/http/http_client.c b/src/http/http_client.c deleted file mode 100644 index 84b64c9c..00000000 --- a/src/http/http_client.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Tvheadend - HTTP client functions - * - * Copyright (C) 2013 Adam Sutton - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "tvheadend.h" -#include "tvhpoll.h" -#include "redblack.h" -#include "queue.h" -#include "url.h" -#include "http.h" - -#if ENABLE_CURL - -#include -#include -#include -#include -#include - - -/* - * Client definition - */ -struct http_client -{ - CURL *hc_curl; - int hc_fd; - url_t hc_url; - int hc_begin; - - /* Callbacks */ - http_client_conn_cb *hc_conn; - http_client_data_cb *hc_data; - http_client_fail_cb *hc_fail; - void *hc_opaque; - - TAILQ_ENTRY(http_client) hc_link; -}; - -/* - * Global state - */ -static tvhpoll_t *http_poll; -static TAILQ_HEAD(,http_client) http_clients; -static pthread_mutex_t http_lock; -static CURLM *http_curlm; - -/* - * Disable - */ -static void -http_remove ( http_client_t *hc ) -{ - tvhpoll_event_t ev; - ev.fd = hc->hc_fd; - - /* Remove */ - curl_multi_remove_handle(http_curlm, hc->hc_curl); - tvhpoll_rem(http_poll, &ev, 1); - TAILQ_REMOVE(&http_clients, hc, hc_link); - - /* Free CURL memory */ - curl_easy_cleanup(hc->hc_curl); - hc->hc_curl = NULL; -} - -/* - * New socket - */ -static int -http_curl_socket ( CURL *c, int fd, int a, void *u, void *s ) -{ - http_client_t *hc; - tvhpoll_event_t ev = { 0 }; - ev.fd = fd; - - /* Find client */ - TAILQ_FOREACH(hc, &http_clients, hc_link) - if (hc->hc_curl == c) - break; - - /* Invalid */ - if (!hc) - goto done; - - /* Remove */ - if (a == CURL_POLL_REMOVE) { - //http_remove(hc); - - /* Set */ - } else if (a & CURL_POLL_INOUT) { - if (a & CURL_POLL_IN) - ev.events |= TVHPOLL_IN; - if (a & CURL_POLL_OUT) - ev.events |= TVHPOLL_OUT; - ev.data.fd = fd; - hc->hc_fd = fd; - tvhpoll_add(http_poll, &ev, 1); - } - - /* Done */ -done: - return 0; -} - -/* - * Data - */ -static size_t -http_curl_data ( void *buf, size_t len, size_t n, void *p ) -{ - http_client_t *hc = p; - if (!hc->hc_begin && hc->hc_conn) - hc->hc_conn(hc->hc_opaque); - hc->hc_begin = 1; - len = hc->hc_data(hc->hc_opaque, buf, len * n); - return len; -} - -/* - * Data thread - */ -static void * -http_thread ( void *p ) -{ - int n, e, run = 0; - tvhpoll_event_t ev; - http_client_t *hc; - - while (tvheadend_running) { - n = tvhpoll_wait(http_poll, &ev, 1, -1); - if (n < 0) { - if (tvheadend_running) - tvherror("http_client", "tvhpoll_wait() error"); - break; - } else { - pthread_mutex_lock(&http_lock); - TAILQ_FOREACH(hc, &http_clients, hc_link) - if (hc->hc_fd == ev.data.fd) - break; - if (hc && (ev.events & (TVHPOLL_IN | TVHPOLL_OUT))) { - e = 0; - if (ev.events & TVHPOLL_IN) e |= CURL_POLL_IN; - if (ev.events & TVHPOLL_OUT) e |= CURL_POLL_OUT; - curl_multi_socket_action(http_curlm, ev.data.fd, 0, &run); - } - pthread_mutex_unlock(&http_lock); - } - } - - return NULL; -} - -/* - * Setup a connection (async) - */ -http_client_t * -http_connect - ( const url_t *url, - http_client_conn_cb conn_cb, - http_client_data_cb data_cb, - http_client_fail_cb fail_cb, - void *p ) -{ - int run; - - /* Setup structure */ - http_client_t *hc = calloc(1, sizeof(http_client_t)); - hc->hc_curl = curl_easy_init(); - hc->hc_url = *url; - hc->hc_conn = conn_cb; - hc->hc_data = data_cb; - hc->hc_fail = fail_cb; - hc->hc_opaque = p; - - /* Store */ - pthread_mutex_lock(&http_lock); - TAILQ_INSERT_TAIL(&http_clients, hc, hc_link); - - /* Setup connection */ - curl_easy_setopt(hc->hc_curl, CURLOPT_URL, url->raw); - curl_easy_setopt(hc->hc_curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEFUNCTION, http_curl_data); - curl_easy_setopt(hc->hc_curl, CURLOPT_WRITEDATA, hc); - curl_multi_add_handle(http_curlm, hc->hc_curl); - curl_multi_socket_action(http_curlm, CURL_SOCKET_TIMEOUT, 0, &run); - pthread_mutex_unlock(&http_lock); - - return hc; -} - -/* - * Cancel - */ -void -http_close ( http_client_t *hc ) -{ - pthread_mutex_lock(&http_lock); - http_remove(hc); - free(hc); - pthread_mutex_unlock(&http_lock); -} - -/* - * Initialise subsystem - */ -pthread_t http_client_tid; - -void -http_client_init ( void ) -{ - /* Setup list */ - pthread_mutex_init(&http_lock, NULL); - TAILQ_INIT(&http_clients); - - /* Initialise curl */ - curl_global_init(CURL_GLOBAL_ALL); - http_curlm = curl_multi_init(); - curl_multi_setopt(http_curlm, CURLMOPT_SOCKETFUNCTION, http_curl_socket); - - /* Setup poll */ - http_poll = tvhpoll_create(10); - - /* Setup thread */ - tvhthread_create(&http_client_tid, NULL, http_thread, NULL, 0); -} - -void -http_client_done ( void ) -{ - pthread_kill(http_client_tid, SIGTERM); - pthread_join(http_client_tid, NULL); - tvhpoll_destroy(http_poll); - curl_multi_cleanup(http_curlm); - curl_global_cleanup(); -} - -#else /* ENABLE_CURL */ - -void -http_client_init ( void ) -{ -} - -void -http_client_done ( void ) -{ -} - -#endif /* ENABLE_CURL */ diff --git a/src/httpc.c b/src/httpc.c new file mode 100644 index 00000000..d949562e --- /dev/null +++ b/src/httpc.c @@ -0,0 +1,1715 @@ +/* + * Tvheadend - HTTP client functions + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "http.h" +#include "tcp.h" + +#include +#include +#include +#include +#include + +#include +#include + +#ifndef SSL_OP_NO_COMPRESSION +#define SSL_OP_NO_COMPRESSION 0 +#endif + +#if ENABLE_TRACE +#define HTTPCLIENT_TESTSUITE 1 +#endif + +struct http_client_ssl { + int connected; + int shutdown; + int notified; + + SSL_CTX *ctx; + SSL *ssl; + + BIO *rbio; + char *rbio_buf; + size_t rbio_size; + size_t rbio_pos; + + BIO *wbio; + char *wbio_buf; + size_t wbio_size; + size_t wbio_pos; +}; + + +static int +http_client_redirected ( http_client_t *hc ); +static int +http_client_ssl_write_update( http_client_t *hc ); +static int +http_client_reconnect + ( http_client_t *hc, http_ver_t ver, const char *scheme, + const char *host, int port ); +#if HTTPCLIENT_TESTSUITE +static void +http_client_testsuite_run( void ); +#endif + + +/* + * Global state + */ +static int http_running; +static tvhpoll_t *http_poll; +static TAILQ_HEAD(,http_client) http_clients; +static pthread_mutex_t http_lock; +static th_pipe_t http_pipe; + +/* + * + */ +static int +http_port( const char *scheme, int port ) +{ + if (port <= 0 || port > 65535) { + if (strcmp(scheme, "http") == 0) + port = 80; + else if (strcmp(scheme, "https") == 0) + port = 443; + else if (strcmp(scheme, "rtsp") == 0) + port = 554; + else { + tvhlog(LOG_ERR, "httpc", "Unknown scheme '%s'", scheme); + return -EINVAL; + } + } + return port; +} + +/* + * Disable + */ +static void +http_client_shutdown ( http_client_t *hc, int force ) +{ + struct http_client_ssl *ssl = hc->hc_ssl; + tvhpoll_t *efd = NULL; + + hc->hc_shutdown = 1; + if (ssl) { + if (!ssl->shutdown) { + SSL_shutdown(hc->hc_ssl->ssl); + http_client_ssl_write_update(hc); + ssl->shutdown = 1; + } + if (!force) + return; + } + if (hc->hc_efd) { + tvhpoll_event_t ev; + if (hc->hc_efd == http_poll) + TAILQ_REMOVE(&http_clients, hc, hc_link); + memset(&ev, 0, sizeof(ev)); + ev.fd = hc->hc_fd; + tvhpoll_rem(efd = hc->hc_efd, &ev, 1); + hc->hc_efd = NULL; + } + if (hc->hc_fd >= 0) { + if (hc->hc_conn_closed) + hc->hc_conn_closed(hc, -hc->hc_result); + if (hc->hc_fd >= 0) + close(hc->hc_fd); + hc->hc_fd = -1; + } +} + +/* + * Poll I/O + */ +static void +http_client_poll_dir ( http_client_t *hc, int in, int out ) +{ + int events = (in ? TVHPOLL_IN : 0) | (out ? TVHPOLL_OUT : 0); + if (hc->hc_efd && hc->hc_pevents != events) { + tvhpoll_event_t ev; + memset(&ev, 0, sizeof(ev)); + ev.fd = hc->hc_fd; + ev.events = events | TVHPOLL_IN; + ev.data.ptr = hc; + tvhpoll_add(hc->hc_efd, &ev, 1); + } + hc->hc_pevents = events; + /* make sure to se the correct errno for our SSL routines */ + errno = EAGAIN; +} + +static void +http_client_direction ( http_client_t *hc, int sending ) +{ + hc->hc_sending = sending; + if (hc->hc_ssl == NULL) + http_client_poll_dir(hc, 1, sending); +} + +/* + * Main I/O routines + */ + +static void +http_client_cmd_destroy( http_client_t *hc, http_client_wcmd_t *cmd ) +{ + TAILQ_REMOVE(&hc->hc_wqueue, cmd, link); + free(cmd->wbuf); + free(cmd); +} + +static int +http_client_flush( http_client_t *hc, int result ) +{ + hc->hc_result = result; + if (result < 0) + http_client_shutdown(hc, 0); + hc->hc_in_data = 0; + hc->hc_hsize = 0; + hc->hc_csize = 0; + hc->hc_rpos = 0; + hc->hc_chunked = 0; + free(hc->hc_chunk); + hc->hc_chunk = 0; + hc->hc_chunk_pos = 0; + hc->hc_chunk_size = 0; + hc->hc_chunk_csize = 0; + hc->hc_chunk_alloc = 0; + hc->hc_chunk_trails = 0; + http_arg_flush(&hc->hc_args); + return result; +} + +int +http_client_clear_state( http_client_t *hc ) +{ + if (hc->hc_shutdown) + return -EBADFD; + free(hc->hc_data); + hc->hc_data = NULL; + hc->hc_data_size = 0; + return http_client_flush(hc, 0); +} + +static int +http_client_ssl_read_update( http_client_t *hc ) +{ + struct http_client_ssl *ssl = hc->hc_ssl; + char *rbuf = alloca(hc->hc_io_size); + ssize_t r, r2; + size_t len; + + if (ssl->rbio_pos > 0) { + r = BIO_write(ssl->rbio, ssl->rbio_buf, ssl->rbio_pos); + if (r >= 0) { + memmove(ssl->rbio_buf, ssl->rbio_buf + r, ssl->rbio_pos - r); + ssl->rbio_pos -= r; + } else if (r < 0) { + errno = EIO; + return -1; + } + } + r = recv(hc->hc_fd, rbuf, hc->hc_io_size, MSG_DONTWAIT); + if (r == 0) { + errno = ESTRPIPE; + return -1; + } + if (r < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) { + http_client_poll_dir(hc, 1, 0); + errno = EAGAIN; + return r; + } + return r; + } + r2 = BIO_write(ssl->rbio, rbuf, r); + len = r - (r2 < 0 ? 0 : r2); + if (len) { + if (ssl->rbio_pos + len > ssl->rbio_size) { + ssl->rbio_buf = realloc(ssl->rbio_buf, ssl->rbio_pos + len); + ssl->rbio_size += len; + } + memcpy(ssl->rbio_buf + ssl->rbio_pos, rbuf + (len - r), len); + ssl->rbio_pos += len; + } + return 0; +} + +static int +http_client_ssl_write_update( http_client_t *hc ) +{ + struct http_client_ssl *ssl = hc->hc_ssl; + char *rbuf = alloca(hc->hc_io_size); + ssize_t r, r2; + size_t len; + + if (ssl->wbio_pos) { + r = send(hc->hc_fd, ssl->wbio_buf, ssl->wbio_pos, MSG_DONTWAIT); + if (r > 0) { + memmove(ssl->wbio_buf, ssl->wbio_buf + r, ssl->wbio_pos - r); + ssl->wbio_pos -= r; + } else if (r < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) { + http_client_poll_dir(hc, 0, 1); + errno = EAGAIN; + return r; + } + return r; + } + if (ssl->wbio_pos) + return 1; + } + r = BIO_read(ssl->wbio, rbuf, hc->hc_io_size); + if (r > 0) { + r2 = send(hc->hc_fd, rbuf, r, MSG_DONTWAIT); + len = r - (r2 < 0 ? 0 : r2); + if (len) { + if (ssl->wbio_pos + len > ssl->wbio_size) { + ssl->wbio_buf = realloc(ssl->wbio_buf, ssl->wbio_pos + len); + ssl->wbio_size += len; + } + memcpy(ssl->wbio_buf + ssl->wbio_pos, rbuf + (len - r), len); + ssl->wbio_pos += len; + } + if (r2 < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) { + http_client_poll_dir(hc, 0, 1); + errno = EAGAIN; + return r2; + } + return r2; + } + return 1; + } + return 0; +} + +static ssize_t +http_client_ssl_recv( http_client_t *hc, void *buf, size_t len ) +{ + ssize_t r; + int e; + + while (1) { + r = SSL_read(hc->hc_ssl->ssl, buf, len); + if (r > 0) + return r; + e = SSL_get_error(hc->hc_ssl->ssl, r); + if (e == SSL_ERROR_WANT_READ) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + r = http_client_ssl_read_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_WANT_WRITE) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_ZERO_RETURN) { + errno = ESTRPIPE; + return -1; + } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) { + errno = EBADFD; + return -1; + } else if (e == SSL_ERROR_SSL) { + errno = EPERM; + return -1; + } else { + errno = EIO; + return -1; + } + } + return 0; +} + +static ssize_t +http_client_ssl_send( http_client_t *hc, const void *buf, size_t len ) +{ + struct http_client_ssl *ssl = hc->hc_ssl; + ssize_t r, r2; + int e; + + if (hc->hc_verify_peer < 0) + http_client_ssl_peer_verify(hc, 1); /* default method - verify */ + while (1) { + if (!ssl->connected) { + r = SSL_connect(ssl->ssl); + if (r > 0) { + ssl->connected = 1; + if (hc->hc_verify_peer > 0) { + if (SSL_get_peer_certificate(ssl->ssl) == NULL || + SSL_get_verify_result(ssl->ssl) != X509_V_OK) { + tvhlog(LOG_ERR, "httpc", "SSL peer verification failed (%s:%i)%s %li", + hc->hc_host, hc->hc_port, + SSL_get_peer_certificate(ssl->ssl) ? " X509" : "", + SSL_get_verify_result(ssl->ssl)); + errno = EPERM; + return -1; + } + } + goto write; + } + } else { +write: + r = SSL_write(ssl->ssl, buf, len); + } + if (r > 0) { + while (1) { + r2 = http_client_ssl_write_update(hc); + if (r2 < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) + break; + return r2; + } + if (r2 == 0) + break; + } + return r; + } + e = SSL_get_error(ssl->ssl, r); + ERR_print_errors_fp(stdout); + if (e == SSL_ERROR_WANT_READ) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + r = http_client_ssl_read_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_WANT_WRITE) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) { + errno = EBADFD; + return -1; + } else if (e == SSL_ERROR_SSL) { + errno = EPERM; + return -1; + } else { + errno = EIO; + return -1; + } + } + return 0; +} + +static ssize_t +http_client_ssl_shutdown( http_client_t *hc ) +{ + ssize_t r; + int e; + + while (1) { + r = SSL_shutdown(hc->hc_ssl->ssl); + if (r > 0) { + /* everything done, bail-out completely */ + http_client_shutdown(hc, 1); + return r; + } + e = SSL_get_error(hc->hc_ssl->ssl, r); + if (e == SSL_ERROR_WANT_READ) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + r = http_client_ssl_read_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_WANT_WRITE) { + r = http_client_ssl_write_update(hc); + if (r < 0) + return r; + } else if (e == SSL_ERROR_WANT_CONNECT || e == SSL_ERROR_WANT_ACCEPT) { + errno = EBADFD; + return -1; + } else if (r == SSL_ERROR_SSL) { + errno = EPERM; + return -1; + } else { + errno = EIO; + return -1; + } + } + return 0; +} + +static int +http_client_send_partial( http_client_t *hc ) +{ + http_client_wcmd_t *wcmd; + ssize_t r; + int res = HTTP_CON_IDLE; + + wcmd = TAILQ_FIRST(&hc->hc_wqueue); + while (wcmd != NULL) { + hc->hc_cmd = wcmd->wcmd; + hc->hc_rcseq = wcmd->wcseq; + if (hc->hc_ssl) + r = http_client_ssl_send(hc, wcmd->wbuf + wcmd->wpos, + wcmd->wsize - wcmd->wpos); + else + r = send(hc->hc_fd, wcmd->wbuf + wcmd->wpos, + wcmd->wsize - wcmd->wpos, MSG_DONTWAIT); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS) { + http_client_direction(hc, 1); + return HTTP_CON_SENDING; + } + return http_client_flush(hc, -errno); + } + wcmd->wpos += r; + if (wcmd->wpos >= wcmd->wsize) { + http_client_cmd_destroy(hc, wcmd); + res = HTTP_CON_SENT; + wcmd = NULL; + } + break; + } + if (wcmd == NULL) { + http_client_direction(hc, 0); + return res; + } else { + http_client_direction(hc, 1); + return HTTP_CON_SENDING; + } +} + +int +http_client_send( http_client_t *hc, enum http_cmd cmd, + const char *path, const char *query, + http_arg_list_t *header, void *body, size_t body_size ) +{ + http_client_wcmd_t *wcmd = calloc(1, sizeof(*wcmd)); + http_arg_t *h; + htsbuf_queue_t q; + const char *s; + + if (hc->hc_shutdown) { + if (header) + http_arg_flush(header); + return -EIO; + } + + wcmd->wcmd = cmd; + hc->hc_keepalive = 1; + + htsbuf_queue_init(&q, 0); + s = http_cmd2str(cmd); + if (s == NULL) { + http_arg_flush(header); + return -EINVAL; + } + htsbuf_append(&q, s, strlen(s)); + htsbuf_append(&q, " ", 1); + if (path == NULL || path[0] == '\0') + path = "/"; + htsbuf_append(&q, path, strlen(path)); + if (query && query[0] != '\0') { + htsbuf_append(&q, "?", 1); + htsbuf_append(&q, query, strlen(query)); + } + htsbuf_append(&q, " ", 1); + s = http_ver2str(hc->hc_version); + if (s == NULL) { + htsbuf_queue_flush(&q); + http_arg_flush(header); + return -EINVAL; + } + htsbuf_append(&q, s, strlen(s)); + htsbuf_append(&q, "\r\n", 2); + + if (header) { + TAILQ_FOREACH(h, header, link) { + htsbuf_append(&q, h->key, strlen(h->key)); + htsbuf_append(&q, ": ", 2); + htsbuf_append(&q, h->val, strlen(h->val)); + htsbuf_append(&q, "\r\n", 2); + if (strcasecmp(h->key, "Connection") == 0 && + strcasecmp(h->val, "close") == 0) + hc->hc_keepalive = 0; + } + http_arg_flush(header); + } + + if (hc->hc_version == HTTP_VERSION_1_0) + hc->hc_keepalive = 0; + if (hc->hc_version == RTSP_VERSION_1_0) { + hc->hc_cseq = (hc->hc_cseq + 1) & 0x7fff; + htsbuf_qprintf(&q, "CSeq: %i\r\n", hc->hc_cseq); + wcmd->wcseq = hc->hc_cseq; + } + htsbuf_append(&q, "\r\n", 2); + if (body && body_size) + htsbuf_append(&q, body, body_size); + + body_size = q.hq_size; + body = malloc(body_size); + htsbuf_read(&q, body, body_size); + +#if ENABLE_TRACE + tvhtrace("httpc", "sending %s cmd", http_ver2str(hc->hc_version)); + tvhlog_hexdump("httpc", body, body_size); +#endif + + wcmd->wbuf = body; + wcmd->wsize = body_size; + + TAILQ_INSERT_TAIL(&hc->hc_wqueue, wcmd, link); + + hc->hc_ping_time = dispatch_clock; + + return http_client_send_partial(hc); +} + +static int +http_client_finish( http_client_t *hc ) +{ + int res; + +#if ENABLE_TRACE + if (hc->hc_data) { + tvhtrace("httpc", "received %s data", http_ver2str(hc->hc_version)); + tvhlog_hexdump("httpc", hc->hc_data, hc->hc_csize); + } +#endif + if (hc->hc_data_complete) { + res = hc->hc_data_complete(hc); + if (res < 0) + return http_client_flush(hc, res); + } + hc->hc_hsize = hc->hc_csize = 0; + if (hc->hc_version != RTSP_VERSION_1_0 && + hc->hc_handle_location && + (hc->hc_code == HTTP_STATUS_MOVED || + hc->hc_code == HTTP_STATUS_FOUND || + hc->hc_code == HTTP_STATUS_SEE_OTHER || + hc->hc_code == HTTP_STATUS_NOT_MODIFIED)) { + const char *p = http_arg_get(&hc->hc_args, "Location"); + if (p) { + hc->hc_location = strdup(p); + res = http_client_redirected(hc); + if (res < 0) + return http_client_flush(hc, res); + return HTTP_CON_RECEIVING; + } + } + if (TAILQ_FIRST(&hc->hc_wqueue) && hc->hc_code == HTTP_STATUS_OK) + return http_client_send_partial(hc); + if (!hc->hc_keepalive) { + http_client_shutdown(hc, 0); + if (hc->hc_ssl) { + /* finish the shutdown I/O sequence, notify owner later */ + errno = EAGAIN; + return HTTP_CON_RECEIVING; + } + } + return hc->hc_reconnected ? HTTP_CON_RECEIVING : HTTP_CON_DONE; +} + +static int +http_client_parse_arg( http_arg_list_t *list, const char *p ) +{ + char *d, *t; + + d = strchr(p, ':'); + if (d) { + *d++ = '\0'; + while (*d && *d <= ' ') + d++; + t = d + strlen(d); + while (--t != d && *t <= ' ') + *t = '\0'; + http_arg_set(list, p, d); + return 0; + } + return -EINVAL; +} + +static int +http_client_data_copy( http_client_t *hc, char *buf, size_t len ) +{ + int res; + + if (hc->hc_data_received) { + res = hc->hc_data_received(hc, buf, len); + if (res < 0) + return res; + } else { + hc->hc_data = realloc(hc->hc_data, hc->hc_data_size + len + 1); + memcpy(hc->hc_data + hc->hc_data_size, buf, len); + hc->hc_data_size += len; + hc->hc_data[hc->hc_data_size] = '\0'; + } + return 0; +} + +static ssize_t +http_client_data_chunked( http_client_t *hc, char *buf, size_t len, int *end ) +{ + size_t old = len, l, l2; + char *d, *s; + int res; + + while (len > 0) { + if (hc->hc_chunk_size) { + s = hc->hc_chunk; + l = len; + if (hc->hc_chunk_pos + l > hc->hc_chunk_size) + l = hc->hc_chunk_size - hc->hc_chunk_pos; + memcpy(s + hc->hc_chunk_pos, buf, l); + hc->hc_chunk_pos += l; + buf += l; + len -= l; + if (hc->hc_chunk_pos >= hc->hc_chunk_size) { + if (s[hc->hc_chunk_size - 2] != '\r' && + s[hc->hc_chunk_size - 1] != '\n') + return -EIO; + res = http_client_data_copy(hc, hc->hc_chunk, hc->hc_chunk_size - 2); + if (res < 0) + return res; + hc->hc_chunk_size = hc->hc_chunk_pos = 0; + } + continue; + } + l = 0; + if (hc->hc_chunk_csize) { + s = d = hc->hc_chunk; + if (buf[0] == '\n' && s[hc->hc_chunk_csize-1] == '\r') + l = 1; + else if (len > 1 && buf[0] == '\r' && buf[1] == '\n') + l = 2; + } else { + d = strstr(s = buf, "\r\n"); + if (d) { + *d = '\0'; + l = (d + 2) - s; + } + } + if (l) { + hc->hc_chunk_csize = 0; + if (hc->hc_chunk_trails) { + buf += l; + len -= l; + if (s[0] == '\0') { + *end = 1; + return old - len; + } + res = http_client_parse_arg(&hc->hc_args, s); + if (res < 0) + return res; + continue; + } + if (s[0] == '0' && s[1] == '\0') + hc->hc_chunk_trails = 1; + else { + hc->hc_chunk_size = strtoll(s, NULL, 16); + if (hc->hc_chunk_size == 0) + return -EIO; + if (hc->hc_chunk_size > 256*1024) + return -EMSGSIZE; + hc->hc_chunk_size += 2; /* CR-LF */ + if (hc->hc_chunk_alloc < hc->hc_chunk_size) { + hc->hc_chunk = realloc(hc->hc_chunk, hc->hc_chunk_size + 1); + hc->hc_chunk[hc->hc_chunk_size] = '\0'; + hc->hc_chunk_alloc = hc->hc_chunk_size; + } + } + buf += l; + len -= l; + } else { + l2 = hc->hc_chunk_csize + len; + if (l2 > hc->hc_chunk_alloc) { + hc->hc_chunk = realloc(hc->hc_chunk, l2 + 1); + hc->hc_chunk[l2] = '\0'; + hc->hc_chunk_alloc = l2; + } + memcpy(hc->hc_chunk + hc->hc_chunk_csize, buf, len); + hc->hc_chunk_csize += len; + buf += len; + len -= len; + } + } + return old; +} + +static int +http_client_data_received( http_client_t *hc, char *buf, ssize_t len ) +{ + ssize_t l, l2, csize; + int res, end = 0; + + buf[len] = '\0'; + + if (len == 0) { + if (hc->hc_csize == -1 || hc->hc_rpos >= hc->hc_csize) + return 1; + return 0; + } + + csize = hc->hc_csize < 0 ? 0 : hc->hc_csize; + l = len; + if (hc->hc_csize && hc->hc_csize != -1 && hc->hc_rpos > csize) { + l2 = hc->hc_rpos - csize; + if (l2 < l) + l = l2; + } + if (l) { + if (hc->hc_chunked) { + l = http_client_data_chunked(hc, buf, l, &end); + if (l < 0) + return l; + } else { + res = http_client_data_copy(hc, buf, l); + if (res < 0) + return res; + } + } + hc->hc_rpos += l; + end |= hc->hc_csize && hc->hc_rpos >= hc->hc_csize; + if (l < len) { + l2 = len - l; + if (l2 > hc->hc_rsize) + hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize = l2 + 1); + memcpy(hc->hc_rbuf, buf + l, l2); + hc->hc_rbuf[l2] = '\0'; + hc->hc_rpos = l2; + } + return end ? 1 : 0; +} + +int +http_client_run( http_client_t *hc ) +{ + char *buf, *saveptr, *argv[3], *d, *p; + http_ver_t ver; + ssize_t r; + size_t len; + int res; + + if (hc == NULL) + return 0; + + if (hc->hc_shutdown) { + if (hc->hc_ssl && hc->hc_ssl->shutdown) { + r = http_client_ssl_shutdown(hc); + if (r < 0) { + if (errno != ESTRPIPE) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) + return HTTP_CON_SENDING; + return r; + } + } + if (r == 0) + return HTTP_CON_SENDING; + } + return hc->hc_result ? hc->hc_result : HTTP_CON_DONE; + } + + if (hc->hc_sending) { + res = http_client_send_partial(hc); + if (res < 0 || res == HTTP_CON_SENDING) + return res; + } + + buf = alloca(hc->hc_io_size); + + if (!hc->hc_in_data && hc->hc_rpos > 3 && + (d = strstr(hc->hc_rbuf, "\r\n\r\n")) != NULL) + goto header; + +retry: + if (hc->hc_ssl) + r = http_client_ssl_recv(hc, buf, hc->hc_io_size); + else + r = recv(hc->hc_fd, buf, hc->hc_io_size, MSG_DONTWAIT); + if (r == 0) { + if (hc->hc_in_data && !hc->hc_keepalive) + return http_client_finish(hc); + return http_client_flush(hc, -ESTRPIPE); + } + if (r < 0) { + if (errno == ESTRPIPE && hc->hc_in_data && !hc->hc_keepalive) + return http_client_finish(hc); + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + return HTTP_CON_RECEIVING; + return http_client_flush(hc, -errno); + } +#if ENABLE_TRACE + if (r > 0) { + tvhtrace("httpc", "received %s answer", http_ver2str(hc->hc_version)); + tvhlog_hexdump("httpc", buf, r); + } +#endif + + if (hc->hc_in_data) { + res = http_client_data_received(hc, buf, r); + if (res < 0) + return http_client_flush(hc, res); + if (res > 0) + return http_client_finish(hc); + if (hc->hc_data_limit && r + hc->hc_rsize >= hc->hc_data_limit) + return http_client_flush(hc, -EOVERFLOW); + goto retry; + } + + if (hc->hc_rsize < r + hc->hc_rpos) { + if (hc->hc_rsize + r > 16*1024) + return http_client_flush(hc, -EMSGSIZE); + hc->hc_rsize += r; + hc->hc_rbuf = realloc(hc->hc_rbuf, hc->hc_rsize + 1); + } + memcpy(hc->hc_rbuf + hc->hc_rpos, buf, r); + hc->hc_rpos += r; + hc->hc_rbuf[hc->hc_rpos] = '\0'; + +next_header: + if (hc->hc_rpos < 3) + return HTTP_CON_RECEIVING; + if ((d = strstr(hc->hc_rbuf, "\r\n\r\n")) == NULL) + return HTTP_CON_RECEIVING; + +header: + *d = '\0'; + len = hc->hc_rpos; + hc->hc_reconnected = 0; + http_client_clear_state(hc); + hc->hc_rpos = len; + hc->hc_hsize = d - hc->hc_rbuf + 4; + p = strtok_r(hc->hc_rbuf, "\r\n", &saveptr); + if (p == NULL) + return http_client_flush(hc, -EINVAL); + tvhtrace("httpc", "%s answer '%s'", http_ver2str(hc->hc_version), p); + if (http_tokenize(p, argv, 3, -1) != 3) + return http_client_flush(hc, -EINVAL); + if ((ver = http_str2ver(argv[0])) < 0) + return http_client_flush(hc, -EINVAL); + if (ver != hc->hc_version) { + /* 1.1 -> 1.0 transition allowed */ + if (hc->hc_version == HTTP_VERSION_1_1 && ver == HTTP_VERSION_1_0) + hc->hc_version = ver; + else + return http_client_flush(hc, -EINVAL); + } + if ((hc->hc_code = atoi(argv[1])) < 200) + return http_client_flush(hc, -EINVAL); + while ((p = strtok_r(NULL, "\r\n", &saveptr)) != NULL) { + res = http_client_parse_arg(&hc->hc_args, p); + if (res < 0) + return http_client_flush(hc, -EINVAL); + } + p = http_arg_get(&hc->hc_args, "Content-Length"); + if (p) { + hc->hc_csize = atoll(p); + if (hc->hc_csize == 0) + hc->hc_csize = -1; + } + p = http_arg_get(&hc->hc_args, "Connection"); + if (p) { + if (hc->hc_keepalive && strcasecmp(p, "keep-alive")) + return http_client_flush(hc, -EINVAL); + if (!hc->hc_keepalive && strcasecmp(p, "close")) + return http_client_flush(hc, -EINVAL); + } + if (ver == RTSP_VERSION_1_0) { + p = http_arg_get(&hc->hc_args, "CSeq"); + if (p == NULL || hc->hc_rcseq != atoi(p)) + return http_client_flush(hc, -EINVAL); + } + p = http_arg_get(&hc->hc_args, "Transfer-Encoding"); + if (p) + hc->hc_chunked = strcasecmp(p, "chunked") == 0; + if (hc->hc_hdr_received) { + res = hc->hc_hdr_received(hc); + if (res < 0) + return http_client_flush(hc, res); + } + hc->hc_rpos -= hc->hc_hsize; + len = hc->hc_rpos; + if (hc->hc_code == HTTP_STATUS_CONTINUE) { + memmove(hc->hc_rbuf, hc->hc_rbuf + hc->hc_hsize, len); + goto next_header; + } + hc->hc_rpos = 0; + if (hc->hc_version == RTSP_VERSION_1_0 && !hc->hc_csize) { + hc->hc_csize = -1; + hc->hc_in_data = 0; + } else { + hc->hc_in_data = 1; + } + res = http_client_data_received(hc, hc->hc_rbuf + hc->hc_hsize, len); + if (res < 0) + return http_client_flush(hc, res); + if (res > 0) + return http_client_finish(hc); + goto retry; +} + +/* + * Redirected + */ +static void +http_client_basic_args ( http_arg_list_t *h, const url_t *url, int keepalive ) +{ + char buf[64]; + + http_arg_init(h); + http_arg_set(h, "Host", url->host); + snprintf(buf, sizeof(buf), "TVHeadend/%s", tvheadend_version); + http_arg_set(h, "User-Agent", buf); + if (!keepalive) + http_arg_set(h, "Connection", "close"); + if (url->user && url->user[0] && url->pass && url->pass[0]) { + size_t plen = strlen(url->pass); + size_t ulen = strlen(url->user); + size_t len = BASE64_SIZE(plen) + 1; + char *buf = alloca(ulen + 1 + len + 1); + strcpy(buf, url->user); + base64_encode(buf + ulen + 1, len, (uint8_t *)url->pass, plen); + buf[ulen] = ':'; + http_arg_set(h, "Authorization", buf); + } +} + +static int +http_client_redirected ( http_client_t *hc ) +{ + char *location, *location2; + http_arg_list_t h; + tvhpoll_t *efd; + url_t u; + int r; + + if (++hc->hc_redirects > 10) + return -ELOOP; + + location = hc->hc_location; + location2 = hc->hc_location = NULL; + + if (location[0] == '\0' || location[0] == '/') { + size_t size2 = strlen(hc->hc_scheme) + 3 + strlen(hc->hc_host) + + 12 + strlen(location) + 1; + location2 = alloca(size2); + snprintf(location2, size2, "%s://%s:%i%s", + hc->hc_scheme, hc->hc_host, hc->hc_port, location); + } + + memset(&u, 0, sizeof(u)); + if (urlparse(location2 ? location2 : location, &u)) { + tvherror("httpc", "redirection - cannot parse url '%s'", + location2 ? location2 : location); + free(location); + return -EIO; + } + free(location); + + if (strcmp(u.scheme, hc->hc_scheme) || + strcmp(u.host, hc->hc_host) || + http_port(u.scheme, u.port) != hc->hc_port || + !hc->hc_keepalive) { + efd = hc->hc_efd; + http_client_shutdown(hc, 1); + r = http_client_reconnect(hc, hc->hc_version, + u.scheme, u.host, u.port); + if (r < 0) { + urlreset(&u); + return r; + } + r = hc->hc_verify_peer; + hc->hc_verify_peer = -1; + http_client_ssl_peer_verify(hc, r); + hc->hc_efd = efd; + } + + http_client_flush(hc, 0); + + http_client_basic_args(&h, &u, hc->hc_keepalive); + hc->hc_reconnected = 1; + hc->hc_shutdown = 0; + hc->hc_pevents = 0; + + r = http_client_send(hc, hc->hc_cmd, u.path, u.query, &h, NULL, 0); + if (r < 0) { + urlreset(&u); + return r; + } + + hc->hc_reconnected = 1; + urlreset(&u); + return 1; +} + +int +http_client_simple( http_client_t *hc, const url_t *url ) +{ + http_arg_list_t h; + + http_client_basic_args(&h, url, 0); + return http_client_send(hc, HTTP_CMD_GET, url->path, url->query, + &h, NULL, 0); +} + +void +http_client_ssl_peer_verify( http_client_t *hc, int verify ) +{ + struct http_client_ssl *ssl; + + if (hc->hc_verify_peer < 0) { + hc->hc_verify_peer = verify ? 1 : 0; + if ((ssl = hc->hc_ssl) != NULL) { + if (!SSL_CTX_set_default_verify_paths(ssl->ctx)) + tvherror("httpc", "SSL - unable to load CA certificates for verification"); + SSL_CTX_set_verify_depth(ssl->ctx, 1); + SSL_CTX_set_verify(ssl->ctx, + hc->hc_verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, + NULL); + } + } else { + tvherror("httpc", "SSL peer verification method must be set only once"); + } +} + +/* + * Data thread + */ +static void * +http_client_thread ( void *p ) +{ + int n; + tvhpoll_event_t ev; + http_client_t *hc; + char c; + + while (http_running) { + n = tvhpoll_wait(http_poll, &ev, 1, -1); + if (n < 0) { + if (http_running && + errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK) + tvherror("httpc", "tvhpoll_wait() error"); + } else if (n > 0) { + if (&http_pipe == ev.data.ptr) { + if (read(http_pipe.rd, &c, 1) == 1) { + /* end-of-task */ + break; + } + continue; + } + pthread_mutex_lock(&http_lock); + TAILQ_FOREACH(hc, &http_clients, hc_link) + if (hc == ev.data.ptr) + break; + http_client_run(hc); + pthread_mutex_unlock(&http_lock); + } + } + + return NULL; +} + +static void +http_client_ssl_free( http_client_t *hc ) +{ + struct http_client_ssl *ssl; + + if ((ssl = hc->hc_ssl) != NULL) { + free(ssl->rbio_buf); + free(ssl->wbio_buf); + SSL_free(ssl->ssl); + SSL_CTX_free(ssl->ctx); + free(ssl); + hc->hc_ssl = NULL; + } +} + +/* + * Setup a connection (async) + */ +static int +http_client_reconnect + ( http_client_t *hc, http_ver_t ver, const char *scheme, + const char *host, int port ) +{ + struct http_client_ssl *ssl; + char errbuf[256]; + + free(hc->hc_scheme); + free(hc->hc_host); + + port = http_port(scheme, port); + hc->hc_pevents = 0; + hc->hc_version = ver; + hc->hc_scheme = strdup(scheme); + hc->hc_host = strdup(host); + hc->hc_port = port; + hc->hc_fd = tcp_connect(host, port, errbuf, sizeof(errbuf), -1); + if (hc->hc_fd < 0) { + tvhlog(LOG_ERR, "httpc", "Unable to connect to %s:%i - %s", host, port, errbuf); + return -EINVAL; + } + tvhtrace("httpc", "Connected to %s:%i", host, port); + http_client_ssl_free(hc); + if (strcasecmp(scheme, "https") == 0 || strcasecmp(scheme, "rtsps") == 0) { + ssl = calloc(1, sizeof(*ssl)); + hc->hc_ssl = ssl; + ssl->ctx = SSL_CTX_new(SSLv23_client_method()); + if (ssl->ctx == NULL) { + tvhlog(LOG_ERR, "httpc", "Unable to get SSL_CTX"); + goto err1; + } + /* do not use SSLv2 */ + SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION); + /* adjust cipher list */ + if (SSL_CTX_set_cipher_list(ssl->ctx, "HIGH:MEDIUM") != 1) { + tvhlog(LOG_ERR, "httpc", "Unable to adjust SSL cipher list"); + goto err2; + } + ssl->rbio = BIO_new(BIO_s_mem()); + ssl->wbio = BIO_new(BIO_s_mem()); + ssl->ssl = SSL_new(ssl->ctx); + if (ssl->ssl == NULL || ssl->rbio == NULL || ssl->wbio == NULL) { + tvhlog(LOG_ERR, "httpc", "Unable to get SSL handle"); + goto err3; + } + SSL_set_bio(ssl->ssl, ssl->rbio, ssl->wbio); + if (!SSL_set_tlsext_host_name(ssl->ssl, host)) { + tvhlog(LOG_ERR, "httpc", "Unable to set SSL hostname"); + goto err4; + } + } + + return 0; + +err4: + SSL_free(ssl->ssl); +err3: + BIO_free(ssl->rbio); + BIO_free(ssl->wbio); +err2: + SSL_CTX_free(ssl->ctx); +err1: + close(hc->hc_fd); + free(ssl); + return -EINVAL; +} + +http_client_t * +http_client_connect + ( void *aux, http_ver_t ver, const char *scheme, const char *host, int port ) +{ + http_client_t *hc; + + hc = calloc(1, sizeof(http_client_t)); + hc->hc_aux = aux; + hc->hc_io_size = 1024; + hc->hc_rtsp_stream_id = -1; + hc->hc_verify_peer = -1; + + TAILQ_INIT(&hc->hc_args); + TAILQ_INIT(&hc->hc_wqueue); + + if (http_client_reconnect(hc, ver, scheme, host, port) < 0) { + free(hc); + return NULL; + } + + return hc; +} + +/* + * Register to the another thread + */ +void +http_client_register( http_client_t *hc ) +{ + assert(hc->hc_data_received || hc->hc_conn_closed); + assert(hc->hc_efd == NULL); + + pthread_mutex_lock(&http_lock); + + TAILQ_INSERT_TAIL(&http_clients, hc, hc_link); + + hc->hc_efd = http_poll; + + pthread_mutex_unlock(&http_lock); +} + +/* + * Cancel + */ +void +http_client_close ( http_client_t *hc ) +{ + http_client_wcmd_t *wcmd; + + if (hc == NULL) + return; + + pthread_mutex_lock(&http_lock); + http_client_shutdown(hc, 1); + http_client_flush(hc, 0); + pthread_mutex_unlock(&http_lock); + while ((wcmd = TAILQ_FIRST(&hc->hc_wqueue)) != NULL) + http_client_cmd_destroy(hc, wcmd); + http_client_ssl_free(hc); + rtsp_clear_session(hc); + free(hc->hc_location); + free(hc->hc_rbuf); + free(hc->hc_data); + free(hc->hc_host); + free(hc->hc_scheme); + free(hc); +} + +/* + * Initialise subsystem + */ +pthread_t http_client_tid; + +void +http_client_init ( void ) +{ + tvhpoll_event_t ev; + + /* Setup list */ + pthread_mutex_init(&http_lock, NULL); + TAILQ_INIT(&http_clients); + + /* Setup pipe */ + tvh_pipe(O_NONBLOCK, &http_pipe); + + /* Setup poll */ + http_poll = tvhpoll_create(10); + memset(&ev, 0, sizeof(ev)); + ev.fd = http_pipe.rd; + ev.events = TVHPOLL_IN; + ev.data.ptr = &http_pipe; + tvhpoll_add(http_poll, &ev, 1); + + /* Setup thread */ + http_running = 1; + tvhthread_create(&http_client_tid, NULL, http_client_thread, NULL, 0); +#if HTTPCLIENT_TESTSUITE + http_client_testsuite_run(); +#endif +} + +void +http_client_done ( void ) +{ + http_running = 0; + tvh_write(http_pipe.wr, "", 1); + pthread_join(http_client_tid, NULL); + assert(TAILQ_FIRST(&http_clients) == NULL); + tvh_pipe_close(&http_pipe); + tvhpoll_destroy(http_poll); +} + +/* + * + * TESTSUITE + * + */ + +#if HTTPCLIENT_TESTSUITE + +static int +http_client_testsuite_hdr_received( http_client_t *hc ) +{ + http_arg_t *ra; + + fprintf(stderr, "HTTPCTS: Received header from %s:%i\n", hc->hc_host, hc->hc_port); + TAILQ_FOREACH(ra, &hc->hc_args, link) + fprintf(stderr, " %s: %s\n", ra->key, ra->val); + return 0; +} + +static void +http_client_testsuite_conn_closed( http_client_t *hc, int result ) +{ + fprintf(stderr, "HTTPCTS: Closed (result=%i - %s)\n", result, strerror(result)); +} + +static int +http_client_testsuite_data_complete( http_client_t *hc ) +{ + fprintf(stderr, "HTTPCTS: Data Complete (code=%i, data=%p, data_size=%zi)\n", + hc->hc_code, hc->hc_data, hc->hc_data_size); + return 0; +} + +static int +http_client_testsuite_data_received( http_client_t *hc, void *data, size_t len ) +{ + fprintf(stderr, "HTTPCTS: Data received (len=%zi)\n", len); + /* check, if the data memory area is OK */ + memset(data, 0xa5, len); + return 0; +} + +static struct strtab HTTP_contab[] = { + { "WAIT_REQUEST", HTTP_CON_WAIT_REQUEST }, + { "READ_HEADER", HTTP_CON_READ_HEADER }, + { "END", HTTP_CON_END }, + { "POST_DATA", HTTP_CON_POST_DATA }, + { "SENDING", HTTP_CON_SENDING }, + { "SENT", HTTP_CON_SENT }, + { "RECEIVING", HTTP_CON_RECEIVING }, + { "DONE", HTTP_CON_DONE }, +}; + +static struct strtab ERRNO_tab[] = { + { "EPERM", EPERM }, + { "ENOENT", ENOENT }, + { "ESRCH", ESRCH }, + { "EINTR", EINTR }, + { "EIO", EIO }, + { "ENXIO", ENXIO }, + { "E2BIG", E2BIG }, + { "ENOEXEC", ENOEXEC }, + { "EBADF", EBADF }, + { "ECHILD", ECHILD }, + { "EAGAIN", EAGAIN }, + { "ENOMEM", ENOMEM }, + { "EACCES", EACCES }, + { "EFAULT", EFAULT }, + { "ENOTBLK", ENOTBLK }, + { "EBUSY", EBUSY }, + { "EEXIST", EEXIST }, + { "EXDEV", EXDEV }, + { "ENODEV", ENODEV }, + { "ENOTDIR", ENOTDIR }, + { "EISDIR", EISDIR }, + { "EINVAL", EINVAL }, + { "ENFILE", ENFILE }, + { "EMFILE", EMFILE }, + { "ENOTTY", ENOTTY }, + { "ETXTBSY", ETXTBSY }, + { "EFBIG", EFBIG }, + { "ENOSPC", ENOSPC }, + { "ESPIPE", ESPIPE }, + { "EROFS", EROFS }, + { "EMLINK", EMLINK }, + { "EPIPE", EPIPE }, + { "EDOM", EDOM }, + { "ERANGE", ERANGE }, + { "EDEADLK", EDEADLK }, + { "ENAMETOOLONG", ENAMETOOLONG }, + { "ENOLCK", ENOLCK }, + { "ENOSYS", ENOSYS }, + { "ENOTEMPTY", ENOTEMPTY }, + { "ELOOP", ELOOP }, + { "EWOULDBLOCK", EWOULDBLOCK }, + { "ENOMSG", ENOMSG }, + { "EIDRM", EIDRM }, + { "ECHRNG", ECHRNG }, + { "EL2NSYNC", EL2NSYNC }, + { "EL3HLT", EL3HLT }, + { "EL3RST", EL3RST }, + { "ELNRNG", ELNRNG }, + { "EUNATCH", EUNATCH }, + { "ENOCSI", ENOCSI }, + { "EL2HLT", EL2HLT }, + { "EBADE", EBADE }, + { "EBADR", EBADR }, + { "EXFULL", EXFULL }, + { "ENOANO", ENOANO }, + { "EBADRQC", EBADRQC }, + { "EBADSLT", EBADSLT }, + { "EDEADLOCK", EDEADLOCK }, + { "EBFONT", EBFONT }, + { "ENOSTR", ENOSTR }, + { "ENODATA", ENODATA }, + { "ETIME", ETIME }, + { "ENOSR", ENOSR }, + { "ENONET", ENONET }, + { "ENOPKG", ENOPKG }, + { "EREMOTE", EREMOTE }, + { "ENOLINK", ENOLINK }, + { "EADV", EADV }, + { "ESRMNT", ESRMNT }, + { "ECOMM", ECOMM }, + { "EPROTO", EPROTO }, + { "EMULTIHOP", EMULTIHOP }, + { "EDOTDOT", EDOTDOT }, + { "EBADMSG", EBADMSG }, + { "EOVERFLOW", EOVERFLOW }, + { "ENOTUNIQ", ENOTUNIQ }, + { "EBADFD", EBADFD }, + { "EREMCHG", EREMCHG }, + { "ELIBACC", ELIBACC }, + { "ELIBBAD", ELIBBAD }, + { "ELIBSCN", ELIBSCN }, + { "ELIBMAX", ELIBMAX }, + { "ELIBEXEC", ELIBEXEC }, + { "EILSEQ", EILSEQ }, + { "ERESTART", ERESTART }, + { "ESTRPIPE", ESTRPIPE }, + { "EUSERS", EUSERS }, + { "ENOTSOCK", ENOTSOCK }, + { "EDESTADDRREQ", EDESTADDRREQ }, + { "EMSGSIZE", EMSGSIZE }, + { "EPROTOTYPE", EPROTOTYPE }, + { "ENOPROTOOPT", ENOPROTOOPT }, + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, + { "ESOCKTNOSUPPORT", ESOCKTNOSUPPORT }, + { "EOPNOTSUPP", EOPNOTSUPP }, + { "EPFNOSUPPORT", EPFNOSUPPORT }, + { "EAFNOSUPPORT", EAFNOSUPPORT }, + { "EADDRINUSE", EADDRINUSE }, + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, + { "ENETDOWN", ENETDOWN }, + { "ENETUNREACH", ENETUNREACH }, + { "ENETRESET", ENETRESET }, + { "ECONNABORTED", ECONNABORTED }, + { "ECONNRESET", ECONNRESET }, + { "ENOBUFS", ENOBUFS }, + { "EISCONN", EISCONN }, + { "ENOTCONN", ENOTCONN }, + { "ESHUTDOWN", ESHUTDOWN }, + { "ETOOMANYREFS", ETOOMANYREFS }, + { "ETIMEDOUT", ETIMEDOUT }, + { "ECONNREFUSED", ECONNREFUSED }, + { "EHOSTDOWN", EHOSTDOWN }, + { "EHOSTUNREACH", EHOSTUNREACH }, + { "EALREADY", EALREADY }, + { "EINPROGRESS", EINPROGRESS }, + { "ESTALE", ESTALE }, + { "EUCLEAN", EUCLEAN }, + { "ENOTNAM", ENOTNAM }, + { "ENAVAIL", ENAVAIL }, + { "EISNAM", EISNAM }, + { "EREMOTEIO", EREMOTEIO }, + { "EDQUOT", EDQUOT }, + { "ENOMEDIUM", ENOMEDIUM }, + { "EMEDIUMTYPE", EMEDIUMTYPE }, + { "ECANCELED", ECANCELED }, + { "ENOKEY", ENOKEY }, + { "EKEYEXPIRED", EKEYEXPIRED }, + { "EKEYREVOKED", EKEYREVOKED }, + { "EKEYREJECTED", EKEYREJECTED }, + { "EOWNERDEAD", EOWNERDEAD }, + { "ENOTRECOVERABLE", ENOTRECOVERABLE }, +#ifdef ERFKILL + { "ERFKILL", ERFKILL }, +#endif +#ifdef EHWPOISON + { "EHWPOISON", EHWPOISON }, +#endif +}; + +void +http_client_testsuite_run( void ) +{ + const char *path, *cs, *cs2; + char line[1024], *s; + http_arg_list_t args; + http_client_t *hc = NULL; + http_cmd_t cmd; + http_ver_t ver = HTTP_VERSION_1_1; + int data_transfer = 0, port = 0; + size_t data_limit = 0; + tvhpoll_event_t ev; + tvhpoll_t *efd; + url_t u1, u2; + FILE *fp; + int r, expected = HTTP_CON_DONE; + int handle_location = 0; + int peer_verify = 1; + + path = getenv("TVHEADEND_HTTPC_TEST"); + if (path == NULL) + path = TVHEADEND_DATADIR "/support/httpc-test.txt"; + fp = fopen(path, "r"); + if (fp == NULL) { + tvhlog(LOG_NOTICE, "httpc", "Test: unable to open '%s': %s", path, strerror(errno)); + return; + } + memset(&u1, 0, sizeof(u1)); + memset(&u2, 0, sizeof(u2)); + http_arg_init(&args); + efd = tvhpoll_create(1); + while (fgets(line, sizeof(line), fp) != NULL && tvheadend_running) { + if (line[0] == '\0') + continue; + s = line + strlen(line) - 1; + while (*s < ' ' && s != line) + s--; + if (*s < ' ') + *s = '\0'; + else + s[1] = '\0'; + s = line; + while (*s && *s < ' ') + s++; + if (*s == '\0' || *s == '#') + continue; + if (strcmp(s, "Reset=1") == 0) { + ver = HTTP_VERSION_1_1; + urlreset(&u1); + urlreset(&u2); + http_client_close(hc); + hc = NULL; + data_transfer = 0; + data_limit = 0; + port = 0; + expected = HTTP_CON_DONE; + handle_location = 0; + peer_verify = 1; + } else if (strcmp(s, "DataTransfer=all") == 0) { + data_transfer = 0; + } else if (strcmp(s, "DataTransfer=cont") == 0) { + data_transfer = 1; + } else if (strcmp(s, "HandleLocation=0") == 0) { + handle_location = 0; + } else if (strcmp(s, "HandleLocation=1") == 0) { + handle_location = 1; + } else if (strcmp(s, "SSLPeerVerify=0") == 0) { + peer_verify = 0; + } else if (strcmp(s, "SSLPeerVerify=1") == 0) { + peer_verify = 1; + } else if (strncmp(s, "DataLimit=", 10) == 0) { + data_limit = atoll(s + 10); + } else if (strncmp(s, "Port=", 5) == 0) { + port = atoi(s + 5); + } else if (strncmp(s, "ExpectedError=", 14) == 0) { + r = str2val(s + 14, HTTP_contab); + if (r < 0) { + r = str2val(s + 14, ERRNO_tab); + if (r < 0) { + fprintf(stderr, "HTTPCTS: Unknown error code '%s'\n", s + 14); + goto fatal; + } else { + r = -r; + } + } + expected = r; + } else if (strncmp(s, "Header=", 7) == 0) { + r = http_client_parse_arg(&args, s + 7); + if (r < 0) + goto fatal; + } else if (strncmp(s, "Version=", 8) == 0) { + ver = http_str2ver(s + 8); + if (ver < 0) + goto fatal; + } else if (strncmp(s, "URL=", 4) == 0) { + urlreset(&u1); + if (urlparse(s + 4, &u1) < 0) + goto fatal; + } else if (strncmp(s, "Command=", 8) == 0) { + if (strcmp(s + 8, "EXIT") == 0) + break; + if (u1.host == NULL || u1.host[0] == '\0') { + fprintf(stderr, "HTTPCTS: Define URL\n"); + goto fatal; + } + cmd = http_str2cmd(s + 8); + if (cmd < 0) + goto fatal; + if (http_arg_get(&args, "Host") == NULL && u1.host && u1.host[0] != '\0') + http_arg_set(&args, "Host", u1.host); + if (u2.host == NULL || u1.host == NULL || strcmp(u1.host, u2.host) || + u2.port != u1.port || !hc->hc_keepalive) { + http_client_close(hc); + if (port) + u1.port = port; + hc = http_client_connect(NULL, ver, u1.scheme, u1.host, u1.port); + if (hc == NULL) { + fprintf(stderr, "HTTPCTS: Unable to connect to %s:%i (%s)\n", u1.host, u1.port, u1.scheme); + goto fatal; + } else { + fprintf(stderr, "HTTPCTS: Connected to %s:%i\n", hc->hc_host, hc->hc_port); + } + http_client_ssl_peer_verify(hc, peer_verify); + } + fprintf(stderr, "HTTPCTS Send: Cmd=%s Ver=%s Host=%s Path=%s\n", + http_cmd2str(cmd), http_ver2str(ver), http_arg_get(&args, "Host"), u1.path); + hc->hc_efd = efd; + hc->hc_handle_location = handle_location; + hc->hc_data_limit = data_limit; + hc->hc_hdr_received = http_client_testsuite_hdr_received; + hc->hc_data_complete = http_client_testsuite_data_complete; + hc->hc_conn_closed = http_client_testsuite_conn_closed; + if (data_transfer) { + hc->hc_data_received = http_client_testsuite_data_received; + } else { + hc->hc_data_received = NULL; + } + r = http_client_send(hc, cmd, u1.path, u1.query, &args, NULL, 0); + if (r < 0) { + fprintf(stderr, "HTTPCTS Send Failed %s\n", strerror(-r)); + goto fatal; + } + while (tvheadend_running) { + fprintf(stderr, "HTTPCTS: Enter Poll\n"); + r = tvhpoll_wait(efd, &ev, 1, -1); + fprintf(stderr, "HTTPCTS: Leave Poll: %i (%s)\n", r, val2str(r, ERRNO_tab)); + if (r < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK)) + continue; + if (r < 0) { + fprintf(stderr, "HTTPCTS: Poll result: %s\n", strerror(-r)); + goto fatal; + } + if (r != 1) + continue; + if (ev.data.ptr != hc) { + fprintf(stderr, "HTTPCTS: Poll returned a wrong value\n"); + goto fatal; + } + r = http_client_run(hc); + cs = val2str(r, HTTP_contab); + if (cs == NULL) + cs = val2str(-r, ERRNO_tab); + cs2 = val2str(expected, HTTP_contab); + if (cs2 == NULL) + cs2 = val2str(-expected, ERRNO_tab); + fprintf(stderr, "HTTPCTS: Run Done, Result = %i (%s), Expected = %i (%s)\n", r, cs, expected, cs2); + if (r == expected) + break; + if (r < 0) + goto fatal; + if (r == HTTP_CON_DONE) + goto fatal; + } + urlreset(&u2); + urlcopy(&u2, &u1); + urlreset(&u1); + http_client_clear_state(hc); + } else { + fprintf(stderr, "HTTPCTS: Wrong line '%s'\n", s); + } + } + urlreset(&u2); + urlreset(&u1); + http_client_close(hc); + tvhpoll_destroy(efd); + http_arg_flush(&args); + fclose(fp); + fprintf(stderr, "HTTPCTS Return To Main\n"); + return; +fatal: + fprintf(stderr, "HTTPCTS Fatal Error\n"); + abort(); +} + +#endif diff --git a/src/idnode.c b/src/idnode.c index f068dba9..192e929a 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -140,20 +140,40 @@ idnode_unlink(idnode_t *in) /** * */ -void -idnode_delete(idnode_t *in) +static void +idnode_handler(size_t off, idnode_t *in) { + void (**fcn)(idnode_t *); lock_assert(&global_lock); const idclass_t *idc = in->in_class; while (idc) { - if (idc->ic_delete) { - idc->ic_delete(in); + fcn = (void *)idc + off; + if (*fcn) { + (*fcn)(in); break; } idc = idc->ic_super; } } +void +idnode_delete(idnode_t *in) +{ + return idnode_handler(offsetof(idclass_t, ic_delete), in); +} + +void +idnode_moveup(idnode_t *in) +{ + return idnode_handler(offsetof(idclass_t, ic_moveup), in); +} + +void +idnode_movedown(idnode_t *in) +{ + return idnode_handler(offsetof(idclass_t, ic_movedown), in); +} + /* ************************************************************************** * Info * *************************************************************************/ @@ -587,6 +607,7 @@ idnode_filter_clear else free(ele->u.s); } + free(ele->key); free(ele); } } diff --git a/src/idnode.h b/src/idnode.h index 4bab5b9c..e5aaf7db 100644 --- a/src/idnode.h +++ b/src/idnode.h @@ -42,7 +42,8 @@ typedef struct idnode_set /* * Class definition */ -typedef struct idclass { +typedef struct idclass idclass_t; +struct idclass { const struct idclass *ic_super; /// Parent class const char *ic_class; /// Class name const char *ic_caption; /// Class description @@ -51,11 +52,13 @@ typedef struct idclass { const char *ic_event; /// Events to fire on add/delete/title /* Callbacks */ - idnode_set_t *(*ic_get_childs)(idnode_t *self); - const char *(*ic_get_title) (idnode_t *self); - void (*ic_save) (idnode_t *self); - void (*ic_delete) (idnode_t *self); -} idclass_t; + idnode_set_t *(*ic_get_childs) (idnode_t *self); + const char *(*ic_get_title) (idnode_t *self); + void (*ic_save) (idnode_t *self); + void (*ic_delete) (idnode_t *self); + void (*ic_moveup) (idnode_t *self); + void (*ic_movedown) (idnode_t *self); +}; /* * Node definition @@ -120,6 +123,8 @@ const char *idnode_get_title (idnode_t *in); int idnode_is_leaf (idnode_t *in); int idnode_is_instance (idnode_t *in, const idclass_t *idc); void idnode_delete (idnode_t *in); +void idnode_moveup (idnode_t *in); +void idnode_movedown (idnode_t *in); void *idnode_find (const char *uuid, const idclass_t *idc); idnode_set_t *idnode_find_all(const idclass_t *idc); diff --git a/src/imagecache.c b/src/imagecache.c index bc219acd..cf17f01d 100644 --- a/src/imagecache.c +++ b/src/imagecache.c @@ -35,12 +35,7 @@ #include "redblack.h" #include "notify.h" #include "prop.h" - -#if ENABLE_IMAGECACHE -#define CURL_STATICLIB -#include -#include -#endif +#include "http.h" /* * Image metadata @@ -139,10 +134,15 @@ imagecache_image_add ( imagecache_image_t *img ) static int imagecache_image_fetch ( imagecache_image_t *img ) { - int res = 1; - CURL *curl; + int res = 1, r; FILE *fp; + url_t url; char tmp[256], path[256]; + tvhpoll_event_t ev; + tvhpoll_t *efd = NULL; + http_client_t *hc; + + memset(&url, 0, sizeof(url)); /* Open file */ if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d", @@ -156,25 +156,54 @@ imagecache_image_fetch ( imagecache_image_t *img ) /* Build command */ tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url); - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, img->url); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend"); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); - if (imagecache_conf.ignore_sslcert) - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + memset(&url, 0, sizeof(url)); + if (urlparse(img->url, &url)) { + tvherror("imagecache", "Unable to parse url '%s'", img->url); + goto error; + } /* Fetch (release lock, incase of delays) */ pthread_mutex_unlock(&global_lock); - res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - fclose(fp); + + hc = http_client_connect(NULL, HTTP_VERSION_1_1, url.scheme, + url.host, url.port); + if (hc == NULL) + goto error; + + http_client_ssl_peer_verify(hc, imagecache_conf.ignore_sslcert ? 0 : 1); + hc->hc_handle_location = 1; + hc->hc_data_limit = 256*1024; + efd = tvhpoll_create(1); + hc->hc_efd = efd; + + r = http_client_simple(hc, &url); + if (r < 0) + goto error; + + while (tvheadend_running) { + r = tvhpoll_wait(efd, &ev, 1, -1); + if (r < 0) + break; + if (r == 0) + continue; + r = http_client_run(hc); + if (r < 0) + break; + if (r == HTTP_CON_DONE) { + if (hc->hc_code == HTTP_STATUS_OK && hc->hc_data_size > 0) { + fwrite(hc->hc_data, hc->hc_data_size, 1, fp); + res = 0; + } + break; + } + } + pthread_mutex_lock(&global_lock); /* Process */ error: + urlreset(&url); + tvhpoll_destroy(efd); img->state = IDLE; time(&img->updated); // even if failed (possibly request sooner?) if (res) { diff --git a/src/input.h b/src/input.h index 6de228f9..1597236f 100644 --- a/src/input.h +++ b/src/input.h @@ -128,6 +128,9 @@ void tvh_input_stream_destroy ( tvh_input_stream_t *st ); #if ENABLE_LINUXDVB #include "input/mpegts/linuxdvb.h" #endif +#if ENABLE_SATIP_CLIENT +#include "input/mpegts/satip/satip.h" +#endif #endif #endif /* __TVH_INPUT_H__ */ diff --git a/src/input/mpegts.c b/src/input/mpegts.c index 8f2a0a5c..87d341a4 100644 --- a/src/input/mpegts.c +++ b/src/input/mpegts.c @@ -19,7 +19,8 @@ #include "input.h" void -mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners ) +mpegts_init ( int linuxdvb_mask, str_list_t *satip_client, + str_list_t *tsfiles, int tstuners ) { /* Register classes (avoid API 400 errors due to not yet defined) */ idclass_register(&mpegts_network_class); @@ -51,6 +52,11 @@ mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners ) linuxdvb_init(linuxdvb_mask); #endif + /* SAT>IP DVB client */ +#if ENABLE_SATIP_CLIENT + satip_init(satip_client); +#endif + /* Mux schedulers */ #if ENABLE_MPEGTS mpegts_mux_sched_init(); @@ -71,6 +77,9 @@ mpegts_done ( void ) #if ENABLE_LINUXDVB tvhftrace("main", linuxdvb_done); #endif +#if ENABLE_SATIP_CLIENT + tvhftrace("main", satip_done); +#endif #if ENABLE_TSFILE tvhftrace("main", tsfile_done); #endif diff --git a/src/input/mpegts.h b/src/input/mpegts.h index 24068b98..ffc0fe1c 100644 --- a/src/input/mpegts.h +++ b/src/input/mpegts.h @@ -68,7 +68,8 @@ extern const idclass_t mpegts_input_class; * Setup / Tear down * *************************************************************************/ -void mpegts_init ( int linuxdvb_mask, str_list_t *tsfiles, int tstuners ); +void mpegts_init ( int linuxdvb_mask, str_list_t *satip_client, + str_list_t *tsfiles, int tstuners ); void mpegts_done ( void ); /* ************************************************************************** @@ -505,7 +506,7 @@ struct mpegts_input /* * Functions */ - int (*mi_is_enabled) (mpegts_input_t*); + int (*mi_is_enabled) (mpegts_input_t*, mpegts_mux_t *mm); void (*mi_enabled_updated)(mpegts_input_t*); void (*mi_display_name) (mpegts_input_t*, char *buf, size_t len); int (*mi_is_free) (mpegts_input_t*); diff --git a/src/input/mpegts/iptv/iptv.c b/src/input/mpegts/iptv/iptv.c index 4fd059c1..f5ed6590 100644 --- a/src/input/mpegts/iptv/iptv.c +++ b/src/input/mpegts/iptv/iptv.c @@ -218,6 +218,7 @@ iptv_input_start_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) im->mm_active = NULL; pthread_mutex_unlock(&iptv_lock); + urlreset(&url); return ret; } @@ -239,7 +240,8 @@ iptv_input_stop_mux ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) /* Close file */ if (im->mm_iptv_fd > 0) { - close(im->mm_iptv_fd); // removes from poll + udp_close(im->mm_iptv_connection); // removes from poll + im->mm_iptv_connection = NULL; im->mm_iptv_fd = -1; } diff --git a/src/input/mpegts/iptv/iptv_http.c b/src/input/mpegts/iptv/iptv_http.c index 746397cc..9aed89be 100644 --- a/src/input/mpegts/iptv/iptv_http.c +++ b/src/input/mpegts/iptv/iptv_http.c @@ -21,27 +21,29 @@ #include "iptv_private.h" #include "http.h" -#if ENABLE_CURL - /* * Connected */ -static void -iptv_http_conn ( void *p ) +static int +iptv_http_header ( http_client_t *hc ) { - pthread_mutex_lock(&global_lock); - iptv_input_mux_started(p); - pthread_mutex_unlock(&global_lock); + /* multiple headers for redirections */ + if (hc->hc_code == HTTP_STATUS_OK) { + pthread_mutex_lock(&global_lock); + iptv_input_mux_started(hc->hc_aux); + pthread_mutex_unlock(&global_lock); + } + return 0; } /* * Receive data */ -static size_t +static int iptv_http_data - ( void *p, void *buf, size_t len ) + ( http_client_t *hc, void *buf, size_t len ) { - iptv_mux_t *im = p; + iptv_mux_t *im = hc->hc_aux; pthread_mutex_lock(&iptv_lock); @@ -52,7 +54,7 @@ iptv_http_data pthread_mutex_unlock(&iptv_lock); - return len; + return 0; } /* @@ -63,8 +65,21 @@ iptv_http_start ( iptv_mux_t *im, const url_t *u ) { http_client_t *hc; - if (!(hc = http_connect(u, iptv_http_conn, iptv_http_data, NULL, im))) + int r; + + if (!(hc = http_client_connect(im, HTTP_VERSION_1_1, u->scheme, + u->host, u->port))) return SM_CODE_TUNING_FAILED; + hc->hc_hdr_received = iptv_http_header; + hc->hc_data_received = iptv_http_data; + hc->hc_handle_location = 1; /* allow redirects */ + hc->hc_chunk_size = 128*1024; /* increase buffering */ + http_client_register(hc); /* register to the HTTP thread */ + r = http_client_simple(hc, u); + if (r < 0) { + http_client_close(hc); + return SM_CODE_TUNING_FAILED; + } im->im_data = hc; return 0; @@ -77,7 +92,7 @@ static void iptv_http_stop ( iptv_mux_t *im ) { - http_close(im->im_data); + http_client_close(im->im_data); } @@ -102,12 +117,3 @@ iptv_http_init ( void ) }; iptv_handler_register(ih, 2); } - -#else /* ENABLE_CURL */ - -void -iptv_http_init ( void ) -{ -} - -#endif /* ENABLE_CURL */ diff --git a/src/input/mpegts/iptv/iptv_private.h b/src/input/mpegts/iptv/iptv_private.h index edfabb5e..966a2746 100644 --- a/src/input/mpegts/iptv/iptv_private.h +++ b/src/input/mpegts/iptv/iptv_private.h @@ -23,6 +23,7 @@ #include "input.h" #include "htsbuf.h" #include "url.h" +#include "udp.h" #define IPTV_PKT_SIZE (300*188) @@ -72,6 +73,7 @@ struct iptv_mux mpegts_mux_t; int mm_iptv_fd; + udp_connection_t *mm_iptv_connection; char *mm_iptv_url; char *mm_iptv_interface; diff --git a/src/input/mpegts/iptv/iptv_udp.c b/src/input/mpegts/iptv/iptv_udp.c index ea8cac0b..c001204f 100644 --- a/src/input/mpegts/iptv/iptv_udp.c +++ b/src/input/mpegts/iptv/iptv_udp.c @@ -45,137 +45,23 @@ static int iptv_udp_start ( iptv_mux_t *im, const url_t *url ) { - int fd, solip, rxsize, reuse = 1, ipv6 = 0; - struct ifreq ifr; - struct in_addr saddr; - struct in6_addr s6addr; - char name[256], buf[256]; + char name[256]; + udp_connection_t *conn; im->mm_display_name((mpegts_mux_t*)im, name, sizeof(name)); - /* Determine if this is IPv6 */ - if (!inet_pton(AF_INET, url->host, &saddr)) { - ipv6 = 1; - if (!inet_pton(AF_INET6, url->host, &s6addr)) { - tvherror("iptv", "%s - failed to process host", name); - return SM_CODE_TUNING_FAILED; - } - } - - /* Open socket */ - if ((fd = tvh_socket(ipv6 ? AF_INET6 : AF_INET, SOCK_DGRAM, 0)) == -1) { - tvherror("iptv", "%s - failed to create socket [%s]", - name, strerror(errno)); + conn = udp_bind("iptv", name, url->host, url->port, + im->mm_iptv_interface, IPTV_PKT_SIZE); + if (conn == UDP_FATAL_ERROR) return SM_CODE_TUNING_FAILED; - } - - /* Mark reuse address */ - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - - /* Bind to interface */ - memset(&ifr, 0, sizeof(ifr)); - if (im->mm_iptv_interface && *im->mm_iptv_interface) { - snprintf(ifr.ifr_name, IFNAMSIZ, "%s", im->mm_iptv_interface); - if (ioctl(fd, SIOCGIFINDEX, &ifr)) { - tvherror("iptv", "%s - could not find interface %s", - name, im->mm_iptv_interface); - goto error; - } - } - - /* IPv4 */ - if (!ipv6) { - struct ip_mreqn m; - struct sockaddr_in sin; - memset(&m, 0, sizeof(m)); - memset(&sin, 0, sizeof(sin)); - - /* Bind */ - sin.sin_family = AF_INET; - sin.sin_port = htons(url->port); - sin.sin_addr = saddr; - if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { - inet_ntop(AF_INET, &sin.sin_addr, buf, sizeof(buf)); - tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]", - name, buf, ntohs(sin.sin_port), strerror(errno)); - goto error; - } - - /* Join group */ - m.imr_multiaddr = sin.sin_addr; - m.imr_address.s_addr = 0; -#if defined(PLATFORM_LINUX) - m.imr_ifindex = ifr.ifr_ifindex; -#elif defined(PLATFORM_FREEBSD) - m.imr_ifindex = ifr.ifr_index; -#endif -#ifdef SOL_IP - solip = SOL_IP; -#else - { - struct protoent *pent; - pent = getprotobyname("ip"); - solip = (pent != NULL) ? pent->p_proto : 0; - } -#endif - - if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) { - inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf)); - tvhwarn("iptv", "%s - cannot join %s [%s]", - name, buf, strerror(errno)); - } - - /* Bind to IPv6 group */ - } else { - struct ipv6_mreq m; - struct sockaddr_in6 sin; - memset(&m, 0, sizeof(m)); - memset(&sin, 0, sizeof(sin)); - - /* Bind */ - sin.sin6_family = AF_INET6; - sin.sin6_port = htons(url->port); - sin.sin6_addr = s6addr; - if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { - inet_ntop(AF_INET6, &sin.sin6_addr, buf, sizeof(buf)); - tvherror("iptv", "%s - cannot bind %s:%hd [e=%s]", - name, buf, ntohs(sin.sin6_port), strerror(errno)); - goto error; - } - - /* Join group */ - m.ipv6mr_multiaddr = sin.sin6_addr; -#if defined(PLATFORM_LINUX) - m.ipv6mr_interface = ifr.ifr_ifindex; -#elif defined(PLATFORM_FREEBSD) - m.ipv6mr_interface = ifr.ifr_index; -#endif -#ifdef SOL_IPV6 - if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) { - inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf)); - tvhwarn("iptv", "%s - cannot join %s [%s]", - name, buf, strerror(errno)); - } -#else - tvherror("iptv", "IPv6 multicast not supported"); - goto error; -#endif - } - - /* Increase RX buffer size */ - rxsize = IPTV_PKT_SIZE; - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1) - tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]", - name, strerror(errno)); + if (conn == NULL) + return -1; /* Done */ - im->mm_iptv_fd = fd; + im->mm_iptv_fd = conn->fd; + im->mm_iptv_connection = conn; iptv_input_mux_started(im); return 0; - -error: - close(fd); - return -1; } static ssize_t diff --git a/src/input/mpegts/linuxdvb/linuxdvb_adapter.c b/src/input/mpegts/linuxdvb/linuxdvb_adapter.c index 74ba3c26..1ee9ba8e 100644 --- a/src/input/mpegts/linuxdvb/linuxdvb_adapter.c +++ b/src/input/mpegts/linuxdvb/linuxdvb_adapter.c @@ -119,7 +119,7 @@ linuxdvb_adapter_is_enabled ( linuxdvb_adapter_t *la ) { linuxdvb_frontend_t *lfe; LIST_FOREACH(lfe, &la->la_frontends, lfe_link) { - if (lfe->mi_is_enabled((mpegts_input_t*)lfe)) + if (lfe->mi_is_enabled((mpegts_input_t*)lfe, NULL)) return 1; } return 0; diff --git a/src/input/mpegts/linuxdvb/linuxdvb_frontend.c b/src/input/mpegts/linuxdvb/linuxdvb_frontend.c index 9cf81aa1..3d1d2f89 100644 --- a/src/input/mpegts/linuxdvb/linuxdvb_frontend.c +++ b/src/input/mpegts/linuxdvb/linuxdvb_frontend.c @@ -252,7 +252,7 @@ linuxdvb_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm ) } static int -linuxdvb_frontend_is_enabled ( mpegts_input_t *mi ) +linuxdvb_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm ) { linuxdvb_frontend_t *lfe = (linuxdvb_frontend_t*)mi; if (lfe->lfe_fe_path == NULL) return 0; diff --git a/src/input/mpegts/mpegts_input.c b/src/input/mpegts/mpegts_input.c index 2b3cd990..8a14cc84 100644 --- a/src/input/mpegts/mpegts_input.c +++ b/src/input/mpegts/mpegts_input.c @@ -162,7 +162,7 @@ const idclass_t mpegts_input_class = * *************************************************************************/ static int -mpegts_input_is_enabled ( mpegts_input_t *mi ) +mpegts_input_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm ) { return mi->mi_enabled; } diff --git a/src/input/mpegts/mpegts_mux.c b/src/input/mpegts/mpegts_mux.c index a19ba78b..a1c96d25 100644 --- a/src/input/mpegts/mpegts_mux.c +++ b/src/input/mpegts/mpegts_mux.c @@ -440,7 +440,7 @@ mpegts_mux_start /* First pass - free only */ if (!pass) { - int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input); + int e = mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm); int f = mmi->mmi_input->mi_is_free(mmi->mmi_input); tvhtrace("mpegts", "%s - enabled %d free %d", buf, e, f); if (e) enabled = 1; @@ -457,7 +457,7 @@ mpegts_mux_start } else { /* Enabled, valid and lower weight */ - if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input) && + if (mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mm) && !mmi->mmi_tune_failed && (weight > mmi->mmi_input->mi_get_weight(mmi->mmi_input))) { tune = mmi; diff --git a/src/input/mpegts/mpegts_mux_dvb.c b/src/input/mpegts/mpegts_mux_dvb.c index 094dd7e3..29d79927 100644 --- a/src/input/mpegts/mpegts_mux_dvb.c +++ b/src/input/mpegts/mpegts_mux_dvb.c @@ -592,7 +592,7 @@ dvb_mux_create_instances ( mpegts_mux_t *mm ) mpegts_network_link_t *mnl; LIST_FOREACH(mnl, &mm->mm_network->mn_inputs, mnl_mn_link) { mpegts_input_t *mi = mnl->mnl_input; - if (mi->mi_is_enabled(mi)) + if (mi->mi_is_enabled(mi, mm)) mi->mi_create_mux_instance(mi, mm); } } diff --git a/src/input/mpegts/mpegts_service.c b/src/input/mpegts/mpegts_service.c index 7a7dd880..15f4a9a2 100644 --- a/src/input/mpegts/mpegts_service.c +++ b/src/input/mpegts/mpegts_service.c @@ -189,7 +189,7 @@ mpegts_service_enlist(service_t *t, struct service_instance_list *sil) if (mmi->mmi_tune_failed) continue; - if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input)) continue; + if (!mmi->mmi_input->mi_is_enabled(mmi->mmi_input, mmi->mmi_mux)) continue; /* Set weight to -1 (forced) for already active mux */ if (mmi->mmi_mux->mm_active == mmi) { diff --git a/src/input/mpegts/satip/satip.c b/src/input/mpegts/satip/satip.c new file mode 100644 index 00000000..313ed747 --- /dev/null +++ b/src/input/mpegts/satip/satip.c @@ -0,0 +1,921 @@ +/* + * Tvheadend - SAT-IP client + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "input.h" +#include "htsbuf.h" +#include "htsmsg_xml.h" +#include "upnp.h" +#include "settings.h" +#include "satip_private.h" + +#include +#include + +static void satip_device_discovery_start( void ); + +/* + * SAT-IP client + */ + +static void +satip_device_class_save ( idnode_t *in ) +{ + satip_device_save((satip_device_t *)in); +} + +static idnode_set_t * +satip_device_class_get_childs ( idnode_t *in ) +{ + satip_device_t *sd = (satip_device_t *)in; + idnode_set_t *is = idnode_set_create(); + satip_frontend_t *lfe; + + TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link) + idnode_set_add(is, &lfe->ti_id, NULL); + return is; +} + +static const char * +satip_device_class_get_title( idnode_t *in ) +{ + static char buf[256]; + satip_device_t *sd = (satip_device_t *)in; + snprintf(buf, sizeof(buf), + "%s - %s", sd->sd_info.friendlyname, sd->sd_info.addr); + return buf; +} + +const idclass_t satip_device_class = +{ + .ic_class = "satip_client", + .ic_caption = "SAT>IP Client", + .ic_save = satip_device_class_save, + .ic_get_childs = satip_device_class_get_childs, + .ic_get_title = satip_device_class_get_title, + .ic_properties = (const property_t[]){ + { + .type = PT_BOOL, + .id = "fullmux_ok", + .name = "Full Mux Rx mode supported", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_fullmux_ok), + }, + { + .type = PT_INT, + .id = "sigscale", + .name = "Signal scale (240 or 100)", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_sig_scale), + }, + { + .type = PT_INT, + .id = "pids_max", + .name = "Maximum PIDs", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_pids_max), + }, + { + .type = PT_INT, + .id = "pids_len", + .name = "Maximum length of PIDs", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_pids_len), + }, + { + .type = PT_BOOL, + .id = "pids_deladd", + .name = "addpids/delpids supported", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_pids_deladd), + }, + { + .type = PT_BOOL, + .id = "pids0", + .name = "PIDs in setup", + .opts = PO_ADVANCED, + .off = offsetof(satip_device_t, sd_pids0), + }, + { + .type = PT_STR, + .id = "addr", + .name = "IP Address", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.addr), + }, + { + .type = PT_STR, + .id = "device_uuid", + .name = "UUID", + .opts = PO_RDONLY, + .off = offsetof(satip_device_t, sd_info.uuid), + }, + { + .type = PT_STR, + .id = "friendly", + .name = "Friendly Name", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.friendlyname), + }, + { + .type = PT_STR, + .id = "serialnum", + .name = "Serial Number", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.serialnum), + }, + { + .type = PT_STR, + .id = "tunercfg", + .name = "Tuner Configuration", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.tunercfg), + }, + { + .type = PT_STR, + .id = "manufacturer", + .name = "Manufacturer", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.manufacturer), + }, + { + .type = PT_STR, + .id = "manufurl", + .name = "Manufacturer URL", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.manufacturerURL), + }, + { + .type = PT_STR, + .id = "modeldesc", + .name = "Model Description", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.modeldesc), + }, + { + .type = PT_STR, + .id = "modelname", + .name = "Model Name", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.modelname), + }, + { + .type = PT_STR, + .id = "modelnum", + .name = "Model Number", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.modelnum), + }, + { + .type = PT_STR, + .id = "bootid", + .name = "Boot ID", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.bootid), + }, + { + .type = PT_STR, + .id = "configid", + .name = "Config ID", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.configid), + }, + { + .type = PT_STR, + .id = "deviceid", + .name = "Device ID", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.deviceid), + }, + { + .type = PT_STR, + .id = "presentation", + .name = "Presentation", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.presentation), + }, + { + .type = PT_STR, + .id = "location", + .name = "Location", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.location), + }, + { + .type = PT_STR, + .id = "server", + .name = "Server", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.server), + }, + { + .type = PT_STR, + .id = "myaddr", + .name = "Local IP Address", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_device_t, sd_info.myaddr), + }, + {} + } +}; + +/* + * Create entry + */ +static void +satip_device_calc_bin_uuid( uint8_t *uuid, const char *satip_uuid ) +{ + SHA_CTX sha1; + + SHA1_Init(&sha1); + SHA1_Update(&sha1, (void*)satip_uuid, strlen(satip_uuid)); + SHA1_Final(uuid, &sha1); +} + +static void +satip_device_calc_uuid( uuid_t *uuid, const char *satip_uuid ) +{ + uint8_t uuidbin[20]; + + satip_device_calc_bin_uuid(uuidbin, satip_uuid); + bin2hex(uuid->hex, sizeof(uuid->hex), uuidbin, sizeof(uuidbin)); +} + +static void +satip_device_hack( satip_device_t *sd ) +{ + if (sd->sd_info.deviceid[0] && + strcmp(sd->sd_info.server, "Linux/1.0 UPnP/1.1 IDL4K/1.0") == 0) { + /* AXE Linux distribution - Inverto firmware */ + /* version V1.13.0.105 and probably less */ + /* really ugly firmware - soooooo much restrictions */ + sd->sd_fullmux_ok = 0; + sd->sd_pids_max = 32; + sd->sd_pids_deladd = 0; + tvhwarn("satip", "Detected old Inverto firmware V1.13.0.105 and less"); + tvhwarn("satip", "Upgrade to V1.16.0.120 - http://http://www.inverto.tv/support/ - IDL400s"); + } else if (strstr(sd->sd_info.location, ":8888/octonet.xml")) { + /* OctopusNet requires pids in the SETUP RTSP command */ + sd->sd_pids0 = 1; + } +} + +static satip_device_t * +satip_device_create( satip_device_info_t *info ) +{ + satip_device_t *sd = calloc(1, sizeof(satip_device_t)); + uuid_t uuid; + htsmsg_t *conf = NULL, *feconf = NULL; + char *argv[10]; + int i, j, n, m, fenum, t2, save = 0; + dvb_fe_type_t type; + + satip_device_calc_uuid(&uuid, info->uuid); + + conf = hts_settings_load("input/satip/adapters/%s", uuid.hex); + + /* some sane defaults */ + sd->sd_fullmux_ok = 1; + sd->sd_pids_len = 127; + sd->sd_pids_max = 32; + sd->sd_pids_deladd = 1; + sd->sd_sig_scale = 240; + + if (!tvh_hardware_create0((tvh_hardware_t*)sd, &satip_device_class, + uuid.hex, conf)) { + free(sd); + return NULL; + } + + TAILQ_INIT(&sd->sd_frontends); + + /* we may check if uuid matches, but the SHA hash should be enough */ + if (sd->sd_info.uuid) + free(sd->sd_info.uuid); + +#define ASSIGN(x) sd->sd_info.x = info->x; info->x = NULL + ASSIGN(myaddr); + ASSIGN(addr); + ASSIGN(uuid); + ASSIGN(bootid); + ASSIGN(configid); + ASSIGN(deviceid); + ASSIGN(server); + ASSIGN(location); + ASSIGN(friendlyname); + ASSIGN(manufacturer); + ASSIGN(manufacturerURL); + ASSIGN(modeldesc); + ASSIGN(modelname); + ASSIGN(modelnum); + ASSIGN(serialnum); + ASSIGN(presentation); + ASSIGN(tunercfg); +#undef ASSIGN + + /* + * device specific hacks + */ + satip_device_hack(sd); + + if (conf) + feconf = htsmsg_get_map(conf, "frontends"); + save = !conf || !feconf; + + n = http_tokenize(sd->sd_info.tunercfg, argv, 10, ','); + for (i = 0, fenum = 1; i < n; i++) { + type = DVB_TYPE_NONE; + t2 = 0; + if (strncmp(argv[i], "DVBS2-", 6) == 0) { + type = DVB_TYPE_S; + m = atoi(argv[i] + 6); + } else if (strncmp(argv[i], "DVBT2-", 6) == 0) { + type = DVB_TYPE_T; + m = atoi(argv[i] + 6); + t2 = 1; + } else if (strncmp(argv[i], "DVBT-", 5) == 0) { + type = DVB_TYPE_T; + m = atoi(argv[i] + 5); + } else if (strncmp(argv[i], "DVBC-", 5) == 0) { + type = DVB_TYPE_C; + m = atoi(argv[i] + 5); + } + if (type == DVB_TYPE_NONE) { + tvhlog(LOG_ERR, "satip", "%s: bad tuner type [%s]", sd->sd_info.addr, argv[i]); + } else if (m < 0 || m > 32) { + tvhlog(LOG_ERR, "satip", "%s: bad tuner count [%s]", sd->sd_info.addr, argv[i]); + } else { + for (j = 0; j < m; j++) + if (satip_frontend_create(feconf, sd, type, t2, fenum)) + fenum++; + } + } + + if (save) + satip_device_save(sd); + + htsmsg_destroy(conf); + + return sd; +} + +static satip_device_t * +satip_device_find( const char *satip_uuid ) +{ + tvh_hardware_t *th; + uint8_t binuuid[20]; + + satip_device_calc_bin_uuid(binuuid, satip_uuid); + TVH_HARDWARE_FOREACH(th) { + if (idnode_is_instance(&th->th_id, &satip_device_class) && + memcmp(th->th_id.in_uuid, binuuid, UUID_BIN_SIZE) == 0) + return (satip_device_t *)th; + } + return NULL; +} + +static satip_device_t * +satip_device_find_by_descurl( const char *descurl ) +{ + tvh_hardware_t *th; + + TVH_HARDWARE_FOREACH(th) { + if (idnode_is_instance(&th->th_id, &satip_device_class) && + strcmp(((satip_device_t *)th)->sd_info.location, descurl) == 0) + return (satip_device_t *)th; + } + return NULL; +} + +void +satip_device_save( satip_device_t *sd ) +{ + satip_frontend_t *lfe; + htsmsg_t *m, *l; + + m = htsmsg_create_map(); + idnode_save(&sd->th_id, m); + + l = htsmsg_create_map(); + TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link) + satip_frontend_save(lfe, l); + htsmsg_add_msg(m, "frontends", l); + + hts_settings_save(m, "input/satip/adapters/%s", + idnode_uuid_as_str(&sd->th_id)); + htsmsg_destroy(m); +} + +void +satip_device_destroy( satip_device_t *sd ) +{ + satip_frontend_t *lfe; + + lock_assert(&global_lock); + + gtimer_disarm(&sd->sd_destroy_timer); + + while ((lfe = TAILQ_FIRST(&sd->sd_frontends)) != NULL) + satip_frontend_delete(lfe); + +#define FREEM(x) free(sd->sd_info.x) + FREEM(myaddr); + FREEM(addr); + FREEM(uuid); + FREEM(bootid); + FREEM(configid); + FREEM(deviceid); + FREEM(location); + FREEM(server); + FREEM(friendlyname); + FREEM(manufacturer); + FREEM(manufacturerURL); + FREEM(modeldesc); + FREEM(modelname); + FREEM(modelnum); + FREEM(serialnum); + FREEM(presentation); + FREEM(tunercfg); +#undef FREEM + + tvh_hardware_delete((tvh_hardware_t*)sd); + free(sd); +} + +static void +satip_device_destroy_cb( void *aux ) +{ + satip_device_destroy((satip_device_t *)aux); + satip_device_discovery_start(); +} + +void +satip_device_destroy_later( satip_device_t *sd, int after ) +{ + gtimer_arm_ms(&sd->sd_destroy_timer, satip_device_destroy_cb, sd, after); +} + +/* + * Discovery job + */ + +typedef struct satip_discovery { + TAILQ_ENTRY(satip_discovery) disc_link; + char *myaddr; + char *location; + char *server; + char *uuid; + char *bootid; + char *configid; + char *deviceid; + url_t url; + http_client_t *http_client; + time_t http_start; +} satip_discovery_t; + +TAILQ_HEAD(satip_discovery_queue, satip_discovery); + +static int satip_discoveries_count; +static struct satip_discovery_queue satip_discoveries; +static upnp_service_t *satip_discovery_service; +static gtimer_t satip_discovery_timer; +static gtimer_t satip_discovery_timerq; +static str_list_t *satip_static_clients; + +static void +satip_discovery_destroy(satip_discovery_t *d, int unlink) +{ + if (d == NULL) + return; + if (unlink) { + satip_discoveries_count--; + TAILQ_REMOVE(&satip_discoveries, d, disc_link); + } + if (d->http_client) + http_client_close(d->http_client); + urlreset(&d->url); + free(d->myaddr); + free(d->location); + free(d->server); + free(d->uuid); + free(d->bootid); + free(d->configid); + free(d->deviceid); + free(d); +} + +static satip_discovery_t * +satip_discovery_find(satip_discovery_t *d) +{ + satip_discovery_t *sd; + + TAILQ_FOREACH(sd, &satip_discoveries, disc_link) + if (strcmp(sd->uuid, d->uuid) == 0) + return sd; + return NULL; +} + +static void +satip_discovery_http_closed(http_client_t *hc, int errn) +{ + satip_discovery_t *d = hc->hc_aux; + char *s; + htsmsg_t *xml = NULL, *tags, *root, *device; + const char *friendlyname, *manufacturer, *manufacturerURL, *modeldesc; + const char *modelname, *modelnum, *serialnum; + const char *presentation, *tunercfg, *udn, *uuid; + const char *cs; + satip_device_info_t info; + char errbuf[100]; + char *argv[10]; + int i, n; + + s = http_arg_get(&hc->hc_args, "Content-Type"); + if (s && strcasecmp(s, "text/xml")) { + errn = EMEDIUMTYPE; + s = NULL; + } + if (errn != 0 || s == NULL || hc->hc_code != 200 || + hc->hc_data_size == 0 || hc->hc_data == NULL) { + tvhlog(LOG_ERR, "satip", "Cannot get %s: %s", d->location, strerror(errn)); + return; + } + +#if ENABLE_TRACE + tvhtrace("satip", "received XML description from %s", hc->hc_host); + tvhlog_hexdump("satip", hc->hc_data, hc->hc_data_size); +#endif + + s = hc->hc_data + hc->hc_data_size - 1; + while (s != hc->hc_data && *s != '/') + s--; + if (s != hc->hc_data) + s--; + if (strncmp(s, "", 7)) + return; + /* Parse */ + xml = htsmsg_xml_deserialize(hc->hc_data, errbuf, sizeof(errbuf)); + hc->hc_data = NULL; + if (!xml) { + tvhlog(LOG_ERR, "satip_discovery_desc", "htsmsg_xml_deserialize error %s", errbuf); + goto finish; + } + if ((tags = htsmsg_get_map(xml, "tags")) == NULL) + goto finish; + if ((root = htsmsg_get_map(tags, "root")) == NULL) + goto finish; + if ((device = htsmsg_get_map(root, "tags")) == NULL) + goto finish; + if ((device = htsmsg_get_map(device, "device")) == NULL) + goto finish; + if ((device = htsmsg_get_map(device, "tags")) == NULL) + goto finish; + if ((cs = htsmsg_xml_get_cdata_str(device, "deviceType")) == NULL) + goto finish; + if (strcmp(cs, "urn:ses-com:device:SatIPServer:1")) + goto finish; + if ((friendlyname = htsmsg_xml_get_cdata_str(device, "friendlyName")) == NULL) + goto finish; + if ((manufacturer = htsmsg_xml_get_cdata_str(device, "manufacturer")) == NULL) + goto finish; + if ((manufacturerURL = htsmsg_xml_get_cdata_str(device, "manufacturerURL")) == NULL) + goto finish; + if ((modeldesc = htsmsg_xml_get_cdata_str(device, "modelDescription")) == NULL) + goto finish; + if ((modelname = htsmsg_xml_get_cdata_str(device, "modelName")) == NULL) + goto finish; + if ((modelnum = htsmsg_xml_get_cdata_str(device, "modelNumber")) == NULL) + goto finish; + if ((serialnum = htsmsg_xml_get_cdata_str(device, "serialNumber")) == NULL) + goto finish; + if ((presentation = htsmsg_xml_get_cdata_str(device, "presentationURL")) == NULL) + goto finish; + if ((udn = htsmsg_xml_get_cdata_str(device, "UDN")) == NULL) + goto finish; + if ((tunercfg = htsmsg_xml_get_cdata_str(device, "urn:ses-com:satipX_SATIPCAP")) == NULL) + goto finish; + + uuid = NULL; + n = http_tokenize((char *)udn, argv, ARRAY_SIZE(argv), ':'); + for (i = 0; i < n+1; i++) + if (argv[i] && strcmp(argv[i], "uuid") == 0) { + uuid = argv[++i]; + break; + } + if (uuid == NULL || (d->uuid[0] && strcmp(uuid, d->uuid))) + goto finish; + + info.myaddr = strdup(d->myaddr); + info.addr = strdup(d->url.host); + info.uuid = strdup(uuid); + info.bootid = strdup(d->bootid); + info.configid = strdup(d->configid); + info.deviceid = strdup(d->deviceid); + info.location = strdup(d->location); + info.server = strdup(d->server); + info.friendlyname = strdup(friendlyname); + info.manufacturer = strdup(manufacturer); + info.manufacturerURL = strdup(manufacturerURL); + info.modeldesc = strdup(modeldesc); + info.modelname = strdup(modelname); + info.modelnum = strdup(modelnum); + info.serialnum = strdup(serialnum); + info.presentation = strdup(presentation); + info.tunercfg = strdup(tunercfg); + htsmsg_destroy(xml); + xml = NULL; + pthread_mutex_lock(&global_lock); + if (!satip_device_find(info.uuid)) + satip_device_create(&info); + pthread_mutex_unlock(&global_lock); + free(info.myaddr); + free(info.location); + free(info.server); + free(info.addr); + free(info.uuid); + free(info.bootid); + free(info.configid); + free(info.deviceid); + free(info.friendlyname); + free(info.manufacturer); + free(info.manufacturerURL); + free(info.modeldesc); + free(info.modelname); + free(info.modelnum); + free(info.serialnum); + free(info.presentation); + free(info.tunercfg); +finish: + htsmsg_destroy(xml); +} + +static void +satip_discovery_timerq_cb(void *aux) +{ + satip_discovery_t *d, *next; + int r; + + lock_assert(&global_lock); + + next = TAILQ_FIRST(&satip_discoveries); + while (next) { + d = next; + next = TAILQ_NEXT(d, disc_link); + if (d->http_client) { + if (dispatch_clock - d->http_start > 4) + satip_discovery_destroy(d, 1); + continue; + } + + d->http_client = http_client_connect(d, HTTP_VERSION_1_1, d->url.scheme, + d->url.host, d->url.port); + if (d->http_client == NULL) + satip_discovery_destroy(d, 1); + else { + d->http_start = dispatch_clock; + d->http_client->hc_conn_closed = satip_discovery_http_closed; + http_client_register(d->http_client); + r = http_client_simple(d->http_client, &d->url); + if (r < 0) + satip_discovery_destroy(d, 1); + } + } + if (TAILQ_FIRST(&satip_discoveries)) + gtimer_arm(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 5); +} + +static void +satip_discovery_service_received + (uint8_t *data, size_t len, udp_connection_t *conn, + struct sockaddr_storage *storage) +{ + char *buf, *ptr, *saveptr; + char *argv[10]; + char *st = NULL; + char *location = NULL; + char *server = NULL; + char *uuid = NULL; + char *bootid = NULL; + char *configid = NULL; + char *deviceid = NULL; + char sockbuf[128]; + satip_discovery_t *d; + int n, i; + + if (len > 8191 || satip_discoveries_count > 100) + return; + buf = alloca(len+1); + memcpy(buf, data, len); + buf[len] = '\0'; + ptr = strtok_r(buf, "\r\n", &saveptr); + /* Request decoder */ + if (ptr) { + if (http_tokenize(ptr, argv, 3, -1) != 3) + return; + if (conn->multicast) { + if (strcmp(argv[0], "NOTIFY")) + return; + if (strcmp(argv[1], "*")) + return; + if (strcmp(argv[2], "HTTP/1.1")) + return; + } else { + if (strcmp(argv[0], "HTTP/1.1")) + return; + if (strcmp(argv[1], "200")) + return; + } + ptr = strtok_r(NULL, "\r\n", &saveptr); + } + /* Header decoder */ + while (1) { + if (ptr == NULL) + break; + if (http_tokenize(ptr, argv, 2, -1) == 2) { + if (strcmp(argv[0], "ST:") == 0) + st = argv[1]; + else if (strcmp(argv[0], "LOCATION:") == 0) + location = argv[1]; + else if (strcmp(argv[0], "SERVER:") == 0) + server = argv[1]; + else if (strcmp(argv[0], "BOOTID.UPNP.ORG:") == 0) + bootid = argv[1]; + else if (strcmp(argv[0], "CONFIGID.UPNP.ORG:") == 0) + configid = argv[1]; + else if (strcmp(argv[0], "DEVICEID.SES.COM:") == 0) + deviceid = argv[1]; + else if (strcmp(argv[0], "USN:") == 0) { + n = http_tokenize(argv[1], argv, ARRAY_SIZE(argv), ':'); + for (i = 0; i < n+1; i++) + if (argv[i] && strcmp(argv[i], "uuid") == 0) { + uuid = argv[++i]; + break; + } + } + } + ptr = strtok_r(NULL, "\r\n", &saveptr); + } + /* Sanity checks */ + if (st == NULL || strcmp(st, "urn:ses-com:device:SatIPServer:1")) + return; + if (uuid == NULL && strlen(uuid) < 16) + return; + if (location == NULL && strncmp(location, "http://", 7)) + return; + if (bootid == NULL || configid == NULL || server == NULL) + return; + + /* Forward information to next layer */ + + d = calloc(1, sizeof(satip_discovery_t)); + if (inet_ntop(storage->ss_family, IP_IN_ADDR(conn->ip), + sockbuf, sizeof(sockbuf)) == NULL) { + satip_discovery_destroy(d, 0); + return; + } + d->myaddr = strdup(sockbuf); + d->location = strdup(location); + d->server = strdup(server); + d->uuid = strdup(uuid); + d->bootid = strdup(bootid); + d->configid = strdup(configid); + d->deviceid = strdup(deviceid ? deviceid : ""); + if (urlparse(d->location, &d->url)) { + satip_discovery_destroy(d, 0); + return; + } + + pthread_mutex_lock(&global_lock); + i = 1; + if (!satip_discovery_find(d) && !satip_device_find(d->uuid)) { + TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link); + satip_discoveries_count++; + gtimer_arm_ms(&satip_discovery_timerq, satip_discovery_timerq_cb, NULL, 250); + i = 0; + } + pthread_mutex_unlock(&global_lock); + if (i) /* duplicate */ + satip_discovery_destroy(d, 0); +} + +static void +satip_discovery_static(const char *descurl) +{ + satip_discovery_t *d; + + lock_assert(&global_lock); + + if (satip_device_find_by_descurl(descurl)) + return; + d = calloc(1, sizeof(satip_discovery_t)); + if (urlparse(descurl, &d->url)) { + satip_discovery_destroy(d, 0); + return; + } + d->myaddr = strdup(d->url.host); + d->location = strdup(descurl); + d->server = strdup(""); + d->uuid = strdup(""); + d->bootid = strdup(""); + d->configid = strdup(""); + d->deviceid = strdup(""); + TAILQ_INSERT_TAIL(&satip_discoveries, d, disc_link); + satip_discoveries_count++; + satip_discovery_timerq_cb(NULL); +} + +static void +satip_discovery_service_destroy(upnp_service_t *us) +{ + satip_discovery_service = NULL; +} + +static void +satip_discovery_timer_cb(void *aux) +{ +#define MSG "\ +M-SEARCH * HTTP/1.1\r\n\ +HOST: 239.255.255.250:1900\r\n\ +MAN: \"ssdp:discover\"\r\n\ +MX: 2\r\n\ +ST: urn:ses-com:device:SatIPServer:1\r\n\ +\r\n" + int i; + + if (!tvheadend_running) + return; + if (!upnp_running) { + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1); + return; + } + if (satip_discovery_service == NULL) { + satip_discovery_service = upnp_service_create(upnp_service); + if (satip_discovery_service) { + satip_discovery_service->us_received = satip_discovery_service_received; + satip_discovery_service->us_destroy = satip_discovery_service_destroy; + } + } + if (satip_discovery_service) { + htsbuf_queue_t q; + htsbuf_queue_init(&q, 0); + htsbuf_append(&q, MSG, sizeof(MSG)-1); + upnp_send(&q, NULL); + htsbuf_queue_flush(&q); + } + for (i = 0; i < satip_static_clients->num; i++) + satip_discovery_static(satip_static_clients->str[i]); + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 3600); +#undef MSG +} + +static void +satip_device_discovery_start( void ) +{ + gtimer_arm(&satip_discovery_timer, satip_discovery_timer_cb, NULL, 1); +} + +/* + * Initialization + */ + +void satip_init ( str_list_t *clients ) +{ + TAILQ_INIT(&satip_discoveries); + satip_static_clients = clients; + satip_device_discovery_start(); +} + +void satip_done ( void ) +{ + tvh_hardware_t *th, *n; + satip_discovery_t *d, *nd; + + pthread_mutex_lock(&global_lock); + for (th = LIST_FIRST(&tvh_hardware); th != NULL; th = n) { + n = LIST_NEXT(th, th_link); + if (idnode_is_instance(&th->th_id, &satip_device_class)) { + satip_device_destroy((satip_device_t *)th); + } + } + for (d = TAILQ_FIRST(&satip_discoveries); d != NULL; d = nd) { + nd = TAILQ_NEXT(d, disc_link); + satip_discovery_destroy(d, 1); + } + pthread_mutex_unlock(&global_lock); +} diff --git a/src/input/mpegts/satip/satip.h b/src/input/mpegts/satip/satip.h new file mode 100644 index 00000000..59d035eb --- /dev/null +++ b/src/input/mpegts/satip/satip.h @@ -0,0 +1,26 @@ +/* + * Tvheadend - SAT-IP DVB private data + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_SATIP_H__ +#define __TVH_SATIP_H__ + +void satip_init( str_list_t *clients ); +void satip_done( void ); + +#endif /* __TVH_SATIP_H__ */ diff --git a/src/input/mpegts/satip/satip_frontend.c b/src/input/mpegts/satip/satip_frontend.c new file mode 100644 index 00000000..5c119389 --- /dev/null +++ b/src/input/mpegts/satip/satip_frontend.c @@ -0,0 +1,1398 @@ +/* + * Tvheadend - SAT>IP DVB frontend + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "tvheadend.h" +#include "tvhpoll.h" +#include "streaming.h" +#include "http.h" +#include "satip_private.h" + +static int +satip_frontend_tune1 + ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi ); + +/* + * + */ +static satip_frontend_t * +satip_frontend_find_by_number( satip_device_t *sd, int num ) +{ + satip_frontend_t *lfe; + + TAILQ_FOREACH(lfe, &sd->sd_frontends, sf_link) + if (lfe->sf_number == num) + return lfe; + return NULL; +} + +/* ************************************************************************** + * Class definition + * *************************************************************************/ + +static void +satip_frontend_class_save ( idnode_t *in ) +{ + satip_device_t *la = ((satip_frontend_t*)in)->sf_device; + satip_device_save(la); +} + +static int +satip_frontend_set_new_type + ( satip_frontend_t *lfe, const char *type ) +{ + free(lfe->sf_type_override); + lfe->sf_type_override = strdup(type); + satip_device_destroy_later(lfe->sf_device, 100); + return 1; +} + +static int +satip_frontend_class_override_set( void *obj, const void * p ) +{ + satip_frontend_t *lfe = obj; + const char *s = p; + + if (lfe->sf_type_override == NULL) { + if (strlen(p) > 0) + return satip_frontend_set_new_type(lfe, s); + } else if (strcmp(lfe->sf_type_override, s)) + return satip_frontend_set_new_type(lfe, s); + return 0; +} + +static htsmsg_t * +satip_frontend_class_override_enum( void * p ) +{ + htsmsg_t *m = htsmsg_create_list(); + htsmsg_add_str(m, NULL, "DVB-T"); + htsmsg_add_str(m, NULL, "DVB-C"); + return m; +} + +const idclass_t satip_frontend_class = +{ + .ic_super = &mpegts_input_class, + .ic_class = "satip_frontend", + .ic_caption = "SAT>IP DVB Frontend", + .ic_save = satip_frontend_class_save, + .ic_properties = (const property_t[]) { + { + .type = PT_INT, + .id = "fe_number", + .name = "Frontend Number", + .opts = PO_RDONLY | PO_NOSAVE, + .off = offsetof(satip_frontend_t, sf_number), + }, + { + .type = PT_INT, + .id = "udp_rtp_port", + .name = "UDP RTP Port Number (2 ports)", + .off = offsetof(satip_frontend_t, sf_udp_rtp_port), + }, + { + .type = PT_BOOL, + .id = "play2", + .name = "Send full PLAY cmd", + .opts = PO_ADVANCED, + .off = offsetof(satip_frontend_t, sf_play2), + }, + {} + } +}; + +const idclass_t satip_frontend_dvbt_class = +{ + .ic_super = &satip_frontend_class, + .ic_class = "satip_frontend_dvbt", + .ic_caption = "SAT>IP DVB-T Frontend", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .id = "fe_override", + .name = "Network Type", + .set = satip_frontend_class_override_set, + .list = satip_frontend_class_override_enum, + .off = offsetof(satip_frontend_t, sf_type_override), + }, + {} + } +}; + +static idnode_set_t * +satip_frontend_dvbs_class_get_childs ( idnode_t *self ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)self; + idnode_set_t *is = idnode_set_create(); + satip_satconf_t *sfc; + TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) + idnode_set_add(is, &sfc->sfc_id, NULL); + return is; +} + +static int +satip_frontend_dvbs_class_positions_set ( void *self, const void *val ) +{ + satip_frontend_t *lfe = self; + int n = *(int *)val; + + if (n < 0 || n > 32) + return 0; + if (n != lfe->sf_positions) { + lfe->sf_positions = n; + satip_satconf_updated_positions(lfe); + return 1; + } + return 0; +} + +static int +satip_frontend_dvbs_class_master_set ( void *self, const void *val ) +{ + satip_frontend_t *lfe = self; + satip_frontend_t *lfe2 = NULL; + int num = *(int *)val, pos = 0; + + if (num) { + TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link) + if (lfe2 != lfe && lfe2->sf_type == lfe->sf_type) + if (++pos == num) { + num = lfe2->sf_number; + break; + } + } + if (lfe2 == NULL) + num = 0; + else if (lfe2->sf_master) + num = lfe2->sf_master; + if (lfe->sf_master != num) { + lfe->sf_master = num; + satip_device_destroy_later(lfe->sf_device, 100); + return 1; + } + return 0; +} + +static htsmsg_t * +satip_frontend_dvbs_class_master_enum( void * self ) +{ + satip_frontend_t *lfe = self, *lfe2; + satip_device_t *sd = lfe->sf_device; + htsmsg_t *m = htsmsg_create_list(); + htsmsg_add_str(m, NULL, "This Tuner"); + TAILQ_FOREACH(lfe2, &sd->sd_frontends, sf_link) + if (lfe2 != lfe && lfe2->sf_type == lfe->sf_type) + htsmsg_add_str(m, NULL, lfe2->mi_name); + return m; +} + +const idclass_t satip_frontend_dvbs_class = +{ + .ic_super = &satip_frontend_class, + .ic_class = "satip_frontend_dvbs", + .ic_caption = "SAT>IP DVB-S Frontend", + .ic_get_childs = satip_frontend_dvbs_class_get_childs, + .ic_properties = (const property_t[]){ + { + .type = PT_INT, + .id = "positions", + .name = "Sattellite Positions", + .set = satip_frontend_dvbs_class_positions_set, + .opts = PO_NOSAVE, + .off = offsetof(satip_frontend_t, sf_positions), + .def.i = 4 + }, + { + .type = PT_INT, + .id = "fe_master", + .name = "Master Tuner", + .set = satip_frontend_dvbs_class_master_set, + .list = satip_frontend_dvbs_class_master_enum, + .off = offsetof(satip_frontend_t, sf_master), + }, + { + .id = "networks", + .type = PT_NONE, + }, + {} + } +}; + +const idclass_t satip_frontend_dvbs_slave_class = +{ + .ic_super = &satip_frontend_class, + .ic_class = "satip_frontend_dvbs", + .ic_caption = "SAT>IP DVB-S Slave Frontend", + .ic_properties = (const property_t[]){ + { + .type = PT_INT, + .id = "fe_master", + .name = "Master Tuner", + .set = satip_frontend_dvbs_class_master_set, + .list = satip_frontend_dvbs_class_master_enum, + .off = offsetof(satip_frontend_t, sf_master), + }, + { + .id = "networks", + .type = PT_NONE, + }, + {} + } +}; + +const idclass_t satip_frontend_dvbc_class = +{ + .ic_super = &satip_frontend_class, + .ic_class = "satip_frontend_dvbc", + .ic_caption = "SAT>IP DVB-C Frontend", + .ic_properties = (const property_t[]){ + { + .type = PT_STR, + .id = "fe_override", + .name = "Network Type", + .set = satip_frontend_class_override_set, + .list = satip_frontend_class_override_enum, + .off = offsetof(satip_frontend_t, sf_type_override), + }, + {} + } +}; + +/* ************************************************************************** + * Class methods + * *************************************************************************/ + +static int +satip_frontend_is_free ( mpegts_input_t *mi ) +{ + /* TODO: Add some RTSP live checks here */ + return mpegts_input_is_free(mi); +} + +static int +satip_frontend_get_weight ( mpegts_input_t *mi ) +{ + return mpegts_input_get_weight(mi); +} + +static int +satip_frontend_get_priority ( mpegts_input_t *mi, mpegts_mux_t *mm ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + int r = mpegts_input_get_priority(mi, mm); + if (lfe->sf_positions) + r += satip_satconf_get_priority(lfe, mm); + return r; +} + +static int +satip_frontend_get_grace ( mpegts_input_t *mi, mpegts_mux_t *mm ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + int r = 5; + if (lfe->sf_positions) + r = 10; + return r; +} + +static int +satip_frontend_match_satcfg ( satip_frontend_t *lfe2, mpegts_mux_t *mm2 ) +{ + mpegts_mux_t *mm1; + dvb_mux_conf_t *mc1, *mc2; + int position, high1, high2; + + if (lfe2->sf_mmi == NULL) + return 0; + mm1 = lfe2->sf_mmi->mmi_mux; + position = satip_satconf_get_position(lfe2, mm2); + if (lfe2->sf_position != position) + return 0; + mc1 = &((dvb_mux_t *)mm1)->lm_tuning; + mc2 = &((dvb_mux_t *)mm2)->lm_tuning; + if (mc1->dmc_fe_type != DVB_TYPE_S || mc2->dmc_fe_type != DVB_TYPE_S) + return 0; + if (mc1->u.dmc_fe_qpsk.polarisation != mc2->u.dmc_fe_qpsk.polarisation) + return 0; + high1 = mc1->dmc_fe_freq > 11700000; + high2 = mc2->dmc_fe_freq > 11700000; + if (high1 != high2) + return 0; + return 1; +} + +static int +satip_frontend_is_enabled ( mpegts_input_t *mi, mpegts_mux_t *mm ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + satip_frontend_t *lfe2; + + lock_assert(&global_lock); + + if (!lfe->mi_enabled) return 0; + if (lfe->sf_type != DVB_TYPE_S) return 1; + /* check if any "blocking" tuner is running */ + TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link) { + if (lfe2 == lfe) continue; + if (lfe2->sf_type != DVB_TYPE_S) continue; + if (lfe->sf_master == lfe2->sf_number) { + if (!lfe2->sf_running) + return 0; /* master must be running */ + return satip_frontend_match_satcfg(lfe2, mm); + } + if (lfe2->sf_master == lfe->sf_number && lfe2->sf_running) { + if (lfe2->sf_mmi == NULL) + return 0; + return satip_frontend_match_satcfg(lfe2, mm); + } + } + return 1; +} + +static void +satip_frontend_stop_mux + ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + char buf1[256], buf2[256]; + + mi->mi_display_name(mi, buf1, sizeof(buf1)); + mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2)); + tvhdebug("satip", "%s - stopping %s", buf1, buf2); + + lfe->sf_running = 0; + lfe->sf_mmi = NULL; + + gtimer_disarm(&lfe->sf_monitor_timer); + + /* Stop thread */ + if (lfe->sf_dvr_pipe.wr > 0) { + tvh_write(lfe->sf_dvr_pipe.wr, "", 1); + tvhtrace("satip", "%s - waiting for dvr thread", buf1); + pthread_join(lfe->sf_dvr_thread, NULL); + tvh_pipe_close(&lfe->sf_dvr_pipe); + tvhdebug("satip", "%s - stopped dvr thread", buf1); + } + + udp_close(lfe->sf_rtp); lfe->sf_rtp = NULL; + udp_close(lfe->sf_rtcp); lfe->sf_rtcp = NULL; + + free(lfe->sf_pids); lfe->sf_pids = NULL; + free(lfe->sf_pids_tuned); lfe->sf_pids_tuned = NULL; +} + +static int +satip_frontend_start_mux + ( mpegts_input_t *mi, mpegts_mux_instance_t *mmi ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + if (lfe->sf_positions > 0) + lfe->sf_position = satip_satconf_get_position(lfe, mmi->mmi_mux); + return satip_frontend_tune1((satip_frontend_t*)mi, mmi); +} + +static int +satip_frontend_add_pid( satip_frontend_t *lfe, int pid) +{ + int mid, div; + + if (pid < 0 || pid >= 8191) + return 0; + + pthread_mutex_lock(&lfe->sf_dvr_lock); + if (lfe->sf_pids_count >= lfe->sf_pids_size) { + lfe->sf_pids_size += 64; + lfe->sf_pids = realloc(lfe->sf_pids, + lfe->sf_pids_size * sizeof(uint16_t)); + lfe->sf_pids_tuned = realloc(lfe->sf_pids_tuned, + lfe->sf_pids_size * sizeof(uint16_t)); + } + + if (lfe->sf_pids_count == 0) { + lfe->sf_pids[lfe->sf_pids_count++] = pid; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + return 1; + } + +#if 0 + printf("Insert PID: %i\n", pid); + if (pid == 0) + printf("HERE!!!\n"); + { int i; for (i = 0; i < lfe->sf_pids_count; i++) + printf("Bpid[%i] = %i\n", i, lfe->sf_pids[i]); } +#endif + /* insert pid to the sorted array */ + mid = div = lfe->sf_pids_count / 2; + while (1) { + assert(mid >= 0 && mid < lfe->sf_pids_count); + if (div > 1) + div /= 2; + if (lfe->sf_pids[mid] == pid) { + pthread_mutex_unlock(&lfe->sf_dvr_lock); + return 0; + } + if (lfe->sf_pids[mid] < pid) { + if (mid + 1 >= lfe->sf_pids_count) { + lfe->sf_pids[lfe->sf_pids_count++] = pid; + break; + } + if (lfe->sf_pids[mid + 1] > pid) { + mid++; + if (mid < lfe->sf_pids_count) + memmove(&lfe->sf_pids[mid + 1], &lfe->sf_pids[mid], + (lfe->sf_pids_count - mid) * sizeof(uint16_t)); + lfe->sf_pids[mid] = pid; + lfe->sf_pids_count++; + break; + } + mid += div; + } else { + if (mid == 0 || lfe->sf_pids[mid - 1] < pid) { + memmove(&lfe->sf_pids[mid+1], &lfe->sf_pids[mid], + (lfe->sf_pids_count - mid) * sizeof(uint16_t)); + lfe->sf_pids[mid] = pid; + lfe->sf_pids_count++; + break; + } + mid -= div; + } + } +#if 0 + { int i; for (i = 0; i < lfe->sf_pids_count; i++) + printf("Apid[%i] = %i\n", i, lfe->sf_pids[i]); } +#endif + pthread_mutex_unlock(&lfe->sf_dvr_lock); + return 1; +} + +static mpegts_pid_t * +satip_frontend_open_pid + ( mpegts_input_t *mi, mpegts_mux_t *mm, int pid, int type, void *owner ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + mpegts_pid_t *mp; + int change = 0; + + if (!(mp = mpegts_input_open_pid(mi, mm, pid, type, owner))) + return NULL; + + if (type == MPEGTS_FULLMUX_PID) { + if (lfe->sf_device->sd_fullmux_ok) { + if (!lfe->sf_pids_any) + lfe->sf_pids_any = change = 1; + } else { + mpegts_service_t *s; + elementary_stream_t *st; + LIST_FOREACH(s, &mm->mm_services, s_dvb_mux_link) { + change |= satip_frontend_add_pid(lfe, s->s_pmt_pid); + change |= satip_frontend_add_pid(lfe, s->s_pcr_pid); + TAILQ_FOREACH(st, &s->s_components, es_link) + change |= satip_frontend_add_pid(lfe, st->es_pid); + } + } + } else { + change |= satip_frontend_add_pid(lfe, mp->mp_pid); + } + + pthread_mutex_lock(&lfe->sf_dvr_lock); + if (change && !lfe->sf_pids_any_tuned) + tvh_write(lfe->sf_dvr_pipe.wr, "c", 1); + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + return mp; +} + +static void +satip_frontend_close_pid + ( mpegts_input_t *mi, mpegts_mux_t *mm, int pid, int type, void *owner ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + int mid, div; + + /* remove PID */ + pthread_mutex_lock(&lfe->sf_dvr_lock); + if (lfe->sf_pids) { + mid = div = lfe->sf_pids_count / 2; + while (1) { + if (div > 1) + div /= 2; + if (lfe->sf_pids[mid] == pid) { + if (mid + 1 < lfe->sf_pids_count) + memmove(&lfe->sf_pids[mid], &lfe->sf_pids[mid+1], + (lfe->sf_pids_count - mid - 1) * sizeof(uint16_t)); + lfe->sf_pids_count--; + break; + } else if (lfe->sf_pids[mid] < pid) { + if (mid + 1 > lfe->sf_pids_count) + break; + if (lfe->sf_pids[mid + 1] > pid) + break; + mid += div; + } else { + if (mid == 0) + break; + if (lfe->sf_pids[mid - 1] < pid) + break; + mid -= div; + } + } + } + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + mpegts_input_close_pid(mi, mm, pid, type, owner); +} + +static idnode_set_t * +satip_frontend_network_list ( mpegts_input_t *mi ) +{ + satip_frontend_t *lfe = (satip_frontend_t*)mi; + const idclass_t *idc; + + if (lfe->sf_type == DVB_TYPE_T) + idc = &dvb_network_dvbt_class; + else if (lfe->sf_type == DVB_TYPE_S) + idc = &dvb_network_dvbs_class; + else if (lfe->sf_type == DVB_TYPE_C) + idc = &dvb_network_dvbc_class; + else + return NULL; + + return idnode_find_all(idc); +} + +/* ************************************************************************** + * Data processing + * *************************************************************************/ + +static void +satip_frontend_decode_rtcp( satip_frontend_t *lfe, const char *name, + mpegts_mux_instance_t *mmi, + uint8_t *rtcp, size_t len ) +{ + signal_state_t status; + uint16_t l, sl; + char *s; + char *argv[4]; + int n; + + /* + * DVB-S/S2: + * ver=.;src=;tuner=,,,,\ + * ,,,,,, + * ,;pids=,..., + * + * DVB-T: + * ver=1.1;tuner=,,,,,,,,\ + * ,,,,,;pids=,..., + * + * DVB-C (OctopusNet): + * ver=0.9;tuner=,<0>,,<0>,,,,;pids=,... + * example: + * ver=0.9;tuner=1,0,1,0,362.000,6900,dvbc,256qam;pids=0,1,16,17,18 + */ + + /* level: + * Numerical value between 0 and 255 + * An incoming L-band satellite signal of + * -25dBm corresponds to 224 + * -65dBm corresponds to 32 + * No signal corresponds to 0 + * + * lock: + * lock Set to one of the following values: + * "0" the frontend is not locked + * "1" the frontend is locked + * + * quality: + * Numerical value between 0 and 15 + * Lowest value corresponds to highest error rate + * The value 15 shall correspond to + * -a BER lower than 2x10-4 after Viterbi for DVB-S + * -a PER lower than 10-7 for DVB-S2 + */ + while (len >= 12) { + if ((rtcp[0] & 0xc0) != 0x80) /* protocol version: v2 */ + return; + l = (((rtcp[2] << 8) | rtcp[3]) + 1) * 4; /* length of payload */ + if (l > len) + return; + if (rtcp[1] == 204 && l > 20 && /* packet type */ + rtcp[8] == 'S' && rtcp[9] == 'E' && + rtcp[10] == 'S' && rtcp[11] == '1') { + sl = (rtcp[14] << 8) | rtcp[15]; + if (sl > 0 && l - 16 >= sl) { + rtcp[sl + 16] = '\0'; + s = (char *)rtcp + 16; + tvhtrace("satip", "Status string: '%s'", s); + status = SIGNAL_NONE; + if (strncmp(s, "ver=0.9;tuner=", 14) == 0) { + n = http_tokenize(s + 14, argv, 4, ','); + if (n < 4) + return; + if (atoi(argv[0]) != lfe->sf_number) + return; + mmi->mmi_stats.signal = + (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale; + if (atoi(argv[2]) > 0) + status = SIGNAL_GOOD; + mmi->mmi_stats.snr = atoi(argv[3]); + if (status == SIGNAL_GOOD && + mmi->mmi_stats.signal == 0 && mmi->mmi_stats.snr == 0) { + /* some values that we're tuned */ + mmi->mmi_stats.signal = 50; + mmi->mmi_stats.snr = 12; + } + goto ok; + } else if (strncmp(s, "ver=1.0;", 8) == 0) { + if ((s = strstr(s + 8, ";tuner=")) == NULL) + return; + s += 7; + n = http_tokenize(s, argv, 4, ','); + if (n < 4) + return; + if (atoi(argv[0]) != lfe->sf_number) + return; + mmi->mmi_stats.signal = + (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale; + if (atoi(argv[2]) > 0) + status = SIGNAL_GOOD; + mmi->mmi_stats.snr = atoi(argv[3]); + goto ok; + } else if (strncmp(s, "ver=1.1;tuner=", 14) == 0) { + n = http_tokenize(s + 14, argv, 4, ','); + if (n < 4) + return; + if (atoi(argv[0]) != lfe->sf_number) + return; + mmi->mmi_stats.signal = + (atoi(argv[1]) * 100) / lfe->sf_device->sd_sig_scale; + if (atoi(argv[2]) > 0) + status = SIGNAL_GOOD; + mmi->mmi_stats.snr = atoi(argv[3]); + goto ok; + } + } + } + rtcp += l; + len -= l; + } + return; + +ok: + if (mmi->mmi_stats.snr < 2 && status == SIGNAL_GOOD) + status = SIGNAL_BAD; + else if (mmi->mmi_stats.snr < 4 && status == SIGNAL_GOOD) + status = SIGNAL_FAINT; + lfe->sf_status = status; +} + +static void +satip_frontend_default_tables + ( satip_frontend_t *lfe, mpegts_mux_t *mm ) +{ + psi_tables_default(mm); + psi_tables_dvb(mm); +} + +static void +satip_frontend_store_pids(char *buf, uint16_t *pids, int count) +{ + int first = 1; + char *s = buf; + + *s = '\0'; + while (count--) { + assert(*pids < 8192); + if (!first) + sprintf(s + strlen(s), ",%i", *(pids++)); + else { + sprintf(s + strlen(s), "%i", *(pids++)); + first = 0; + } + } +} + +static void +satip_frontend_pid_changed( http_client_t *rtsp, + satip_frontend_t *lfe, const char *name ) +{ + char *add, *del; + int i, j, r, count, any = lfe->sf_pids_any; + int deleted; + int max_pids_len = lfe->sf_device->sd_pids_len; + + if (!lfe->sf_running) + return; + + pthread_mutex_lock(&lfe->sf_dvr_lock); + + if (lfe->sf_pids_count > lfe->sf_device->sd_pids_max) + any = lfe->sf_device->sd_fullmux_ok ? 1 : 0; + + if (any) { + + if (lfe->sf_pids_any_tuned) { + pthread_mutex_unlock(&lfe->sf_dvr_lock); + return; + } + lfe->sf_pids_any_tuned = 1; + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + r = satip_rtsp_play(rtsp, "all", NULL, NULL, max_pids_len); + + } else if (!lfe->sf_device->sd_pids_deladd || + lfe->sf_pids_any_tuned || + lfe->sf_pids_tcount == 0) { + + lfe->sf_pids_any_tuned = 0; + count = lfe->sf_pids_count; + if (count > lfe->sf_device->sd_pids_max) + count = lfe->sf_device->sd_pids_max; + add = alloca(count * 5); + /* prioritize higher PIDs (tables are low prio) */ + satip_frontend_store_pids(add, + &lfe->sf_pids[lfe->sf_pids_count - count], + count); + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + r = satip_rtsp_play(rtsp, add, NULL, NULL, max_pids_len); + + } else { + + add = alloca(lfe->sf_pids_count * 5); + del = alloca(lfe->sf_pids_count * 5); + add[0] = del[0] = '\0'; + +#if 0 + for (i = 0; i < lfe->sf_pids_count; i++) + printf("pid[%i] = %i\n", i, lfe->sf_pids[i]); + for (i = 0; i < lfe->sf_pids_tcount; i++) + printf("tuned[%i] = %i\n", i, lfe->sf_pids_tuned[i]); +#endif + + i = j = deleted = 0; + while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) { + if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) { + i++; j++; + } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) { + i++; + } else { + sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]); + deleted++; + } + } + while (j < lfe->sf_pids_tcount) { + sprintf(del + strlen(del), ",%i", lfe->sf_pids_tuned[j++]); + deleted++; + } + + count = lfe->sf_pids_count + (lfe->sf_pids_tcount - deleted); + if (count > lfe->sf_device->sd_pids_max) + count = lfe->sf_device->sd_pids_max; + /* prioritize higher PIDs (tables are low prio) */ + /* count means "skip count" in following code */ + count = lfe->sf_pids_count - count; + + i = j = 0; + while (i < lfe->sf_pids_count && j < lfe->sf_pids_tcount) { + if (lfe->sf_pids[i] == lfe->sf_pids_tuned[j]) { + i++; j++; + } else if (lfe->sf_pids[i] < lfe->sf_pids_tuned[j]) { + if (count > 0) { + count--; + } else { + sprintf(add + strlen(add), ",%i", lfe->sf_pids[i]); + } + i++; + } else { + j++; + } + } + while (i < lfe->sf_pids_count) { + if (count > 0) + count--; + else + sprintf(add + strlen(add), ",%i", lfe->sf_pids[i++]); + } + + memcpy(lfe->sf_pids_tuned, lfe->sf_pids, + lfe->sf_pids_count * sizeof(uint16_t)); + lfe->sf_pids_tcount = lfe->sf_pids_count; + pthread_mutex_unlock(&lfe->sf_dvr_lock); + + if (add[0] != '\0' || del[0] != '\0') + r = satip_rtsp_play(rtsp, NULL, add, del, max_pids_len); + else + r = 0; + } + + if (r < 0) + tvherror("satip", "%s - failed to modify pids: %s", name, strerror(-r)); +} + +static void * +satip_frontend_input_thread ( void *aux ) +{ +#define RTP_PKTS 64 +#define UDP_PKT_SIZE 1472 /* this is maximum UDP payload (standard ethernet) */ +#define RTP_PKT_SIZE (UDP_PKT_SIZE - 12) /* minus RTP minimal RTP header */ +#define HTTP_CMD_NONE 9874 + satip_frontend_t *lfe = aux, *lfe2; + mpegts_mux_instance_t *mmi = lfe->sf_mmi; + http_client_t *rtsp; + dvb_mux_t *lm; + char buf[256]; + struct iovec *iovec; + uint8_t rtcp[2048]; + uint8_t *p; + sbuf_t sb; + int pos, nfds, i, r; + size_t c; + int tc; + tvhpoll_event_t ev[4]; + tvhpoll_t *efd; + int changing = 0, ms = -1, fatal = 0; + uint32_t seq = -1, nseq; + udp_multirecv_t um; + int play2 = 1, position, rtsp_flags = 0; + + lfe->mi_display_name((mpegts_input_t*)lfe, buf, sizeof(buf)); + + if (lfe->sf_rtp == NULL || lfe->sf_rtcp == NULL || mmi == NULL) + return NULL; + + lm = (dvb_mux_t *)mmi->mmi_mux; + + rtsp = http_client_connect(lfe, RTSP_VERSION_1_0, "rstp", + lfe->sf_device->sd_info.addr, 554); + if (rtsp == NULL) + return NULL; + + /* Setup poll */ + efd = tvhpoll_create(4); + memset(ev, 0, sizeof(ev)); + ev[0].events = TVHPOLL_IN; + ev[0].fd = lfe->sf_rtp->fd; + ev[0].data.ptr = lfe->sf_rtp; + ev[1].events = TVHPOLL_IN; + ev[1].fd = lfe->sf_rtcp->fd; + ev[1].data.ptr = lfe->sf_rtcp; + ev[2].events = TVHPOLL_IN; + ev[2].fd = rtsp->hc_fd; + ev[2].data.ptr = rtsp; + ev[3].events = TVHPOLL_IN; + ev[3].fd = lfe->sf_dvr_pipe.rd; + ev[3].data.ptr = NULL; + tvhpoll_add(efd, ev, 4); + rtsp->hc_efd = efd; + + position = lfe->sf_position; + if (lfe->sf_master) { + lfe2 = satip_frontend_find_by_number(lfe->sf_device, lfe->sf_master); + if (lfe2) + position = lfe2->sf_position; + } + if (lfe->sf_device->sd_pids0) + rtsp_flags |= SATIP_SETUP_PIDS0; + r = satip_rtsp_setup(rtsp, + position, lfe->sf_number, + lfe->sf_rtp_port, &lm->lm_tuning, + rtsp_flags); + if (r < 0) { + tvherror("satip", "%s - failed to tune", buf); + goto done; + } + + udp_multirecv_init(&um, RTP_PKTS, RTP_PKT_SIZE); + sbuf_init_fixed(&sb, RTP_PKTS * RTP_PKT_SIZE); + + while (tvheadend_running && !fatal) { + + nfds = tvhpoll_wait(efd, ev, 1, ms); + + if (nfds > 0 && ev[0].data.ptr == NULL) { + c = read(lfe->sf_dvr_pipe.rd, rtcp, 1); + if (c == 1 && rtcp[0] == 'c') { + ms = 20; + changing = 1; + continue; + } + tvhtrace("satip", "%s - input thread received shutdown", buf); + break; + } + + if (changing && rtsp->hc_cmd == HTTP_CMD_NONE) { + ms = -1; + changing = 0; + satip_frontend_pid_changed(rtsp, lfe, buf); + continue; + } + + if (nfds < 1) continue; + + if (ev[0].data.ptr == rtsp) { + r = http_client_run(rtsp); + if (r < 0) { + tvhlog(LOG_ERR, "satip", "%s - RTSP error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code); + fatal = 1; + } else if (r == HTTP_CON_DONE) { + switch (rtsp->hc_cmd) { + case RTSP_CMD_OPTIONS: + r = rtsp_options_decode(rtsp); + if (r < 0) { + tvhlog(LOG_ERR, "satip", "%s - RTSP OPTIONS error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code); + fatal = 1; + } + break; + case RTSP_CMD_SETUP: + r = rtsp_setup_decode(rtsp, 1); + if (r < 0 || rtsp->hc_rtp_port != lfe->sf_rtp_port || + rtsp->hc_rtpc_port != lfe->sf_rtp_port + 1) { + tvhlog(LOG_ERR, "satip", "%s - RTSP SETUP error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code); + fatal = 1; + } else { + tvhdebug("satip", "%s #%i - new session %s stream id %li", + rtsp->hc_host, lfe->sf_number, + rtsp->hc_rtsp_session, rtsp->hc_rtsp_stream_id); + pthread_mutex_lock(&global_lock); + satip_frontend_default_tables(lfe, mmi->mmi_mux); + pthread_mutex_unlock(&global_lock); + if (lfe->sf_play2) { + r = satip_rtsp_setup(rtsp, position, lfe->sf_number, + lfe->sf_rtp_port, &lm->lm_tuning, + rtsp_flags | SATIP_SETUP_PLAY); + if (r < 0) { + tvherror("satip", "%s - failed to tune2", buf); + fatal = 1; + } + continue; + } else { + satip_frontend_pid_changed(rtsp, lfe, buf); + } + } + break; + case RTSP_CMD_PLAY: + if (rtsp->hc_code == 200 && play2) { + satip_frontend_pid_changed(rtsp, lfe, buf); + play2 = 0; + } + /* fall thru */ + default: + if (rtsp->hc_code >= 400) { + tvhlog(LOG_ERR, "satip", "%s - RTSP cmd error %d (%s) [%i-%i]", + buf, r, strerror(-r), rtsp->hc_cmd, rtsp->hc_code); + fatal = 1; + } + break; + } + rtsp->hc_cmd = HTTP_CMD_NONE; + } + } + + /* We need to keep the session alive */ + if (rtsp->hc_ping_time + rtsp->hc_rtp_timeout / 2 < dispatch_clock && + rtsp->hc_cmd == HTTP_CMD_NONE) + rtsp_options(rtsp); + + if (ev[0].data.ptr == lfe->sf_rtcp) { + c = recv(lfe->sf_rtcp->fd, rtcp, sizeof(rtcp), MSG_DONTWAIT); + if (c > 0) + satip_frontend_decode_rtcp(lfe, buf, mmi, rtcp, c); + continue; + } + + if (ev[0].data.ptr != lfe->sf_rtp) + continue; + + tc = udp_multirecv_read(&um, lfe->sf_rtp->fd, RTP_PKTS, &iovec); + + if (tc < 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) + continue; + if (errno == EOVERFLOW) { + tvhlog(LOG_WARNING, "satip", "%s - recvmsg() EOVERFLOW", buf); + continue; + } + tvhlog(LOG_ERR, "satip", "%s - multirecv error %d (%s)", + buf, errno, strerror(errno)); + break; + } + + for (i = 0; i < tc; i++) { + p = iovec[i].iov_base; + c = iovec[i].iov_len; + + /* Strip RTP header */ + if (c < 12) + continue; + if ((p[0] & 0xc0) != 0x80) + continue; + if ((p[1] & 0x7f) != 33) + continue; + pos = ((p[0] & 0x0f) * 4) + 12; + if (p[0] & 0x10) { + if (c < pos + 4) + continue; + pos += (((p[pos+2] << 8) | p[pos+3]) + 1) * 4; + } + if (c <= pos || ((c - pos) % 188) != 0) + continue; + /* Use uncorrectable value to notify RTP delivery issues */ + nseq = (p[2] << 8) | p[3]; + if (seq == -1) + seq = nseq; + else if (((seq + 1) & 0xffff) != nseq) + mmi->mmi_stats.unc += ((c - pos) / 188) * + (uint32_t)((uint16_t)nseq-(uint16_t)(seq+1)); + seq = nseq; + /* Process */ + sbuf_append(&sb, p + pos, c - pos); + } + mpegts_input_recv_packets((mpegts_input_t*)lfe, mmi, + &sb, 0, NULL, NULL); + } + + sbuf_free(&sb); + udp_multirecv_free(&um); + + ev[0].events = TVHPOLL_IN; + ev[0].fd = lfe->sf_rtp->fd; + ev[0].data.ptr = lfe->sf_rtp; + ev[1].events = TVHPOLL_IN; + ev[1].fd = lfe->sf_rtcp->fd; + ev[1].data.ptr = lfe->sf_rtcp; + ev[2].events = TVHPOLL_IN; + ev[2].fd = lfe->sf_dvr_pipe.rd; + ev[2].data.ptr = NULL; + tvhpoll_rem(efd, ev, 3); + + if (rtsp->hc_rtsp_stream_id >= 0) { + snprintf((char *)rtcp, sizeof(rtcp), "/stream=%li", rtsp->hc_rtsp_stream_id); + r = rtsp_teardown(rtsp, (char *)rtcp, NULL); + if (r < 0) { + tvhtrace("satip", "%s - bad teardown", buf); + } else { + while (1) { + r = http_client_run(rtsp); + if (r != HTTP_CON_RECEIVING && r != HTTP_CON_SENDING) + break; + nfds = tvhpoll_wait(efd, ev, 1, -1); + if (nfds <= 0) { + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) + continue; + break; + } + } + } + } + +done: + http_client_close(rtsp); + tvhpoll_destroy(efd); + return NULL; +#undef PKTS +} + +/* ************************************************************************** + * Tuning + * *************************************************************************/ + +static void +satip_frontend_signal_cb( void *aux ) +{ + satip_frontend_t *lfe = aux; + mpegts_mux_instance_t *mmi = LIST_FIRST(&lfe->mi_mux_active); + streaming_message_t sm; + signal_status_t sigstat; + service_t *svc; + + if (mmi == NULL) + return; + sigstat.status_text = signal2str(lfe->sf_status); + sigstat.snr = mmi->mmi_stats.snr; + sigstat.signal = mmi->mmi_stats.signal; + sigstat.ber = mmi->mmi_stats.ber; + sigstat.unc = mmi->mmi_stats.unc; + sm.sm_type = SMT_SIGNAL_STATUS; + sm.sm_data = &sigstat; + LIST_FOREACH(svc, &lfe->mi_transports, s_active_link) { + pthread_mutex_lock(&svc->s_stream_mutex); + streaming_pad_deliver(&svc->s_streaming_pad, &sm); + pthread_mutex_unlock(&svc->s_stream_mutex); + } + gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250); +} + +static int +satip_frontend_tune0 + ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi ) +{ + mpegts_mux_instance_t *cur = LIST_FIRST(&lfe->mi_mux_active); + + if (cur != NULL) { + /* Already tuned */ + if (mmi == cur) + return 0; + + /* Stop current */ + cur->mmi_mux->mm_stop(cur->mmi_mux, 1); + } + assert(LIST_FIRST(&lfe->mi_mux_active) == NULL); + + if (udp_bind_double(&lfe->sf_rtp, &lfe->sf_rtcp, + "satip", "rtp", "rtpc", + lfe->sf_device->sd_info.myaddr, lfe->sf_udp_rtp_port, + NULL, SATIP_BUF_SIZE, 16384) < 0) + return SM_CODE_TUNING_FAILED; + + lfe->sf_rtp_port = ntohs(IP_PORT(lfe->sf_rtp->ip)); + + assert(lfe->sf_pids == NULL); + assert(lfe->sf_pids_tuned == NULL); + lfe->sf_pids_count = 0; + lfe->sf_pids_tcount = 0; + lfe->sf_pids_size = 512; + lfe->sf_pids = calloc(lfe->sf_pids_size, sizeof(uint16_t)); + lfe->sf_pids_tuned = calloc(lfe->sf_pids_size, sizeof(uint16_t)); + lfe->sf_pids_any = 0; + lfe->sf_pids_any_tuned = 0; + lfe->sf_status = SIGNAL_NONE; + + tvhtrace("satip", "%s - local RTP port %i RTCP port %i", + lfe->mi_name, + ntohs(IP_PORT(lfe->sf_rtp->ip)), + ntohs(IP_PORT(lfe->sf_rtcp->ip))); + + lfe->sf_mmi = mmi; + + tvh_pipe(O_NONBLOCK, &lfe->sf_dvr_pipe); + tvhthread_create(&lfe->sf_dvr_thread, NULL, + satip_frontend_input_thread, lfe, 0); + + gtimer_arm_ms(&lfe->sf_monitor_timer, satip_frontend_signal_cb, lfe, 250); + + lfe->sf_running = 1; + return 0; +} + +static int +satip_frontend_tune1 + ( satip_frontend_t *lfe, mpegts_mux_instance_t *mmi ) +{ + char buf1[256], buf2[256]; + + lfe->mi_display_name((mpegts_input_t*)lfe, buf1, sizeof(buf1)); + mmi->mmi_mux->mm_display_name(mmi->mmi_mux, buf2, sizeof(buf2)); + tvhdebug("satip", "%s - starting %s", buf1, buf2); + + /* Tune */ + return satip_frontend_tune0(lfe, mmi); +} + +/* ************************************************************************** + * Creation/Config + * *************************************************************************/ + +static void +satip_frontend_hacks( satip_frontend_t *lfe ) +{ + if (strstr(lfe->sf_device->sd_info.location, ":8888/octonet.xml")) { + if (lfe->sf_type == DVB_TYPE_S) + lfe->sf_play2 = 1; + } +} + +satip_frontend_t * +satip_frontend_create + ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num ) +{ + const idclass_t *idc; + const char *uuid = NULL, *override = NULL; + char id[16], lname[256]; + satip_frontend_t *lfe; + uint32_t master = 0; + int i; + + /* Override type */ + snprintf(id, sizeof(id), "override #%d", num); + if (conf && type != DVB_TYPE_S) { + override = htsmsg_get_str(conf, id); + if (override) { + i = dvb_str2type(override); + if ((i == DVB_TYPE_T || i == DVB_TYPE_C || DVB_TYPE_S) && i != type) + type = i; + else + override = NULL; + } + } + /* Tuner slave */ + snprintf(id, sizeof(id), "master for #%d", num); + if (conf && type == DVB_TYPE_S) { + if (htsmsg_get_u32(conf, id, &master)) + master = 0; + if (master == num) + master = 0; + } + /* Internal config ID */ + snprintf(id, sizeof(id), "%s #%d", dvb_type2str(type), num); + if (conf) + conf = htsmsg_get_map(conf, id); + if (conf) + uuid = htsmsg_get_str(conf, "uuid"); + + /* Class */ + if (type == DVB_TYPE_S) + idc = master ? &satip_frontend_dvbs_slave_class : + &satip_frontend_dvbs_class; + else if (type == DVB_TYPE_T) + idc = &satip_frontend_dvbt_class; + else if (type == DVB_TYPE_C) + idc = &satip_frontend_dvbc_class; + else { + tvherror("satip", "unknown FE type %d", type); + return NULL; + } + + // Note: there is a bit of a chicken/egg issue below, without the + // correct "fe_type" we cannot set the network (which is done + // in mpegts_input_create()). So we must set early. + lfe = calloc(1, sizeof(satip_frontend_t)); + lfe->sf_device = sd; + lfe->sf_number = num; + lfe->sf_type = type; + lfe->sf_type_t2 = t2; + lfe->sf_master = master; + lfe->sf_type_override = override ? strdup(override) : NULL; + satip_frontend_hacks(lfe); + TAILQ_INIT(&lfe->sf_satconf); + pthread_mutex_init(&lfe->sf_dvr_lock, NULL); + lfe = (satip_frontend_t*)mpegts_input_create0((mpegts_input_t*)lfe, idc, uuid, conf); + if (!lfe) return NULL; + + /* Defaults */ + lfe->sf_position = -1; + + /* Callbacks */ + lfe->mi_is_free = satip_frontend_is_free; + lfe->mi_get_weight = satip_frontend_get_weight; + lfe->mi_get_priority = satip_frontend_get_priority; + lfe->mi_get_grace = satip_frontend_get_grace; + + /* Default name */ + if (!lfe->mi_name || + (strncmp(lfe->mi_name, "SAT>IP ", 7) == 0 && + strstr(lfe->mi_name, " Tuner ") && + strstr(lfe->mi_name, " #"))) { + snprintf(lname, sizeof(lname), "SAT>IP %s Tuner #%i (%s)", + dvb_type2str(type), num, sd->sd_info.addr); + free(lfe->mi_name); + lfe->mi_name = strdup(lname); + } + + /* Input callbacks */ + lfe->mi_is_enabled = satip_frontend_is_enabled; + lfe->mi_start_mux = satip_frontend_start_mux; + lfe->mi_stop_mux = satip_frontend_stop_mux; + lfe->mi_network_list = satip_frontend_network_list; + lfe->mi_open_pid = satip_frontend_open_pid; + lfe->mi_close_pid = satip_frontend_close_pid; + + /* Adapter link */ + lfe->sf_device = sd; + TAILQ_INSERT_TAIL(&sd->sd_frontends, lfe, sf_link); + + /* Create satconf */ + if (lfe->sf_type == DVB_TYPE_S && master == 0) + satip_satconf_create(lfe, conf); + + /* Slave networks update */ + if (master) { + satip_frontend_t *lfe2 = satip_frontend_find_by_number(sd, master); + if (lfe2) { + htsmsg_t *l = (htsmsg_t *)mpegts_input_class_network_get(lfe2); + if (l) { + mpegts_input_class_network_set(lfe, l); + htsmsg_destroy(l); + } + } + } + + return lfe; +} + +void +satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *fe ) +{ + char id[16]; + htsmsg_t *m = htsmsg_create_map(); + + /* Save frontend */ + mpegts_input_save((mpegts_input_t*)lfe, m); + htsmsg_add_str(m, "type", dvb_type2str(lfe->sf_type)); + if (lfe->ti_id.in_class == &satip_frontend_dvbs_class) { + satip_satconf_save(lfe, m); + htsmsg_delete_field(m, "networks"); + } + htsmsg_delete_field(m, "fe_override"); + htsmsg_delete_field(m, "fe_master"); + + /* Add to list */ + snprintf(id, sizeof(id), "%s #%d", dvb_type2str(lfe->sf_type), lfe->sf_number); + htsmsg_add_msg(fe, id, m); + if (lfe->sf_type_override) { + snprintf(id, sizeof(id), "override #%d", lfe->sf_number); + htsmsg_add_str(fe, id, lfe->sf_type_override); + } + if (lfe->sf_master) { + snprintf(id, sizeof(id), "master for #%d", lfe->sf_number); + htsmsg_add_u32(fe, id, lfe->sf_master); + } +} + +void +satip_frontend_delete ( satip_frontend_t *lfe ) +{ + lock_assert(&global_lock); + + /* Ensure we're stopped */ + mpegts_input_stop_all((mpegts_input_t*)lfe); + + /* Stop timer */ + gtimer_disarm(&lfe->sf_monitor_timer); + + /* Remove from adapter */ + TAILQ_REMOVE(&lfe->sf_device->sd_frontends, lfe, sf_link); + + /* Delete satconf */ + satip_satconf_destroy(lfe); + + free(lfe->sf_type_override); + + /* Finish */ + mpegts_input_delete((mpegts_input_t*)lfe, 0); +} diff --git a/src/input/mpegts/satip/satip_private.h b/src/input/mpegts/satip/satip_private.h new file mode 100644 index 00000000..6423eb3b --- /dev/null +++ b/src/input/mpegts/satip/satip_private.h @@ -0,0 +1,218 @@ +/* + * Tvheadend - SAT-IP DVB private data + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_SATIP_PRIVATE_H__ +#define __TVH_SATIP_PRIVATE_H__ + +#include "input.h" +#include "htsbuf.h" +#include "udp.h" +#include "http.h" +#include "satip.h" + +#define SATIP_BUF_SIZE (4000*188) + +typedef struct satip_device_info satip_device_info_t; +typedef struct satip_device satip_device_t; +typedef struct satip_frontend satip_frontend_t; +typedef struct satip_satconf satip_satconf_t; + +struct satip_device_info +{ + char *myaddr; /* IP address of this host received data from the SAT>IP device */ + char *addr; /* IP address */ + char *uuid; + char *bootid; + char *configid; + char *deviceid; + char *location; /*< URL of the XML file */ + char *server; + char *friendlyname; + char *manufacturer; + char *manufacturerURL; + char *modeldesc; + char *modelname; + char *modelnum; + char *serialnum; + char *presentation; + char *tunercfg; /*< XML urn:ses-com:satipX_SATIPCAP contents */ +}; + +struct satip_device +{ + tvh_hardware_t; + + gtimer_t sd_destroy_timer; + + /* + * Adapter info + */ + satip_device_info_t sd_info; + + /* + * Frontends + */ + TAILQ_HEAD(,satip_frontend) sd_frontends; + + /* + * RTSP + */ + int sd_fullmux_ok; + int sd_pids_max; + int sd_pids_len; + int sd_pids_deladd; + int sd_sig_scale; + int sd_pids0; +}; + +struct satip_frontend +{ + mpegts_input_t; + + /* + * Device + */ + satip_device_t *sf_device; + TAILQ_ENTRY(satip_frontend) sf_link; + + /* + * Frontend info + */ + int sf_number; + dvb_fe_type_t sf_type; + int sf_type_t2; + char *sf_type_override; + int sf_master; + int sf_udp_rtp_port; + int sf_play2; + + /* + * Reception + */ + pthread_t sf_dvr_thread; + th_pipe_t sf_dvr_pipe; + pthread_mutex_t sf_dvr_lock; + pthread_cond_t sf_dvr_cond; + uint16_t *sf_pids; + uint16_t *sf_pids_tuned; + int sf_pids_any; + int sf_pids_any_tuned; + int sf_pids_size; + int sf_pids_count; + int sf_pids_tcount; /*< tuned count */ + int sf_running; + int sf_position; + udp_connection_t *sf_rtp; + udp_connection_t *sf_rtcp; + int sf_rtp_port; + mpegts_mux_instance_t *sf_mmi; + signal_state_t sf_status; + gtimer_t sf_monitor_timer; + + /* + * Configuration + */ + int sf_positions; + TAILQ_HEAD(,satip_satconf) sf_satconf; +}; + +struct satip_satconf +{ + + idnode_t sfc_id; + /* + * Parent + */ + satip_frontend_t *sfc_lfe; + TAILQ_ENTRY(satip_satconf) sfc_link; + + /* + * Config + */ + int sfc_enabled; + int sfc_position; + int sfc_priority; + char *sfc_name; + + /* + * Assigned networks to this SAT configuration + */ + idnode_set_t *sfc_networks; +}; + +/* + * Methods + */ + +void satip_device_init ( void ); + +void satip_device_done ( void ); + +void satip_device_save ( satip_device_t *sd ); + +void satip_device_destroy ( satip_device_t *sd ); + +void satip_device_destroy_later( satip_device_t *sd, int after_ms ); + +satip_frontend_t * +satip_frontend_create + ( htsmsg_t *conf, satip_device_t *sd, dvb_fe_type_t type, int t2, int num ); + +void satip_frontend_save ( satip_frontend_t *lfe, htsmsg_t *m ); + +void satip_frontend_delete ( satip_frontend_t *lfe ); + +/* + * SAT>IP Satconf configuration + */ +void satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m ); + +void satip_satconf_destroy ( satip_frontend_t *lfe ); + +void satip_satconf_create + ( satip_frontend_t *lfe, htsmsg_t *conf ); + +void satip_satconf_updated_positions + ( satip_frontend_t *lfe ); + +int satip_satconf_get_priority + ( satip_frontend_t *lfe, mpegts_mux_t *mm ); + +int satip_satconf_get_position + ( satip_frontend_t *lfe, mpegts_mux_t *mm ); + +/* + * RTSP part + */ + +#define SATIP_SETUP_PLAY (1<<0) +#define SATIP_SETUP_PIDS0 (1<<1) + +int +satip_rtsp_setup( http_client_t *hc, + int src, int fe, int udp_port, + const dvb_mux_conf_t *dmc, + int pids0 ); + +int +satip_rtsp_play( http_client_t *hc, const char *pids, + const char *addpids, const char *delpids, + int max_pids_len ); + +#endif /* __TVH_SATIP_PRIVATE_H__ */ diff --git a/src/input/mpegts/satip/satip_rtsp.c b/src/input/mpegts/satip/satip_rtsp.c new file mode 100644 index 00000000..19d2ee8f --- /dev/null +++ b/src/input/mpegts/satip/satip_rtsp.c @@ -0,0 +1,282 @@ +/* + * Tvheadend - SAT>IP DVB RTSP client + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "tvheadend.h" +#include "htsbuf.h" +#include "tcp.h" +#include "http.h" +#include "satip_private.h" + +/* + * + */ + +typedef struct tvh2satip { + int t; ///< TVH internal value + const char *s; ///< SATIP API value +} tvh2satip_t; + +#define TABLE_EOD -1 + +static const char * +satip_rtsp_setup_find(const char *prefix, tvh2satip_t *tbl, + int src, const char *defval) +{ + while (tbl->t >= 0) { + if (tbl->t == src) + return tbl->s; + tbl++; + } + tvhtrace("satip", "%s - cannot translate %d", prefix, src); + return defval; +} + +#define ADD(s, d, def) \ + strcat(buf, "&" #d "="), strcat(buf, satip_rtsp_setup_find(#d, d, dmc->s, def)) + +static void +satip_rtsp_add_val(const char *name, char *buf, uint32_t val) +{ + char sec[5]; + + sprintf(buf + strlen(buf), "&%s=%i", name, val / 1000); + if (val % 1000) { + sprintf(sec, ".%03i", val % 1000); + if (sec[3] == '0') { + sec[3] = '\0'; + if (sec[2] == '0') + sec[2] = '\0'; + } + } +} + +int +satip_rtsp_setup( http_client_t *hc, int src, int fe, + int udp_port, const dvb_mux_conf_t *dmc, int flags ) +{ + static tvh2satip_t msys[] = { + { .t = DVB_SYS_DVBT, "dvbt" }, + { .t = DVB_SYS_DVBT2, "dvbt2" }, + { .t = DVB_SYS_DVBS, "dvbs" }, + { .t = DVB_SYS_DVBS2, "dvbs2" }, + { .t = DVB_SYS_DVBC_ANNEX_A, "dvbc" }, + { .t = DVB_SYS_DVBC_ANNEX_B, "dvbc" }, + { .t = DVB_SYS_DVBC_ANNEX_C, "dvbc" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t pol[] = { + { .t = DVB_POLARISATION_HORIZONTAL, "h" }, + { .t = DVB_POLARISATION_VERTICAL, "v" }, + { .t = DVB_POLARISATION_CIRCULAR_LEFT, "l" }, + { .t = DVB_POLARISATION_CIRCULAR_RIGHT, "r" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t ro[] = { + { .t = DVB_ROLLOFF_AUTO, "0.35" }, + { .t = DVB_ROLLOFF_20, "0.20" }, + { .t = DVB_ROLLOFF_25, "0.25" }, + { .t = DVB_ROLLOFF_35, "0.35" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t mtype[] = { + { .t = DVB_MOD_AUTO, "auto" }, + { .t = DVB_MOD_QAM_16, "16qam" }, + { .t = DVB_MOD_QAM_32, "32qam" }, + { .t = DVB_MOD_QAM_64, "64qam" }, + { .t = DVB_MOD_QAM_128, "128qam"}, + { .t = DVB_MOD_QAM_256, "256qam"}, + { .t = DVB_MOD_QPSK, "qpsk" }, + { .t = DVB_MOD_PSK_8, "8psk" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t plts[] = { + { .t = DVB_PILOT_AUTO, "auto" }, + { .t = DVB_PILOT_ON, "on" }, + { .t = DVB_PILOT_OFF, "off" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t fec[] = { + { .t = DVB_FEC_AUTO, "auto" }, + { .t = DVB_FEC_1_2, "12" }, + { .t = DVB_FEC_2_3, "23" }, + { .t = DVB_FEC_3_4, "34" }, + { .t = DVB_FEC_3_5, "35" }, + { .t = DVB_FEC_4_5, "45" }, + { .t = DVB_FEC_5_6, "56" }, + { .t = DVB_FEC_7_8, "78" }, + { .t = DVB_FEC_8_9, "89" }, + { .t = DVB_FEC_9_10, "910" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t tmode[] = { + { .t = DVB_TRANSMISSION_MODE_AUTO, "auto" }, + { .t = DVB_TRANSMISSION_MODE_1K, "1k" }, + { .t = DVB_TRANSMISSION_MODE_2K, "2k" }, + { .t = DVB_TRANSMISSION_MODE_4K, "4k" }, + { .t = DVB_TRANSMISSION_MODE_8K, "8k" }, + { .t = DVB_TRANSMISSION_MODE_16K, "16k" }, + { .t = DVB_TRANSMISSION_MODE_32K, "32k" }, + { .t = TABLE_EOD } + }; + static tvh2satip_t gi[] = { + { .t = DVB_GUARD_INTERVAL_AUTO, "auto" }, + { .t = DVB_GUARD_INTERVAL_1_4, "14" }, + { .t = DVB_GUARD_INTERVAL_1_8, "18" }, + { .t = DVB_GUARD_INTERVAL_1_16, "116" }, + { .t = DVB_GUARD_INTERVAL_1_32, "132" }, + { .t = DVB_GUARD_INTERVAL_1_128, "1128" }, + { .t = DVB_GUARD_INTERVAL_19_128, "19128" }, + { .t = DVB_GUARD_INTERVAL_19_256, "19256" }, + { .t = TABLE_EOD } + }; + + char buf[512]; + char *stream = NULL; + char _stream[32]; + + if (src > 0) + sprintf(buf, "src=%i&", src); + else + buf[0] = '\0'; + sprintf(buf + strlen(buf), "fe=%i", fe); + satip_rtsp_add_val("freq", buf, dmc->dmc_fe_freq); + if (dmc->dmc_fe_delsys == DVB_SYS_DVBS || + dmc->dmc_fe_delsys == DVB_SYS_DVBS2) { + satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qpsk.symbol_rate); + ADD(dmc_fe_delsys, msys, "dvbs"); + if (dmc->dmc_fe_modulation != DVB_MOD_NONE && + dmc->dmc_fe_modulation != DVB_MOD_AUTO) + ADD(dmc_fe_modulation, mtype, "qpsk"); + ADD(u.dmc_fe_qpsk.polarisation, pol, "h" ); + if (dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_NONE && + dmc->u.dmc_fe_qpsk.fec_inner != DVB_FEC_AUTO) + ADD(u.dmc_fe_qpsk.fec_inner, fec, "auto"); + if (dmc->dmc_fe_rolloff != DVB_ROLLOFF_NONE && + dmc->dmc_fe_rolloff != DVB_ROLLOFF_AUTO) + ADD(dmc_fe_rolloff, ro, "0.35"); + if (dmc->dmc_fe_pilot != DVB_PILOT_NONE && + dmc->dmc_fe_pilot != DVB_PILOT_AUTO) + ADD(dmc_fe_pilot, plts, "auto"); + } else if (dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_A || + dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_B || + dmc->dmc_fe_delsys == DVB_SYS_DVBC_ANNEX_C) { + satip_rtsp_add_val("sr", buf, dmc->u.dmc_fe_qam.symbol_rate); + ADD(dmc_fe_delsys, msys, "dvbc"); + ADD(dmc_fe_modulation, mtype, "64qam"); + /* missing plp */ + if (dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_NONE && + dmc->u.dmc_fe_qam.fec_inner != DVB_FEC_AUTO) + /* note: OctopusNet device does not handle 'fec=auto' */ + ADD(u.dmc_fe_qam.fec_inner, fec, "auto"); + } else { + if (dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_AUTO && + dmc->u.dmc_fe_ofdm.bandwidth != DVB_BANDWIDTH_NONE) + satip_rtsp_add_val("bw", buf, dmc->u.dmc_fe_ofdm.bandwidth); + ADD(dmc_fe_delsys, msys, "dvbt"); + if (dmc->dmc_fe_modulation != DVB_MOD_AUTO && + dmc->dmc_fe_modulation != DVB_MOD_NONE && + dmc->dmc_fe_modulation != DVB_MOD_QAM_AUTO) + ADD(dmc_fe_modulation, mtype, "64qam"); + if (dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_AUTO && + dmc->u.dmc_fe_ofdm.transmission_mode != DVB_TRANSMISSION_MODE_NONE) + ADD(u.dmc_fe_ofdm.transmission_mode, tmode, "8k"); + if (dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_AUTO && + dmc->u.dmc_fe_ofdm.guard_interval != DVB_GUARD_INTERVAL_NONE) + ADD(u.dmc_fe_ofdm.guard_interval, gi, "18"); + } + if (flags & SATIP_SETUP_PIDS0) + strcat(buf, "&pids=0"); + tvhtrace("satip", "setup params - %s", buf); + if (hc->hc_rtsp_stream_id >= 0) + snprintf(stream = _stream, sizeof(_stream), "/stream=%li", + hc->hc_rtsp_stream_id); + if (flags & SATIP_SETUP_PLAY) + return rtsp_play(hc, stream, buf); + return rtsp_setup(hc, stream, buf, NULL, udp_port, udp_port + 1); +} + +static const char * +satip_rtsp_pids_strip( const char *s, int maxlen ) +{ + char *ptr; + + if (s == NULL) + return NULL; + while (*s == ',') + s++; + while (strlen(s) > maxlen) { + ptr = rindex(s, ','); + if (ptr == NULL) + abort(); + *ptr = '\0'; + } + if (*s == '\0') + return NULL; + return s; +} + +int +satip_rtsp_play( http_client_t *hc, const char *pids, + const char *addpids, const char *delpids, + int max_pids_len ) +{ + htsbuf_queue_t q; + char *stream = NULL; + char _stream[32]; + char *query; + int r, split = 0; + + pids = satip_rtsp_pids_strip(pids , max_pids_len); + addpids = satip_rtsp_pids_strip(addpids, max_pids_len); + delpids = satip_rtsp_pids_strip(delpids, max_pids_len); + + if (pids == NULL && addpids == NULL && delpids == NULL) + return -EINVAL; + + //printf("pids = '%s' addpids = '%s' delpids = '%s'\n", pids, addpids, delpids); + + htsbuf_queue_init(&q, 0); + /* pids setup and add/del requests cannot be mixed per specification */ + if (pids) { + htsbuf_qprintf(&q, "pids=%s", pids); + } else { + if (delpids) + htsbuf_qprintf(&q, "delpids=%s", delpids); + if (addpids) { + if (delpids) { + /* try to maintain the maximum request size - simple split */ + if (strlen(addpids) + strlen(delpids) <= max_pids_len) + split = 1; + else + htsbuf_append(&q, "&", 1); + } + if (!split) + htsbuf_qprintf(&q, "addpids=%s", addpids); + } + } + if (hc->hc_rtsp_stream_id >= 0) + snprintf(stream = _stream, sizeof(_stream), "/stream=%li", + hc->hc_rtsp_stream_id); + query = htsbuf_to_string(&q); + r = rtsp_play(hc, stream, query); + free(query); + return r; +} diff --git a/src/input/mpegts/satip/satip_satconf.c b/src/input/mpegts/satip/satip_satconf.c new file mode 100644 index 00000000..5e2a2b08 --- /dev/null +++ b/src/input/mpegts/satip/satip_satconf.c @@ -0,0 +1,327 @@ +/* + * Tvheadend - SAT>IP DVB satconf + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tvheadend.h" +#include "satip_private.h" +#include "settings.h" + +/* ************************************************************************** + * Frontend callbacks + * *************************************************************************/ + +static satip_satconf_t * +satip_satconf_find_ele( satip_frontend_t *lfe, mpegts_mux_t *mux ) +{ + satip_satconf_t *sfc; + TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) { + if (idnode_set_exists(sfc->sfc_networks, &mux->mm_network->mn_id)) + return sfc; + } + return NULL; +} + +int +satip_satconf_get_priority + ( satip_frontend_t *lfe, mpegts_mux_t *mm ) +{ + satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm); + return sfc ? sfc->sfc_priority : 0; +} + +int +satip_satconf_get_position + ( satip_frontend_t *lfe, mpegts_mux_t *mm ) +{ + satip_satconf_t *sfc = satip_satconf_find_ele(lfe, mm); + return sfc ? sfc->sfc_position : 0; +} + +/* ************************************************************************** + * Class definition + * *************************************************************************/ + +static const void * +satip_satconf_class_network_get( void *o ) +{ + satip_satconf_t *sfc = o; + return idnode_set_as_htsmsg(sfc->sfc_networks); +} + +static int +satip_satconf_class_network_set( void *o, const void *p ) +{ + satip_satconf_t *sfc = o; + const htsmsg_t *msg = p; + mpegts_network_t *mn; + idnode_set_t *n = idnode_set_create(); + htsmsg_field_t *f; + const char *str; + int i, save; + + HTSMSG_FOREACH(f, msg) { + if (!(str = htsmsg_field_get_str(f))) continue; + if (!(mn = mpegts_network_find(str))) continue; + idnode_set_add(n, &mn->mn_id, NULL); + } + + save = n->is_count != sfc->sfc_networks->is_count; + if (!save) { + for (i = 0; i < n->is_count; i++) + if (!idnode_set_exists(sfc->sfc_networks, n->is_array[i])) { + save = 1; + break; + } + } + if (save) { + /* update the local (antenna satconf) network list */ + idnode_set_free(sfc->sfc_networks); + sfc->sfc_networks = n; + /* update the input (frontend) network list */ + htsmsg_t *l = htsmsg_create_list(); + satip_frontend_t *lfe = sfc->sfc_lfe, *lfe2; + satip_satconf_t *sfc2; + TAILQ_FOREACH(sfc2, &lfe->sf_satconf, sfc_link) { + for (i = 0; i < sfc2->sfc_networks->is_count; i++) + htsmsg_add_str(l, NULL, + idnode_uuid_as_str(sfc2->sfc_networks->is_array[i])); + } + mpegts_input_class_network_set(lfe, l); + /* update the slave tuners, too */ + TAILQ_FOREACH(lfe2, &lfe->sf_device->sd_frontends, sf_link) + if (lfe2->sf_number != lfe->sf_number && + lfe2->sf_master == lfe->sf_number) + mpegts_input_class_network_set(lfe2, l); + htsmsg_destroy(l); + } else { + idnode_set_free(n); + } + return save; +} + +static htsmsg_t * +satip_satconf_class_network_enum( void *o ) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_t *p = htsmsg_create_map(); + htsmsg_add_str(m, "type", "api"); + htsmsg_add_str(m, "uri", "idnode/load"); + htsmsg_add_str(m, "event", "mpegts_network"); + htsmsg_add_u32(p, "enum", 1); + htsmsg_add_str(p, "class", dvb_network_dvbs_class.ic_class); + htsmsg_add_msg(m, "params", p); + + return m; +} + +static char * +satip_satconf_class_network_rend( void *o ) +{ + satip_satconf_t *sfc = o; + htsmsg_t *l = idnode_set_as_htsmsg(sfc->sfc_networks); + char *str = htsmsg_list_2_csv(l); + htsmsg_destroy(l); + return str; +} + +static const char * +satip_satconf_class_get_title ( idnode_t *o ) +{ + return ((satip_satconf_t *)o)->sfc_name; +} + +static void +satip_satconf_class_save ( idnode_t *in ) +{ + satip_satconf_t *sfc = (satip_satconf_t*)in; + satip_device_save(sfc->sfc_lfe->sf_device); +} + +const idclass_t satip_satconf_class = +{ + .ic_class = "satip_satconf", + .ic_caption = "Satconf", + .ic_get_title = satip_satconf_class_get_title, + .ic_save = satip_satconf_class_save, + .ic_properties = (const property_t[]) { + { + .type = PT_BOOL, + .id = "enabled", + .name = "Enabled", + .off = offsetof(satip_satconf_t, sfc_enabled), + }, + { + .type = PT_STR, + .id = "displayname", + .name = "Name", + .off = offsetof(satip_satconf_t, sfc_name), + .notify = idnode_notify_title_changed, + }, + { + .type = PT_INT, + .id = "priority", + .name = "Priority", + .off = offsetof(satip_satconf_t, sfc_priority), + .opts = PO_ADVANCED, + }, + { + .type = PT_INT, + .id = "position", + .name = "Position", + .off = offsetof(satip_satconf_t, sfc_position), + .def.i = 1, + .opts = PO_RDONLY | PO_ADVANCED, + }, + { + .type = PT_STR, + .id = "networks", + .name = "Networks", + .islist = 1, + .set = satip_satconf_class_network_set, + .get = satip_satconf_class_network_get, + .list = satip_satconf_class_network_enum, + .rend = satip_satconf_class_network_rend, + }, + {} + } +}; + +/* ************************************************************************** + * Creation/Config + * *************************************************************************/ + +static satip_satconf_t * +satip_satconf_create0 + ( satip_frontend_t *lfe, htsmsg_t *conf, int position ) +{ + static const char *tbl[] = {" (AA)", " (AB)", " (BA)", " (BB)"}; + const char *uuid = NULL; + satip_satconf_t *sfc = calloc(1, sizeof(*sfc)); + char buf[32]; + const char *s; + + /* defaults */ + sfc->sfc_priority = 1; + + if (conf) + uuid = htsmsg_get_str(conf, "uuid"); + if (idnode_insert(&sfc->sfc_id, uuid, &satip_satconf_class)) { + free(sfc); + return NULL; + } + sfc->sfc_networks = idnode_set_create(); + sfc->sfc_lfe = lfe; + sfc->sfc_position = position + 1; + TAILQ_INSERT_TAIL(&lfe->sf_satconf, sfc, sfc_link); + if (conf) + idnode_load(&sfc->sfc_id, conf); + if (sfc->sfc_name == NULL || sfc->sfc_name[0] == '\0') { + free(sfc->sfc_name); + s = position < 4 ? tbl[position] : ""; + snprintf(buf, sizeof(buf), "Position #%i%s", position + 1, s); + sfc->sfc_name = strdup(buf); + } + + return sfc; +} + +void +satip_satconf_create + ( satip_frontend_t *lfe, htsmsg_t *conf ) +{ + htsmsg_t *l, *e; + htsmsg_field_t *f; + int pos = 0; + + if (conf && (l = htsmsg_get_list(conf, "satconf"))) { + satip_satconf_destroy(lfe); + HTSMSG_FOREACH(f, l) { + if (!(e = htsmsg_field_get_map(f))) continue; + if (satip_satconf_create0(lfe, e, pos++)) + lfe->sf_positions++; + } + } + + if (lfe->sf_positions == 0) + for ( ; lfe->sf_positions < 4; lfe->sf_positions++) + satip_satconf_create0(lfe, NULL, lfe->sf_positions); +} + +static void +satip_satconf_destroy0 + ( satip_satconf_t *sfc ) +{ + satip_frontend_t *lfe = sfc->sfc_lfe; + TAILQ_REMOVE(&lfe->sf_satconf, sfc, sfc_link); + idnode_unlink(&sfc->sfc_id); + idnode_set_free(sfc->sfc_networks); + free(sfc->sfc_name); + free(sfc); +} + +void +satip_satconf_updated_positions + ( satip_frontend_t *lfe ) +{ + satip_satconf_t *sfc, *sfc_old; + int i; + + sfc = TAILQ_FIRST(&lfe->sf_satconf); + for (i = 0; i < lfe->sf_positions; i++) { + if (sfc == NULL) + satip_satconf_create0(lfe, NULL, i); + sfc = sfc ? TAILQ_NEXT(sfc, sfc_link) : NULL; + } + while (sfc) { + sfc_old = sfc; + sfc = TAILQ_NEXT(sfc, sfc_link); + satip_satconf_destroy0(sfc_old); + } +} + +void +satip_satconf_destroy ( satip_frontend_t *lfe ) +{ + satip_satconf_t *sfc; + + while ((sfc = TAILQ_FIRST(&lfe->sf_satconf)) != NULL) + satip_satconf_destroy0(sfc); + lfe->sf_positions = 0; +} + +void +satip_satconf_save ( satip_frontend_t *lfe, htsmsg_t *m ) +{ + satip_satconf_t *sfc; + htsmsg_t *l, *e; + + l = htsmsg_create_list(); + TAILQ_FOREACH(sfc, &lfe->sf_satconf, sfc_link) { + e = htsmsg_create_map(); + idnode_save(&sfc->sfc_id, e); + htsmsg_add_msg(l, NULL, e); + } + htsmsg_add_msg(m, "satconf", l); +} + + +/****************************************************************************** + * Editor Configuration + * + * vim:sts=2:ts=2:sw=2:et + *****************************************************************************/ diff --git a/src/input/mpegts/scanfile.c b/src/input/mpegts/scanfile.c index bf540557..6e9c6016 100644 --- a/src/input/mpegts/scanfile.c +++ b/src/input/mpegts/scanfile.c @@ -469,7 +469,7 @@ scanfile_find ( const char *id ) /* Type */ if (!(tok = strtok_r(tmp, "/", &s))) - return NULL; + goto fail; if (!strcasecmp(tok, "dvbt")) l = &scanfile_regions_DVBT; else if (!strcasecmp(tok, "dvbc")) @@ -479,22 +479,27 @@ scanfile_find ( const char *id ) else if (!strcasecmp(tok, "atsc")) l = &scanfile_regions_ATSC; else - return NULL; + goto fail; /* Region */ if (!(tok = strtok_r(NULL, "/", &s))) - return NULL; + goto fail; LIST_FOREACH(r, l, sfr_link) if (!strcmp(r->sfr_id, tok)) break; - if (!r) return NULL; + if (!r) goto fail; /* Network */ if (!(tok = strtok_r(NULL, "/", &s))) - return NULL; + goto fail; LIST_FOREACH(n, &r->sfr_networks, sfn_link) if (!strcmp(n->sfn_id, tok)) break; + free(tmp); return n; + +fail: + free(tmp); + return NULL; } diff --git a/src/main.c b/src/main.c index 6f818d18..3dcd09c9 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,7 @@ #include "tcp.h" #include "access.h" #include "http.h" +#include "upnp.h" #include "webui/webui.h" #include "epggrab.h" #include "spawn.h" @@ -60,6 +61,7 @@ #include "timeshift.h" #include "fsmonitor.h" #include "lang_codes.h" +#include "esfilter.h" #if ENABLE_LIBAV #include "libav.h" #include "plumbing/transcoding.h" @@ -68,6 +70,11 @@ #ifdef PLATFORM_LINUX #include #endif +#include +#include +#include +#include +#include pthread_t main_tid; @@ -133,6 +140,9 @@ const tvh_caps_t tvheadend_capabilities[] = { #if ENABLE_LINUXDVB { "linuxdvb", NULL }, #endif +#if ENABLE_SATIP_CLIENT + { "satip_client", NULL }, +#endif #if ENABLE_LIBAV { "transcoding", &transcoding_enabled }, #endif @@ -467,6 +477,7 @@ main(int argc, char **argv) #endif *opt_bindaddr = NULL, *opt_subscribe = NULL; + str_list_t opt_satip_xml = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) }; str_list_t opt_tsfile = { .max = 10, .num = 0, .str = calloc(10, sizeof(char*)) }; cmdline_opt_t cmdline_opts[] = { { 0, NULL, "Generic Options", OPT_BOOL, NULL }, @@ -488,6 +499,10 @@ main(int argc, char **argv) #if ENABLE_LINUXDVB { 'a', "adapters", "Only use specified DVB adapters (comma separated)", OPT_STR, &opt_dvb_adapters }, +#endif +#if ENABLE_SATIP_CLIENT + { 0, "satip_xml", "URL with the SAT>IP server XML location", + OPT_STR_LIST, &opt_satip_xml }, #endif { 0, NULL, "Server Connectivity", OPT_BOOL, NULL }, { '6', "ipv6", "Listen on IPv6", OPT_BOOL, &opt_ipv6 }, @@ -726,6 +741,11 @@ main(int argc, char **argv) sigfillset(&set); sigprocmask(SIG_BLOCK, &set, NULL); trap_init(argv[0]); + + /* SSL library init */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); /* Initialise configuration */ uuid_init(); @@ -747,10 +767,13 @@ main(int argc, char **argv) imagecache_init(); + http_client_init(); + esfilter_init(); + service_init(); #if ENABLE_MPEGTS - mpegts_init(adapter_mask, &opt_tsfile, opt_tsfile_tuner); + mpegts_init(adapter_mask, &opt_satip_xml, &opt_tsfile, opt_tsfile_tuner); #endif channel_init(); @@ -763,10 +786,12 @@ main(int argc, char **argv) timeshift_init(); #endif - http_client_init(); tcp_server_init(opt_ipv6); http_server_init(opt_bindaddr); webui_init(); +#if ENABLE_UPNP + upnp_server_init(opt_bindaddr); +#endif service_mapper_init(); @@ -813,14 +838,17 @@ main(int argc, char **argv) mainloop(); +#if ENABLE_UPNP + tvhftrace("main", upnp_server_done); +#endif tvhftrace("main", htsp_done); tvhftrace("main", http_server_done); tvhftrace("main", webui_done); - tvhftrace("main", http_client_done); tvhftrace("main", fsmonitor_done); #if ENABLE_MPEGTS tvhftrace("main", mpegts_done); #endif + tvhftrace("main", http_client_done); // Note: the locking is obviously a bit redundant, but without // we need to disable the gtimer_arm call in epg_save() @@ -851,6 +879,8 @@ main(int argc, char **argv) tvhftrace("main", hts_settings_done); tvhftrace("main", dvb_done); tvhftrace("main", lang_str_done); + tvhftrace("main", esfilter_done); + tvhftrace("main", urlparse_done); tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend"); tvhlog_end(); @@ -859,6 +889,23 @@ main(int argc, char **argv) unlink(opt_pidpath); free(opt_tsfile.str); + free(opt_satip_xml.str); + + /* OpenSSL - welcome to the "cleanup" hell */ + ENGINE_cleanup(); + RAND_cleanup(); + CRYPTO_cleanup_all_ex_data(); + EVP_cleanup(); + CONF_modules_free(); + COMP_zlib_cleanup(); + ERR_remove_state(0); + ERR_free_strings(); + { + struct stack_st_SSL_COMP * pCOMP = SSL_COMP_get_compression_methods(); + if (pCOMP) + sk_SSL_COMP_free(pCOMP); + } + /* end of OpenSSL cleanup code */ return 0; } diff --git a/src/rtsp.c b/src/rtsp.c new file mode 100644 index 00000000..339dc0fd --- /dev/null +++ b/src/rtsp.c @@ -0,0 +1,199 @@ +/* + * Tvheadend - RTSP routines + * + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "tvheadend.h" +#include "htsbuf.h" +#include "tcp.h" +#include "http.h" + +/* + * Utils + */ +int +rtsp_send( http_client_t *hc, http_cmd_t cmd, + const char *path, const char *query, + http_arg_list_t *hdr ) +{ + http_arg_list_t h; + size_t blen = 7 + strlen(hc->hc_host) + (path ? strlen(path) : 1) + 1; + char *buf = alloca(blen); + + if (hc->hc_rtsp_session && cmd != RTSP_CMD_OPTIONS) { + if (hdr == NULL) { + hdr = &h; + http_arg_init(&h); + } + http_arg_set(hdr, "Session", hc->hc_rtsp_session); + } + snprintf(buf, blen, "rtsp://%s%s", hc->hc_host, path ? path : "/"); + return http_client_send(hc, cmd, buf, query, hdr, NULL, 0); +} + +void +rtsp_clear_session( http_client_t *hc ) +{ + free(hc->hc_rtsp_session); + free(hc->hc_rtp_dest); + hc->hc_rtp_port = 0; + hc->hc_rtpc_port = 0; + hc->hc_rtsp_session = NULL; + hc->hc_rtp_dest = NULL; + hc->hc_rtp_multicast = 0; + hc->hc_rtsp_stream_id = -1; + hc->hc_rtp_timeout = 60; +} + +/* + * Options + */ + +int +rtsp_options_decode( http_client_t *hc ) +{ + char *argv[32], *p; + int i, n, what = 0; + + p = http_arg_get(&hc->hc_args, "Public"); + n = http_tokenize(p, argv, 32, ','); + for (i = 1; i < n; i++) { + if (strcmp(argv[i], "DESCRIBE") == 0) + what |= 1; + else if (strcmp(argv[i], "SETUP") == 0) + what |= 2; + else if (strcmp(argv[i], "PLAY") == 0) + what |= 4; + else if (strcmp(argv[i], "TEARDOWN") == 0) + what |= 8; + } + return (hc->hc_code != 200 && what != 0x0f) ? -EIO : HTTP_CON_OK; +} + +int +rtsp_setup_decode( http_client_t *hc, int satip ) +{ + char *argv[32], *argv2[2], *p; + int i, n, j; + +#if 0 + { http_arg_t *ra; + TAILQ_FOREACH(ra, &hc->hc_args, link) + printf(" %s: %s\n", ra->key, ra->val); } +#endif + rtsp_clear_session(hc); + if (hc->hc_code != 200) + return -EIO; + p = http_arg_get(&hc->hc_args, "Session"); + if (p == NULL) + return -EIO; + n = http_tokenize(p, argv, 32, ';'); + if (n < 1) + return -EIO; + hc->hc_rtsp_session = strdup(argv[0]); + for (i = 1; i < n; i++) { + if (strncasecmp(argv[i], "timeout=", 8) == 0) { + hc->hc_rtp_timeout = atoi(argv[i] + 8); + if (hc->hc_rtp_timeout <= 20 || hc->hc_rtp_timeout > 3600) + return -EIO; + } + } + if (satip) { + p = http_arg_get(&hc->hc_args, "com.ses.streamID"); + if (p == NULL) + return -EIO; + /* zero is valid stream id per specification */ + while (*p && (*p == '0' || *p < ' ')) + p++; + if (p[0] == '0' && p[1] == '\0') { + hc->hc_rtsp_stream_id = 0; + } else { + hc->hc_rtsp_stream_id = atoll(p); + if (hc->hc_rtsp_stream_id <= 0) + return -EIO; + } + } + p = http_arg_get(&hc->hc_args, "Transport"); + if (p == NULL) + return -EIO; + n = http_tokenize(p, argv, 32, ';'); + if (n < 3) + return -EIO; + if (strcasecmp(argv[0], "RTP/AVP")) + return -EIO; + hc->hc_rtp_multicast = strcasecmp(argv[1], "multicast") == 0; + if (strcasecmp(argv[1], "unicast") && !hc->hc_rtp_multicast) + return -EIO; + for (i = 2; i < n; i++) { + if (strncmp(argv[i], "destination=", 12) == 0) + hc->hc_rtp_dest = strdup(argv[i] + 12); + else if (strncmp(argv[i], "client_port=", 12) == 0) { + j = http_tokenize(argv[i] + 12, argv2, 2, '-'); + if (j > 0) { + hc->hc_rtp_port = atoi(argv2[0]); + if (hc->hc_rtp_port <= 0) + return -EIO; + if (j > 1) { + hc->hc_rtpc_port = atoi(argv2[1]); + if (hc->hc_rtpc_port <= 0) + return -EIO; + } + } else { + return -EIO; + } + } + } + return HTTP_CON_OK; +} + +int +rtsp_setup( http_client_t *hc, + const char *path, const char *query, + const char *multicast_addr, + int rtp_port, int rtpc_port ) +{ + http_arg_list_t h; + char transport[256]; + + if (multicast_addr) { + snprintf(transport, sizeof(transport), + "RTP/AVP;multicast;destination=%s;ttl=1;client_port=%i-%i", + multicast_addr, rtp_port, rtpc_port); + } else { + snprintf(transport, sizeof(transport), + "RTP/AVP;unicast;client_port=%i-%i", rtp_port, rtpc_port); + } + + http_arg_init(&h); + http_arg_set(&h, "Transport", transport); + return rtsp_send(hc, RTSP_CMD_SETUP, path, query, &h); +} + +int +rtsp_describe_decode( http_client_t *hc ) +{ + http_arg_t *ra; + + /* TODO: Probably rewrite the data to the htsmsg tree ? */ + printf("describe: %i\n", hc->hc_code); + TAILQ_FOREACH(ra, &hc->hc_args, link) + printf(" %s: %s\n", ra->key, ra->val); + printf("data:\n%s\n", hc->hc_data); + return HTTP_CON_OK; +} diff --git a/src/service.c b/src/service.c index fd765e79..7c34e652 100644 --- a/src/service.c +++ b/src/service.c @@ -44,6 +44,7 @@ #include "lang_codes.h" #include "descrambler.h" #include "input.h" +#include "esfilter.h" static void service_data_timeout(void *aux); static void service_class_save(struct idnode *self); @@ -284,7 +285,7 @@ service_stop(service_t *t) /** * Clean up each stream */ - TAILQ_FOREACH(st, &t->s_components, es_link) + TAILQ_FOREACH(st, &t->s_filt_components, es_link) stream_clean(st); t->s_status = SERVICE_IDLE; @@ -319,6 +320,161 @@ service_remove_subscriber(service_t *t, th_subscription_t *s, } +/** + * + */ +#define ESFM_USED (1<<0) +#define ESFM_IGNORE (1<<1) + +static void +service_build_filter_add(service_t *t, elementary_stream_t *st, + elementary_stream_t **sta, int *p) +{ + /* only once */ + if (st->es_filter & ESFM_USED) + return; + st->es_filter |= ESFM_USED; + TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link); + sta[*p] = st; + (*p)++; +} + +/** + * + */ +void +service_build_filter(service_t *t) +{ + elementary_stream_t *st, *st2, **sta; + esfilter_t *esf; + caid_t *ca; + int i, n, p, o, exclusive; + uint32_t mask; + + /* rebuild the filtered and ordered components */ + TAILQ_INIT(&t->s_filt_components); + + for (i = ESF_CLASS_VIDEO; i <= ESF_CLASS_LAST; i++) + if (!TAILQ_EMPTY(&esfilters[i])) + goto filter; + + TAILQ_FOREACH(st, &t->s_components, es_link) + TAILQ_INSERT_TAIL(&t->s_filt_components, st, es_filt_link); + return; + +filter: + n = 0; + TAILQ_FOREACH(st, &t->s_components, es_link) { + st->es_filter = 0; + n++; + } + + sta = alloca(sizeof(elementary_stream_t *) * n); + + for (i = ESF_CLASS_VIDEO, p = 0; i <= ESF_CLASS_LAST; i++) { + o = p; + mask = esfilterclsmask[i]; + if (TAILQ_EMPTY(&esfilters[i])) { + TAILQ_FOREACH(st, &t->s_components, es_link) { + if ((mask & SCT_MASK(st->es_type)) != 0) + service_build_filter_add(t, st, sta, &p); + } + continue; + } + exclusive = 0; + TAILQ_FOREACH(esf, &esfilters[i], esf_link) { + if (!esf->esf_enabled) + continue; + TAILQ_FOREACH(st, &t->s_components, es_link) { + if ((mask & SCT_MASK(st->es_type)) == 0) + continue; + if (esf->esf_type && (esf->esf_type & SCT_MASK(st->es_type)) == 0) + continue; + if (esf->esf_language[0] && + strncmp(esf->esf_language, st->es_lang, 4)) + continue; + if (esf->esf_service && esf->esf_service[0]) { + if (strcmp(esf->esf_service, idnode_uuid_as_str(&t->s_id))) + continue; + if (esf->esf_pid && esf->esf_pid != st->es_pid) + continue; + } + if (i == ESF_CLASS_CA && + (esf->esf_caid != -1 || esf->esf_caprovider != -1)) { + LIST_FOREACH(ca, &st->es_caids, link) { + if (esf->esf_caid != -1 && ca->caid != esf->esf_caid) + continue; + if (esf->esf_caprovider != -1 && ca->providerid != esf->esf_caprovider) + continue; + break; + } + if (ca == NULL) + continue; + } + if (esf->esf_log) + tvhlog(LOG_INFO, "service", "esfilter: %s %03d %05d %s %s %s %s", + esfilter_class2txt(i), esf->esf_index, + st->es_pid, streaming_component_type2txt(st->es_type), + lang_code_get(st->es_lang), t->s_nicename, + esfilter_action2txt(esf->esf_action)); + switch (esf->esf_action) { + case ESFA_NONE: + break; + case ESFA_IGNORE: + st->es_filter |= ESFM_IGNORE; + break; + case ESFA_USE: + service_build_filter_add(t, st, sta, &p); + break; + case ESFA_ONCE: + if (esf->esf_language[0] == '\0') { + service_build_filter_add(t, st, sta, &p); + } else { + int present = 0; + TAILQ_FOREACH(st2, &t->s_components, es_link) { + if ((st2->es_filter & ESFM_USED) == 0) + continue; + if (strcmp(st2->es_lang, st->es_lang) == 0) { + present = 1; + break; + } + } + if (!present) + service_build_filter_add(t, st, sta, &p); + } + break; + case ESFA_EXCLUSIVE: + break; + case ESFA_EMPTY: + if (p == o) + service_build_filter_add(t, st, sta, &p); + break; + default: + tvhlog(LOG_DEBUG, "service", "Unknown esfilter action %d", esf->esf_action); + break; + } + if (esf->esf_action == ESFA_EXCLUSIVE) { + /* forget previous work */ + while (p > o) { + p--; + TAILQ_REMOVE(&t->s_filt_components, sta[p], es_filt_link); + } + service_build_filter_add(t, st, sta, &p); + exclusive = 1; + break; + } + } + } + if (!exclusive) { + TAILQ_FOREACH(st, &t->s_components, es_link) { + if ((mask & SCT_MASK(st->es_type)) != 0 && + (st->es_filter & (ESFM_USED|ESFM_IGNORE)) == 0) + service_build_filter_add(t, st, sta, &p); + } + } + } +} + /** * */ @@ -336,6 +492,8 @@ service_start(service_t *t, int instance) t->s_streaming_status = 0; t->s_scrambled_seen = 0; + service_build_filter(t); + if((r = t->s_start_feed(t, instance))) return r; @@ -349,7 +507,7 @@ service_start(service_t *t, int instance) /** * Initialize stream */ - TAILQ_FOREACH(st, &t->s_components, es_link) + TAILQ_FOREACH(st, &t->s_filt_components, es_link) stream_init(st); pthread_mutex_unlock(&t->s_stream_mutex); @@ -501,6 +659,7 @@ service_destroy(service_t *t, int delconf) t->s_status = SERVICE_ZOMBIE; + TAILQ_INIT(&t->s_filt_components); while((st = TAILQ_FIRST(&t->s_components)) != NULL) service_stream_destroy(t, st); @@ -552,6 +711,7 @@ service_create0 t->s_channel_name = service_channel_name; t->s_provider_name = service_provider_name; TAILQ_INIT(&t->s_components); + TAILQ_INIT(&t->s_filt_components); t->s_last_pid = -1; streaming_pad_init(&t->s_streaming_pad); @@ -631,7 +791,7 @@ elementary_stream_t * service_stream_create(service_t *t, int pid, streaming_component_type_t type) { - elementary_stream_t *st; + elementary_stream_t *st, *st2; int i = 0; int idx = 0; lock_assert(&t->s_stream_mutex); @@ -662,8 +822,14 @@ service_stream_create(service_t *t, int pid, if(t->s_flags & S_DEBUG) tvhlog(LOG_DEBUG, "service", "Add stream %s", st->es_nicename); - if(t->s_status == SERVICE_RUNNING) - stream_init(st); + if(t->s_status == SERVICE_RUNNING) { + service_build_filter(t); + TAILQ_FOREACH(st2, &t->s_filt_components, es_filt_link) + if (st2 == st) { + stream_init(st); + break; + } + } return st; } @@ -842,9 +1008,10 @@ service_restart(service_t *t, int had_components) streaming_msg_free(sm); } + service_build_filter(t); descrambler_service_start(t); - if(TAILQ_FIRST(&t->s_components) != NULL) { + if(TAILQ_FIRST(&t->s_filt_components) != NULL) { sm = streaming_msg_create_data(SMT_START, service_build_stream_start(t)); streaming_pad_deliver(&t->s_streaming_pad, sm); @@ -871,7 +1038,7 @@ service_build_stream_start(service_t *t) lock_assert(&t->s_stream_mutex); - TAILQ_FOREACH(st, &t->s_components, es_link) + TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) n++; ss = calloc(1, sizeof(streaming_start_t) + @@ -880,7 +1047,7 @@ service_build_stream_start(service_t *t) ss->ss_num_components = n; n = 0; - TAILQ_FOREACH(st, &t->s_components, es_link) { + TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) { streaming_start_component_t *ssc = &ss->ss_components[n++]; ssc->ssc_index = st->es_index; ssc->ssc_type = st->es_type; diff --git a/src/service.h b/src/service.h index ab53082b..2aa5b56f 100644 --- a/src/service.h +++ b/src/service.h @@ -35,6 +35,7 @@ struct channel; typedef struct elementary_stream { TAILQ_ENTRY(elementary_stream) es_link; + TAILQ_ENTRY(elementary_stream) es_filt_link; int es_position; struct service *es_service; @@ -124,6 +125,9 @@ typedef struct elementary_stream { /* SI section processing (horrible hack) */ void *es_section; + /* Filter temporary variable */ + uint32_t es_filter; + } elementary_stream_t; @@ -400,9 +404,10 @@ typedef struct service { int s_caid; /** - * List of all components. + * List of all and filtered components. */ struct elementary_stream_queue s_components; + struct elementary_stream_queue s_filt_components; int s_last_pid; elementary_stream_t *s_last_es; @@ -429,6 +434,8 @@ void service_done(void); int service_start(service_t *t, int instance); +void service_build_filter(service_t *t); + service_t *service_create0(service_t *t, const idclass_t *idc, const char *uuid, int source_type, htsmsg_t *conf); #define service_create(t, c, u, s, m)\ diff --git a/src/subscriptions.c b/src/subscriptions.c index 0154291b..80985199 100644 --- a/src/subscriptions.c +++ b/src/subscriptions.c @@ -74,7 +74,7 @@ subscription_link_service(th_subscription_t *s, service_t *t) pthread_mutex_lock(&t->s_stream_mutex); - if(TAILQ_FIRST(&t->s_components) != NULL) { + if(TAILQ_FIRST(&t->s_filt_components) != NULL) { if(s->ths_start_message != NULL) streaming_msg_free(s->ths_start_message); @@ -122,7 +122,7 @@ subscription_unlink_service0(th_subscription_t *s, int reason, int stop) streaming_target_disconnect(&t->s_streaming_pad, &s->ths_input); if(stop && - TAILQ_FIRST(&t->s_components) != NULL && + TAILQ_FIRST(&t->s_filt_components) != NULL && s->ths_state == SUBSCRIPTION_GOT_SERVICE) { // Send a STOP message to the subscription client sm = streaming_msg_create_code(SMT_STOP, reason); diff --git a/src/tcp.c b/src/tcp.c index 89cedf2a..af60bc10 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -142,7 +142,9 @@ tcp_connect(const char *hostname, int port, char *errbuf, size_t errbufsize, free(tmphstbuf); if(r == -1) { - if(errno == EINPROGRESS) { + if(errno == EINPROGRESS && timeout < 0) { + err = 0; + } else if(errno == EINPROGRESS) { struct pollfd pfd; pfd.fd = fd; @@ -640,6 +642,9 @@ tcp_server_delete(void *server) tcp_server_t *ts = server; tvhpoll_event_t ev; + if (server == NULL) + return; + memset(&ev, 0, sizeof(ev)); ev.fd = ts->serverfd; ev.events = TVHPOLL_IN; diff --git a/src/tvheadend.h b/src/tvheadend.h index 15031f45..83d29fe3 100644 --- a/src/tvheadend.h +++ b/src/tvheadend.h @@ -220,8 +220,11 @@ typedef enum { SCT_MP4A, SCT_VP8, SCT_VORBIS, + SCT_LAST = SCT_VORBIS } streaming_component_type_t; +#define SCT_MASK(t) (1 << (t)) + #define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 || \ (t) == SCT_VP8) @@ -575,6 +578,11 @@ uint32_t tvh_crc32(const uint8_t *data, size_t datalen, uint32_t crc); int base64_decode(uint8_t *out, const char *in, int out_size); +char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size); + +/* Calculate the output size needed to base64-encode x bytes. */ +#define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) + int put_utf8(char *out, int c); static inline int64_t ts_rescale(int64_t ts, int tb) diff --git a/src/tvhlog.c b/src/tvhlog.c index 772fbd86..343e9909 100644 --- a/src/tvhlog.c +++ b/src/tvhlog.c @@ -270,7 +270,7 @@ void tvhlogv ( const char *file, int line, pthread_mutex_lock(&tvhlog_mutex); /* Check for full */ - if (tvhlog_queue_full) { + if (tvhlog_queue_full || !tvhlog_run) { pthread_mutex_unlock(&tvhlog_mutex); return; } diff --git a/src/tvhpoll.c b/src/tvhpoll.c index 29e58936..1783dc12 100644 --- a/src/tvhpoll.c +++ b/src/tvhpoll.c @@ -91,6 +91,8 @@ tvhpoll_create ( size_t n ) void tvhpoll_destroy ( tvhpoll_t *tp ) { + if (tp == NULL) + return; #if ENABLE_EPOLL || ENABLE_KQUEUE free(tp->ev); close(tp->fd); diff --git a/src/udp.c b/src/udp.c new file mode 100644 index 00000000..35ec2fcf --- /dev/null +++ b/src/udp.c @@ -0,0 +1,485 @@ +/* + * TVHeadend - UDP common routines + * + * Copyright (C) 2013 Adam Sutton + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE +#include "tvheadend.h" +#include "udp.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(PLATFORM_LINUX) +#include +#elif defined(PLATFORM_FREEBSD) +# include +# ifndef IPV6_ADD_MEMBERSHIP +# define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +# define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +# endif +#endif + +extern int tcp_preferred_address_family; + +static int +udp_resolve( udp_connection_t *uc, int receiver ) +{ + struct addrinfo hints, *res, *ressave, *use = NULL; + char port_buf[6]; + int x; + + snprintf(port_buf, 6, "%d", uc->port); + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = receiver ? AI_PASSIVE : 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + x = getaddrinfo(uc->host, port_buf, &hints, &res); + if (x < 0) { + tvhlog(LOG_ERR, uc->subsystem, "getaddrinfo: %s: %s", + uc->host != NULL ? uc->host : "*", + x == EAI_SYSTEM ? strerror(errno) : gai_strerror(x)); + return -1; + } + + ressave = res; + while (res) { + if (res->ai_family == tcp_preferred_address_family) { + use = res; + break; + } else if (use == NULL) { + use = res; + } + res = res->ai_next; + } + if (use->ai_family == AF_INET6) { + uc->ip.ss_family = AF_INET6; + IP_AS_V6(uc->ip, port) = htons(uc->port); + memcpy(&IP_AS_V6(uc->ip, addr), &((struct sockaddr_in6 *)use->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + uc->multicast = !!IN6_IS_ADDR_MULTICAST(&IP_AS_V6(uc->ip, addr)); + } else if (use->ai_family == AF_INET) { + uc->ip.ss_family = AF_INET; + IP_AS_V4(uc->ip, port) = htons(uc->port); + IP_AS_V4(uc->ip, addr) = ((struct sockaddr_in *)use->ai_addr)->sin_addr; + uc->multicast = !!IN_MULTICAST(ntohl(IP_AS_V4(uc->ip, addr.s_addr))); + } + freeaddrinfo(ressave); + if (uc->ip.ss_family != AF_INET && uc->ip.ss_family != AF_INET6) { + tvherror(uc->subsystem, "%s - failed to process host '%s'", uc->name, uc->host); + return -1; + } + return 0; +} + +udp_connection_t * +udp_bind ( const char *subsystem, const char *name, + const char *bindaddr, int port, + const char *ifname, int rxsize ) +{ + int fd, solip, reuse = 1; + struct ifreq ifr; + udp_connection_t *uc; + char buf[256]; + socklen_t addrlen; + + uc = calloc(1, sizeof(udp_connection_t)); + uc->fd = -1; + uc->host = bindaddr ? strdup(bindaddr) : NULL; + uc->port = port; + uc->ifname = ifname ? strdup(ifname) : NULL; + uc->subsystem = subsystem ? strdup(subsystem) : NULL; + uc->name = name ? strdup(name) : NULL; + uc->rxtxsize = rxsize; + + if (udp_resolve(uc, 1) < 0) { + udp_close(uc); + return UDP_FATAL_ERROR; + } + + /* Open socket */ + if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) { + tvherror(subsystem, "%s - failed to create socket [%s]", + name, strerror(errno)); + udp_close(uc); + return UDP_FATAL_ERROR; + } + + /* Mark reuse address */ + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + + /* Bind to interface */ + memset(&ifr, 0, sizeof(ifr)); + if (ifname && *ifname) { + snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + tvherror(subsystem, "%s - could not find interface %s", + name, ifname); + goto error; + } + } + + /* IPv4 */ + if (uc->ip.ss_family == AF_INET) { + struct ip_mreqn m; + memset(&m, 0, sizeof(m)); + + /* Bind */ + if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in)) == -1) { + inet_ntop(AF_INET, &IP_AS_V4(uc->ip, addr), buf, sizeof(buf)); + tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]", + name, buf, ntohs(IP_AS_V4(uc->ip, port)), strerror(errno)); + goto error; + } + + if (uc->multicast) { + /* Join group */ + m.imr_multiaddr = IP_AS_V4(uc->ip, addr); + m.imr_address.s_addr = 0; +#if defined(PLATFORM_LINUX) + m.imr_ifindex = ifr.ifr_ifindex; +#elif defined(PLATFORM_FREEBSD) + m.imr_ifindex = ifr.ifr_index; +#endif +#ifdef SOL_IP + solip = SOL_IP; +#else + { + struct protoent *pent; + pent = getprotobyname("ip"); + solip = (pent != NULL) ? pent->p_proto : 0; + } +#endif + + if (setsockopt(fd, solip, IP_ADD_MEMBERSHIP, &m, sizeof(m))) { + inet_ntop(AF_INET, &m.imr_multiaddr, buf, sizeof(buf)); + tvhwarn("iptv", "%s - cannot join %s [%s]", + name, buf, strerror(errno)); + } + } + + /* Bind to IPv6 group */ + } else { + struct ipv6_mreq m; + memset(&m, 0, sizeof(m)); + + /* Bind */ + if (bind(fd, (struct sockaddr *)&uc->ip, sizeof(struct sockaddr_in6)) == -1) { + inet_ntop(AF_INET6, &IP_AS_V6(uc->ip, addr), buf, sizeof(buf)); + tvherror(subsystem, "%s - cannot bind %s:%hu [e=%s]", + name, buf, ntohs(IP_AS_V6(uc->ip, port)), strerror(errno)); + goto error; + } + + if (uc->multicast) { + /* Join group */ + m.ipv6mr_multiaddr = IP_AS_V6(uc->ip, addr); +#if defined(PLATFORM_LINUX) + m.ipv6mr_interface = ifr.ifr_ifindex; +#elif defined(PLATFORM_FREEBSD) + m.ipv6mr_interface = ifr.ifr_index; +#endif +#ifdef SOL_IPV6 + if (setsockopt(fd, SOL_IPV6, IPV6_ADD_MEMBERSHIP, &m, sizeof(m))) { + inet_ntop(AF_INET, &m.ipv6mr_multiaddr, buf, sizeof(buf)); + tvhwarn(subsystem, "%s - cannot join %s [%s]", + name, buf, strerror(errno)); + } +#else + tvherror("iptv", "IPv6 multicast not supported"); + goto error; +#endif + } + } + + addrlen = sizeof(uc->ip); + getsockname(fd, (struct sockaddr *)&uc->ip, &addrlen); + + /* Increase RX buffer size */ + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rxsize, sizeof(rxsize)) == -1) + tvhwarn("iptv", "%s - cannot increase UDP rx buffer size [%s]", + name, strerror(errno)); + + uc->fd = fd; + return uc; + +error: + udp_close(uc); + return NULL; +} + +int +udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2, + const char *subsystem, const char *name1, + const char *name2, const char *host, int port, + const char *ifname, int rxsize1, int rxsize2 ) +{ + udp_connection_t *u1 = NULL, *u2 = NULL; + udp_connection_t *ucs[10] = { NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL }; + int pos = 0, i, port2; + + memset(&ucs, 0, sizeof(ucs)); + while (1) { + u1 = udp_bind(subsystem, name1, host, port, ifname, rxsize1); + if (u1 == NULL || u1 == UDP_FATAL_ERROR) + goto fail; + port2 = ntohs(IP_PORT(u1->ip)); + /* RTP port should be even, RTCP port should be odd */ + if ((port2 % 2) == 0) { + u2 = udp_bind(subsystem, name2, host, port2 + 1, ifname, rxsize2); + if (u2 != NULL && u2 != UDP_FATAL_ERROR) + break; + } + ucs[pos++] = u1; + if (port || pos >= ARRAY_SIZE(ucs)) + goto fail; + } + for (i = 0; i < pos; i++) + udp_close(ucs[i]); + *_u1 = u1; + *_u2 = u2; + return 0; +fail: + for (i = 0; i < pos; i++) + udp_close(ucs[i]); + return -1; +} + +udp_connection_t * +udp_connect ( const char *subsystem, const char *name, + const char *host, int port, + const char *ifname, int txsize ) +{ + int fd; + struct ifreq ifr; + udp_connection_t *uc; + + uc = calloc(1, sizeof(udp_connection_t)); + uc->fd = -1; + uc->host = host ? strdup(host) : NULL; + uc->port = port; + uc->ifname = ifname ? strdup(ifname) : NULL; + uc->subsystem = subsystem ? strdup(subsystem) : NULL; + uc->name = name ? strdup(name) : NULL; + uc->rxtxsize = txsize; + + if (udp_resolve(uc, 1) < 0) { + udp_close(uc); + return UDP_FATAL_ERROR; + } + + /* Open socket */ + if ((fd = tvh_socket(uc->ip.ss_family, SOCK_DGRAM, 0)) == -1) { + tvherror(subsystem, "%s - failed to create socket [%s]", + name, strerror(errno)); + udp_close(uc); + return UDP_FATAL_ERROR; + } + + /* Bind to interface */ + memset(&ifr, 0, sizeof(ifr)); + if (ifname && *ifname) { + snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname); + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + tvherror(subsystem, "%s - could not find interface %s", + name, ifname); + goto error; + } + } + + /* Increase TX buffer size */ + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &txsize, sizeof(txsize)) == -1) + tvhwarn("iptv", "%s - cannot increase UDP tx buffer size [%s]", + name, strerror(errno)); + + uc->fd = fd; + return uc; + +error: + udp_close(uc); + return NULL; +} + +void +udp_close( udp_connection_t *uc ) +{ + if (uc == NULL || uc == UDP_FATAL_ERROR) + return; + if (uc->fd >= 0) + close(uc->fd); + free(uc->host); + free(uc->ifname); + free(uc->subsystem); + free(uc->name); + free(uc); +} + +int +udp_write( udp_connection_t *uc, const void *buf, size_t len, + struct sockaddr_storage *storage ) +{ + int r; + + if (storage == NULL) + storage = &uc->ip; + while (len) { + r = sendto(uc->fd, buf, len, 0, (struct sockaddr*)storage, + storage->ss_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { + usleep(100); + continue; + } + break; + } + len -= r; + buf += r; + } + return len; +} + +int +udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q, + struct sockaddr_storage *storage ) +{ + htsbuf_data_t *hd; + int l, r = 0; + void *p; + + while ((hd = TAILQ_FIRST(&q->hq_q)) != NULL) { + if (!r) { + l = hd->hd_data_len - hd->hd_data_off; + p = hd->hd_data + hd->hd_data_off; + r = udp_write(uc, p, l, storage); + } + htsbuf_data_free(q, hd); + } + q->hq_size = 0; + return r; +} + +/* + * UDP multi packet receive support + */ + +#if !defined (CONFIG_RECVMMSG) && defined(__linux__) +/* define the syscall - works only for linux */ +#include +#ifdef __NR_recvmmsg + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, + unsigned int flags, struct timespec *timeout); + +int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, + unsigned int flags, struct timespec *timeout) +{ + return syscall(__NR_recvmmsg, sockfd, msgvec, vlen, flags, timeout); +} + +#define CONFIG_RECVMMSG + +#endif +#endif + + +#ifndef CONFIG_RECVMMSG + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +static int +recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, + unsigned int flags, struct timespec *timeout) +{ + ssize_t r; + unsigned int i; + + for (i = 0; i < vlen; i++) { + r = recvmsg(sockfd, &msgvec->msg_hdr, flags); + if (r < 0) + return (i > 0) ? i : r; + msgvec->msg_len = r; + msgvec++; + } + return i; +} + +#endif + + +void +udp_multirecv_init( udp_multirecv_t *um, int packets, int psize ) +{ + int i; + + um->um_psize = psize; + um->um_packets = packets; + um->um_data = malloc(packets * psize); + um->um_iovec = malloc(packets * sizeof(struct iovec)); + um->um_riovec = malloc(packets * sizeof(struct iovec)); + um->um_msg = calloc(packets, sizeof(struct mmsghdr)); + for (i = 0; i < packets; i++) { + ((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iov = &um->um_iovec[i]; + ((struct mmsghdr *)um->um_msg)[i].msg_hdr.msg_iovlen = 1; + um->um_iovec[i].iov_base = /* follow thru */ + um->um_riovec[i].iov_base = um->um_data + i * psize; + um->um_iovec[i].iov_len = psize; + } +} + +void +udp_multirecv_free( udp_multirecv_t *um ) +{ + free(um->um_msg); um->um_msg = NULL; + free(um->um_iovec); um->um_iovec = NULL; + free(um->um_data); um->um_data = NULL; + um->um_psize = 0; + um->um_packets = 0; +} + +int +udp_multirecv_read( udp_multirecv_t *um, int fd, int packets, + struct iovec **iovec ) +{ + int n, i; + if (packets > um->um_packets) + packets = um->um_packets; + n = recvmmsg(fd, (struct mmsghdr *)um->um_msg, packets, MSG_DONTWAIT, NULL); + if (n > 0) { + for (i = 0; i < n; i++) + um->um_riovec[i].iov_len = ((struct mmsghdr *)um->um_msg)[i].msg_len; + *iovec = um->um_riovec; + } + return n; +} diff --git a/src/udp.h b/src/udp.h new file mode 100644 index 00000000..c5fa2eef --- /dev/null +++ b/src/udp.h @@ -0,0 +1,91 @@ +/* + * tvheadend, UDP interface + * Copyright (C) 2013 Adam Sutton + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UDP_H_ +#define UDP_H_ + +#include +#include "tcp.h" + +#define UDP_FATAL_ERROR ((void *)-1) + +#define IP_AS_V4(storage, f) ((struct sockaddr_in *)&(storage))->sin_##f +#define IP_AS_V6(storage, f) ((struct sockaddr_in6 *)&(storage))->sin6_##f +#define IP_IN_ADDR(storage) \ + ((storage).ss_family == AF_INET6 ? \ + &((struct sockaddr_in6 *)&(storage))->sin6_addr : \ + (void *)&((struct sockaddr_in *)&(storage))->sin_addr) +#define IP_PORT(storage) \ + ((storage).ss_family == AF_INET6 ? \ + ((struct sockaddr_in6 *)&(storage))->sin6_port : \ + ((struct sockaddr_in *)&(storage))->sin_port) + +typedef struct udp_connection { + char *host; + int port; + int multicast; + char *ifname; + struct sockaddr_storage ip; + int fd; + char *subsystem; + char *name; + int rxtxsize; +} udp_connection_t; + +udp_connection_t * +udp_bind ( const char *subsystem, const char *name, + const char *bindaddr, int port, + const char *ifname, int rxsize ); +int +udp_bind_double ( udp_connection_t **_u1, udp_connection_t **_u2, + const char *subsystem, const char *name1, + const char *name2, const char *host, int port, + const char *ifname, int rxsize1, int rxsize2 ); +udp_connection_t * +udp_connect ( const char *subsystem, const char *name, + const char *host, int port, + const char *ifname, int txsize ); +void +udp_close ( udp_connection_t *uc ); +int +udp_write( udp_connection_t *uc, const void *buf, size_t len, + struct sockaddr_storage *storage ); +int +udp_write_queue( udp_connection_t *uc, htsbuf_queue_t *q, + struct sockaddr_storage *storage ); + +typedef struct udp_multirecv { + int um_psize; + int um_packets; + uint8_t *um_data; + struct iovec *um_iovec; + struct iovec *um_riovec; + struct mmsghdr *um_msg; +} udp_multirecv_t; + +void +udp_multirecv_init( udp_multirecv_t *um, int packets, int psize ); +void +udp_multirecv_free( udp_multirecv_t *um ); +int +udp_multirecv_read( udp_multirecv_t *um, int fd, int packets, + struct iovec **iovec ); + + +#endif /* UDP_H_ */ diff --git a/src/upnp.c b/src/upnp.c new file mode 100644 index 00000000..849abbe0 --- /dev/null +++ b/src/upnp.c @@ -0,0 +1,219 @@ +/* + * tvheadend, UPnP interface + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvheadend.h" +#include "tvhpoll.h" +#include "upnp.h" + +int upnp_running; +static pthread_t upnp_tid; +pthread_mutex_t upnp_lock; + +TAILQ_HEAD(upnp_active_services, upnp_service); + +typedef struct upnp_data { + TAILQ_ENTRY(upnp_data) data_link; + struct sockaddr_storage storage; + htsbuf_queue_t queue; +} upnp_data_t; + +TAILQ_HEAD(upnp_data_queue_write, upnp_data); + +static struct upnp_active_services upnp_services; +static struct upnp_data_queue_write upnp_data_write; +static struct sockaddr_storage upnp_ipv4_multicast; + +/* + * + */ +upnp_service_t *upnp_service_create0( upnp_service_t *us ) +{ + pthread_mutex_lock(&upnp_lock); + TAILQ_INSERT_TAIL(&upnp_services, us, us_link); + pthread_mutex_unlock(&upnp_lock); + return us; +} + +void upnp_service_destroy( upnp_service_t *us ) +{ + pthread_mutex_lock(&upnp_lock); + TAILQ_REMOVE(&upnp_services, us, us_link); + us->us_destroy(us); + pthread_mutex_unlock(&upnp_lock); + free(us); +} + +/* + * + */ +void +upnp_send( htsbuf_queue_t *q, struct sockaddr_storage *storage ) +{ + upnp_data_t *data; + + if (!upnp_running) + return; + data = calloc(1, sizeof(upnp_data_t)); + htsbuf_queue_init(&data->queue, 0); + htsbuf_appendq(&data->queue, q); + if (storage == NULL) + data->storage = upnp_ipv4_multicast; + else + data->storage = *storage; + pthread_mutex_lock(&upnp_lock); + TAILQ_INSERT_TAIL(&upnp_data_write, data, data_link); + pthread_mutex_unlock(&upnp_lock); +} + +/* + * Discovery thread + */ +static void * +upnp_thread( void *aux ) +{ + char *bindaddr = aux; + tvhpoll_t *poll = tvhpoll_create(2); + tvhpoll_event_t ev[2]; + upnp_data_t *data; + udp_connection_t *multicast = NULL, *unicast = NULL; + udp_connection_t *conn; + unsigned char buf[16384]; + upnp_service_t *us; + struct sockaddr_storage ip; + socklen_t iplen; + size_t size; + int r; + + multicast = udp_bind("upnp", "upnp_thread_multicast", + "239.255.255.250", 1900, + NULL, 32*1024); + if (multicast == NULL || multicast == UDP_FATAL_ERROR) + goto error; + unicast = udp_bind("upnp", "upnp_thread_unicast", bindaddr, 1900, + NULL, 32*1024); + if (unicast == NULL || unicast == UDP_FATAL_ERROR) + goto error; + + memset(&ev, 0, sizeof(ev)); + ev[0].fd = multicast->fd; + ev[0].events = TVHPOLL_IN; + ev[0].data.ptr = multicast; + ev[1].fd = unicast->fd; + ev[1].events = TVHPOLL_IN; + ev[1].data.ptr = unicast; + tvhpoll_add(poll, ev, 2); + + while (upnp_running && multicast->fd >= 0) { + r = tvhpoll_wait(poll, ev, 2, 1000); + + while (r-- > 0) { + if ((ev[r].events & TVHPOLL_IN) != 0) { + conn = ev[r].data.ptr; + iplen = sizeof(ip); + size = recvfrom(conn->fd, buf, sizeof(buf), 0, + (struct sockaddr *)&ip, &iplen); +#if ENABLE_TRACE + if (size > 0) { + char tbuf[256]; + inet_ntop(ip.ss_family, IP_IN_ADDR(ip), tbuf, sizeof(tbuf)); + tvhtrace("upnp", "%s - received data from %s:%hu [size=%zi]", + conn == multicast ? "multicast" : "unicast", + tbuf, IP_PORT(ip), size); + tvhlog_hexdump("upnp", buf, size); + } +#endif + /* TODO: a filter */ + TAILQ_FOREACH(us, &upnp_services, us_link) + us->us_received(buf, size, conn, &ip); + } + } + + while (1) { + pthread_mutex_lock(&upnp_lock); + data = TAILQ_FIRST(&upnp_data_write); + if (data) + TAILQ_REMOVE(&upnp_data_write, data, data_link); + pthread_mutex_unlock(&upnp_lock); + if (data == NULL) + break; + udp_write_queue(unicast, &data->queue, &data->storage); + htsbuf_queue_flush(&data->queue); + free(data); + } + } + +error: + upnp_running = 0; + tvhpoll_destroy(poll); + udp_close(unicast); + udp_close(multicast); + return NULL; +} + +/* + * Fire up UPnP server + */ +void +upnp_server_init(const char *bindaddr) +{ + int r; + + memset(&upnp_ipv4_multicast, 0, sizeof(upnp_ipv4_multicast)); + upnp_ipv4_multicast.ss_family = AF_INET; + IP_AS_V4(upnp_ipv4_multicast, port) = htons(1900); + r = inet_pton(AF_INET, "239.255.255.250", &IP_AS_V4(upnp_ipv4_multicast, addr)); + assert(r); + + pthread_mutex_init(&upnp_lock, NULL); + TAILQ_INIT(&upnp_data_write); + TAILQ_INIT(&upnp_services); + upnp_running = 1; + tvhthread_create(&upnp_tid, NULL, upnp_thread, (char *)bindaddr, 0); +} + +void +upnp_server_done(void) +{ + upnp_data_t *data; + upnp_service_t *us; + + upnp_running = 0; + pthread_kill(upnp_tid, SIGTERM); + pthread_join(upnp_tid, NULL); + while ((us = TAILQ_FIRST(&upnp_services)) != NULL) + upnp_service_destroy(us); + while ((data = TAILQ_FIRST(&upnp_data_write)) != NULL) { + TAILQ_REMOVE(&upnp_data_write, data, data_link); + htsbuf_queue_flush(&data->queue); + free(data); + } +} diff --git a/src/upnp.h b/src/upnp.h new file mode 100644 index 00000000..7753e430 --- /dev/null +++ b/src/upnp.h @@ -0,0 +1,47 @@ +/* + * tvheadend, UPnP interface + * Copyright (C) 2014 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UPNP_H_ +#define UPNP_H_ + +#include "http.h" +#include "udp.h" + +extern int upnp_running; + +typedef struct upnp_service upnp_service_t; + +struct upnp_service { + TAILQ_ENTRY(upnp_service) us_link; + void (*us_received)(uint8_t *data, size_t len, + udp_connection_t *conn, + struct sockaddr_storage *storage); + void (*us_destroy)(upnp_service_t *us); +}; + +upnp_service_t *upnp_service_create0(upnp_service_t *us); +#define upnp_service_create(us) \ + upnp_service_create0(calloc(1, sizeof(struct us))) +void upnp_service_destroy(upnp_service_t *service); + +void upnp_send(htsbuf_queue_t *q, struct sockaddr_storage *storage); + +void upnp_server_init(const char *bindaddr); +void upnp_server_done(void); + +#endif /* UPNP_H_ */ diff --git a/src/url.c b/src/url.c index 8398b2da..c9a8cd60 100644 --- a/src/url.c +++ b/src/url.c @@ -25,11 +25,40 @@ #include #include + +void +urlreset ( url_t *url ) +{ + free(url->scheme); + free(url->user); + free(url->pass); + free(url->host); + free(url->path); + free(url->query); + free(url->frag); + free(url->raw); + memset(url, 0, sizeof(*url)); +} + +void +urlcopy ( url_t *dst, const url_t *src ) +{ + dst->scheme = strdup(src->scheme); + dst->user = strdup(src->user); + dst->pass = strdup(src->pass); + dst->host = strdup(src->host); + dst->port = src->port; + dst->path = strdup(src->path); + dst->query = strdup(src->query); + dst->frag = strdup(src->frag); + dst->raw = strdup(src->raw); +} + /* Use liburiparser if available */ #if ENABLE_URIPARSER #include -int +int urlparse ( const char *str, url_t *url ) { UriParserStateA state; @@ -37,6 +66,11 @@ urlparse ( const char *str, url_t *url ) UriUriA uri; char *s, buf[256]; + if (str == NULL || url == NULL) + return -1; + + urlreset(url); + /* Parse */ state.uri = &uri; if (uriParseUriA(&state, str) != URI_SUCCESS) { @@ -45,44 +79,52 @@ urlparse ( const char *str, url_t *url ) } /* Store raw */ - strncpy(url->raw, str, sizeof(url->raw)); + url->raw = strdup(str); /* Copy */ #define uri_copy(y, x)\ + if (x.first) {\ + size_t len = x.afterLast - x.first;\ + y = strndup(x.first, len);\ + } +#define uri_copy_static(y, x)\ if (x.first) {\ size_t len = x.afterLast - x.first;\ strncpy(y, x.first, len);\ - y[len] = 0;\ + y[len] = '\0';\ } else {\ - *y = 0;\ + y[0] = '\0';\ } uri_copy(url->scheme, uri.scheme); uri_copy(url->host, uri.hostText); uri_copy(url->user, uri.userInfo); uri_copy(url->query, uri.query); uri_copy(url->frag, uri.fragment); - uri_copy(buf, uri.portText); + uri_copy_static(buf, uri.portText); if (*buf) url->port = atoi(buf); else url->port = 0; - *url->path = 0; path = uri.pathHead; while (path) { + uri_copy_static(buf, path->text); + if (url->path) + url->path = realloc(url->path, strlen(url->path) + strlen(buf) + 2); + else + url->path = calloc(1, strlen(buf) + 2); strcat(url->path, "/"); - uri_copy(buf, path->text); strcat(url->path, buf); path = path->next; } // TODO: query/fragment /* Split user/pass */ - s = strstr(url->user, ":"); - if (s) { - strcpy(url->pass, s+1); - *s = 0; - } else { - *url->pass = 0; + if (url->user) { + s = strstr(url->user, ":"); + if (s) { + strcpy(url->pass, s+1); + *s = 0; + } } /* Cleanup */ @@ -90,6 +132,11 @@ urlparse ( const char *str, url_t *url ) return 0; } +void +urlparse_done( void ) +{ +} + /* Fallback to limited support */ #else /* ENABLE_URIPARSER */ @@ -100,29 +147,38 @@ urlparse ( const char *str, url_t *url ) #define HC "[a-z0-9\\-\\.]" #define URL_RE "^([A-Za-z]+)://(("UC"+)(:("PC"+))?@)?("HC"+)(:([0-9]+))?(/[^\\?]*)?(.([^#]*))?(#(.*))?" +static regex_t *urlparse_exp = NULL; int urlparse ( const char *str, url_t *url ) { - static regex_t *exp = NULL; regmatch_t m[16]; char buf[16]; + if (str == NULL || url == NULL) + return -1; + + urlreset(url); + /* Create regexp */ - if (!exp) { - exp = calloc(1, sizeof(regex_t)); - if (regcomp(exp, URL_RE, REG_ICASE | REG_EXTENDED)) { + if (!urlparse_exp) { + urlparse_exp = calloc(1, sizeof(regex_t)); + if (regcomp(urlparse_exp, URL_RE, REG_ICASE | REG_EXTENDED)) { tvherror("url", "failed to compile regexp"); exit(1); } } /* Execute */ - if (regexec(exp, str, ARRAY_SIZE(m), m, 0)) - return 1; + if (regexec(urlparse_exp, str, ARRAY_SIZE(m), m, 0)) + return -1; /* Extract data */ #define copy(x, i)\ + {\ + x = strndup(str+m[i].rm_so, m[i].rm_eo - m[i].rm_so);\ + }(void)0 +#define copy_static(x, i)\ {\ int len = m[i].rm_eo - m[i].rm_so;\ if (len >= sizeof(x) - 1)\ @@ -135,14 +191,23 @@ urlparse ( const char *str, url_t *url ) copy(url->pass, 5); copy(url->host, 6); copy(url->path, 9); - copy(buf, 8); + copy_static(buf, 8); url->port = atoi(buf); copy(url->query, 11); copy(url->frag, 13); - strncpy(url->raw, str, sizeof(url->raw)); + url->raw = strdup(str); return 0; } +void +urlparse_done( void ) +{ + if (urlparse_exp) { + regfree(urlparse_exp); + free(urlparse_exp); + } +} + #endif /* ENABLE_URIPARSER */ diff --git a/src/url.h b/src/url.h index 200c5905..4d7da6f3 100644 --- a/src/url.h +++ b/src/url.h @@ -22,23 +22,23 @@ #include -// TODO: limits are a bit arbitrary and it's a bit inflexible, but it -// does keep things simple, not having dynamically allocated strings - /* URL structure */ typedef struct url { - char scheme[32]; - char user[128]; - char pass[128]; - char host[256]; - short port; - char path[256]; - char query[1024]; - char frag[256]; - char raw[2048]; + char *scheme; + char *user; + char *pass; + char *host; + short port; + char *path; + char *query; + char *frag; + char *raw; } url_t; +void urlreset ( url_t *url ); int urlparse ( const char *str, url_t *url ); +void urlparse_done ( void ); +void urlcopy ( url_t *dst, const url_t *src ); #endif diff --git a/src/utils.c b/src/utils.c index 7861b57a..6278facc 100644 --- a/src/utils.c +++ b/src/utils.c @@ -193,6 +193,41 @@ base64_decode(uint8_t *out, const char *in, int out_size) return dst - out; } +/* + * b64_encode: Stolen from VLC's http.c. + * Simplified by Michael. + * Fixed edge cases and made it work from data (vs. strings) by Ryan. + */ + +char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size) +{ + static const char b64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *ret, *dst; + unsigned i_bits = 0; + int i_shift = 0; + int bytes_remaining = in_size; + + if (in_size >= UINT_MAX / 4 || + out_size < BASE64_SIZE(in_size)) + return NULL; + ret = dst = out; + while (bytes_remaining) { + i_bits = (i_bits << 8) + *in++; + bytes_remaining--; + i_shift += 8; + + do { + *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; + i_shift -= 6; + } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); + } + while ((dst - ret) & 3) + *dst++ = '='; + *dst = '\0'; + + return ret; +} /** * diff --git a/src/webui/extjs.c b/src/webui/extjs.c index bd828bbc..ed7e5b2e 100755 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -149,6 +149,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque) extjs_load(hq, "static/app/capmteditor.js"); extjs_load(hq, "static/app/tvadapters.js"); extjs_load(hq, "static/app/idnode.js"); + extjs_load(hq, "static/app/esfilter.js"); #if ENABLE_LINUXDVB extjs_load(hq, "static/app/mpegts.js"); #endif diff --git a/src/webui/static/app/esfilter.js b/src/webui/static/app/esfilter.js new file mode 100644 index 00000000..781b46ad --- /dev/null +++ b/src/webui/static/app/esfilter.js @@ -0,0 +1,108 @@ +/* + * Elementary Stream Filters + */ + +tvheadend.esfilter_tab = function(panel) +{ + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/video', + comet : 'esfilter_video', + titleS : 'Video Stream Filter', + titleP : 'Video Stream Filters', + tabIndex : 0, + add : { + url : 'api/esfilter/video', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); + + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/audio', + comet : 'esfilter_audio', + titleS : 'Audio Stream Filter', + titleP : 'Audio Stream Filters', + tabIndex : 1, + add : { + url : 'api/esfilter/audio', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); + + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/teletext', + comet : 'esfilter_teletext', + titleS : 'Teletext Stream Filter', + titleP : 'Teletext Stream Filters', + tabIndex : 2, + add : { + url : 'api/esfilter/teletext', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); + + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/subtit', + comet : 'esfilter_subtit', + titleS : 'Subtitle Stream Filter', + titleP : 'Subtitle Stream Filters', + tabIndex : 3, + add : { + url : 'api/esfilter/subtit', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); + + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/ca', + comet : 'esfilter_ca', + titleS : 'CA Stream Filter', + titleP : 'CA Stream Filters', + tabIndex : 4, + add : { + url : 'api/esfilter/ca', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); + + tvheadend.idnode_grid(panel, { + url : 'api/esfilter/other', + comet : 'esfilter_other', + titleS : 'Other Stream Filter', + titleP : 'Other Stream Filters', + tabIndex : 5, + add : { + url : 'api/esfilter/other', + create : {} + }, + del : true, + move : true, + help : function() { + new tvheadend.help('Elementary Stream Filter', 'config_esfilter.html'); + } + }); +} diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css index 4152748a..810ed5df 100644 --- a/src/webui/static/app/ext.css +++ b/src/webui/static/app/ext.css @@ -148,6 +148,14 @@ background-image: url(../icons/delete.png) !important; } +.moveup { + background-image: url(../icons/arrow_up.png) !important; +} + +.movedown { + background-image: url(../icons/arrow_down.png) !important; +} + .save { background-image: url(../icons/save.png) !important; } @@ -286,6 +294,11 @@ .arrow_switch { background-image: url(../icons/arrow_switch.png) !important; + +} + +.stream_config { + background-image: url(../icons/film_edit.png) !important; } .x-smallhdr { diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index c172ff18..8c7108d1 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -145,7 +145,6 @@ tvheadend.IdNodeField = function (conf) width : w, dataIndex: this.id, header : this.text, - sortable : true, editor : this.editor({create: false}), renderer : this.renderer(), hidden : this.hidden, @@ -458,6 +457,7 @@ tvheadend.idnode_editor_form = function ( d, panel ) tvheadend.idnode_editor = function(item, conf) { var panel = null; + var buttons = []; /* Buttons */ var saveBtn = new Ext.Button({ @@ -476,6 +476,15 @@ tvheadend.idnode_editor = function(item, conf) }); } }); + buttons.push(saveBtn); + + if (conf.help) { + var helpBtn = new Ext.Button({ + text : 'Help', + handler : conf.help + }); + buttons.push(helpBtn); + } panel = new Ext.FormPanel({ title : conf.title || null, @@ -490,7 +499,7 @@ tvheadend.idnode_editor = function(item, conf) //defaults: {width: 330}, defaultType : 'textfield', buttonAlign : 'left', - buttons : [ saveBtn ] + buttons : buttons }); tvheadend.idnode_editor_form(item.props || item.params, panel); @@ -655,10 +664,12 @@ tvheadend.idnode_grid = function(panel, conf) var undoBtn = null; var addBtn = null; var delBtn = null; + var upBtn = null; + var downBtn = null; var editBtn = null; /* Model */ - var idnode = new tvheadend.IdNode(d); + var idnode = new tvheadend.IdNode(d); for (var i = 0; i < idnode.length(); i++) { var f = idnode.field(i); var c = f.column(); @@ -698,8 +709,11 @@ tvheadend.idnode_grid = function(panel, conf) }); /* Model */ + var sortable = true; + if (conf.move) + sortable = false; var model = new Ext.grid.ColumnModel({ - defaultSortable : true, + defaultSortable : sortable, columns : columns }); @@ -717,6 +731,10 @@ tvheadend.idnode_grid = function(panel, conf) select.on('selectionchange', function(s){ if (delBtn) delBtn.setDisabled(s.getCount() == 0); + if (upBtn) { + upBtn.setDisabled(s.getCount() == 0); + downBtn.setDisabled(s.getCount() == 0); + } editBtn.setDisabled(s.getCount() != 1); if (conf.selected) conf.selected(s); @@ -799,7 +817,59 @@ tvheadend.idnode_grid = function(panel, conf) }); buttons.push(delBtn); } - if (conf.add || conf.del) + if (conf.move) { + upBtn = new Ext.Toolbar.Button({ + tooltip : 'Move selected entries up', + iconCls : 'moveup', + text : 'Move Up', + disabled : true, + handler : function() { + var r = select.getSelections(); + if (r && r.length > 0) { + var uuids = [] + for ( var i = 0; i < r.length; i++ ) + uuids.push(r[i].id) + Ext.Ajax.request({ + url : 'api/idnode/moveup', + params : { + uuid: Ext.encode(uuids) + }, + success : function(d) + { + store.reload(); + } + }); + } + } + }); + buttons.push(upBtn); + downBtn = new Ext.Toolbar.Button({ + tooltip : 'Move selected entries down', + iconCls : 'movedown', + text : 'Move Down', + disabled : true, + handler : function() { + var r = select.getSelections(); + if (r && r.length > 0) { + var uuids = [] + for ( var i = 0; i < r.length; i++ ) + uuids.push(r[i].id) + Ext.Ajax.request({ + url : 'api/idnode/movedown', + params : { + uuid: Ext.encode(uuids) + }, + success : function(d) + { + store.reload(); + } + }); + } + } + }); + buttons.push(downBtn); + } + if (conf.add || conf.del || conf.move) buttons.push('-'); editBtn = new Ext.Toolbar.Button({ tooltip : 'Edit selected entry', @@ -1037,7 +1107,8 @@ tvheadend.idnode_tree = function (conf) if(!n.isRoot) current = panel.add(new tvheadend.idnode_editor(n.attributes, { title : 'Parameters', - fixedHeight : true + fixedHeight : true, + help : conf.help || null, })); panel.doLayout(); } diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js index 1dedd02a..3249bbec 100644 --- a/src/webui/static/app/mpegts.js +++ b/src/webui/static/app/mpegts.js @@ -99,15 +99,6 @@ tvheadend.show_service_streams = function ( data ) { var i, j; var html = ''; - html += ''; + } + + function stream ( s ) { var d = ' '; var p = '0x' + hexstr(s.pid) + ' / ' + fixstr(s.pid); @@ -146,16 +152,36 @@ tvheadend.show_service_streams = function ( data ) { } html += ''; html += ''; - } + } + + header(); + + if (data.streams.length) { + for (i = 0; i < data.streams.length; i++) + stream(data.streams[i]); + } else + single('None'); + + single(' '); + single('

After filtering and reordering (without PCR and PMT)

'); + header(); + + if (data.fstreams.length) + for (i = 0; i < data.fstreams.length; i++) + stream(data.fstreams[i]); + else + single('

None

'); var win = new Ext.Window({ title : 'Service details for ' + data.name, layout : 'fit', width : 650, - height : 300, + height : 400, plain : true, bodyStyle : 'padding: 5px', - html : html + html : html, + autoScroll: true, + autoShow: true }); win.show(); } diff --git a/src/webui/static/app/tvadapters.js b/src/webui/static/app/tvadapters.js index a15bb21b..d3353c09 100644 --- a/src/webui/static/app/tvadapters.js +++ b/src/webui/static/app/tvadapters.js @@ -1,3 +1,10 @@ tvheadend.tvadapters = function() { - return tvheadend.idnode_tree({ url: 'api/hardware/tree', title: 'TV adapters', comet: 'hardware'}); + return tvheadend.idnode_tree( { + url : 'api/hardware/tree', + title : 'TV adapters', + comet : 'hardware', + help : function() { + new tvheadend.help('TV adapters', 'config_tvadapters.html'); + } + }); } diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 36b188bf..dd9806a9 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -295,6 +295,17 @@ function accessUpdate(o) { tabs1.push(tvheadend.conf_csa); } + /* Stream Config */ + tvheadend.conf_stream = new Ext.TabPanel({ + activeTab: 0, + autoScroll: true, + title: 'Stream', + iconCls: 'stream_config', + items: [] + }); + tvheadend.esfilter_tab(tvheadend.conf_stream); + tabs1.push(tvheadend.conf_stream); + /* Debug */ tabs1.push(new tvheadend.tvhlog); diff --git a/src/webui/static/icons/film_edit.png b/src/webui/static/icons/film_edit.png new file mode 120000 index 00000000..03a05bfb --- /dev/null +++ b/src/webui/static/icons/film_edit.png @@ -0,0 +1 @@ +../../../../vendor/famfamsilk/film_edit.png \ No newline at end of file diff --git a/support/httpc-test.txt b/support/httpc-test.txt new file mode 100644 index 00000000..418bc254 --- /dev/null +++ b/support/httpc-test.txt @@ -0,0 +1,82 @@ +# +# File format: +# Header= pass this in the http request +# URL= URL +# HandleLocation= Handle the location (redirection) requests (0 or 1) +# DataTransfer=all grab all data and pass then in the data_complete callback +# DataTransfer=cont continuous passing data to the data_received callback +# DataLimit= limit data to these bytes +# ExpectedError= expected error +# SSLPeerVerify= Enable SSL/TLS peer verification (0 or 1) +# Reset=1 reset the initial state - close the current keep-alive conn +# Command= HTTP/RTSP command or EXIT +# + +Header=Connection: close +URL=http://www.google.com +DataTransfer=all +HandleLocation=1 +Command=GET + +Header=Connection: close +URL=http://www.google.com +DataTransfer=all +DataLimit=10 +ExpectedError=EOVERFLOW +HandleLocation=1 +Command=GET + +# +# Keep-alive connection test +# + +Reset=1 + +DataTransfer=all +URL=http://httpbin.org +Command=GET +URL=http://httpbin.org +Command=GET +URL=http://httpbin.org +Command=GET +HandleLocation=1 +URL=http://httpbin.org/relative-redirect/20 +ExpectedError=ELOOP +Command=GET + +# +# Keep-alive SSL test +# + +Reset=1 + +URL=https://httpbin.org +Command=GET +URL=https://httpbin.org +Command=GET +URL=https://httpbin.org +Command=GET +HandleLocation=1 +URL=https://httpbin.org/relative-redirect/20 +ExpectedError=ELOOP +Command=GET + +# +# Google SSL test +# + +Reset=1 + +Header=Connection: close +URL=https://www.google.com +DataTransfer=all +HandleLocation=1 +Command=GET + +Header=Connection: close +URL=https://www.google.com +DataTransfer=all +DataLimit=10 +HandleLocation=1 +ExpectedError=EOVERFLOW +Command=GET
' + d + '