diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b7682d --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ + +LIBSRCS += hdhomerun_channels.c +LIBSRCS += hdhomerun_channelscan.c +LIBSRCS += hdhomerun_control.c +LIBSRCS += hdhomerun_debug.c +LIBSRCS += hdhomerun_device.c +LIBSRCS += hdhomerun_device_selector.c +LIBSRCS += hdhomerun_discover.c +LIBSRCS += hdhomerun_os_posix.c +LIBSRCS += hdhomerun_pkt.c +LIBSRCS += hdhomerun_sock_posix.c +LIBSRCS += hdhomerun_video.c + +CC := $(CROSS_COMPILE)gcc +STRIP := $(CROSS_COMPILE)strip + +CFLAGS += -Wall -O2 -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wpointer-arith +LDFLAGS += -lpthread +SHARED = -shared -Wl,-soname,libhdhomerun$(LIBEXT) + +ifeq ($(OS),Windows_NT) + BINEXT := .exe + LIBEXT := .dll + LDFLAGS += -liphlpapi +else + OS := $(shell uname -s) + LIBEXT := .so + ifeq ($(OS),Linux) + LDFLAGS += -lrt + endif + ifeq ($(OS),SunOS) + LDFLAGS += -lsocket + endif + ifeq ($(OS),Darwin) + CFLAGS += -arch i386 -arch x86_64 + LIBEXT := .dylib + SHARED := -dynamiclib -install_name libhdhomerun$(LIBEXT) + endif +endif + +all : hdhomerun_config$(BINEXT) libhdhomerun$(LIBEXT) + +hdhomerun_config$(BINEXT) : hdhomerun_config.c $(LIBSRCS) + $(CC) $(CFLAGS) $+ $(LDFLAGS) -o $@ + $(STRIP) $@ + +libhdhomerun$(LIBEXT) : $(LIBSRCS) + $(CC) $(CFLAGS) -fPIC -DDLL_EXPORT $(SHARED) $+ $(LDFLAGS) -o $@ + +clean : + -rm -f hdhomerun_config$(BINEXT) + -rm -f libhdhomerun$(LIBEXT) + +distclean : clean + +%: + @echo "(ignoring request to make $@)" + +.PHONY: all list clean distclean diff --git a/README.md b/README.md index a0a7c08..3c0bb57 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# libhdhomerun \ No newline at end of file +/* + * README + * + * Copyright © 2005-2009 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +Top level include file: hdhomerun.h + +Top level API: hdhomerun_device. See hdhomerun_device.h for documentation. + +The hdhomerun_device API should be used rather than the low level control and video APIs required with previous versions. + +Additional libraries required: +- pthread +- iphlpapi (windows only) diff --git a/hdhomerun.h b/hdhomerun.h new file mode 100644 index 0000000..dd287ee --- /dev/null +++ b/hdhomerun.h @@ -0,0 +1,32 @@ +/* + * hdhomerun.h + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun_os.h" +#include "hdhomerun_types.h" +#include "hdhomerun_pkt.h" +#include "hdhomerun_sock.h" +#include "hdhomerun_debug.h" +#include "hdhomerun_discover.h" +#include "hdhomerun_control.h" +#include "hdhomerun_video.h" +#include "hdhomerun_channels.h" +#include "hdhomerun_channelscan.h" +#include "hdhomerun_device.h" +#include "hdhomerun_device_selector.h" diff --git a/hdhomerun_channels.c b/hdhomerun_channels.c new file mode 100644 index 0000000..9beec5e --- /dev/null +++ b/hdhomerun_channels.c @@ -0,0 +1,406 @@ +/* + * hdhomerun_channels.c + * + * Copyright © 2007-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_channel_entry_t { + struct hdhomerun_channel_entry_t *next; + struct hdhomerun_channel_entry_t *prev; + uint32_t frequency; + uint16_t channel_number; + char name[16]; +}; + +struct hdhomerun_channel_list_t { + struct hdhomerun_channel_entry_t *head; + struct hdhomerun_channel_entry_t *tail; +}; + +struct hdhomerun_channelmap_range_t { + uint16_t channel_range_start; + uint16_t channel_range_end; + uint32_t frequency; + uint32_t spacing; +}; + +struct hdhomerun_channelmap_record_t { + const char *channelmap; + const struct hdhomerun_channelmap_range_t *range_list; + const char *channelmap_scan_group; + const char *countrycodes; +}; + +/* AU antenna channels. Channels {6, 7, 8, 9, 9A} are numbered {5, 6, 7, 8, 9} by the HDHomeRun. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_au_bcast[] = { + { 5, 12, 177500000, 7000000}, + { 21, 69, 480500000, 7000000}, + { 0, 0, 0, 0} +}; + +/* EU antenna channels. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_eu_bcast[] = { + { 5, 12, 177500000, 7000000}, + { 21, 69, 474000000, 8000000}, + { 0, 0, 0, 0} +}; + +/* EU cable channels. No common standard - use frequency in MHz for channel number. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_eu_cable[] = { + {108, 862, 108000000, 1000000}, + { 0, 0, 0, 0} +}; + +/* KR cable channels. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_kr_cable[] = { + { 2, 4, 57000000, 6000000}, + { 5, 6, 79000000, 6000000}, + { 7, 13, 177000000, 6000000}, + { 14, 22, 123000000, 6000000}, + { 23, 153, 219000000, 6000000}, + { 0, 0, 0, 0} +}; + +/* JP antenna channels. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_jp_bcast[] = { + { 13, 62, 473000000, 6000000}, + { 0, 0, 0, 0} +}; + +/* US antenna channels. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_bcast[] = { + { 2, 4, 57000000, 6000000}, + { 5, 6, 79000000, 6000000}, + { 7, 13, 177000000, 6000000}, + { 14, 69, 473000000, 6000000}, + { 0, 0, 0, 0} +}; + +/* US cable channels. */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_cable[] = { + { 2, 4, 57000000, 6000000}, + { 5, 6, 79000000, 6000000}, + { 7, 13, 177000000, 6000000}, + { 14, 22, 123000000, 6000000}, + { 23, 94, 219000000, 6000000}, + { 95, 99, 93000000, 6000000}, + {100, 158, 651000000, 6000000}, + { 0, 0, 0, 0} +}; + +/* US cable channels (HRC). */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_hrc[] = { + { 2, 4, 55752700, 6000300}, + { 5, 6, 79753900, 6000300}, + { 7, 13, 175758700, 6000300}, + { 14, 22, 121756000, 6000300}, + { 23, 94, 217760800, 6000300}, + { 95, 99, 91754500, 6000300}, + {100, 158, 649782400, 6000300}, + { 0, 0, 0, 0} +}; + +/* US cable channels (IRC). */ +static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_us_irc[] = { + { 2, 4, 57012500, 6000000}, + { 5, 6, 81012500, 6000000}, + { 7, 13, 177012500, 6000000}, + { 14, 22, 123012500, 6000000}, + { 23, 41, 219012500, 6000000}, + { 42, 42, 333025000, 6000000}, + { 43, 94, 339012500, 6000000}, + { 95, 97, 93012500, 6000000}, + { 98, 99, 111025000, 6000000}, + {100, 158, 651012500, 6000000}, + { 0, 0, 0, 0} +}; + +static const struct hdhomerun_channelmap_record_t hdhomerun_channelmap_table[] = { + {"au-bcast", hdhomerun_channelmap_range_au_bcast, "au-bcast", "AU"}, + {"au-cable", hdhomerun_channelmap_range_eu_cable, "au-cable", "AU"}, + {"eu-bcast", hdhomerun_channelmap_range_eu_bcast, "eu-bcast", NULL}, + {"eu-cable", hdhomerun_channelmap_range_eu_cable, "eu-cable", NULL}, + {"tw-bcast", hdhomerun_channelmap_range_us_bcast, "tw-bcast", "TW"}, + {"tw-cable", hdhomerun_channelmap_range_us_cable, "tw-cable", "TW"}, + + {"kr-bcast", hdhomerun_channelmap_range_us_bcast, "kr-bcast", "KR"}, + {"kr-cable", hdhomerun_channelmap_range_kr_cable, "kr-cable", "KR"}, + {"us-bcast", hdhomerun_channelmap_range_us_bcast, "us-bcast", NULL}, + {"us-cable", hdhomerun_channelmap_range_us_cable, "us-cable us-hrc us-irc", NULL}, + {"us-hrc", hdhomerun_channelmap_range_us_hrc , "us-cable us-hrc us-irc", NULL}, + {"us-irc", hdhomerun_channelmap_range_us_irc, "us-cable us-hrc us-irc", NULL}, + + {"jp-bcast", hdhomerun_channelmap_range_jp_bcast, "jp-bcast", "JP" }, + { NULL, NULL, NULL, NULL } +}; + +const char *hdhomerun_channelmap_get_channelmap_from_country_source(const char *countrycode, const char *source, const char *supported) +{ + const char *default_result = NULL; + + const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table; + while (record->channelmap) { + /* Ignore records that do not match the requested source. */ + if (!strstr(record->channelmap, source)) { + record++; + continue; + } + + /* Ignore records that are not supported by the hardware. */ + if (!strstr(supported, record->channelmap)) { + record++; + continue; + } + + /* If this record is the default result then remember it and keep searching. */ + if (!record->countrycodes) { + default_result = record->channelmap; + record++; + continue; + } + + /* Ignore records that have a countrycode filter and do not match. */ + if (!strstr(record->countrycodes, countrycode)) { + record++; + continue; + } + + /* Record found with exact match for source and countrycode. */ + return record->channelmap; + } + + return default_result; +} + +const char *hdhomerun_channelmap_get_channelmap_scan_group(const char *channelmap) +{ + const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table; + while (record->channelmap) { + if (strstr(channelmap, record->channelmap)) { + return record->channelmap_scan_group; + } + record++; + } + + return NULL; +} + +uint16_t hdhomerun_channel_entry_channel_number(struct hdhomerun_channel_entry_t *entry) +{ + return entry->channel_number; +} + +uint32_t hdhomerun_channel_entry_frequency(struct hdhomerun_channel_entry_t *entry) +{ + return entry->frequency; +} + +const char *hdhomerun_channel_entry_name(struct hdhomerun_channel_entry_t *entry) +{ + return entry->name; +} + +struct hdhomerun_channel_entry_t *hdhomerun_channel_list_first(struct hdhomerun_channel_list_t *channel_list) +{ + return channel_list->head; +} + +struct hdhomerun_channel_entry_t *hdhomerun_channel_list_last(struct hdhomerun_channel_list_t *channel_list) +{ + return channel_list->tail; +} + +struct hdhomerun_channel_entry_t *hdhomerun_channel_list_next(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry) +{ + return entry->next; +} + +struct hdhomerun_channel_entry_t *hdhomerun_channel_list_prev(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry) +{ + return entry->prev; +} + +uint32_t hdhomerun_channel_list_total_count(struct hdhomerun_channel_list_t *channel_list) +{ + uint32_t count = 0; + + struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list); + while (entry) { + count++; + entry = hdhomerun_channel_list_next(channel_list, entry); + } + + return count; +} + +uint32_t hdhomerun_channel_list_frequency_count(struct hdhomerun_channel_list_t *channel_list) +{ + uint32_t count = 0; + uint32_t last_frequency = 0; + + struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list); + while (entry) { + if (entry->frequency != last_frequency) { + last_frequency = entry->frequency; + count++; + } + + entry = hdhomerun_channel_list_next(channel_list, entry); + } + + return count; +} + +uint32_t hdhomerun_channel_frequency_round(uint32_t frequency, uint32_t resolution) +{ + frequency += resolution / 2; + return (frequency / resolution) * resolution; +} + +uint32_t hdhomerun_channel_frequency_round_normal(uint32_t frequency) +{ + return hdhomerun_channel_frequency_round(frequency, 125000); +} + +uint32_t hdhomerun_channel_number_to_frequency(struct hdhomerun_channel_list_t *channel_list, uint16_t channel_number) +{ + struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list); + while (entry) { + if (entry->channel_number == channel_number) { + return entry->frequency; + } + + entry = hdhomerun_channel_list_next(channel_list, entry); + } + + return 0; +} + +uint16_t hdhomerun_channel_frequency_to_number(struct hdhomerun_channel_list_t *channel_list, uint32_t frequency) +{ + frequency = hdhomerun_channel_frequency_round_normal(frequency); + + struct hdhomerun_channel_entry_t *entry = hdhomerun_channel_list_first(channel_list); + while (entry) { + if (entry->frequency == frequency) { + return entry->channel_number; + } + if (entry->frequency > frequency) { + return 0; + } + + entry = hdhomerun_channel_list_next(channel_list, entry); + } + + return 0; +} + +static void hdhomerun_channel_list_build_insert(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry) +{ + struct hdhomerun_channel_entry_t *prev = NULL; + struct hdhomerun_channel_entry_t *next = channel_list->head; + + while (next) { + if (next->frequency > entry->frequency) { + break; + } + + prev = next; + next = next->next; + } + + entry->prev = prev; + entry->next = next; + + if (prev) { + prev->next = entry; + } else { + channel_list->head = entry; + } + + if (next) { + next->prev = entry; + } else { + channel_list->tail = entry; + } +} + +static void hdhomerun_channel_list_build_range(struct hdhomerun_channel_list_t *channel_list, const char *channelmap, const struct hdhomerun_channelmap_range_t *range) +{ + uint16_t channel_number; + for (channel_number = range->channel_range_start; channel_number <= range->channel_range_end; channel_number++) { + struct hdhomerun_channel_entry_t *entry = (struct hdhomerun_channel_entry_t *)calloc(1, sizeof(struct hdhomerun_channel_entry_t)); + if (!entry) { + return; + } + + entry->channel_number = channel_number; + entry->frequency = range->frequency + ((uint32_t)(channel_number - range->channel_range_start) * range->spacing); + entry->frequency = hdhomerun_channel_frequency_round_normal(entry->frequency); + hdhomerun_sprintf(entry->name, entry->name + sizeof(entry->name), "%s:%u", channelmap, entry->channel_number); + + hdhomerun_channel_list_build_insert(channel_list, entry); + } +} + +static void hdhomerun_channel_list_build_ranges(struct hdhomerun_channel_list_t *channel_list, const struct hdhomerun_channelmap_record_t *record) +{ + const struct hdhomerun_channelmap_range_t *range = record->range_list; + while (range->frequency) { + hdhomerun_channel_list_build_range(channel_list, record->channelmap, range); + range++; + } +} + +void hdhomerun_channel_list_destroy(struct hdhomerun_channel_list_t *channel_list) +{ + while (channel_list->head) { + struct hdhomerun_channel_entry_t *entry = channel_list->head; + channel_list->head = entry->next; + free(entry); + } + + free(channel_list); +} + +struct hdhomerun_channel_list_t *hdhomerun_channel_list_create(const char *channelmap) +{ + struct hdhomerun_channel_list_t *channel_list = (struct hdhomerun_channel_list_t *)calloc(1, sizeof(struct hdhomerun_channel_list_t)); + if (!channel_list) { + return NULL; + } + + const struct hdhomerun_channelmap_record_t *record = hdhomerun_channelmap_table; + while (record->channelmap) { + if (!strstr(channelmap, record->channelmap)) { + record++; + continue; + } + + hdhomerun_channel_list_build_ranges(channel_list, record); + record++; + } + + if (!channel_list->head) { + free(channel_list); + return NULL; + } + + return channel_list; +} diff --git a/hdhomerun_channels.h b/hdhomerun_channels.h new file mode 100644 index 0000000..73be727 --- /dev/null +++ b/hdhomerun_channels.h @@ -0,0 +1,52 @@ +/* + * hdhomerun_channels.h + * + * Copyright © 2007-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_channel_entry_t; +struct hdhomerun_channel_list_t; + +extern LIBTYPE const char *hdhomerun_channelmap_get_channelmap_from_country_source(const char *countrycode, const char *source, const char *supported); +extern LIBTYPE const char *hdhomerun_channelmap_get_channelmap_scan_group(const char *channelmap); + +extern LIBTYPE uint16_t hdhomerun_channel_entry_channel_number(struct hdhomerun_channel_entry_t *entry); +extern LIBTYPE uint32_t hdhomerun_channel_entry_frequency(struct hdhomerun_channel_entry_t *entry); +extern LIBTYPE const char *hdhomerun_channel_entry_name(struct hdhomerun_channel_entry_t *entry); + +extern LIBTYPE struct hdhomerun_channel_list_t *hdhomerun_channel_list_create(const char *channelmap); +extern LIBTYPE void hdhomerun_channel_list_destroy(struct hdhomerun_channel_list_t *channel_list); + +extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_first(struct hdhomerun_channel_list_t *channel_list); +extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_last(struct hdhomerun_channel_list_t *channel_list); +extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_next(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry); +extern LIBTYPE struct hdhomerun_channel_entry_t *hdhomerun_channel_list_prev(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry); +extern LIBTYPE uint32_t hdhomerun_channel_list_total_count(struct hdhomerun_channel_list_t *channel_list); +extern LIBTYPE uint32_t hdhomerun_channel_list_frequency_count(struct hdhomerun_channel_list_t *channel_list); + +extern LIBTYPE uint32_t hdhomerun_channel_frequency_round(uint32_t frequency, uint32_t resolution); +extern LIBTYPE uint32_t hdhomerun_channel_frequency_round_normal(uint32_t frequency); +extern LIBTYPE uint32_t hdhomerun_channel_number_to_frequency(struct hdhomerun_channel_list_t *channel_list, uint16_t channel_number); +extern LIBTYPE uint16_t hdhomerun_channel_frequency_to_number(struct hdhomerun_channel_list_t *channel_list, uint32_t frequency); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_channelscan.c b/hdhomerun_channelscan.c new file mode 100644 index 0000000..eef2a5f --- /dev/null +++ b/hdhomerun_channelscan.c @@ -0,0 +1,347 @@ +/* + * hdhomerun_channelscan.c + * + * Copyright © 2007-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_channelscan_t { + struct hdhomerun_device_t *hd; + uint32_t scanned_channels; + struct hdhomerun_channel_list_t *channel_list; + struct hdhomerun_channel_entry_t *next_channel; +}; + +struct hdhomerun_channelscan_t *channelscan_create(struct hdhomerun_device_t *hd, const char *channelmap) +{ + struct hdhomerun_channelscan_t *scan = (struct hdhomerun_channelscan_t *)calloc(1, sizeof(struct hdhomerun_channelscan_t)); + if (!scan) { + return NULL; + } + + scan->hd = hd; + + scan->channel_list = hdhomerun_channel_list_create(channelmap); + if (!scan->channel_list) { + free(scan); + return NULL; + } + + scan->next_channel = hdhomerun_channel_list_last(scan->channel_list); + return scan; +} + +void channelscan_destroy(struct hdhomerun_channelscan_t *scan) +{ + hdhomerun_channel_list_destroy(scan->channel_list); + free(scan); +} + +static int channelscan_find_lock(struct hdhomerun_channelscan_t *scan, uint32_t frequency, struct hdhomerun_channelscan_result_t *result) +{ + /* Set channel. */ + char channel_str[64]; + hdhomerun_sprintf(channel_str, channel_str + sizeof(channel_str), "auto:%u", (unsigned int)frequency); + + int ret = hdhomerun_device_set_tuner_channel(scan->hd, channel_str); + if (ret <= 0) { + return ret; + } + + /* Wait for lock. */ + ret = hdhomerun_device_wait_for_lock(scan->hd, &result->status); + if (ret <= 0) { + return ret; + } + if (!result->status.lock_supported) { + return 1; + } + + /* Wait for symbol quality = 100%. */ + uint64_t timeout = getcurrenttime() + 5000; + while (1) { + ret = hdhomerun_device_get_tuner_status(scan->hd, NULL, &result->status); + if (ret <= 0) { + return ret; + } + + if (result->status.symbol_error_quality == 100) { + return 1; + } + + if (getcurrenttime() >= timeout) { + return 1; + } + + msleep_approx(250); + } +} + +static void channelscan_extract_name(struct hdhomerun_channelscan_program_t *program, const char *line) +{ + /* Find start of name. */ + const char *start = strchr(line, ' '); + if (!start) { + return; + } + start++; + + start = strchr(start, ' '); + if (!start) { + return; + } + start++; + + /* Find end of name. */ + const char *end = strstr(start, " ("); + if (!end) { + end = strchr(line, 0); + } + + if (end <= start) { + return; + } + + /* Extract name. */ + size_t length = (size_t)(end - start); + if (length > sizeof(program->name) - 1) { + length = sizeof(program->name) - 1; + } + + strncpy(program->name, start, length); + program->name[length] = 0; +} + +static int channelscan_detect_programs(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result, bool_t *pchanged, bool_t *pincomplete) +{ + *pchanged = FALSE; + *pincomplete = FALSE; + + char *streaminfo; + int ret = hdhomerun_device_get_tuner_streaminfo(scan->hd, &streaminfo); + if (ret <= 0) { + return ret; + } + + char *next_line = streaminfo; + int program_count = 0; + + while (1) { + char *line = next_line; + + next_line = strchr(line, '\n'); + if (!next_line) { + break; + } + *next_line++ = 0; + + unsigned int transport_stream_id; + if (sscanf(line, "tsid=0x%x", &transport_stream_id) == 1) { + result->transport_stream_id = transport_stream_id; + result->transport_stream_id_detected = TRUE; + continue; + } + + unsigned int original_network_id; + if (sscanf(line, "onid=0x%x", &original_network_id) == 1) { + result->original_network_id = original_network_id; + result->original_network_id_detected = TRUE; + continue; + } + + if (program_count >= HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT) { + continue; + } + + struct hdhomerun_channelscan_program_t program; + memset(&program, 0, sizeof(program)); + + hdhomerun_sprintf(program.program_str, program.program_str + sizeof(program.program_str), "%s", line); + + unsigned int program_number; + unsigned int virtual_major, virtual_minor; + if (sscanf(line, "%u: %u.%u", &program_number, &virtual_major, &virtual_minor) != 3) { + if (sscanf(line, "%u: %u", &program_number, &virtual_major) != 2) { + continue; + } + virtual_minor = 0; + } + + program.program_number = program_number; + program.virtual_major = virtual_major; + program.virtual_minor = virtual_minor; + + channelscan_extract_name(&program, line); + + if (strstr(line, "(control)")) { + program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_CONTROL; + } else if (strstr(line, "(encrypted)")) { + program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_ENCRYPTED; + } else if (strstr(line, "(no data)")) { + program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_NODATA; + *pincomplete = TRUE; + } else { + program.type = HDHOMERUN_CHANNELSCAN_PROGRAM_NORMAL; + if ((program.virtual_major == 0) || (program.name[0] == 0)) { + *pincomplete = TRUE; + } + } + + if (memcmp(&result->programs[program_count], &program, sizeof(program)) != 0) { + memcpy(&result->programs[program_count], &program, sizeof(program)); + *pchanged = TRUE; + } + + program_count++; + } + + if (program_count == 0) { + *pincomplete = TRUE; + } + if (result->program_count != program_count) { + result->program_count = program_count; + *pchanged = TRUE; + } + + return 1; +} + +int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result) +{ + memset(result, 0, sizeof(struct hdhomerun_channelscan_result_t)); + + struct hdhomerun_channel_entry_t *entry = scan->next_channel; + if (!entry) { + return 0; + } + + /* Combine channels with same frequency. */ + result->frequency = hdhomerun_channel_entry_frequency(entry); + + char *ptr = result->channel_str; + char *end = result->channel_str + sizeof(result->channel_str); + hdhomerun_sprintf(ptr, end, hdhomerun_channel_entry_name(entry)); + + while (1) { + entry = hdhomerun_channel_list_prev(scan->channel_list, entry); + if (!entry) { + scan->next_channel = NULL; + break; + } + + if (hdhomerun_channel_entry_frequency(entry) != result->frequency) { + scan->next_channel = entry; + break; + } + + ptr = strchr(ptr, 0); + hdhomerun_sprintf(ptr, end, ", %s", hdhomerun_channel_entry_name(entry)); + } + + return 1; +} + +int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result) +{ + scan->scanned_channels++; + + /* Find lock. */ + int ret = channelscan_find_lock(scan, result->frequency, result); + if (ret <= 0) { + return ret; + } + if (!result->status.lock_supported) { + return 1; + } + + /* Detect programs. */ + result->program_count = 0; + + uint64_t timeout; + if (strstr(hdhomerun_device_get_model_str(scan->hd), "atsc")) { + timeout = getcurrenttime() + 4000; + } else { + timeout = getcurrenttime() + 10000; + } + + uint64_t complete_time = getcurrenttime() + 1000; + + while (1) { + bool_t changed, incomplete; + ret = channelscan_detect_programs(scan, result, &changed, &incomplete); + if (ret <= 0) { + return ret; + } + + if (changed) { + complete_time = getcurrenttime() + 1000; + } + + if (!incomplete && (getcurrenttime() >= complete_time)) { + break; + } + + if (getcurrenttime() >= timeout) { + break; + } + + msleep_approx(250); + } + + /* Lock => skip overlapping channels. */ + uint32_t max_next_frequency = result->frequency - 5500000; + while (1) { + if (!scan->next_channel) { + break; + } + + if (hdhomerun_channel_entry_frequency(scan->next_channel) <= max_next_frequency) { + break; + } + + scan->next_channel = hdhomerun_channel_list_prev(scan->channel_list, scan->next_channel); + } + + /* Success. */ + return 1; +} + +uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan) +{ + struct hdhomerun_channel_entry_t *entry = scan->next_channel; + if (!entry) { + return 100; + } + + uint32_t channels_remaining = 1; + uint32_t frequency = hdhomerun_channel_entry_frequency(entry); + + while (1) { + entry = hdhomerun_channel_list_prev(scan->channel_list, entry); + if (!entry) { + break; + } + + if (hdhomerun_channel_entry_frequency(entry) != frequency) { + channels_remaining++; + frequency = hdhomerun_channel_entry_frequency(entry); + } + } + + return scan->scanned_channels * 100 / (scan->scanned_channels + channels_remaining); +} diff --git a/hdhomerun_channelscan.h b/hdhomerun_channelscan.h new file mode 100644 index 0000000..1b2849d --- /dev/null +++ b/hdhomerun_channelscan.h @@ -0,0 +1,41 @@ +/* + * hdhomerun_channelscan.h + * + * Copyright © 2007-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define HDHOMERUN_CHANNELSCAN_PROGRAM_NORMAL 0 +#define HDHOMERUN_CHANNELSCAN_PROGRAM_NODATA 1 +#define HDHOMERUN_CHANNELSCAN_PROGRAM_CONTROL 2 +#define HDHOMERUN_CHANNELSCAN_PROGRAM_ENCRYPTED 3 + +struct hdhomerun_channelscan_t; + +extern LIBTYPE struct hdhomerun_channelscan_t *channelscan_create(struct hdhomerun_device_t *hd, const char *channelmap); +extern LIBTYPE void channelscan_destroy(struct hdhomerun_channelscan_t *scan); + +extern LIBTYPE int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result); +extern LIBTYPE int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result); +extern LIBTYPE uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_config.c b/hdhomerun_config.c new file mode 100644 index 0000000..debd946 --- /dev/null +++ b/hdhomerun_config.c @@ -0,0 +1,693 @@ +/* + * hdhomerun_config.c + * + * Copyright © 2006-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +/* + * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing. + * Attempting to restore on exit fails to restore if the program is terminated by the user. + * Solution - set the output format each printf. + */ +#if defined(__WINDOWS__) +#define printf console_printf +#define vprintf console_vprintf +#endif + +static const char *appname; + +struct hdhomerun_device_t *hd; + +static int help(void) +{ + printf("Usage:\n"); + printf("\t%s discover\n", appname); + printf("\t%s get help\n", appname); + printf("\t%s get \n", appname); + printf("\t%s set \n", appname); + printf("\t%s scan []\n", appname); + printf("\t%s save \n", appname); + printf("\t%s upgrade \n", appname); + return -1; +} + +static void extract_appname(const char *argv0) +{ + const char *ptr = strrchr(argv0, '/'); + if (ptr) { + argv0 = ptr + 1; + } + ptr = strrchr(argv0, '\\'); + if (ptr) { + argv0 = ptr + 1; + } + appname = argv0; +} + +static bool_t contains(const char *arg, const char *cmpstr) +{ + if (strcmp(arg, cmpstr) == 0) { + return TRUE; + } + + if (*arg++ != '-') { + return FALSE; + } + if (*arg++ != '-') { + return FALSE; + } + if (strcmp(arg, cmpstr) == 0) { + return TRUE; + } + + return FALSE; +} + +static uint32_t parse_ip_addr(const char *str) +{ + unsigned int a[4]; + if (sscanf(str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) != 4) { + return 0; + } + + return (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); +} + +static int discover_print(char *target_ip_str) +{ + uint32_t target_ip = 0; + if (target_ip_str) { + target_ip = parse_ip_addr(target_ip_str); + if (target_ip == 0) { + fprintf(stderr, "invalid ip address: %s\n", target_ip_str); + return -1; + } + } + + struct hdhomerun_discover_device_t result_list[64]; + int count = hdhomerun_discover_find_devices_custom_v2(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, HDHOMERUN_DEVICE_ID_WILDCARD, result_list, 64); + if (count < 0) { + fprintf(stderr, "error sending discover request\n"); + return -1; + } + if (count == 0) { + printf("no devices found\n"); + return 0; + } + + int index; + for (index = 0; index < count; index++) { + struct hdhomerun_discover_device_t *result = &result_list[index]; + printf("hdhomerun device %08X found at %u.%u.%u.%u\n", + (unsigned int)result->device_id, + (unsigned int)(result->ip_addr >> 24) & 0x0FF, (unsigned int)(result->ip_addr >> 16) & 0x0FF, + (unsigned int)(result->ip_addr >> 8) & 0x0FF, (unsigned int)(result->ip_addr >> 0) & 0x0FF + ); + } + + return count; +} + +static int cmd_get(const char *item) +{ + char *ret_value; + char *ret_error; + if (hdhomerun_device_get_var(hd, item, &ret_value, &ret_error) < 0) { + fprintf(stderr, "communication error sending request to hdhomerun device\n"); + return -1; + } + + if (ret_error) { + printf("%s\n", ret_error); + return 0; + } + + printf("%s\n", ret_value); + return 1; +} + +static int cmd_set_internal(const char *item, const char *value) +{ + char *ret_error; + if (hdhomerun_device_set_var(hd, item, value, NULL, &ret_error) < 0) { + fprintf(stderr, "communication error sending request to hdhomerun device\n"); + return -1; + } + + if (ret_error) { + printf("%s\n", ret_error); + return 0; + } + + return 1; +} + +static int cmd_set(const char *item, const char *value) +{ + if (strcmp(value, "-") == 0) { + char *buffer = NULL; + size_t pos = 0; + + while (1) { + buffer = (char *)realloc(buffer, pos + 1024); + if (!buffer) { + fprintf(stderr, "out of memory\n"); + return -1; + } + + size_t size = fread(buffer + pos, 1, 1024, stdin); + pos += size; + + if (size < 1024) { + break; + } + } + + buffer[pos] = 0; + + int ret = cmd_set_internal(item, buffer); + + free(buffer); + return ret; + } + + return cmd_set_internal(item, value); +} + +static volatile sig_atomic_t sigabort_flag = FALSE; +static volatile sig_atomic_t siginfo_flag = FALSE; + +static void sigabort_handler(int arg) +{ + sigabort_flag = TRUE; +} + +static void siginfo_handler(int arg) +{ + siginfo_flag = TRUE; +} + +static void register_signal_handlers(sig_t sigpipe_handler, sig_t sigint_handler, sig_t siginfo_handler) +{ +#if defined(SIGPIPE) + signal(SIGPIPE, sigpipe_handler); +#endif +#if defined(SIGINT) + signal(SIGINT, sigint_handler); +#endif +#if defined(SIGINFO) + signal(SIGINFO, siginfo_handler); +#endif +} + +static void cmd_scan_printf(FILE *fp, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + if (fp) { + va_list apc; + va_copy(apc, ap); + + vfprintf(fp, fmt, apc); + fflush(fp); + + va_end(apc); + } + + vprintf(fmt, ap); + fflush(stdout); + + va_end(ap); +} + +static int cmd_scan(const char *tuner_str, const char *filename) +{ + if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) { + fprintf(stderr, "invalid tuner number\n"); + return -1; + } + + char *ret_error; + if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) { + fprintf(stderr, "failed to lock tuner\n"); + if (ret_error) { + fprintf(stderr, "%s\n", ret_error); + } + return -1; + } + + hdhomerun_device_set_tuner_target(hd, "none"); + + char *channelmap; + if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) { + fprintf(stderr, "failed to query channelmap from device\n"); + return -1; + } + + const char *channelmap_scan_group = hdhomerun_channelmap_get_channelmap_scan_group(channelmap); + if (!channelmap_scan_group) { + fprintf(stderr, "unknown channelmap '%s'\n", channelmap); + return -1; + } + + if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) { + fprintf(stderr, "failed to initialize channel scan\n"); + return -1; + } + + FILE *fp = NULL; + if (filename) { + fp = fopen(filename, "w"); + if (!fp) { + fprintf(stderr, "unable to create file: %s\n", filename); + return -1; + } + } + + register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler); + + int ret = 0; + while (!sigabort_flag) { + struct hdhomerun_channelscan_result_t result; + ret = hdhomerun_device_channelscan_advance(hd, &result); + if (ret <= 0) { + break; + } + + cmd_scan_printf(fp, "SCANNING: %u (%s)\n", + (unsigned int)result.frequency, result.channel_str + ); + + ret = hdhomerun_device_channelscan_detect(hd, &result); + if (ret < 0) { + break; + } + if (ret == 0) { + continue; + } + + cmd_scan_printf(fp, "LOCK: %s (ss=%u snq=%u seq=%u)\n", + result.status.lock_str, result.status.signal_strength, + result.status.signal_to_noise_quality, result.status.symbol_error_quality + ); + + if (result.transport_stream_id_detected) { + cmd_scan_printf(fp, "TSID: 0x%04X\n", result.transport_stream_id); + } + if (result.original_network_id_detected) { + cmd_scan_printf(fp, "ONID: 0x%04X\n", result.original_network_id); + } + + int i; + for (i = 0; i < result.program_count; i++) { + struct hdhomerun_channelscan_program_t *program = &result.programs[i]; + cmd_scan_printf(fp, "PROGRAM %s\n", program->program_str); + } + } + + hdhomerun_device_tuner_lockkey_release(hd); + + if (fp) { + fclose(fp); + } + if (ret < 0) { + fprintf(stderr, "communication error sending request to hdhomerun device\n"); + } + return ret; +} + +static void cmd_save_print_stats(void) +{ + struct hdhomerun_video_stats_t stats; + hdhomerun_device_get_video_stats(hd, &stats); + + fprintf(stderr, "%u packets received, %u overflow errors, %u network errors, %u transport errors, %u sequence errors\n", + (unsigned int)stats.packet_count, + (unsigned int)stats.overflow_error_count, + (unsigned int)stats.network_error_count, + (unsigned int)stats.transport_error_count, + (unsigned int)stats.sequence_error_count + ); +} + +static int cmd_save(const char *tuner_str, const char *filename) +{ + if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) { + fprintf(stderr, "invalid tuner number\n"); + return -1; + } + + FILE *fp; + if (strcmp(filename, "null") == 0) { + fp = NULL; + } else if (strcmp(filename, "-") == 0) { + fp = stdout; + } else { + fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "unable to create file %s\n", filename); + return -1; + } + } + + int ret = hdhomerun_device_stream_start(hd); + if (ret <= 0) { + fprintf(stderr, "unable to start stream\n"); + if (fp && fp != stdout) { + fclose(fp); + } + return ret; + } + + register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler); + + struct hdhomerun_video_stats_t stats_old, stats_cur; + hdhomerun_device_get_video_stats(hd, &stats_old); + + uint64_t next_progress = getcurrenttime() + 1000; + + while (!sigabort_flag) { + uint64_t loop_start_time = getcurrenttime(); + + if (siginfo_flag) { + fprintf(stderr, "\n"); + cmd_save_print_stats(); + siginfo_flag = FALSE; + } + + size_t actual_size; + uint8_t *ptr = hdhomerun_device_stream_recv(hd, VIDEO_DATA_BUFFER_SIZE_1S, &actual_size); + if (!ptr) { + msleep_approx(64); + continue; + } + + if (fp) { + if (fwrite(ptr, 1, actual_size, fp) != actual_size) { + fprintf(stderr, "error writing output\n"); + return -1; + } + } + + if (loop_start_time >= next_progress) { + next_progress += 1000; + if (loop_start_time >= next_progress) { + next_progress = loop_start_time + 1000; + } + + /* Windows - indicate activity to suppress auto sleep mode. */ + #if defined(__WINDOWS__) + SetThreadExecutionState(ES_SYSTEM_REQUIRED); + #endif + + /* Video stats. */ + hdhomerun_device_get_video_stats(hd, &stats_cur); + + if (stats_cur.overflow_error_count > stats_old.overflow_error_count) { + fprintf(stderr, "o"); + } else if (stats_cur.network_error_count > stats_old.network_error_count) { + fprintf(stderr, "n"); + } else if (stats_cur.transport_error_count > stats_old.transport_error_count) { + fprintf(stderr, "t"); + } else if (stats_cur.sequence_error_count > stats_old.sequence_error_count) { + fprintf(stderr, "s"); + } else { + fprintf(stderr, "."); + } + + stats_old = stats_cur; + fflush(stderr); + } + + int32_t delay = 64 - (int32_t)(getcurrenttime() - loop_start_time); + if (delay <= 0) { + continue; + } + + msleep_approx(delay); + } + + if (fp) { + fclose(fp); + } + + hdhomerun_device_stream_stop(hd); + + fprintf(stderr, "\n"); + fprintf(stderr, "-- Video statistics --\n"); + cmd_save_print_stats(); + + return 0; +} + +static int cmd_upgrade(const char *filename) +{ + FILE *fp = fopen(filename, "rb"); + if (!fp) { + fprintf(stderr, "unable to open file %s\n", filename); + return -1; + } + + printf("uploading firmware...\n"); + if (hdhomerun_device_upgrade(hd, fp) <= 0) { + fprintf(stderr, "error sending upgrade file to hdhomerun device\n"); + fclose(fp); + return -1; + } + + fclose(fp); + msleep_minimum(2000); + + printf("upgrading firmware...\n"); + msleep_minimum(8000); + + printf("rebooting...\n"); + int count = 0; + char *version_str; + while (1) { + if (hdhomerun_device_get_version(hd, &version_str, NULL) >= 0) { + break; + } + + count++; + if (count > 30) { + fprintf(stderr, "error finding device after firmware upgrade\n"); + return -1; + } + + msleep_minimum(1000); + } + + printf("upgrade complete - now running firmware %s\n", version_str); + return 0; +} + +static int cmd_execute(void) +{ + char *ret_value; + char *ret_error; + if (hdhomerun_device_get_var(hd, "/sys/boot", &ret_value, &ret_error) < 0) { + fprintf(stderr, "communication error sending request to hdhomerun device\n"); + return -1; + } + + if (ret_error) { + printf("%s\n", ret_error); + return 0; + } + + char *end = ret_value + strlen(ret_value); + char *pos = ret_value; + + while (1) { + if (pos >= end) { + break; + } + + char *eol_r = strchr(pos, '\r'); + if (!eol_r) { + eol_r = end; + } + + char *eol_n = strchr(pos, '\n'); + if (!eol_n) { + eol_n = end; + } + + char *eol = eol_r; + if (eol_n < eol) { + eol = eol_n; + } + + char *sep = strchr(pos, ' '); + if (!sep || sep > eol) { + pos = eol + 1; + continue; + } + + *sep = 0; + *eol = 0; + + char *item = pos; + char *value = sep + 1; + + printf("set %s \"%s\"\n", item, value); + + cmd_set_internal(item, value); + + pos = eol + 1; + } + + return 1; +} + +static int main_cmd(int argc, char *argv[]) +{ + if (argc < 1) { + return help(); + } + + char *cmd = *argv++; argc--; + + if (contains(cmd, "key")) { + if (argc < 2) { + return help(); + } + uint32_t lockkey = strtoul(argv[0], NULL, 0); + hdhomerun_device_tuner_lockkey_use_value(hd, lockkey); + + cmd = argv[1]; + argv+=2; argc-=2; + } + + if (contains(cmd, "get")) { + if (argc < 1) { + return help(); + } + return cmd_get(argv[0]); + } + + if (contains(cmd, "set")) { + if (argc < 2) { + return help(); + } + return cmd_set(argv[0], argv[1]); + } + + if (contains(cmd, "scan")) { + if (argc < 1) { + return help(); + } + if (argc < 2) { + return cmd_scan(argv[0], NULL); + } else { + return cmd_scan(argv[0], argv[1]); + } + } + + if (contains(cmd, "save")) { + if (argc < 2) { + return help(); + } + return cmd_save(argv[0], argv[1]); + } + + if (contains(cmd, "upgrade")) { + if (argc < 1) { + return help(); + } + return cmd_upgrade(argv[0]); + } + + if (contains(cmd, "execute")) { + return cmd_execute(); + } + + return help(); +} + +static int main_internal(int argc, char *argv[]) +{ +#if defined(__WINDOWS__) + /* Initialize network socket support. */ + WORD wVersionRequested = MAKEWORD(2, 0); + WSADATA wsaData; + WSAStartup(wVersionRequested, &wsaData); +#endif + + extract_appname(argv[0]); + argv++; + argc--; + + if (argc == 0) { + return help(); + } + + char *id_str = *argv++; argc--; + if (contains(id_str, "help")) { + return help(); + } + if (contains(id_str, "discover")) { + if (argc < 1) { + return discover_print(NULL); + } else { + return discover_print(argv[0]); + } + } + + /* Device object. */ + hd = hdhomerun_device_create_from_str(id_str, NULL); + if (!hd) { + fprintf(stderr, "invalid device id: %s\n", id_str); + return -1; + } + + /* Device ID check. */ + uint32_t device_id_requested = hdhomerun_device_get_device_id_requested(hd); + if (!hdhomerun_discover_validate_device_id(device_id_requested)) { + fprintf(stderr, "invalid device id: %08X\n", (unsigned int)device_id_requested); + } + + /* Connect to device and check model. */ + const char *model = hdhomerun_device_get_model_str(hd); + if (!model) { + fprintf(stderr, "unable to connect to device\n"); + hdhomerun_device_destroy(hd); + return -1; + } + + /* Command. */ + int ret = main_cmd(argc, argv); + + /* Cleanup. */ + hdhomerun_device_destroy(hd); + + /* Complete. */ + return ret; +} + +int main(int argc, char *argv[]) +{ + int ret = main_internal(argc, argv); + if (ret <= 0) { + return 1; + } + return 0; +} diff --git a/hdhomerun_control.c b/hdhomerun_control.c new file mode 100644 index 0000000..db57e93 --- /dev/null +++ b/hdhomerun_control.c @@ -0,0 +1,431 @@ +/* + * hdhomerun_control.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#define HDHOMERUN_CONTROL_CONNECT_TIMEOUT 2500 +#define HDHOMERUN_CONTROL_SEND_TIMEOUT 2500 +#define HDHOMERUN_CONTROL_RECV_TIMEOUT 2500 +#define HDHOMERUN_CONTROL_UPGRADE_TIMEOUT 30000 + +struct hdhomerun_control_sock_t { + uint32_t desired_device_id; + uint32_t desired_device_ip; + uint32_t actual_device_id; + uint32_t actual_device_ip; + hdhomerun_sock_t sock; + struct hdhomerun_debug_t *dbg; + struct hdhomerun_pkt_t tx_pkt; + struct hdhomerun_pkt_t rx_pkt; +}; + +static void hdhomerun_control_close_sock(struct hdhomerun_control_sock_t *cs) +{ + if (cs->sock == HDHOMERUN_SOCK_INVALID) { + return; + } + + hdhomerun_sock_destroy(cs->sock); + cs->sock = HDHOMERUN_SOCK_INVALID; +} + +void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip) +{ + hdhomerun_control_close_sock(cs); + + cs->desired_device_id = device_id; + cs->desired_device_ip = device_ip; + cs->actual_device_id = 0; + cs->actual_device_ip = 0; +} + +struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_control_sock_t *cs = (struct hdhomerun_control_sock_t *)calloc(1, sizeof(struct hdhomerun_control_sock_t)); + if (!cs) { + hdhomerun_debug_printf(dbg, "hdhomerun_control_create: failed to allocate control object\n"); + return NULL; + } + + cs->dbg = dbg; + cs->sock = HDHOMERUN_SOCK_INVALID; + hdhomerun_control_set_device(cs, device_id, device_ip); + + return cs; +} + +void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs) +{ + hdhomerun_control_close_sock(cs); + free(cs); +} + +static bool_t hdhomerun_control_connect_sock(struct hdhomerun_control_sock_t *cs) +{ + if (cs->sock != HDHOMERUN_SOCK_INVALID) { + return TRUE; + } + + if ((cs->desired_device_id == 0) && (cs->desired_device_ip == 0)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: no device specified\n"); + return FALSE; + } + if (hdhomerun_discover_is_ip_multicast(cs->desired_device_ip)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: cannot use multicast ip address for device operations\n"); + return FALSE; + } + + /* Find device. */ + struct hdhomerun_discover_device_t result; + if (hdhomerun_discover_find_devices_custom_v2(cs->desired_device_ip, HDHOMERUN_DEVICE_TYPE_WILDCARD, cs->desired_device_id, &result, 1) <= 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: device not found\n"); + return FALSE; + } + cs->actual_device_ip = result.ip_addr; + cs->actual_device_id = result.device_id; + + /* Create socket. */ + cs->sock = hdhomerun_sock_create_tcp(); + if (cs->sock == HDHOMERUN_SOCK_INVALID) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to create socket (%d)\n", hdhomerun_sock_getlasterror()); + return FALSE; + } + + /* Initiate connection. */ + if (!hdhomerun_sock_connect(cs->sock, cs->actual_device_ip, HDHOMERUN_CONTROL_TCP_PORT, HDHOMERUN_CONTROL_CONNECT_TIMEOUT)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to connect (%d)\n", hdhomerun_sock_getlasterror()); + hdhomerun_control_close_sock(cs); + return FALSE; + } + + /* Success. */ + return TRUE; +} + +uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs) +{ + if (!hdhomerun_control_connect_sock(cs)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_id: connect failed\n"); + return 0; + } + + return cs->actual_device_id; +} + +uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs) +{ + if (!hdhomerun_control_connect_sock(cs)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_ip: connect failed\n"); + return 0; + } + + return cs->actual_device_ip; +} + +uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs) +{ + return cs->desired_device_id; +} + +uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs) +{ + return cs->desired_device_ip; +} + +uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs) +{ + if (!hdhomerun_control_connect_sock(cs)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: connect failed\n"); + return 0; + } + + uint32_t addr = hdhomerun_sock_getsockname_addr(cs->sock); + if (addr == 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: getsockname failed (%d)\n", hdhomerun_sock_getlasterror()); + return 0; + } + + return addr; +} + +static bool_t hdhomerun_control_send_sock(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt) +{ + if (!hdhomerun_sock_send(cs->sock, tx_pkt->start, tx_pkt->end - tx_pkt->start, HDHOMERUN_CONTROL_SEND_TIMEOUT)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_sock: send failed (%d)\n", hdhomerun_sock_getlasterror()); + hdhomerun_control_close_sock(cs); + return FALSE; + } + + return TRUE; +} + +static bool_t hdhomerun_control_recv_sock(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *rx_pkt, uint16_t *ptype, uint64_t recv_timeout) +{ + uint64_t stop_time = getcurrenttime() + recv_timeout; + hdhomerun_pkt_reset(rx_pkt); + + while (1) { + uint64_t current_time = getcurrenttime(); + if (current_time >= stop_time) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: timeout\n"); + hdhomerun_control_close_sock(cs); + return FALSE; + } + + size_t length = rx_pkt->limit - rx_pkt->end; + if (!hdhomerun_sock_recv(cs->sock, rx_pkt->end, &length, stop_time - current_time)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: recv failed (%d)\n", hdhomerun_sock_getlasterror()); + hdhomerun_control_close_sock(cs); + return FALSE; + } + + rx_pkt->end += length; + + int ret = hdhomerun_pkt_open_frame(rx_pkt, ptype); + if (ret < 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_recv_sock: frame error\n"); + hdhomerun_control_close_sock(cs); + return FALSE; + } + if (ret > 0) { + return TRUE; + } + } +} + +static int hdhomerun_control_send_recv_internal(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type, uint64_t recv_timeout) +{ + hdhomerun_pkt_seal_frame(tx_pkt, type); + + int i; + for (i = 0; i < 2; i++) { + if (cs->sock == HDHOMERUN_SOCK_INVALID) { + if (!hdhomerun_control_connect_sock(cs)) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: connect failed\n"); + return -1; + } + } + + if (!hdhomerun_control_send_sock(cs, tx_pkt)) { + continue; + } + if (!rx_pkt) { + return 1; + } + + uint16_t rsp_type; + if (!hdhomerun_control_recv_sock(cs, rx_pkt, &rsp_type, recv_timeout)) { + continue; + } + if (rsp_type != type + 1) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: unexpected frame type\n"); + hdhomerun_control_close_sock(cs); + continue; + } + + return 1; + } + + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_send_recv: failed\n"); + return -1; +} + +int hdhomerun_control_send_recv(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type) +{ + return hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, type, HDHOMERUN_CONTROL_RECV_TIMEOUT); +} + +static int hdhomerun_control_get_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror) +{ + struct hdhomerun_pkt_t *tx_pkt = &cs->tx_pkt; + struct hdhomerun_pkt_t *rx_pkt = &cs->rx_pkt; + + /* Request. */ + hdhomerun_pkt_reset(tx_pkt); + + int name_len = (int)strlen(name) + 1; + if (tx_pkt->end + 3 + name_len > tx_pkt->limit) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n"); + return -1; + } + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_NAME); + hdhomerun_pkt_write_var_length(tx_pkt, name_len); + hdhomerun_pkt_write_mem(tx_pkt, (const void *)name, name_len); + + if (value) { + int value_len = (int)strlen(value) + 1; + if (tx_pkt->end + 3 + value_len > tx_pkt->limit) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n"); + return -1; + } + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_VALUE); + hdhomerun_pkt_write_var_length(tx_pkt, value_len); + hdhomerun_pkt_write_mem(tx_pkt, (const void *)value, value_len); + } + + if (lockkey != 0) { + if (tx_pkt->end + 6 > tx_pkt->limit) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: request too long\n"); + return -1; + } + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_GETSET_LOCKKEY); + hdhomerun_pkt_write_var_length(tx_pkt, 4); + hdhomerun_pkt_write_u32(tx_pkt, lockkey); + } + + /* Send/Recv. */ + if (hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, HDHOMERUN_TYPE_GETSET_REQ, HDHOMERUN_CONTROL_RECV_TIMEOUT) < 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: send/recv error\n"); + return -1; + } + + /* Response. */ + while (1) { + uint8_t tag; + size_t len; + uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len); + if (!next) { + break; + } + + switch (tag) { + case HDHOMERUN_TAG_GETSET_VALUE: + if (pvalue) { + *pvalue = (char *)rx_pkt->pos; + rx_pkt->pos[len] = 0; + } + if (perror) { + *perror = NULL; + } + return 1; + + case HDHOMERUN_TAG_ERROR_MESSAGE: + rx_pkt->pos[len] = 0; + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: %s\n", rx_pkt->pos); + + if (pvalue) { + *pvalue = NULL; + } + if (perror) { + *perror = (char *)rx_pkt->pos; + } + + return 0; + } + + rx_pkt->pos = next; + } + + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_set: missing response tags\n"); + return -1; +} + +int hdhomerun_control_get(struct hdhomerun_control_sock_t *cs, const char *name, char **pvalue, char **perror) +{ + return hdhomerun_control_get_set(cs, name, NULL, 0, pvalue, perror); +} + +int hdhomerun_control_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, char **pvalue, char **perror) +{ + return hdhomerun_control_get_set(cs, name, value, 0, pvalue, perror); +} + +int hdhomerun_control_set_with_lockkey(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror) +{ + return hdhomerun_control_get_set(cs, name, value, lockkey, pvalue, perror); +} + +int hdhomerun_control_upgrade(struct hdhomerun_control_sock_t *cs, FILE *upgrade_file) +{ + struct hdhomerun_pkt_t *tx_pkt = &cs->tx_pkt; + struct hdhomerun_pkt_t *rx_pkt = &cs->rx_pkt; + bool_t upload_delay = FALSE; + uint32_t sequence = 0; + + /* Special case detection. */ + char *version_str; + int ret = hdhomerun_control_get(cs, "/sys/version", &version_str, NULL); + if (ret > 0) { + upload_delay = strcmp(version_str, "20120704beta1") == 0; + } + + /* Upload. */ + while (1) { + uint8_t data[1024]; + size_t length = fread(data, 1, 1024, upgrade_file); + if (length == 0) { + break; + } + + hdhomerun_pkt_reset(tx_pkt); + hdhomerun_pkt_write_u32(tx_pkt, sequence); + hdhomerun_pkt_write_mem(tx_pkt, data, length); + + if (hdhomerun_control_send_recv_internal(cs, tx_pkt, NULL, HDHOMERUN_TYPE_UPGRADE_REQ, 0) < 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: send/recv failed\n"); + return -1; + } + + sequence += (uint32_t)length; + + if (upload_delay) { + msleep_approx(25); + } + } + + if (sequence == 0) { + /* No data in file. Error, but no need to close connection. */ + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: zero length file\n"); + return 0; + } + + /* Execute upgrade. */ + hdhomerun_pkt_reset(tx_pkt); + hdhomerun_pkt_write_u32(tx_pkt, 0xFFFFFFFF); + + if (hdhomerun_control_send_recv_internal(cs, tx_pkt, rx_pkt, HDHOMERUN_TYPE_UPGRADE_REQ, HDHOMERUN_CONTROL_UPGRADE_TIMEOUT) < 0) { + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: send/recv failed\n"); + return -1; + } + + /* Check response. */ + while (1) { + uint8_t tag; + size_t len; + uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len); + if (!next) { + break; + } + + switch (tag) { + case HDHOMERUN_TAG_ERROR_MESSAGE: + rx_pkt->pos[len] = 0; + hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_upgrade: %s\n", (char *)rx_pkt->pos); + return 0; + + default: + break; + } + + rx_pkt->pos = next; + } + + return 1; +} diff --git a/hdhomerun_control.h b/hdhomerun_control.h new file mode 100644 index 0000000..7e2512a --- /dev/null +++ b/hdhomerun_control.h @@ -0,0 +1,103 @@ +/* + * hdhomerun_control.h + * + * Copyright © 2006 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_control_sock_t; + +/* + * Create a control socket. + * + * This function will not attempt to connect to the device. + * The connection will be established when first used. + * + * uint32_t device_id = 32-bit device id of device. Set to HDHOMERUN_DEVICE_ID_WILDCARD to match any device ID. + * uint32_t device_ip = IP address of device. Set to 0 to auto-detect. + * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL. + * + * Returns a pointer to the newly created control socket. + * + * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy. + */ +extern LIBTYPE struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs); + +/* + * Get the actual device id or ip of the device. + * + * Returns 0 if the device id cannot be determined. + */ +extern LIBTYPE uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs); +extern LIBTYPE uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs); +extern LIBTYPE uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs); +extern LIBTYPE uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs); + +extern LIBTYPE void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip); + +/* + * Get the local machine IP address used when communicating with the device. + * + * This function is useful for determining the IP address to use with set target commands. + * + * Returns 32-bit IP address with native endianness, or 0 on error. + */ +extern LIBTYPE uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs); + +/* + * Low-level communication. + */ +extern LIBTYPE int hdhomerun_control_send_recv(struct hdhomerun_control_sock_t *cs, struct hdhomerun_pkt_t *tx_pkt, struct hdhomerun_pkt_t *rx_pkt, uint16_t type); + +/* + * Get/set a control variable on the device. + * + * const char *name: The name of var to get/set (c-string). The supported vars is device/firmware dependant. + * const char *value: The value to set (c-string). The format is device/firmware dependant. + + * char **pvalue: If provided, the caller-supplied char pointer will be populated with a pointer to the value + * string returned by the device, or NULL if the device returned an error string. The string will remain + * valid until the next call to a control sock function. + * char **perror: If provided, the caller-supplied char pointer will be populated with a pointer to the error + * string returned by the device, or NULL if the device returned an value string. The string will remain + * valid until the next call to a control sock function. + * + * Returns 1 if the operation was successful (pvalue set, perror NULL). + * Returns 0 if the operation was rejected (pvalue NULL, perror set). + * Returns -1 if a communication error occurs. + */ +extern LIBTYPE int hdhomerun_control_get(struct hdhomerun_control_sock_t *cs, const char *name, char **pvalue, char **perror); +extern LIBTYPE int hdhomerun_control_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, char **pvalue, char **perror); +extern LIBTYPE int hdhomerun_control_set_with_lockkey(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, uint32_t lockkey, char **pvalue, char **perror); + +/* + * Upload new firmware to the device. + * + * FILE *upgrade_file: File pointer to read from. The file must have been opened in binary mode for reading. + * + * Returns 1 if the upload succeeded. + * Returns 0 if the upload was rejected. + * Returns -1 if an error occurs. + */ +extern LIBTYPE int hdhomerun_control_upgrade(struct hdhomerun_control_sock_t *cs, FILE *upgrade_file); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_debug.c b/hdhomerun_debug.c new file mode 100644 index 0000000..5514603 --- /dev/null +++ b/hdhomerun_debug.c @@ -0,0 +1,449 @@ +/* + * hdhomerun_debug.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * The debug logging includes optional support for connecting to the + * Silicondust support server. This option should not be used without + * being explicitly enabled by the user. Debug information should be + * limited to information useful to diagnosing a problem. + * - Silicondust. + */ + +#include "hdhomerun.h" + +#if !defined(HDHOMERUN_DEBUG_HOST) +#define HDHOMERUN_DEBUG_HOST "debug.silicondust.com" +#endif +#if !defined(HDHOMERUN_DEBUG_PORT) +#define HDHOMERUN_DEBUG_PORT 8002 +#endif + +#define HDHOMERUN_DEBUG_CONNECT_RETRY_TIME 30000 +#define HDHOMERUN_DEBUG_CONNECT_TIMEOUT 10000 +#define HDHOMERUN_DEBUG_SEND_TIMEOUT 10000 + +struct hdhomerun_debug_message_t +{ + struct hdhomerun_debug_message_t *next; + struct hdhomerun_debug_message_t *prev; + char buffer[2048]; +}; + +struct hdhomerun_debug_t +{ + pthread_t thread; + volatile bool_t enabled; + volatile bool_t terminate; + char *prefix; + + pthread_mutex_t print_lock; + pthread_mutex_t queue_lock; + pthread_mutex_t send_lock; + + struct hdhomerun_debug_message_t *queue_head; + struct hdhomerun_debug_message_t *queue_tail; + uint32_t queue_depth; + + uint64_t connect_delay; + + char *file_name; + FILE *file_fp; + hdhomerun_sock_t sock; +}; + +static THREAD_FUNC_PREFIX hdhomerun_debug_thread_execute(void *arg); + +struct hdhomerun_debug_t *hdhomerun_debug_create(void) +{ + struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)calloc(1, sizeof(struct hdhomerun_debug_t)); + if (!dbg) { + return NULL; + } + + dbg->sock = HDHOMERUN_SOCK_INVALID; + + pthread_mutex_init(&dbg->print_lock, NULL); + pthread_mutex_init(&dbg->queue_lock, NULL); + pthread_mutex_init(&dbg->send_lock, NULL); + + if (pthread_create(&dbg->thread, NULL, &hdhomerun_debug_thread_execute, dbg) != 0) { + free(dbg); + return NULL; + } + + return dbg; +} + +void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg) +{ + if (!dbg) { + return; + } + + dbg->terminate = TRUE; + pthread_join(dbg->thread, NULL); + + if (dbg->prefix) { + free(dbg->prefix); + } + if (dbg->file_name) { + free(dbg->file_name); + } + if (dbg->file_fp) { + fclose(dbg->file_fp); + } + if (dbg->sock != HDHOMERUN_SOCK_INVALID) { + hdhomerun_sock_destroy(dbg->sock); + } + + free(dbg); +} + +/* Send lock held by caller */ +static void hdhomerun_debug_close_internal(struct hdhomerun_debug_t *dbg) +{ + if (dbg->file_fp) { + fclose(dbg->file_fp); + dbg->file_fp = NULL; + } + + if (dbg->sock != HDHOMERUN_SOCK_INVALID) { + hdhomerun_sock_destroy(dbg->sock); + dbg->sock = HDHOMERUN_SOCK_INVALID; + } +} + +void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout) +{ + if (!dbg) { + return; + } + + if (timeout > 0) { + hdhomerun_debug_flush(dbg, timeout); + } + + pthread_mutex_lock(&dbg->send_lock); + hdhomerun_debug_close_internal(dbg); + dbg->connect_delay = 0; + pthread_mutex_unlock(&dbg->send_lock); +} + +void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename) +{ + if (!dbg) { + return; + } + + pthread_mutex_lock(&dbg->send_lock); + + if (!filename && !dbg->file_name) { + pthread_mutex_unlock(&dbg->send_lock); + return; + } + if (filename && dbg->file_name) { + if (strcmp(filename, dbg->file_name) == 0) { + pthread_mutex_unlock(&dbg->send_lock); + return; + } + } + + hdhomerun_debug_close_internal(dbg); + dbg->connect_delay = 0; + + if (dbg->file_name) { + free(dbg->file_name); + dbg->file_name = NULL; + } + if (filename) { + dbg->file_name = strdup(filename); + } + + pthread_mutex_unlock(&dbg->send_lock); +} + +void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix) +{ + if (!dbg) { + return; + } + + pthread_mutex_lock(&dbg->print_lock); + + if (dbg->prefix) { + free(dbg->prefix); + dbg->prefix = NULL; + } + + if (prefix) { + dbg->prefix = strdup(prefix); + } + + pthread_mutex_unlock(&dbg->print_lock); +} + +void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg) +{ + if (!dbg) { + return; + } + + dbg->enabled = TRUE; +} + +void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg) +{ + if (!dbg) { + return; + } + + dbg->enabled = FALSE; +} + +bool_t hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg) +{ + if (!dbg) { + return FALSE; + } + + return dbg->enabled; +} + +void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout) +{ + if (!dbg) { + return; + } + + timeout = getcurrenttime() + timeout; + + while (getcurrenttime() < timeout) { + pthread_mutex_lock(&dbg->queue_lock); + struct hdhomerun_debug_message_t *message = dbg->queue_tail; + pthread_mutex_unlock(&dbg->queue_lock); + + if (!message) { + return; + } + + msleep_approx(10); + } +} + +void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + hdhomerun_debug_vprintf(dbg, fmt, args); + va_end(args); +} + +void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args) +{ + if (!dbg) { + return; + } + if (!dbg->enabled) { + return; + } + + struct hdhomerun_debug_message_t *message = (struct hdhomerun_debug_message_t *)malloc(sizeof(struct hdhomerun_debug_message_t)); + if (!message) { + return; + } + + char *ptr = message->buffer; + char *end = message->buffer + sizeof(message->buffer) - 2; + *end = 0; + + /* + * Timestamp. + */ + time_t current_time = time(NULL); + ptr += strftime(ptr, end - ptr, "%Y%m%d-%H:%M:%S ", localtime(¤t_time)); + if (ptr > end) { + ptr = end; + } + + /* + * Debug prefix. + */ + pthread_mutex_lock(&dbg->print_lock); + + if (dbg->prefix) { + hdhomerun_sprintf(ptr, end, "%s ", dbg->prefix); + ptr = strchr(ptr, 0); + } + + pthread_mutex_unlock(&dbg->print_lock); + + /* + * Message text. + */ + hdhomerun_vsprintf(ptr, end, fmt, args); + ptr = strchr(ptr, 0); + + /* + * Force newline. + */ + if (ptr[-1] != '\n') { + hdhomerun_sprintf(ptr, end, "\n"); + } + + /* + * Enqueue. + */ + pthread_mutex_lock(&dbg->queue_lock); + + message->prev = NULL; + message->next = dbg->queue_head; + dbg->queue_head = message; + if (message->next) { + message->next->prev = message; + } else { + dbg->queue_tail = message; + } + dbg->queue_depth++; + + pthread_mutex_unlock(&dbg->queue_lock); +} + +/* Send lock held by caller */ +static bool_t hdhomerun_debug_output_message_file(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message) +{ + if (!dbg->file_fp) { + uint64_t current_time = getcurrenttime(); + if (current_time < dbg->connect_delay) { + return FALSE; + } + dbg->connect_delay = current_time + 30*1000; + + dbg->file_fp = fopen(dbg->file_name, "a"); + if (!dbg->file_fp) { + return FALSE; + } + } + + fprintf(dbg->file_fp, "%s", message->buffer); + fflush(dbg->file_fp); + + return TRUE; +} + +/* Send lock held by caller */ +static bool_t hdhomerun_debug_output_message_sock(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message) +{ + if (dbg->sock == HDHOMERUN_SOCK_INVALID) { + uint64_t current_time = getcurrenttime(); + if (current_time < dbg->connect_delay) { + return FALSE; + } + dbg->connect_delay = current_time + HDHOMERUN_DEBUG_CONNECT_RETRY_TIME; + + dbg->sock = hdhomerun_sock_create_tcp(); + if (dbg->sock == HDHOMERUN_SOCK_INVALID) { + return FALSE; + } + + uint32_t remote_addr = hdhomerun_sock_getaddrinfo_addr(dbg->sock, HDHOMERUN_DEBUG_HOST); + if (remote_addr == 0) { + hdhomerun_debug_close_internal(dbg); + return FALSE; + } + + if (!hdhomerun_sock_connect(dbg->sock, remote_addr, HDHOMERUN_DEBUG_PORT, HDHOMERUN_DEBUG_CONNECT_TIMEOUT)) { + hdhomerun_debug_close_internal(dbg); + return FALSE; + } + } + + size_t length = strlen(message->buffer); + if (!hdhomerun_sock_send(dbg->sock, message->buffer, length, HDHOMERUN_DEBUG_SEND_TIMEOUT)) { + hdhomerun_debug_close_internal(dbg); + return FALSE; + } + + return TRUE; +} + +static bool_t hdhomerun_debug_output_message(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message) +{ + pthread_mutex_lock(&dbg->send_lock); + + bool_t ret; + if (dbg->file_name) { + ret = hdhomerun_debug_output_message_file(dbg, message); + } else { + ret = hdhomerun_debug_output_message_sock(dbg, message); + } + + pthread_mutex_unlock(&dbg->send_lock); + return ret; +} + +static void hdhomerun_debug_pop_and_free_message(struct hdhomerun_debug_t *dbg) +{ + pthread_mutex_lock(&dbg->queue_lock); + + struct hdhomerun_debug_message_t *message = dbg->queue_tail; + dbg->queue_tail = message->prev; + if (message->prev) { + message->prev->next = NULL; + } else { + dbg->queue_head = NULL; + } + dbg->queue_depth--; + + pthread_mutex_unlock(&dbg->queue_lock); + + free(message); +} + +static THREAD_FUNC_PREFIX hdhomerun_debug_thread_execute(void *arg) +{ + struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)arg; + + while (!dbg->terminate) { + + pthread_mutex_lock(&dbg->queue_lock); + struct hdhomerun_debug_message_t *message = dbg->queue_tail; + uint32_t queue_depth = dbg->queue_depth; + pthread_mutex_unlock(&dbg->queue_lock); + + if (!message) { + msleep_approx(250); + continue; + } + + if (queue_depth > 1024) { + hdhomerun_debug_pop_and_free_message(dbg); + continue; + } + + if (!hdhomerun_debug_output_message(dbg, message)) { + msleep_approx(250); + continue; + } + + hdhomerun_debug_pop_and_free_message(dbg); + } + + return 0; +} diff --git a/hdhomerun_debug.h b/hdhomerun_debug.h new file mode 100644 index 0000000..fbc2da9 --- /dev/null +++ b/hdhomerun_debug.h @@ -0,0 +1,52 @@ +/* + * hdhomerun_debug.h + * + * Copyright © 2006 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * The debug logging includes optional support for connecting to the + * Silicondust support server. This option should not be used without + * being explicitly enabled by the user. Debug information should be + * limited to information useful to diagnosing a problem. + * - Silicondust. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_debug_t; + +extern LIBTYPE struct hdhomerun_debug_t *hdhomerun_debug_create(void); +extern LIBTYPE void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg); + +extern LIBTYPE void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix); +extern LIBTYPE void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename); +extern LIBTYPE void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg); +extern LIBTYPE bool_t hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg); + +extern LIBTYPE void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout); +extern LIBTYPE void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout); + +extern LIBTYPE void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...); +extern LIBTYPE void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_device.c b/hdhomerun_device.c new file mode 100644 index 0000000..fa4a508 --- /dev/null +++ b/hdhomerun_device.c @@ -0,0 +1,1359 @@ +/* + * hdhomerun_device.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_device_t { + struct hdhomerun_control_sock_t *cs; + struct hdhomerun_video_sock_t *vs; + struct hdhomerun_debug_t *dbg; + struct hdhomerun_channelscan_t *scan; + uint32_t multicast_ip; + uint16_t multicast_port; + uint32_t device_id; + unsigned int tuner; + uint32_t lockkey; + char name[32]; + char model[32]; +}; + +int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip) +{ + if ((device_id == 0) && (device_ip == 0)) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: device not specified\n"); + return -1; + } + + if (hdhomerun_discover_is_ip_multicast(device_ip)) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: invalid address %08X\n", (unsigned int)device_ip); + return -1; + } + + if (!hd->cs) { + hd->cs = hdhomerun_control_create(0, 0, hd->dbg); + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: failed to create control object\n"); + return -1; + } + } + + hdhomerun_control_set_device(hd->cs, device_id, device_ip); + + if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { + device_id = hdhomerun_control_get_device_id(hd->cs); + } + + hd->multicast_ip = 0; + hd->multicast_port = 0; + hd->device_id = device_id; + hd->tuner = 0; + hd->lockkey = 0; + + hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "%08X-%u", (unsigned int)hd->device_id, hd->tuner); + hdhomerun_sprintf(hd->model, hd->model + sizeof(hd->model), ""); /* clear cached model string */ + + return 1; +} + +int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip, uint16_t multicast_port) +{ + if (!hdhomerun_discover_is_ip_multicast(multicast_ip)) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid address %08X\n", (unsigned int)multicast_ip); + return -1; + } + + if (multicast_port == 0) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid port %u\n", (unsigned int)multicast_port); + return -1; + } + + if (hd->cs) { + hdhomerun_control_destroy(hd->cs); + hd->cs = NULL; + } + + hd->multicast_ip = multicast_ip; + hd->multicast_port = multicast_port; + hd->device_id = 0; + hd->tuner = 0; + hd->lockkey = 0; + + unsigned int ip = multicast_ip; + hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "%u.%u.%u.%u:%u", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF, (unsigned int)multicast_port); + hdhomerun_sprintf(hd->model, hd->model + sizeof(hd->model), "multicast"); + + return 1; +} + +int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner) +{ + if (hd->multicast_ip != 0) { + if (tuner != 0) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner: tuner cannot be specified in multicast mode\n"); + return -1; + } + + return 1; + } + + hd->tuner = tuner; + hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "%08X-%u", (unsigned int)hd->device_id, hd->tuner); + + return 1; +} + +int hdhomerun_device_set_tuner_from_str(struct hdhomerun_device_t *hd, const char *tuner_str) +{ + unsigned int tuner; + if (sscanf(tuner_str, "%u", &tuner) == 1) { + hdhomerun_device_set_tuner(hd, tuner); + return 1; + } + if (sscanf(tuner_str, "/tuner%u", &tuner) == 1) { + hdhomerun_device_set_tuner(hd, tuner); + return 1; + } + + return -1; +} + +static struct hdhomerun_device_t *hdhomerun_device_create_internal(struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_device_t *hd = (struct hdhomerun_device_t *)calloc(1, sizeof(struct hdhomerun_device_t)); + if (!hd) { + hdhomerun_debug_printf(dbg, "hdhomerun_device_create: failed to allocate device object\n"); + return NULL; + } + + hd->dbg = dbg; + return hd; +} + +struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); + if (!hd) { + return NULL; + } + + if ((device_id == 0) && (device_ip == 0) && (tuner == 0)) { + return hd; + } + + if (hdhomerun_device_set_device(hd, device_id, device_ip) <= 0) { + free(hd); + return NULL; + } + if (hdhomerun_device_set_tuner(hd, tuner) <= 0) { + free(hd); + return NULL; + } + + return hd; +} + +struct hdhomerun_device_t *hdhomerun_device_create_multicast(uint32_t multicast_ip, uint16_t multicast_port, struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); + if (!hd) { + return NULL; + } + + if (hdhomerun_device_set_multicast(hd, multicast_ip, multicast_port) <= 0) { + free(hd); + return NULL; + } + + return hd; +} + +void hdhomerun_device_destroy(struct hdhomerun_device_t *hd) +{ + if (hd->scan) { + channelscan_destroy(hd->scan); + } + + if (hd->vs) { + hdhomerun_video_destroy(hd->vs); + } + + if (hd->cs) { + hdhomerun_control_destroy(hd->cs); + } + + free(hd); +} + +struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg) +{ + /* + * IP address based device_str. + */ + unsigned int a[4]; + if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) { + uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); + + /* + * Multicast IP address. + */ + unsigned int port; + if (sscanf(device_str, "%u.%u.%u.%u:%u", &a[0], &a[1], &a[2], &a[3], &port) == 5) { + return hdhomerun_device_create_multicast(ip_addr, port, dbg); + } + + /* + * IP address + tuner number. + */ + unsigned int tuner; + if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) { + return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, dbg); + } + + /* + * IP address only. + */ + return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, 0, dbg); + } + + /* + * Device ID based device_str. + */ + char *end; + uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16); + if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) { + /* + * IP address + tuner number. + */ + if (*end == '-') { + unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10); + return hdhomerun_device_create(device_id, 0, tuner, dbg); + } + + /* + * Device ID only. + */ + return hdhomerun_device_create(device_id, 0, 0, dbg); + } + + /* + * DNS based device_str. + */ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *sock_info; + if (getaddrinfo(device_str, "65001", &hints, &sock_info) != 0) { + return NULL; + } + + struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; + uint32_t ip_addr = (uint32_t)ntohl(sock_addr->sin_addr.s_addr); + freeaddrinfo(sock_info); + + if (ip_addr == 0) { + return NULL; + } + + return hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, 0, dbg); +} + +const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd) +{ + return hd->name; +} + +uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd) +{ + return hd->device_id; +} + +uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd) +{ + if (hd->multicast_ip != 0) { + return hd->multicast_ip; + } + if (hd->cs) { + return hdhomerun_control_get_device_ip(hd->cs); + } + + return 0; +} + +uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd) +{ + if (hd->multicast_ip != 0) { + return 0; + } + if (hd->cs) { + return hdhomerun_control_get_device_id_requested(hd->cs); + } + + return 0; +} + +uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd) +{ + if (hd->multicast_ip != 0) { + return hd->multicast_ip; + } + if (hd->cs) { + return hdhomerun_control_get_device_ip_requested(hd->cs); + } + + return 0; +} + +unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd) +{ + return hd->tuner; +} + +struct hdhomerun_control_sock_t *hdhomerun_device_get_control_sock(struct hdhomerun_device_t *hd) +{ + return hd->cs; +} + +struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_device_t *hd) +{ + if (hd->vs) { + return hd->vs; + } + + bool_t allow_port_reuse = (hd->multicast_port != 0); + + hd->vs = hdhomerun_video_create(hd->multicast_port, allow_port_reuse, VIDEO_DATA_BUFFER_SIZE_1S * 2, hd->dbg); + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_video_sock: failed to create video object\n"); + return NULL; + } + + return hd->vs; +} + +uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd) +{ + if (hd->cs) { + return hdhomerun_control_get_local_addr(hd->cs); + } + + return 0; +} + +static uint32_t hdhomerun_device_get_status_parse(const char *status_str, const char *tag) +{ + const char *ptr = strstr(status_str, tag); + if (!ptr) { + return 0; + } + + unsigned int value = 0; + sscanf(ptr + strlen(tag), "%u", &value); + + return (uint32_t)value; +} + +static bool_t hdhomerun_device_get_tuner_status_lock_is_bcast(struct hdhomerun_tuner_status_t *status) +{ + if (strcmp(status->lock_str, "8vsb") == 0) { + return TRUE; + } + if (strncmp(status->lock_str, "t8", 2) == 0) { + return TRUE; + } + if (strncmp(status->lock_str, "t7", 2) == 0) { + return TRUE; + } + if (strncmp(status->lock_str, "t6", 2) == 0) { + return TRUE; + } + + return FALSE; +} + +uint32_t hdhomerun_device_get_tuner_status_ss_color(struct hdhomerun_tuner_status_t *status) +{ + unsigned int ss_yellow_min; + unsigned int ss_green_min; + + if (!status->lock_supported) { + return HDHOMERUN_STATUS_COLOR_NEUTRAL; + } + + if (hdhomerun_device_get_tuner_status_lock_is_bcast(status)) { + ss_yellow_min = 50; /* -30dBmV */ + ss_green_min = 75; /* -15dBmV */ + } else { + ss_yellow_min = 80; /* -12dBmV */ + ss_green_min = 90; /* -6dBmV */ + } + + if (status->signal_strength >= ss_green_min) { + return HDHOMERUN_STATUS_COLOR_GREEN; + } + if (status->signal_strength >= ss_yellow_min) { + return HDHOMERUN_STATUS_COLOR_YELLOW; + } + + return HDHOMERUN_STATUS_COLOR_RED; +} + +uint32_t hdhomerun_device_get_tuner_status_snq_color(struct hdhomerun_tuner_status_t *status) +{ + if (status->signal_to_noise_quality >= 70) { + return HDHOMERUN_STATUS_COLOR_GREEN; + } + if (status->signal_to_noise_quality >= 50) { + return HDHOMERUN_STATUS_COLOR_YELLOW; + } + + return HDHOMERUN_STATUS_COLOR_RED; +} + +uint32_t hdhomerun_device_get_tuner_status_seq_color(struct hdhomerun_tuner_status_t *status) +{ + if (status->symbol_error_quality >= 100) { + return HDHOMERUN_STATUS_COLOR_GREEN; + } + + return HDHOMERUN_STATUS_COLOR_RED; +} + +int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_status: device not set\n"); + return -1; + } + + memset(status, 0, sizeof(struct hdhomerun_tuner_status_t)); + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/status", hd->tuner); + + char *status_str; + int ret = hdhomerun_control_get(hd->cs, name, &status_str, NULL); + if (ret <= 0) { + return ret; + } + + if (pstatus_str) { + *pstatus_str = status_str; + } + + if (status) { + char *channel = strstr(status_str, "ch="); + if (channel) { + sscanf(channel + 3, "%31s", status->channel); + } + + char *lock = strstr(status_str, "lock="); + if (lock) { + sscanf(lock + 5, "%31s", status->lock_str); + } + + status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss="); + status->signal_to_noise_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "snq="); + status->symbol_error_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "seq="); + status->raw_bits_per_second = hdhomerun_device_get_status_parse(status_str, "bps="); + status->packets_per_second = hdhomerun_device_get_status_parse(status_str, "pps="); + + status->signal_present = status->signal_strength >= 45; + + if (strcmp(status->lock_str, "none") != 0) { + if (status->lock_str[0] == '(') { + status->lock_unsupported = TRUE; + } else { + status->lock_supported = TRUE; + } + } + } + + return 1; +} + +int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_oob_status: device not set\n"); + return -1; + } + + memset(status, 0, sizeof(struct hdhomerun_tuner_status_t)); + + char *status_str; + int ret = hdhomerun_control_get(hd->cs, "/oob/status", &status_str, NULL); + if (ret <= 0) { + return ret; + } + + if (pstatus_str) { + *pstatus_str = status_str; + } + + if (status) { + char *channel = strstr(status_str, "ch="); + if (channel) { + sscanf(channel + 3, "%31s", status->channel); + } + + char *lock = strstr(status_str, "lock="); + if (lock) { + sscanf(lock + 5, "%31s", status->lock_str); + } + + status->signal_strength = (unsigned int)hdhomerun_device_get_status_parse(status_str, "ss="); + status->signal_to_noise_quality = (unsigned int)hdhomerun_device_get_status_parse(status_str, "snq="); + status->signal_present = status->signal_strength >= 45; + status->lock_supported = (strcmp(status->lock_str, "none") != 0); + } + + return 1; +} + +int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvstatus_str, struct hdhomerun_tuner_vstatus_t *vstatus) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_vstatus: device not set\n"); + return -1; + } + + memset(vstatus, 0, sizeof(struct hdhomerun_tuner_vstatus_t)); + + char var_name[32]; + hdhomerun_sprintf(var_name, var_name + sizeof(var_name), "/tuner%u/vstatus", hd->tuner); + + char *vstatus_str; + int ret = hdhomerun_control_get(hd->cs, var_name, &vstatus_str, NULL); + if (ret <= 0) { + return ret; + } + + if (pvstatus_str) { + *pvstatus_str = vstatus_str; + } + + if (vstatus) { + char *vch = strstr(vstatus_str, "vch="); + if (vch) { + sscanf(vch + 4, "%31s", vstatus->vchannel); + } + + char *name = strstr(vstatus_str, "name="); + if (name) { + sscanf(name + 5, "%31s", vstatus->name); + } + + char *auth = strstr(vstatus_str, "auth="); + if (auth) { + sscanf(auth + 5, "%31s", vstatus->auth); + } + + char *cci = strstr(vstatus_str, "cci="); + if (cci) { + sscanf(cci + 4, "%31s", vstatus->cci); + } + + char *cgms = strstr(vstatus_str, "cgms="); + if (cgms) { + sscanf(cgms + 5, "%31s", vstatus->cgms); + } + + if (strncmp(vstatus->auth, "not-subscribed", 14) == 0) { + vstatus->not_subscribed = TRUE; + } + + if (strncmp(vstatus->auth, "error", 5) == 0) { + vstatus->not_available = TRUE; + } + if (strncmp(vstatus->auth, "dialog", 6) == 0) { + vstatus->not_available = TRUE; + } + + if (strncmp(vstatus->cci, "protected", 9) == 0) { + vstatus->copy_protected = TRUE; + } + if (strncmp(vstatus->cgms, "protected", 9) == 0) { + vstatus->copy_protected = TRUE; + } + } + + return 1; +} + +int hdhomerun_device_get_tuner_streaminfo(struct hdhomerun_device_t *hd, char **pstreaminfo) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_streaminfo: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/streaminfo", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pstreaminfo, NULL); +} + +int hdhomerun_device_get_tuner_channel(struct hdhomerun_device_t *hd, char **pchannel) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_channel: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/channel", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pchannel, NULL); +} + +int hdhomerun_device_get_tuner_vchannel(struct hdhomerun_device_t *hd, char **pvchannel) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_vchannel: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/vchannel", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pvchannel, NULL); +} + +int hdhomerun_device_get_tuner_channelmap(struct hdhomerun_device_t *hd, char **pchannelmap) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_channelmap: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/channelmap", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pchannelmap, NULL); +} + +int hdhomerun_device_get_tuner_filter(struct hdhomerun_device_t *hd, char **pfilter) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_filter: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/filter", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pfilter, NULL); +} + +int hdhomerun_device_get_tuner_program(struct hdhomerun_device_t *hd, char **pprogram) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_program: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/program", hd->tuner); + return hdhomerun_control_get(hd->cs, name, pprogram, NULL); +} + +int hdhomerun_device_get_tuner_target(struct hdhomerun_device_t *hd, char **ptarget) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_target: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/target", hd->tuner); + return hdhomerun_control_get(hd->cs, name, ptarget, NULL); +} + +static int hdhomerun_device_get_tuner_plotsample_internal(struct hdhomerun_device_t *hd, const char *name, struct hdhomerun_plotsample_t **psamples, size_t *pcount) +{ + char *result; + int ret = hdhomerun_control_get(hd->cs, name, &result, NULL); + if (ret <= 0) { + return ret; + } + + struct hdhomerun_plotsample_t *samples = (struct hdhomerun_plotsample_t *)result; + *psamples = samples; + size_t count = 0; + + while (1) { + char *ptr = strchr(result, ' '); + if (!ptr) { + break; + } + *ptr++ = 0; + + unsigned int raw; + if (sscanf(result, "%x", &raw) != 1) { + break; + } + + uint16_t real = (raw >> 12) & 0x0FFF; + if (real & 0x0800) { + real |= 0xF000; + } + + uint16_t imag = (raw >> 0) & 0x0FFF; + if (imag & 0x0800) { + imag |= 0xF000; + } + + samples->real = (int16_t)real; + samples->imag = (int16_t)imag; + samples++; + count++; + + result = ptr; + } + + *pcount = count; + return 1; +} + +int hdhomerun_device_get_tuner_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_plotsample: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/plotsample", hd->tuner); + return hdhomerun_device_get_tuner_plotsample_internal(hd, name, psamples, pcount); +} + +int hdhomerun_device_get_oob_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_oob_plotsample: device not set\n"); + return -1; + } + + return hdhomerun_device_get_tuner_plotsample_internal(hd, "/oob/plotsample", psamples, pcount); +} + +int hdhomerun_device_get_tuner_lockkey_owner(struct hdhomerun_device_t *hd, char **powner) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_lockkey_owner: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/lockkey", hd->tuner); + return hdhomerun_control_get(hd->cs, name, powner, NULL); +} + +int hdhomerun_device_get_ir_target(struct hdhomerun_device_t *hd, char **ptarget) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_ir_target: device not set\n"); + return -1; + } + + return hdhomerun_control_get(hd->cs, "/ir/target", ptarget, NULL); +} + +int hdhomerun_device_get_version(struct hdhomerun_device_t *hd, char **pversion_str, uint32_t *pversion_num) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_version: device not set\n"); + return -1; + } + + char *version_str; + int ret = hdhomerun_control_get(hd->cs, "/sys/version", &version_str, NULL); + if (ret <= 0) { + return ret; + } + + if (pversion_str) { + *pversion_str = version_str; + } + + if (pversion_num) { + unsigned int version_num; + if (sscanf(version_str, "%u", &version_num) != 1) { + *pversion_num = 0; + } else { + *pversion_num = (uint32_t)version_num; + } + } + + return 1; +} + +int hdhomerun_device_get_supported(struct hdhomerun_device_t *hd, char *prefix, char **pstr) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channel: device not set\n"); + return -1; + } + + char *features; + int ret = hdhomerun_control_get(hd->cs, "/sys/features", &features, NULL); + if (ret <= 0) { + return ret; + } + + if (!prefix) { + *pstr = features; + return 1; + } + + char *ptr = strstr(features, prefix); + if (!ptr) { + return 0; + } + + ptr += strlen(prefix); + *pstr = ptr; + + ptr = strchr(ptr, '\n'); + if (ptr) { + *ptr = 0; + } + + return 1; +} + +int hdhomerun_device_set_tuner_channel(struct hdhomerun_device_t *hd, const char *channel) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channel: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/channel", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, channel, hd->lockkey, NULL, NULL); +} + +int hdhomerun_device_set_tuner_vchannel(struct hdhomerun_device_t *hd, const char *vchannel) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_vchannel: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/vchannel", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, vchannel, hd->lockkey, NULL, NULL); +} + +int hdhomerun_device_set_tuner_channelmap(struct hdhomerun_device_t *hd, const char *channelmap) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_channelmap: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/channelmap", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, channelmap, hd->lockkey, NULL, NULL); +} + +int hdhomerun_device_set_tuner_filter(struct hdhomerun_device_t *hd, const char *filter) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_filter: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/filter", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, filter, hd->lockkey, NULL, NULL); +} + +static bool_t hdhomerun_device_set_tuner_filter_by_array_append(char *ptr, char *end, uint16_t range_begin, uint16_t range_end) +{ + if (range_begin == range_end) { + return hdhomerun_sprintf(ptr, end, "0x%04x ", (unsigned int)range_begin); + } else { + return hdhomerun_sprintf(ptr, end, "0x%04x-0x%04x ", (unsigned int)range_begin, (unsigned int)range_end); + } +} + +int hdhomerun_device_set_tuner_filter_by_array(struct hdhomerun_device_t *hd, unsigned char filter_array[0x2000]) +{ + char filter[1024]; + char *ptr = filter; + char *end = filter + sizeof(filter); + + uint16_t range_begin = 0xFFFF; + uint16_t range_end = 0xFFFF; + + uint16_t i; + for (i = 0; i <= 0x1FFF; i++) { + if (!filter_array[i]) { + if (range_begin == 0xFFFF) { + continue; + } + if (!hdhomerun_device_set_tuner_filter_by_array_append(ptr, end, range_begin, range_end)) { + return 0; + } + ptr = strchr(ptr, 0); + range_begin = 0xFFFF; + range_end = 0xFFFF; + continue; + } + + if (range_begin == 0xFFFF) { + range_begin = i; + range_end = i; + continue; + } + + range_end = i; + } + + if (range_begin != 0xFFFF) { + if (!hdhomerun_device_set_tuner_filter_by_array_append(ptr, end, range_begin, range_end)) { + return 0; + } + ptr = strchr(ptr, 0); + } + + /* Remove trailing space. */ + if (ptr > filter) { + ptr--; + *ptr = 0; + } + + return hdhomerun_device_set_tuner_filter(hd, filter); +} + +int hdhomerun_device_set_tuner_program(struct hdhomerun_device_t *hd, const char *program) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_program: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/program", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, program, hd->lockkey, NULL, NULL); +} + +int hdhomerun_device_set_tuner_target(struct hdhomerun_device_t *hd, const char *target) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/target", hd->tuner); + return hdhomerun_control_set_with_lockkey(hd->cs, name, target, hd->lockkey, NULL, NULL); +} + +static int hdhomerun_device_set_tuner_target_to_local(struct hdhomerun_device_t *hd, const char *protocol) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target_to_local: device not set\n"); + return -1; + } + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_tuner_target_to_local: video not initialized\n"); + return -1; + } + + /* Set target. */ + char target[64]; + uint32_t local_ip = hdhomerun_control_get_local_addr(hd->cs); + uint16_t local_port = hdhomerun_video_get_local_port(hd->vs); + hdhomerun_sprintf(target, target + sizeof(target), "%s://%u.%u.%u.%u:%u", + protocol, + (unsigned int)(local_ip >> 24) & 0xFF, (unsigned int)(local_ip >> 16) & 0xFF, + (unsigned int)(local_ip >> 8) & 0xFF, (unsigned int)(local_ip >> 0) & 0xFF, + (unsigned int)local_port + ); + + return hdhomerun_device_set_tuner_target(hd, target); +} + +int hdhomerun_device_set_ir_target(struct hdhomerun_device_t *hd, const char *target) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_ir_target: device not set\n"); + return -1; + } + + return hdhomerun_control_set(hd->cs, "/ir/target", target, NULL, NULL); +} + +int hdhomerun_device_set_sys_dvbc_modulation(struct hdhomerun_device_t *hd, const char *modulation_list) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_sys_dvbc_modulation: device not set\n"); + return -1; + } + + return hdhomerun_control_set(hd->cs, "/sys/dvbc_modulation", modulation_list, NULL, NULL); +} + +int hdhomerun_device_get_var(struct hdhomerun_device_t *hd, const char *name, char **pvalue, char **perror) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_var: device not set\n"); + return -1; + } + + return hdhomerun_control_get(hd->cs, name, pvalue, perror); +} + +int hdhomerun_device_set_var(struct hdhomerun_device_t *hd, const char *name, const char *value, char **pvalue, char **perror) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_var: device not set\n"); + return -1; + } + + return hdhomerun_control_set_with_lockkey(hd->cs, name, value, hd->lockkey, pvalue, perror); +} + +int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror) +{ + if (hd->multicast_ip != 0) { + return 1; + } + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_request: device not set\n"); + return -1; + } + + uint32_t new_lockkey = random_get32(); + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/lockkey", hd->tuner); + + char new_lockkey_str[64]; + hdhomerun_sprintf(new_lockkey_str, new_lockkey_str + sizeof(new_lockkey_str), "%u", (unsigned int)new_lockkey); + + int ret = hdhomerun_control_set_with_lockkey(hd->cs, name, new_lockkey_str, hd->lockkey, NULL, perror); + if (ret <= 0) { + hd->lockkey = 0; + return ret; + } + + hd->lockkey = new_lockkey; + return ret; +} + +int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd) +{ + if (hd->multicast_ip != 0) { + return 1; + } + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_release: device not set\n"); + return -1; + } + + if (hd->lockkey == 0) { + return 1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/lockkey", hd->tuner); + int ret = hdhomerun_control_set_with_lockkey(hd->cs, name, "none", hd->lockkey, NULL, NULL); + + hd->lockkey = 0; + return ret; +} + +int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd) +{ + if (hd->multicast_ip != 0) { + return 1; + } + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_tuner_lockkey_force: device not set\n"); + return -1; + } + + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/lockkey", hd->tuner); + int ret = hdhomerun_control_set(hd->cs, name, "force", NULL, NULL); + + hd->lockkey = 0; + return ret; +} + +void hdhomerun_device_tuner_lockkey_use_value(struct hdhomerun_device_t *hd, uint32_t lockkey) +{ + if (hd->multicast_ip != 0) { + return; + } + + hd->lockkey = lockkey; +} + +int hdhomerun_device_wait_for_lock(struct hdhomerun_device_t *hd, struct hdhomerun_tuner_status_t *status) +{ + /* Delay for SS reading to be valid (signal present). */ + msleep_minimum(250); + + /* Wait for up to 2.5 seconds for lock. */ + uint64_t timeout = getcurrenttime() + 2500; + while (1) { + /* Get status to check for lock. Quality numbers will not be valid yet. */ + int ret = hdhomerun_device_get_tuner_status(hd, NULL, status); + if (ret <= 0) { + return ret; + } + + if (!status->signal_present) { + return 1; + } + if (status->lock_supported || status->lock_unsupported) { + return 1; + } + + if (getcurrenttime() >= timeout) { + return 1; + } + + msleep_approx(250); + } +} + +int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd) +{ + hdhomerun_device_get_video_sock(hd); + if (!hd->vs) { + return -1; + } + + /* Set target. */ + if (hd->multicast_ip != 0) { + int ret = hdhomerun_video_join_multicast_group(hd->vs, hd->multicast_ip, 0); + if (ret <= 0) { + return ret; + } + } else { + int ret = hdhomerun_device_set_tuner_target_to_local(hd, HDHOMERUN_TARGET_PROTOCOL_RTP); + if (ret == 0) { + ret = hdhomerun_device_set_tuner_target_to_local(hd, HDHOMERUN_TARGET_PROTOCOL_UDP); + } + if (ret <= 0) { + return ret; + } + } + + /* Flush video buffer. */ + msleep_minimum(64); + hdhomerun_video_flush(hd->vs); + + /* Success. */ + return 1; +} + +uint8_t *hdhomerun_device_stream_recv(struct hdhomerun_device_t *hd, size_t max_size, size_t *pactual_size) +{ + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_recv: video not initialized\n"); + return NULL; + } + + return hdhomerun_video_recv(hd->vs, max_size, pactual_size); +} + +void hdhomerun_device_stream_flush(struct hdhomerun_device_t *hd) +{ + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_flush: video not initialized\n"); + return; + } + + hdhomerun_video_flush(hd->vs); +} + +void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd) +{ + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_stop: video not initialized\n"); + return; + } + + if (hd->multicast_ip != 0) { + hdhomerun_video_leave_multicast_group(hd->vs, hd->multicast_ip, 0); + } else { + hdhomerun_device_set_tuner_target(hd, "none"); + } +} + +int hdhomerun_device_channelscan_init(struct hdhomerun_device_t *hd, const char *channelmap) +{ + if (hd->scan) { + channelscan_destroy(hd->scan); + } + + hd->scan = channelscan_create(hd, channelmap); + if (!hd->scan) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_init: failed to create scan object\n"); + return -1; + } + + return 1; +} + +int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result) +{ + if (!hd->scan) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_advance: scan not initialized\n"); + return 0; + } + + int ret = channelscan_advance(hd->scan, result); + if (ret <= 0) { /* Free scan if normal finish or fatal error */ + channelscan_destroy(hd->scan); + hd->scan = NULL; + } + + return ret; +} + +int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result) +{ + if (!hd->scan) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_detect: scan not initialized\n"); + return 0; + } + + int ret = channelscan_detect(hd->scan, result); + if (ret < 0) { /* Free scan if fatal error */ + channelscan_destroy(hd->scan); + hd->scan = NULL; + } + + return ret; +} + +uint8_t hdhomerun_device_channelscan_get_progress(struct hdhomerun_device_t *hd) +{ + if (!hd->scan) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_channelscan_get_progress: scan not initialized\n"); + return 0; + } + + return channelscan_get_progress(hd->scan); +} + +const char *hdhomerun_device_get_hw_model_str(struct hdhomerun_device_t *hd) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_hw_model_str: device not set\n"); + return NULL; + } + + char *model_str; + int ret = hdhomerun_control_get(hd->cs, "/sys/hwmodel", &model_str, NULL); + if (ret < 0) { + return NULL; + } + return model_str; +} + + +const char *hdhomerun_device_get_model_str(struct hdhomerun_device_t *hd) +{ + if (*hd->model) { + return hd->model; + } + + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_model_str: device not set\n"); + return NULL; + } + + char *model_str; + int ret = hdhomerun_control_get(hd->cs, "/sys/model", &model_str, NULL); + if (ret < 0) { + return NULL; + } + if (ret == 0) { + hdhomerun_sprintf(hd->model, hd->model + sizeof(hd->model), "hdhomerun_atsc"); + return hd->model; + } + + hdhomerun_sprintf(hd->model, hd->model + sizeof(hd->model), "%s", model_str); + return hd->model; +} + +int hdhomerun_device_upgrade(struct hdhomerun_device_t *hd, FILE *upgrade_file) +{ + if (!hd->cs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_upgrade: device not set\n"); + return -1; + } + + hdhomerun_control_set(hd->cs, "/tuner0/lockkey", "force", NULL, NULL); + hdhomerun_control_set(hd->cs, "/tuner0/channel", "none", NULL, NULL); + + hdhomerun_control_set(hd->cs, "/tuner1/lockkey", "force", NULL, NULL); + hdhomerun_control_set(hd->cs, "/tuner1/channel", "none", NULL, NULL); + + return hdhomerun_control_upgrade(hd->cs, upgrade_file); +} + +void hdhomerun_device_debug_print_video_stats(struct hdhomerun_device_t *hd) +{ + if (!hdhomerun_debug_enabled(hd->dbg)) { + return; + } + + if (hd->cs) { + char name[32]; + hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/debug", hd->tuner); + + char *debug_str; + char *error_str; + int ret = hdhomerun_control_get(hd->cs, name, &debug_str, &error_str); + if (ret < 0) { + hdhomerun_debug_printf(hd->dbg, "video dev: communication error getting debug stats\n"); + return; + } + + if (error_str) { + hdhomerun_debug_printf(hd->dbg, "video dev: %s\n", error_str); + } else { + hdhomerun_debug_printf(hd->dbg, "video dev: %s\n", debug_str); + } + } + + if (hd->vs) { + hdhomerun_video_debug_print_stats(hd->vs); + } +} + +void hdhomerun_device_get_video_stats(struct hdhomerun_device_t *hd, struct hdhomerun_video_stats_t *stats) +{ + if (!hd->vs) { + hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_stream_flush: video not initialized\n"); + memset(stats, 0, sizeof(struct hdhomerun_video_stats_t)); + return; + } + + hdhomerun_video_get_stats(hd->vs, stats); +} diff --git a/hdhomerun_device.h b/hdhomerun_device.h new file mode 100644 index 0000000..3693943 --- /dev/null +++ b/hdhomerun_device.h @@ -0,0 +1,257 @@ +/* + * hdhomerun_device.h + * + * Copyright © 2006-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define HDHOMERUN_DEVICE_MAX_TUNE_TO_LOCK_TIME 1500 +#define HDHOMERUN_DEVICE_MAX_LOCK_TO_DATA_TIME 2000 +#define HDHOMERUN_DEVICE_MAX_TUNE_TO_DATA_TIME (HDHOMERUN_DEVICE_MAX_TUNE_TO_LOCK_TIME + HDHOMERUN_DEVICE_MAX_LOCK_TO_DATA_TIME) + +#define HDHOMERUN_TARGET_PROTOCOL_UDP "udp" +#define HDHOMERUN_TARGET_PROTOCOL_RTP "rtp" + +/* + * Create a device object. + * + * Typically a device object will be created for each tuner. + * It is valid to have multiple device objects communicating with a single HDHomeRun. + * + * For example, a threaded application that streams video from 4 tuners (2 HDHomeRun devices) and has + * GUI feedback to the user of the selected tuner might use 5 device objects: 4 for streaming video + * (one per thread) and one for the GUI display that can switch between tuners. + * + * This function will not attempt to connect to the device. The connection will be established when first used. + * + * uint32_t device_id = 32-bit device id of device. Set to HDHOMERUN_DEVICE_ID_WILDCARD to match any device ID. + * uint32_t device_ip = IP address of device. Set to 0 to auto-detect. + * unsigned int tuner = tuner index (0 or 1). Can be changed later by calling hdhomerun_device_set_tuner. + * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL. + * + * Returns a pointer to the newly created device object. + * + * When no longer needed, the socket should be destroyed by calling hdhomerun_device_destroy. + * + * The hdhomerun_device_create_from_str function creates a device object from the given device_str. + * The device_str parameter can be any of the following forms: + * + * - + * + * If the tuner index is not included in the device_str then it is set to zero. Use hdhomerun_device_set_tuner + * or hdhomerun_device_set_tuner_from_str to set the tuner. + * + * The hdhomerun_device_set_tuner_from_str function sets the tuner from the given tuner_str. + * The tuner_str parameter can be any of the following forms: + * + * /tuner + */ +extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg); +extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_create_multicast(uint32_t multicast_ip, uint16_t multicast_port, struct hdhomerun_debug_t *dbg); +extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_device_destroy(struct hdhomerun_device_t *hd); + +/* + * Get the device id, ip, or tuner of the device instance. + */ +extern LIBTYPE const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd); +extern LIBTYPE uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd); +extern LIBTYPE uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd); +extern LIBTYPE uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd); +extern LIBTYPE uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd); +extern LIBTYPE unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd); + +extern LIBTYPE int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip); +extern LIBTYPE int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip, uint16_t multicast_port); +extern LIBTYPE int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner); +extern LIBTYPE int hdhomerun_device_set_tuner_from_str(struct hdhomerun_device_t *hd, const char *tuner_str); + +/* + * Get the local machine IP address used when communicating with the device. + * + * This function is useful for determining the IP address to use with set target commands. + * + * Returns 32-bit IP address with native endianness, or 0 on error. + */ +extern LIBTYPE uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd); + +/* + * Get operations. + * + * struct hdhomerun_tuner_status_t *status = Pointer to caller supplied status struct to be populated with result. + * const char **p = Caller supplied char * to be updated to point to the result string. The string will remain + * valid until another call to a device function. + * + * Returns 1 if the operation was successful. + * Returns 0 if the operation was rejected. + * Returns -1 if a communication error occurred. + */ +extern LIBTYPE int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status); +extern LIBTYPE int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvstatus_str, struct hdhomerun_tuner_vstatus_t *vstatus); +extern LIBTYPE int hdhomerun_device_get_tuner_streaminfo(struct hdhomerun_device_t *hd, char **pstreaminfo); +extern LIBTYPE int hdhomerun_device_get_tuner_channel(struct hdhomerun_device_t *hd, char **pchannel); +extern LIBTYPE int hdhomerun_device_get_tuner_vchannel(struct hdhomerun_device_t *hd, char **pvchannel); +extern LIBTYPE int hdhomerun_device_get_tuner_channelmap(struct hdhomerun_device_t *hd, char **pchannelmap); +extern LIBTYPE int hdhomerun_device_get_tuner_filter(struct hdhomerun_device_t *hd, char **pfilter); +extern LIBTYPE int hdhomerun_device_get_tuner_program(struct hdhomerun_device_t *hd, char **pprogram); +extern LIBTYPE int hdhomerun_device_get_tuner_target(struct hdhomerun_device_t *hd, char **ptarget); +extern LIBTYPE int hdhomerun_device_get_tuner_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount); +extern LIBTYPE int hdhomerun_device_get_tuner_lockkey_owner(struct hdhomerun_device_t *hd, char **powner); +extern LIBTYPE int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status); +extern LIBTYPE int hdhomerun_device_get_oob_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount); +extern LIBTYPE int hdhomerun_device_get_ir_target(struct hdhomerun_device_t *hd, char **ptarget); +extern LIBTYPE int hdhomerun_device_get_version(struct hdhomerun_device_t *hd, char **pversion_str, uint32_t *pversion_num); +extern LIBTYPE int hdhomerun_device_get_supported(struct hdhomerun_device_t *hd, char *prefix, char **pstr); + +extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_ss_color(struct hdhomerun_tuner_status_t *status); +extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_snq_color(struct hdhomerun_tuner_status_t *status); +extern LIBTYPE uint32_t hdhomerun_device_get_tuner_status_seq_color(struct hdhomerun_tuner_status_t *status); + +extern LIBTYPE const char *hdhomerun_device_get_hw_model_str(struct hdhomerun_device_t *hd); +extern LIBTYPE const char *hdhomerun_device_get_model_str(struct hdhomerun_device_t *hd); + +/* + * Set operations. + * + * const char * = String to send to device. + * + * Returns 1 if the operation was successful. + * Returns 0 if the operation was rejected. + * Returns -1 if a communication error occurred. + */ +extern LIBTYPE int hdhomerun_device_set_tuner_channel(struct hdhomerun_device_t *hd, const char *channel); +extern LIBTYPE int hdhomerun_device_set_tuner_vchannel(struct hdhomerun_device_t *hd, const char *vchannel); +extern LIBTYPE int hdhomerun_device_set_tuner_channelmap(struct hdhomerun_device_t *hd, const char *channelmap); +extern LIBTYPE int hdhomerun_device_set_tuner_filter(struct hdhomerun_device_t *hd, const char *filter); +extern LIBTYPE int hdhomerun_device_set_tuner_filter_by_array(struct hdhomerun_device_t *hd, unsigned char filter_array[0x2000]); +extern LIBTYPE int hdhomerun_device_set_tuner_program(struct hdhomerun_device_t *hd, const char *program); +extern LIBTYPE int hdhomerun_device_set_tuner_target(struct hdhomerun_device_t *hd, const char *target); +extern LIBTYPE int hdhomerun_device_set_ir_target(struct hdhomerun_device_t *hd, const char *target); +extern LIBTYPE int hdhomerun_device_set_sys_dvbc_modulation(struct hdhomerun_device_t *hd, const char *modulation_list); + +/* + * Get/set a named control variable on the device. + * + * const char *name: The name of var to get/set (c-string). The supported vars is device/firmware dependant. + * const char *value: The value to set (c-string). The format is device/firmware dependant. + + * char **pvalue: If provided, the caller-supplied char pointer will be populated with a pointer to the value + * string returned by the device, or NULL if the device returned an error string. The string will remain + * valid until the next call to a control sock function. + * char **perror: If provided, the caller-supplied char pointer will be populated with a pointer to the error + * string returned by the device, or NULL if the device returned an value string. The string will remain + * valid until the next call to a control sock function. + * + * Returns 1 if the operation was successful (pvalue set, perror NULL). + * Returns 0 if the operation was rejected (pvalue NULL, perror set). + * Returns -1 if a communication error occurs. + */ +extern LIBTYPE int hdhomerun_device_get_var(struct hdhomerun_device_t *hd, const char *name, char **pvalue, char **perror); +extern LIBTYPE int hdhomerun_device_set_var(struct hdhomerun_device_t *hd, const char *name, const char *value, char **pvalue, char **perror); + +/* + * Tuner locking. + * + * The hdhomerun_device_tuner_lockkey_request function is used to obtain a lock + * or to verify that the hdhomerun_device object still holds the lock. + * Returns 1 if the lock request was successful and the lock was obtained. + * Returns 0 if the lock request was rejected. + * Returns -1 if a communication error occurs. + * + * The hdhomerun_device_tuner_lockkey_release function is used to release a + * previously held lock. If locking is used then this function must be called + * before destroying the hdhomerun_device object. + */ +extern LIBTYPE int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror); +extern LIBTYPE int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd); +extern LIBTYPE int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd); + +/* + * Intended only for non persistent connections; eg, hdhomerun_config. + */ +extern LIBTYPE void hdhomerun_device_tuner_lockkey_use_value(struct hdhomerun_device_t *hd, uint32_t lockkey); + +/* + * Wait for tuner lock after channel change. + * + * The hdhomerun_device_wait_for_lock function is used to detect/wait for a lock vs no lock indication + * after a channel change. + * + * It will return quickly if a lock is aquired. + * It will return quickly if there is no signal detected. + * Worst case it will time out after 1.5 seconds - the case where there is signal but no lock. + */ +extern LIBTYPE int hdhomerun_device_wait_for_lock(struct hdhomerun_device_t *hd, struct hdhomerun_tuner_status_t *status); + +/* + * Stream a filtered program or the unfiltered stream. + * + * The hdhomerun_device_stream_start function initializes the process and tells the device to start streamin data. + * + * uint16_t program_number = The program number to filer, or 0 for unfiltered. + * + * Returns 1 if the oprtation started successfully. + * Returns 0 if the operation was rejected. + * Returns -1 if a communication error occurs. + * + * The hdhomerun_device_stream_recv function should be called periodically to receive the stream data. + * The buffer can losslessly store 1 second of data, however a more typical call rate would be every 15ms. + * + * The hdhomerun_device_stream_stop function tells the device to stop streaming data. + */ +extern LIBTYPE int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd); +extern LIBTYPE uint8_t *hdhomerun_device_stream_recv(struct hdhomerun_device_t *hd, size_t max_size, size_t *pactual_size); +extern LIBTYPE void hdhomerun_device_stream_flush(struct hdhomerun_device_t *hd); +extern LIBTYPE void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd); + +/* + * Channel scan API. + */ +extern LIBTYPE int hdhomerun_device_channelscan_init(struct hdhomerun_device_t *hd, const char *channelmap); +extern LIBTYPE int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result); +extern LIBTYPE int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result); +extern LIBTYPE uint8_t hdhomerun_device_channelscan_get_progress(struct hdhomerun_device_t *hd); + +/* + * Upload new firmware to the device. + * + * FILE *upgrade_file: File pointer to read from. The file must have been opened in binary mode for reading. + * + * Returns 1 if the upload succeeded. + * Returns 0 if the upload was rejected. + * Returns -1 if an error occurs. + */ +extern LIBTYPE int hdhomerun_device_upgrade(struct hdhomerun_device_t *hd, FILE *upgrade_file); + +/* + * Low level accessor functions. + */ +extern LIBTYPE struct hdhomerun_control_sock_t *hdhomerun_device_get_control_sock(struct hdhomerun_device_t *hd); +extern LIBTYPE struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_device_t *hd); + +/* + * Debug print internal stats. + */ +extern LIBTYPE void hdhomerun_device_debug_print_video_stats(struct hdhomerun_device_t *hd); +extern LIBTYPE void hdhomerun_device_get_video_stats(struct hdhomerun_device_t *hd, struct hdhomerun_video_stats_t *stats); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_device_selector.c b/hdhomerun_device_selector.c new file mode 100644 index 0000000..bc46c84 --- /dev/null +++ b/hdhomerun_device_selector.c @@ -0,0 +1,447 @@ +/* + * hdhomerun_device_selector.c + * + * Copyright © 2009-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_device_selector_t { + struct hdhomerun_device_t **hd_list; + size_t hd_count; + struct hdhomerun_debug_t *dbg; +}; + +struct hdhomerun_device_selector_t *hdhomerun_device_selector_create(struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_device_selector_t *hds = (struct hdhomerun_device_selector_t *)calloc(1, sizeof(struct hdhomerun_device_selector_t)); + if (!hds) { + hdhomerun_debug_printf(dbg, "hdhomerun_device_selector_create: failed to allocate selector object\n"); + return NULL; + } + + hds->dbg = dbg; + + return hds; +} + +void hdhomerun_device_selector_destroy(struct hdhomerun_device_selector_t *hds, bool_t destroy_devices) +{ + if (destroy_devices) { + size_t index; + for (index = 0; index < hds->hd_count; index++) { + struct hdhomerun_device_t *entry = hds->hd_list[index]; + hdhomerun_device_destroy(entry); + } + } + + if (hds->hd_list) { + free(hds->hd_list); + } + + free(hds); +} + +LIBTYPE int hdhomerun_device_selector_get_device_count(struct hdhomerun_device_selector_t *hds) +{ + return (int)hds->hd_count; +} + +void hdhomerun_device_selector_add_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd) +{ + size_t index; + for (index = 0; index < hds->hd_count; index++) { + struct hdhomerun_device_t *entry = hds->hd_list[index]; + if (entry == hd) { + return; + } + } + + hds->hd_list = (struct hdhomerun_device_t **)realloc(hds->hd_list, (hds->hd_count + 1) * sizeof(struct hdhomerun_device_selector_t *)); + if (!hds->hd_list) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_add_device: failed to allocate device list\n"); + return; + } + + hds->hd_list[hds->hd_count++] = hd; +} + +void hdhomerun_device_selector_remove_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd) +{ + size_t index = 0; + while (1) { + if (index >= hds->hd_count) { + return; + } + + struct hdhomerun_device_t *entry = hds->hd_list[index]; + if (entry == hd) { + break; + } + + index++; + } + + while (index + 1 < hds->hd_count) { + hds->hd_list[index] = hds->hd_list[index + 1]; + index++; + } + + hds->hd_list[index] = NULL; + hds->hd_count--; +} + +struct hdhomerun_device_t *hdhomerun_device_selector_find_device(struct hdhomerun_device_selector_t *hds, uint32_t device_id, unsigned int tuner_index) +{ + size_t index; + for (index = 0; index < hds->hd_count; index++) { + struct hdhomerun_device_t *entry = hds->hd_list[index]; + if (hdhomerun_device_get_device_id(entry) != device_id) { + continue; + } + if (hdhomerun_device_get_tuner(entry) != tuner_index) { + continue; + } + return entry; + } + + return NULL; +} + +static int hdhomerun_device_selector_load_from_str_discover(struct hdhomerun_device_selector_t *hds, uint32_t target_ip, uint32_t device_id) +{ + struct hdhomerun_discover_device_t result_list[64]; + int discover_count = hdhomerun_discover_find_devices_custom_v2(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, device_id, result_list, 64); + + int count = 0; + int result_index; + struct hdhomerun_discover_device_t *result = result_list; + for (result_index = 0; result_index < discover_count; result_index++) { + unsigned int tuner_index; + for (tuner_index = 0; tuner_index < result->tuner_count; tuner_index++) { + struct hdhomerun_device_t *hd = hdhomerun_device_create(result->device_id, result->ip_addr, tuner_index, hds->dbg); + if (!hd) { + continue; + } + + hdhomerun_device_selector_add_device(hds, hd); + count++; + } + + result++; + } + + return count; +} + +int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str) +{ + /* + * IP address based device_str. + */ + unsigned int a[4]; + if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) { + uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); + + /* + * Multicast IP address. + */ + unsigned int port; + if (sscanf(device_str, "%u.%u.%u.%u:%u", &a[0], &a[1], &a[2], &a[3], &port) == 5) { + struct hdhomerun_device_t *hd = hdhomerun_device_create_multicast(ip_addr, port, hds->dbg); + if (!hd) { + return 0; + } + + hdhomerun_device_selector_add_device(hds, hd); + return 1; + } + + /* + * IP address + tuner number. + */ + unsigned int tuner; + if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) { + struct hdhomerun_device_t *hd = hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, hds->dbg); + if (!hd) { + return 0; + } + + hdhomerun_device_selector_add_device(hds, hd); + return 1; + } + + /* + * IP address only - discover and add tuners. + */ + return hdhomerun_device_selector_load_from_str_discover(hds, ip_addr, HDHOMERUN_DEVICE_ID_WILDCARD); + } + + /* + * Device ID based device_str. + */ + char *end; + uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16); + if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) { + /* + * IP address + tuner number. + */ + if (*end == '-') { + unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10); + struct hdhomerun_device_t *hd = hdhomerun_device_create(device_id, 0, tuner, hds->dbg); + if (!hd) { + return 0; + } + + hdhomerun_device_selector_add_device(hds, hd); + return 1; + } + + /* + * Device ID only - discover and add tuners. + */ + return hdhomerun_device_selector_load_from_str_discover(hds, 0, device_id); + } + + /* + * DNS based device_str. + */ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *sock_info; + if (getaddrinfo(device_str, "65001", &hints, &sock_info) != 0) { + return 0; + } + + struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; + uint32_t ip_addr = (uint32_t)ntohl(sock_addr->sin_addr.s_addr); + freeaddrinfo(sock_info); + + if (ip_addr == 0) { + return 0; + } + + return hdhomerun_device_selector_load_from_str_discover(hds, ip_addr, HDHOMERUN_DEVICE_ID_WILDCARD); +} + +int hdhomerun_device_selector_load_from_file(struct hdhomerun_device_selector_t *hds, char *filename) +{ + FILE *fp = fopen(filename, "r"); + if (!fp) { + return 0; + } + + int count = 0; + while(1) { + char device_str[32]; + if (!fgets(device_str, sizeof(device_str), fp)) { + break; + } + + count += hdhomerun_device_selector_load_from_str(hds, device_str); + } + + fclose(fp); + return count; +} + +#if defined(__WINDOWS__) +int hdhomerun_device_selector_load_from_windows_registry(struct hdhomerun_device_selector_t *hds, wchar_t *wsource) +{ + HKEY tuners_key; + LONG ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Silicondust\\HDHomeRun\\Tuners", 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &tuners_key); + if (ret != ERROR_SUCCESS) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_windows_registry: failed to open tuners registry key (%ld)\n", (long)ret); + return 0; + } + + int count = 0; + DWORD index = 0; + while (1) { + /* Next tuner device. */ + wchar_t wdevice_str[32]; + DWORD size = sizeof(wdevice_str); + ret = RegEnumKeyEx(tuners_key, index++, wdevice_str, &size, NULL, NULL, NULL, NULL); + if (ret != ERROR_SUCCESS) { + break; + } + + /* Check device configuation. */ + HKEY device_key; + ret = RegOpenKeyEx(tuners_key, wdevice_str, 0, KEY_QUERY_VALUE, &device_key); + if (ret != ERROR_SUCCESS) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_windows_registry: failed to open registry key for %S (%ld)\n", wdevice_str, (long)ret); + continue; + } + + wchar_t wsource_test[32]; + size = sizeof(wsource_test); + if (RegQueryValueEx(device_key, L"Source", NULL, NULL, (LPBYTE)&wsource_test, &size) != ERROR_SUCCESS) { + wsprintf(wsource_test, L"Unknown"); + } + + RegCloseKey(device_key); + + if (_wcsicmp(wsource_test, wsource) != 0) { + continue; + } + + /* Create and add device. */ + char device_str[32]; + hdhomerun_sprintf(device_str, device_str + sizeof(device_str), "%S", wdevice_str); + count += hdhomerun_device_selector_load_from_str(hds, device_str); + } + + RegCloseKey(tuners_key); + return count; +} +#endif + +static bool_t hdhomerun_device_selector_choose_test(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *test_hd) +{ + const char *name = hdhomerun_device_get_name(test_hd); + + /* + * Attempt to aquire lock. + */ + char *error; + int ret = hdhomerun_device_tuner_lockkey_request(test_hd, &error); + if (ret > 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s chosen\n", name); + return TRUE; + } + if (ret < 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name); + return FALSE; + } + + /* + * In use - check target. + */ + char *target; + ret = hdhomerun_device_get_tuner_target(test_hd, &target); + if (ret < 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name); + return FALSE; + } + if (ret == 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, failed to read target\n", name); + return FALSE; + } + + if (strcmp(target, "none") == 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, no target set\n", name); + return FALSE; + } + + if ((strncmp(target, "udp://", 6) != 0) && (strncmp(target, "rtp://", 6) != 0)) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by %s\n", name, target); + return FALSE; + } + + unsigned int a[4]; + unsigned int target_port; + if (sscanf(target + 6, "%u.%u.%u.%u:%u", &a[0], &a[1], &a[2], &a[3], &target_port) != 5) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, unexpected target set (%s)\n", name, target); + return FALSE; + } + + uint32_t target_ip = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); + uint32_t local_ip = hdhomerun_device_get_local_machine_addr(test_hd); + if (target_ip != local_ip) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by %s\n", name, target); + return FALSE; + } + + /* + * Test local port. + */ + hdhomerun_sock_t test_sock = hdhomerun_sock_create_udp(); + if (test_sock == HDHOMERUN_SOCK_INVALID) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, failed to create test sock\n", name); + return FALSE; + } + + bool_t inuse = (hdhomerun_sock_bind(test_sock, INADDR_ANY, (uint16_t)target_port, FALSE) == FALSE); + hdhomerun_sock_destroy(test_sock); + + if (inuse) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine\n", name); + return FALSE; + } + + /* + * Dead local target, force clear lock. + */ + ret = hdhomerun_device_tuner_lockkey_force(test_hd); + if (ret < 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name); + return FALSE; + } + if (ret == 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine, dead target, failed to force release lockkey\n", name); + return FALSE; + } + + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use by local machine, dead target, lockkey force successful\n", name); + + /* + * Attempt to aquire lock. + */ + ret = hdhomerun_device_tuner_lockkey_request(test_hd, &error); + if (ret > 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s chosen\n", name); + return TRUE; + } + if (ret < 0) { + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s communication error\n", name); + return FALSE; + } + + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s still in use after lockkey force (%s)\n", name, error); + return FALSE; +} + +struct hdhomerun_device_t *hdhomerun_device_selector_choose_and_lock(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *prefered) +{ + /* Test prefered device first. */ + if (prefered) { + if (hdhomerun_device_selector_choose_test(hds, prefered)) { + return prefered; + } + } + + /* Test other tuners. */ + size_t index; + for (index = 0; index < hds->hd_count; index++) { + struct hdhomerun_device_t *entry = hds->hd_list[index]; + if (entry == prefered) { + continue; + } + + if (hdhomerun_device_selector_choose_test(hds, entry)) { + return entry; + } + } + + hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_and_lock: no devices available\n"); + return NULL; +} diff --git a/hdhomerun_device_selector.h b/hdhomerun_device_selector.h new file mode 100644 index 0000000..24fcd80 --- /dev/null +++ b/hdhomerun_device_selector.h @@ -0,0 +1,86 @@ +/* + * hdhomerun_device_selector.h + * + * Copyright © 2009 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a device selector object for use with dynamic tuner allocation support. + * All tuners registered with a specific device selector instance must have the same signal source. + * The dbg parameter may be null. + */ +extern LIBTYPE struct hdhomerun_device_selector_t *hdhomerun_device_selector_create(struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_device_selector_destroy(struct hdhomerun_device_selector_t *hds, bool_t destroy_devices); + +/* + * Get the number of devices in the list. + */ +extern LIBTYPE int hdhomerun_device_selector_get_device_count(struct hdhomerun_device_selector_t *hds); + +/* + * Populate device selector with devices from given source. + * Returns the number of devices populated. + */ +extern LIBTYPE int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str); +extern LIBTYPE int hdhomerun_device_selector_load_from_file(struct hdhomerun_device_selector_t *hds, char *filename); +#if defined(__WINDOWS__) +extern LIBTYPE int hdhomerun_device_selector_load_from_windows_registry(struct hdhomerun_device_selector_t *hds, wchar_t *wsource); +#endif + +/* + * Add/remove a device from the selector list. + */ +extern LIBTYPE void hdhomerun_device_selector_add_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd); +extern LIBTYPE void hdhomerun_device_selector_remove_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd); + +/* + * Find a device in the selector list. + */ +extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_selector_find_device(struct hdhomerun_device_selector_t *hds, uint32_t device_id, unsigned int tuner_index); + +/* + * Select and lock an available device. + * If not null, preference will be given to the prefered device specified. + * The device resource lock must be released by the application when no longer needed by + * calling hdhomerun_device_tuner_lockkey_release(). + * + * Recommended channel change logic: + * + * Start (inactive -> active): + * - Call hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner. + * + * Stop (active -> inactive): + * - Call hdhomerun_device_tuner_lockkey_release() to release the resource lock and allow the tuner + * to be allocated by other computers. + * + * Channel change (active -> active): + * - If the new channel has a different signal source then call hdhomerun_device_tuner_lockkey_release() + * to release the lock on the tuner playing the previous channel, then call + * hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner. + * - If the new channel has the same signal source then call hdhomerun_device_tuner_lockkey_request() + * to refresh the lock. If this function succeeds then the same device can be used. If this fucntion fails + * then call hdhomerun_device_selector_choose_and_lock() to choose and lock an available tuner. + */ +extern LIBTYPE struct hdhomerun_device_t *hdhomerun_device_selector_choose_and_lock(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *prefered); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_discover.c b/hdhomerun_discover.c new file mode 100644 index 0000000..f60ab63 --- /dev/null +++ b/hdhomerun_discover.c @@ -0,0 +1,496 @@ +/* + * hdhomerun_discover.c + * + * Copyright © 2006-2015 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +#define HDHOMERUN_DISOCVER_MAX_SOCK_COUNT 16 + +struct hdhomerun_discover_sock_t { + hdhomerun_sock_t sock; + bool_t detected; + uint32_t local_ip; + uint32_t subnet_mask; +}; + +struct hdhomerun_discover_t { + struct hdhomerun_discover_sock_t socks[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT]; + unsigned int sock_count; + struct hdhomerun_pkt_t tx_pkt; + struct hdhomerun_pkt_t rx_pkt; + struct hdhomerun_debug_t *dbg; +}; + +static bool_t hdhomerun_discover_sock_add(struct hdhomerun_discover_t *ds, uint32_t local_ip, uint32_t subnet_mask) +{ + unsigned int i; + for (i = 1; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + + if ((dss->local_ip == local_ip) && (dss->subnet_mask == subnet_mask)) { + dss->detected = TRUE; + return TRUE; + } + } + + if (ds->sock_count >= HDHOMERUN_DISOCVER_MAX_SOCK_COUNT) { + return FALSE; + } + + /* Create socket. */ + hdhomerun_sock_t sock = hdhomerun_sock_create_udp(); + if (sock == HDHOMERUN_SOCK_INVALID) { + return FALSE; + } + + /* Bind socket. */ + if (!hdhomerun_sock_bind(sock, local_ip, 0, FALSE)) { + hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to %u.%u.%u.%u:0\n", (unsigned int)(local_ip >> 24) & 0xFF, (unsigned int)(local_ip >> 16) & 0xFF, (unsigned int)(local_ip >> 8) & 0xFF, (unsigned int)(local_ip >> 0) & 0xFF); + hdhomerun_sock_destroy(sock); + return FALSE; + } + + /* Write sock entry. */ + struct hdhomerun_discover_sock_t *dss = &ds->socks[ds->sock_count++]; + dss->sock = sock; + dss->detected = TRUE; + dss->local_ip = local_ip; + dss->subnet_mask = subnet_mask; + + return TRUE; +} + +struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg) +{ + struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)calloc(1, sizeof(struct hdhomerun_discover_t)); + if (!ds) { + return NULL; + } + + ds->dbg = dbg; + + /* Create a routable socket (always first entry). */ + if (!hdhomerun_discover_sock_add(ds, 0, 0)) { + free(ds); + return NULL; + } + + /* Success. */ + return ds; +} + +void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds) +{ + unsigned int i; + for (i = 0; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + hdhomerun_sock_destroy(dss->sock); + } + + free(ds); +} + +static void hdhomerun_discover_sock_detect(struct hdhomerun_discover_t *ds) +{ + unsigned int i; + for (i = 1; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + dss->detected = FALSE; + } + + struct hdhomerun_local_ip_info_t ip_info_list[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT]; + int count = hdhomerun_local_ip_info(ip_info_list, HDHOMERUN_DISOCVER_MAX_SOCK_COUNT); + if (count < 0) { + hdhomerun_debug_printf(ds->dbg, "discover: hdhomerun_local_ip_info returned error\n"); + count = 0; + } + if (count > HDHOMERUN_DISOCVER_MAX_SOCK_COUNT) { + hdhomerun_debug_printf(ds->dbg, "discover: too many local IP addresses\n"); + count = HDHOMERUN_DISOCVER_MAX_SOCK_COUNT; + } + + int index; + for (index = 0; index < count; index++) { + struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[index]; + hdhomerun_discover_sock_add(ds, ip_info->ip_addr, ip_info->subnet_mask); + } + + struct hdhomerun_discover_sock_t *src = &ds->socks[1]; + struct hdhomerun_discover_sock_t *dst = &ds->socks[1]; + count = 1; + for (i = 1; i < ds->sock_count; i++) { + if (!src->detected) { + hdhomerun_sock_destroy(src->sock); + src++; + continue; + } + if (dst != src) { + *dst = *src; + } + src++; + dst++; + count++; + } + + ds->sock_count = count; +} + +static bool_t hdhomerun_discover_send_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +{ + struct hdhomerun_pkt_t *tx_pkt = &ds->tx_pkt; + hdhomerun_pkt_reset(tx_pkt); + + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_TYPE); + hdhomerun_pkt_write_var_length(tx_pkt, 4); + hdhomerun_pkt_write_u32(tx_pkt, device_type); + hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_ID); + hdhomerun_pkt_write_var_length(tx_pkt, 4); + hdhomerun_pkt_write_u32(tx_pkt, device_id); + hdhomerun_pkt_seal_frame(tx_pkt, HDHOMERUN_TYPE_DISCOVER_REQ); + + return hdhomerun_sock_sendto(dss->sock, target_ip, HDHOMERUN_DISCOVER_UDP_PORT, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0); +} + +static bool_t hdhomerun_discover_send_wildcard_ip(struct hdhomerun_discover_t *ds, uint32_t device_type, uint32_t device_id) +{ + bool_t result = FALSE; + + /* + * Send subnet broadcast using each local ip socket. + * This will work with multiple separate 169.254.x.x interfaces. + */ + unsigned int i; + for (i = 1; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + uint32_t target_ip = dss->local_ip | ~dss->subnet_mask; + result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id); + } + + /* + * If no local ip sockets then fall back to sending a global broadcast letting the OS choose the interface. + */ + if (!result) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[0]; + result = hdhomerun_discover_send_internal(ds, dss, 0xFFFFFFFF, device_type, device_id); + } + + return result; +} + +static bool_t hdhomerun_discover_send_target_ip(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +{ + bool_t result = FALSE; + + /* + * Send targeted packet from any local ip that is in the same subnet. + * This will work with multiple separate 169.254.x.x interfaces. + */ + unsigned int i; + for (i = 1; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + if ((target_ip & dss->subnet_mask) != (dss->local_ip & dss->subnet_mask)) { + continue; + } + + result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id); + } + + /* + * If target IP does not match a local subnet then fall back to letting the OS choose the gateway interface. + */ + if (!result) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[0]; + result = hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id); + } + + return result; +} + +static bool_t hdhomerun_discover_send(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id) +{ + if (target_ip == 0) { + return hdhomerun_discover_send_wildcard_ip(ds, device_type, device_id); + } else { + return hdhomerun_discover_send_target_ip(ds, target_ip, device_type, device_id); + } +} + +static bool_t hdhomerun_discover_is_legacy(uint32_t device_id) +{ + switch (device_id >> 20) { + case 0x100: /* TECH-US/TECH3-US */ + return (device_id < 0x10040000); + + case 0x120: /* TECH3-EU */ + return (device_id < 0x12030000); + + case 0x101: /* HDHR-US */ + case 0x102: /* HDHR-T1-US */ + case 0x103: /* HDHR3-US */ + case 0x111: /* HDHR3-DT */ + case 0x121: /* HDHR-EU */ + case 0x122: /* HDHR3-EU */ + return TRUE; + + default: + return FALSE; + } +} + +static bool_t hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, struct hdhomerun_discover_device_t *result) +{ + static char hdhomerun_discover_recv_base64_encode_table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; + hdhomerun_pkt_reset(rx_pkt); + + uint32_t remote_addr; + uint16_t remote_port; + size_t length = rx_pkt->limit - rx_pkt->end; + if (!hdhomerun_sock_recvfrom(dss->sock, &remote_addr, &remote_port, rx_pkt->end, &length, 0)) { + return FALSE; + } + + rx_pkt->end += length; + + uint16_t type; + if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) { + return FALSE; + } + if (type != HDHOMERUN_TYPE_DISCOVER_RPY) { + return FALSE; + } + + memset(result, 0, sizeof(struct hdhomerun_discover_device_t)); + result->ip_addr = remote_addr; + + hdhomerun_sprintf(result->base_url, result->base_url + sizeof(result->base_url), "http://%u.%u.%u.%u:80", + (remote_addr >> 24) & 0xFF, (remote_addr >> 16) & 0xFF, (remote_addr >> 8) & 0xFF, (remote_addr >> 0) & 0xFF + ); + + while (1) { + uint8_t tag; + size_t len; + uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len); + if (!next) { + break; + } + + int i; + switch (tag) { + case HDHOMERUN_TAG_DEVICE_TYPE: + if (len != 4) { + break; + } + result->device_type = hdhomerun_pkt_read_u32(rx_pkt); + break; + + case HDHOMERUN_TAG_DEVICE_ID: + if (len != 4) { + break; + } + result->device_id = hdhomerun_pkt_read_u32(rx_pkt); + result->is_legacy = hdhomerun_discover_is_legacy(result->device_id); + break; + + case HDHOMERUN_TAG_TUNER_COUNT: + if (len != 1) { + break; + } + result->tuner_count = hdhomerun_pkt_read_u8(rx_pkt); + break; + + case HDHOMERUN_TAG_DEVICE_AUTH_STR: + if (len >= sizeof(result->device_auth)) { + break; + } + hdhomerun_pkt_read_mem(rx_pkt, result->device_auth, len); + result->device_auth[len] = 0; + break; + + case HDHOMERUN_TAG_DEVICE_AUTH_BIN: + if (len != 18) { + break; + } + for (i = 0; i < 24; i += 4) { + uint32_t raw24; + raw24 = (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 16; + raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 8; + raw24 |= (uint32_t)hdhomerun_pkt_read_u8(rx_pkt) << 0; + result->device_auth[i + 0] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 18) & 0x3F]; + result->device_auth[i + 1] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 12) & 0x3F]; + result->device_auth[i + 2] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 6) & 0x3F]; + result->device_auth[i + 3] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 0) & 0x3F]; + } + result->device_auth[24] = 0; + break; + + case HDHOMERUN_TAG_BASE_URL: + if (len >= sizeof(result->base_url)) { + break; + } + hdhomerun_pkt_read_mem(rx_pkt, result->base_url, len); + result->base_url[len] = 0; + break; + + default: + break; + } + + rx_pkt->pos = next; + } + + /* Fixup for old firmware. */ + if (result->tuner_count == 0) { + switch (result->device_id >> 20) { + case 0x102: + result->tuner_count = 1; + break; + + case 0x100: + case 0x101: + case 0x121: + result->tuner_count = 2; + break; + + default: + break; + } + } + + return TRUE; +} + +static bool_t hdhomerun_discover_recv(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_device_t *result) +{ + unsigned int i; + for (i = 0; i < ds->sock_count; i++) { + struct hdhomerun_discover_sock_t *dss = &ds->socks[i]; + + if (hdhomerun_discover_recv_internal(ds, dss, result)) { + return TRUE; + } + } + + return FALSE; +} + +static struct hdhomerun_discover_device_t *hdhomerun_discover_find_in_list(struct hdhomerun_discover_device_t result_list[], int count, struct hdhomerun_discover_device_t *lookup) +{ + int index; + for (index = 0; index < count; index++) { + struct hdhomerun_discover_device_t *entry = &result_list[index]; + if (memcmp(lookup, entry, sizeof(struct hdhomerun_discover_device_t)) == 0) { + return entry; + } + } + + return NULL; +} + +int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count) +{ + hdhomerun_discover_sock_detect(ds); + + int count = 0; + int attempt; + for (attempt = 0; attempt < 2; attempt++) { + if (!hdhomerun_discover_send(ds, target_ip, device_type, device_id)) { + return -1; + } + + uint64_t timeout = getcurrenttime() + 200; + while (1) { + struct hdhomerun_discover_device_t *result = &result_list[count]; + memset(result, 0, sizeof(struct hdhomerun_discover_device_t)); + + if (!hdhomerun_discover_recv(ds, result)) { + if (getcurrenttime() >= timeout) { + break; + } + msleep_approx(10); + continue; + } + + /* Filter. */ + if (device_type != HDHOMERUN_DEVICE_TYPE_WILDCARD) { + if (device_type != result->device_type) { + continue; + } + } + if (device_id != HDHOMERUN_DEVICE_ID_WILDCARD) { + if (device_id != result->device_id) { + continue; + } + } + + /* Ensure not already in list. */ + if (hdhomerun_discover_find_in_list(result_list, count, result)) { + continue; + } + + /* Add to list. */ + count++; + if (count >= max_count) { + return count; + } + } + } + + return count; +} + +int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count) +{ + if (hdhomerun_discover_is_ip_multicast(target_ip)) { + return 0; + } + + struct hdhomerun_discover_t *ds = hdhomerun_discover_create(NULL); + if (!ds) { + return -1; + } + + int ret = hdhomerun_discover_find_devices_v2(ds, target_ip, device_type, device_id, result_list, max_count); + + hdhomerun_discover_destroy(ds); + return ret; +} + +bool_t hdhomerun_discover_validate_device_id(uint32_t device_id) +{ + static uint8_t lookup_table[16] = {0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0}; + + uint8_t checksum = 0; + + checksum ^= lookup_table[(device_id >> 28) & 0x0F]; + checksum ^= (device_id >> 24) & 0x0F; + checksum ^= lookup_table[(device_id >> 20) & 0x0F]; + checksum ^= (device_id >> 16) & 0x0F; + checksum ^= lookup_table[(device_id >> 12) & 0x0F]; + checksum ^= (device_id >> 8) & 0x0F; + checksum ^= lookup_table[(device_id >> 4) & 0x0F]; + checksum ^= (device_id >> 0) & 0x0F; + + return (checksum == 0); +} + +bool_t hdhomerun_discover_is_ip_multicast(uint32_t ip_addr) +{ + return (ip_addr >= 0xE0000000) && (ip_addr < 0xF0000000); +} diff --git a/hdhomerun_discover.h b/hdhomerun_discover.h new file mode 100644 index 0000000..947aaa1 --- /dev/null +++ b/hdhomerun_discover.h @@ -0,0 +1,78 @@ +/* + * hdhomerun_discover.h + * + * Copyright © 2006-2015 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_discover_device_t { + uint32_t ip_addr; + uint32_t device_type; + uint32_t device_id; + uint8_t tuner_count; + bool_t is_legacy; + char device_auth[25]; + char base_url[29]; +}; + +/* + * Find devices. + * + * The device information is stored in caller-supplied array of hdhomerun_discover_device_t vars. + * Multiple attempts are made to find devices. + * Execution time is typically 400ms if max_count is not reached. + * + * Set target_ip to zero to auto-detect the IP address. + * Set device_type to HDHOMERUN_DEVICE_TYPE_TUNER to detect HDHomeRun tuner devices. + * Set device_id to HDHOMERUN_DEVICE_ID_WILDCARD to detect all device ids. + * + * Returns the number of devices found. + * Retruns -1 on error. + */ +extern LIBTYPE int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count); + +/* + * Optional: persistent discover instance available for discover polling use. + */ +extern LIBTYPE struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds); +extern LIBTYPE int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count); + +/* + * Verify that the device ID given is valid. + * + * The device ID contains a self-check sequence that detects common user input errors including + * single-digit errors and two digit transposition errors. + * + * Returns TRUE if valid. + * Returns FALSE if not valid. + */ +extern LIBTYPE bool_t hdhomerun_discover_validate_device_id(uint32_t device_id); + +/* + * Detect if an IP address is multicast. + * + * Returns TRUE if multicast. + * Returns FALSE if zero, unicast, expermental, or broadcast. + */ +extern LIBTYPE bool_t hdhomerun_discover_is_ip_multicast(uint32_t ip_addr); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_os.h b/hdhomerun_os.h new file mode 100644 index 0000000..1976632 --- /dev/null +++ b/hdhomerun_os.h @@ -0,0 +1,37 @@ +/* + * hdhomerun_os.h + * + * Copyright © 2006-2008 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +#include "hdhomerun_os_windows.h" +#else +#include "hdhomerun_os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif diff --git a/hdhomerun_os_posix.c b/hdhomerun_os_posix.c new file mode 100644 index 0000000..5a38edf --- /dev/null +++ b/hdhomerun_os_posix.c @@ -0,0 +1,128 @@ +/* + * hdhomerun_os_posix.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun_os.h" + +#if defined(__APPLE__) +#include +#include +#endif + +static pthread_once_t random_get32_once = PTHREAD_ONCE_INIT; +static FILE *random_get32_fp = NULL; + +static void random_get32_init(void) +{ + random_get32_fp = fopen("/dev/urandom", "rb"); +} + +uint32_t random_get32(void) +{ + pthread_once(&random_get32_once, random_get32_init); + + if (!random_get32_fp) { + return (uint32_t)getcurrenttime(); + } + + uint32_t Result; + if (fread(&Result, 4, 1, random_get32_fp) != 1) { + return (uint32_t)getcurrenttime(); + } + + return Result; +} + +uint64_t getcurrenttime(void) +{ +#if defined(CLOCK_MONOTONIC) + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_nsec / 1000000); +#elif defined(__APPLE__) + clock_serv_t clock_serv; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock_serv); + + struct mach_timespec t; + clock_get_time(clock_serv, &t); + + mach_port_deallocate(mach_task_self(), clock_serv); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_nsec / 1000000); +#else +#error no clock source for getcurrenttime() +#endif +} + +void msleep_approx(uint64_t ms) +{ + unsigned int delay_s = ms / 1000; + if (delay_s > 0) { + sleep(delay_s); + ms -= delay_s * 1000; + } + + unsigned int delay_us = ms * 1000; + if (delay_us > 0) { + usleep(delay_us); + } +} + +void msleep_minimum(uint64_t ms) +{ + uint64_t stop_time = getcurrenttime() + ms; + + while (1) { + uint64_t current_time = getcurrenttime(); + if (current_time >= stop_time) { + return; + } + + msleep_approx(stop_time - current_time); + } +} + +bool_t hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap) +{ + if (buffer >= end) { + return FALSE; + } + + int length = vsnprintf(buffer, end - buffer - 1, fmt, ap); + if (length < 0) { + *buffer = 0; + return FALSE; + } + + if (buffer + length + 1 > end) { + *(end - 1) = 0; + return FALSE; + + } + + return TRUE; +} + +bool_t hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + bool_t result = hdhomerun_vsprintf(buffer, end, fmt, ap); + va_end(ap); + return result; +} diff --git a/hdhomerun_os_posix.h b/hdhomerun_os_posix.h new file mode 100644 index 0000000..006f026 --- /dev/null +++ b/hdhomerun_os_posix.h @@ -0,0 +1,63 @@ +/* + * hdhomerun_os_posix.h + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef void (*sig_t)(int); + +#define LIBTYPE +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +#ifdef __cplusplus +extern "C" { +#endif + +extern LIBTYPE uint32_t random_get32(void); +extern LIBTYPE uint64_t getcurrenttime(void); +extern LIBTYPE void msleep_approx(uint64_t ms); +extern LIBTYPE void msleep_minimum(uint64_t ms); + +extern LIBTYPE bool_t hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap); +extern LIBTYPE bool_t hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_os_windows.c b/hdhomerun_os_windows.c new file mode 100644 index 0000000..1e5f9d8 --- /dev/null +++ b/hdhomerun_os_windows.c @@ -0,0 +1,152 @@ +/* + * hdhomerun_os_windows.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun_os.h" + +static DWORD random_get32_context_tls = TlsAlloc(); + +uint32_t random_get32(void) +{ + HCRYPTPROV *phProv = (HCRYPTPROV *)TlsGetValue(random_get32_context_tls); + if (!phProv) { + phProv = (HCRYPTPROV *)calloc(1, sizeof(HCRYPTPROV)); + CryptAcquireContext(phProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + TlsSetValue(random_get32_context_tls, phProv); + } + + uint32_t Result; + if (!CryptGenRandom(*phProv, sizeof(Result), (BYTE *)&Result)) { + return (uint32_t)getcurrenttime(); + } + + return Result; +} + +uint64_t getcurrenttime(void) +{ + return GetTickCount64(); +} + +void msleep_approx(uint64_t ms) +{ + Sleep((DWORD)ms); +} + +void msleep_minimum(uint64_t ms) +{ + uint64_t stop_time = getcurrenttime() + ms; + + while (1) { + uint64_t current_time = getcurrenttime(); + if (current_time >= stop_time) { + return; + } + + msleep_approx(stop_time - current_time); + } +} + +int pthread_create(pthread_t *tid, void *attr, LPTHREAD_START_ROUTINE start, void *arg) +{ + *tid = CreateThread(NULL, 0, start, arg, 0, NULL); + if (!*tid) { + return (int)GetLastError(); + } + return 0; +} + +int pthread_join(pthread_t tid, void **value_ptr) +{ + while (1) { + DWORD ExitCode = 0; + if (!GetExitCodeThread(tid, &ExitCode)) { + return (int)GetLastError(); + } + if (ExitCode != STILL_ACTIVE) { + return 0; + } + } +} + +void pthread_mutex_init(pthread_mutex_t *mutex, void *attr) +{ + *mutex = CreateMutex(NULL, FALSE, NULL); +} + +void pthread_mutex_lock(pthread_mutex_t *mutex) +{ + WaitForSingleObject(*mutex, INFINITE); +} + +void pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + ReleaseMutex(*mutex); +} + +bool_t hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap) +{ + if (buffer >= end) { + return FALSE; + } + + int length = _vsnprintf(buffer, end - buffer - 1, fmt, ap); + if (length < 0) { + *buffer = 0; + return FALSE; + } + + if (buffer + length + 1 > end) { + *(end - 1) = 0; + return FALSE; + + } + + return TRUE; +} + +bool_t hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + bool_t result = hdhomerun_vsprintf(buffer, end, fmt, ap); + va_end(ap); + return result; +} + +/* + * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing. + * Attempting to restore on exit fails to restore if the program is terminated by the user. + * Solution - set the output format each printf. + */ +void console_vprintf(const char *fmt, va_list ap) +{ + UINT cp = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + vprintf(fmt, ap); + SetConsoleOutputCP(cp); +} + +void console_printf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_vprintf(fmt, ap); + va_end(ap); +} diff --git a/hdhomerun_os_windows.h b/hdhomerun_os_windows.h new file mode 100644 index 0000000..8f7585a --- /dev/null +++ b/hdhomerun_os_windows.h @@ -0,0 +1,95 @@ +/* + * hdhomerun_os_windows.h + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _WINSOCKAPI_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(DLL_IMPORT) +#define LIBTYPE __declspec( dllexport ) +#elif defined(DLL_EXPORT) +#define LIBTYPE __declspec( dllimport ) +#else +#define LIBTYPE +#endif + +typedef int bool_t; +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef void (*sig_t)(int); +typedef HANDLE pthread_t; +typedef HANDLE pthread_mutex_t; + +#if !defined(va_copy) +#define va_copy(x, y) x = y +#endif + +#define atoll _atoi64 +#define strdup _strdup +#define strcasecmp _stricmp +#define fseeko _fseeki64 +#define ftello _ftelli64 +#define THREAD_FUNC_PREFIX DWORD WINAPI + +#ifdef __cplusplus +extern "C" { +#endif + +extern LIBTYPE uint32_t random_get32(void); +extern LIBTYPE uint64_t getcurrenttime(void); +extern LIBTYPE void msleep_approx(uint64_t ms); +extern LIBTYPE void msleep_minimum(uint64_t ms); + +extern LIBTYPE int pthread_create(pthread_t *tid, void *attr, LPTHREAD_START_ROUTINE start, void *arg); +extern LIBTYPE int pthread_join(pthread_t tid, void **value_ptr); +extern LIBTYPE void pthread_mutex_init(pthread_mutex_t *mutex, void *attr); +extern LIBTYPE void pthread_mutex_lock(pthread_mutex_t *mutex); +extern LIBTYPE void pthread_mutex_unlock(pthread_mutex_t *mutex); + +extern LIBTYPE bool_t hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap); +extern LIBTYPE bool_t hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...); + +/* + * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing. + * Attempting to restore on exit fails to restore if the program is terminated by the user. + * Solution - set the output format each printf. + */ +extern LIBTYPE void console_vprintf(const char *fmt, va_list ap); +extern LIBTYPE void console_printf(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_pkt.c b/hdhomerun_pkt.c new file mode 100644 index 0000000..13ed8bd --- /dev/null +++ b/hdhomerun_pkt.c @@ -0,0 +1,239 @@ +/* + * hdhomerun_pkt.c + * + * Copyright © 2005-2006 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_pkt_t *hdhomerun_pkt_create(void) +{ + struct hdhomerun_pkt_t *pkt = (struct hdhomerun_pkt_t *)calloc(1, sizeof(struct hdhomerun_pkt_t)); + if (!pkt) { + return NULL; + } + + hdhomerun_pkt_reset(pkt); + + return pkt; +} + +void hdhomerun_pkt_destroy(struct hdhomerun_pkt_t *pkt) +{ + free(pkt); +} + +void hdhomerun_pkt_reset(struct hdhomerun_pkt_t *pkt) +{ + pkt->limit = pkt->buffer + sizeof(pkt->buffer) - 4; + pkt->start = pkt->buffer + 1024; + pkt->end = pkt->start; + pkt->pos = pkt->start; +} + +static uint32_t hdhomerun_pkt_calc_crc(uint8_t *start, uint8_t *end) +{ + uint8_t *pos = start; + uint32_t crc = 0xFFFFFFFF; + while (pos < end) { + uint8_t x = (uint8_t)(crc) ^ *pos++; + crc >>= 8; + if (x & 0x01) crc ^= 0x77073096; + if (x & 0x02) crc ^= 0xEE0E612C; + if (x & 0x04) crc ^= 0x076DC419; + if (x & 0x08) crc ^= 0x0EDB8832; + if (x & 0x10) crc ^= 0x1DB71064; + if (x & 0x20) crc ^= 0x3B6E20C8; + if (x & 0x40) crc ^= 0x76DC4190; + if (x & 0x80) crc ^= 0xEDB88320; + } + return crc ^ 0xFFFFFFFF; +} + +uint8_t hdhomerun_pkt_read_u8(struct hdhomerun_pkt_t *pkt) +{ + uint8_t v = *pkt->pos++; + return v; +} + +uint16_t hdhomerun_pkt_read_u16(struct hdhomerun_pkt_t *pkt) +{ + uint16_t v; + v = (uint16_t)*pkt->pos++ << 8; + v |= (uint16_t)*pkt->pos++ << 0; + return v; +} + +uint32_t hdhomerun_pkt_read_u32(struct hdhomerun_pkt_t *pkt) +{ + uint32_t v; + v = (uint32_t)*pkt->pos++ << 24; + v |= (uint32_t)*pkt->pos++ << 16; + v |= (uint32_t)*pkt->pos++ << 8; + v |= (uint32_t)*pkt->pos++ << 0; + return v; +} + +size_t hdhomerun_pkt_read_var_length(struct hdhomerun_pkt_t *pkt) +{ + size_t length; + + if (pkt->pos + 1 > pkt->end) { + return (size_t)-1; + } + + length = (size_t)*pkt->pos++; + if (length & 0x0080) { + if (pkt->pos + 1 > pkt->end) { + return (size_t)-1; + } + + length &= 0x007F; + length |= (size_t)*pkt->pos++ << 7; + } + + return length; +} + +uint8_t *hdhomerun_pkt_read_tlv(struct hdhomerun_pkt_t *pkt, uint8_t *ptag, size_t *plength) +{ + if (pkt->pos + 2 > pkt->end) { + return NULL; + } + + *ptag = hdhomerun_pkt_read_u8(pkt); + *plength = hdhomerun_pkt_read_var_length(pkt); + + if (pkt->pos + *plength > pkt->end) { + return NULL; + } + + return pkt->pos + *plength; +} + +void hdhomerun_pkt_read_mem(struct hdhomerun_pkt_t *pkt, void *mem, size_t length) +{ + memcpy(mem, pkt->pos, length); + pkt->pos += length; +} + +void hdhomerun_pkt_write_u8(struct hdhomerun_pkt_t *pkt, uint8_t v) +{ + *pkt->pos++ = v; + + if (pkt->pos > pkt->end) { + pkt->end = pkt->pos; + } +} + +void hdhomerun_pkt_write_u16(struct hdhomerun_pkt_t *pkt, uint16_t v) +{ + *pkt->pos++ = (uint8_t)(v >> 8); + *pkt->pos++ = (uint8_t)(v >> 0); + + if (pkt->pos > pkt->end) { + pkt->end = pkt->pos; + } +} + +void hdhomerun_pkt_write_u32(struct hdhomerun_pkt_t *pkt, uint32_t v) +{ + *pkt->pos++ = (uint8_t)(v >> 24); + *pkt->pos++ = (uint8_t)(v >> 16); + *pkt->pos++ = (uint8_t)(v >> 8); + *pkt->pos++ = (uint8_t)(v >> 0); + + if (pkt->pos > pkt->end) { + pkt->end = pkt->pos; + } +} + +void hdhomerun_pkt_write_var_length(struct hdhomerun_pkt_t *pkt, size_t v) +{ + if (v <= 127) { + *pkt->pos++ = (uint8_t)v; + } else { + *pkt->pos++ = (uint8_t)(v | 0x80); + *pkt->pos++ = (uint8_t)(v >> 7); + } + + if (pkt->pos > pkt->end) { + pkt->end = pkt->pos; + } +} + +void hdhomerun_pkt_write_mem(struct hdhomerun_pkt_t *pkt, const void *mem, size_t length) +{ + memcpy(pkt->pos, mem, length); + pkt->pos += length; + + if (pkt->pos > pkt->end) { + pkt->end = pkt->pos; + } +} + +int hdhomerun_pkt_open_frame(struct hdhomerun_pkt_t *pkt, uint16_t *ptype) +{ + pkt->pos = pkt->start; + + if (pkt->pos + 4 > pkt->end) { + return 0; + } + + *ptype = hdhomerun_pkt_read_u16(pkt); + size_t length = hdhomerun_pkt_read_u16(pkt); + pkt->pos += length; + + if (pkt->pos + 4 > pkt->end) { + pkt->pos = pkt->start; + return 0; + } + + uint32_t calc_crc = hdhomerun_pkt_calc_crc(pkt->start, pkt->pos); + + uint32_t packet_crc; + packet_crc = (uint32_t)*pkt->pos++ << 0; + packet_crc |= (uint32_t)*pkt->pos++ << 8; + packet_crc |= (uint32_t)*pkt->pos++ << 16; + packet_crc |= (uint32_t)*pkt->pos++ << 24; + if (calc_crc != packet_crc) { + return -1; + } + + pkt->start += 4; + pkt->end = pkt->start + length; + pkt->pos = pkt->start; + return 1; +} + +void hdhomerun_pkt_seal_frame(struct hdhomerun_pkt_t *pkt, uint16_t frame_type) +{ + size_t length = pkt->end - pkt->start; + + pkt->start -= 4; + pkt->pos = pkt->start; + hdhomerun_pkt_write_u16(pkt, frame_type); + hdhomerun_pkt_write_u16(pkt, (uint16_t)length); + + uint32_t crc = hdhomerun_pkt_calc_crc(pkt->start, pkt->end); + *pkt->end++ = (uint8_t)(crc >> 0); + *pkt->end++ = (uint8_t)(crc >> 8); + *pkt->end++ = (uint8_t)(crc >> 16); + *pkt->end++ = (uint8_t)(crc >> 24); + + pkt->pos = pkt->start; +} diff --git a/hdhomerun_pkt.h b/hdhomerun_pkt.h new file mode 100644 index 0000000..008a96a --- /dev/null +++ b/hdhomerun_pkt.h @@ -0,0 +1,170 @@ +/* + * hdhomerun_pkt.h + * + * Copyright © 2005-2006 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The discover protocol (UDP port 65001) and control protocol (TCP port 65001) + * both use the same packet based format: + * uint16_t Packet type + * uint16_t Payload length (bytes) + * uint8_t[] Payload data (0-n bytes). + * uint32_t CRC (Ethernet style 32-bit CRC) + * + * All variables are big-endian except for the crc which is little-endian. + * + * Valid values for the packet type are listed below as defines prefixed + * with "HDHOMERUN_TYPE_" + * + * Discovery: + * + * The payload for a discovery request or reply is a simple sequence of + * tag-length-value data: + * uint8_t Tag + * varlen Length + * uint8_t[] Value (0-n bytes) + * + * The length field can be one or two bytes long. + * For a length <= 127 bytes the length is expressed as a single byte. The + * most-significant-bit is clear indicating a single-byte length. + * For a length >= 128 bytes the length is expressed as a sequence of two bytes as follows: + * The first byte is contains the least-significant 7-bits of the length. The + * most-significant bit is then set (add 0x80) to indicate that it is a two byte length. + * The second byte contains the length shifted down 7 bits. + * + * A discovery request packet has a packet type of HDHOMERUN_TYPE_DISCOVER_REQ and should + * contain two tags: HDHOMERUN_TAG_DEVICE_TYPE and HDHOMERUN_TAG_DEVICE_ID. + * The HDHOMERUN_TAG_DEVICE_TYPE value should be set to HDHOMERUN_DEVICE_TYPE_TUNER. + * The HDHOMERUN_TAG_DEVICE_ID value should be set to HDHOMERUN_DEVICE_ID_WILDCARD to match + * all devices, or to the 32-bit device id number to match a single device. + * + * The discovery response packet has a packet type of HDHOMERUN_TYPE_DISCOVER_RPY and has the + * same format as the discovery request packet with the two tags: HDHOMERUN_TAG_DEVICE_TYPE and + * HDHOMERUN_TAG_DEVICE_ID. In the future additional tags may also be returned - unknown tags + * should be skipped and not treated as an error. + * + * Control get/set: + * + * The payload for a control get/set request is a simple sequence of tag-length-value data + * following the same format as for discover packets. + * + * A get request packet has a packet type of HDHOMERUN_TYPE_GETSET_REQ and should contain + * the tag: HDHOMERUN_TAG_GETSET_NAME. The HDHOMERUN_TAG_GETSET_NAME value should be a sequence + * of bytes forming a null-terminated string, including the NULL. The TLV length must include + * the NULL character so the length field should be set to strlen(str) + 1. + * + * A set request packet has a packet type of HDHOMERUN_TYPE_GETSET_REQ (same as a get request) + * and should contain two tags: HDHOMERUN_TAG_GETSET_NAME and HDHOMERUN_TAG_GETSET_VALUE. + * The HDHOMERUN_TAG_GETSET_NAME value should be a sequence of bytes forming a null-terminated + * string, including the NULL. + * The HDHOMERUN_TAG_GETSET_VALUE value should be a sequence of bytes forming a null-terminated + * string, including the NULL. + * + * The get and set reply packets have the packet type HDHOMERUN_TYPE_GETSET_RPY and have the same + * format as the set request packet with the two tags: HDHOMERUN_TAG_GETSET_NAME and + * HDHOMERUN_TAG_GETSET_VALUE. A set request is also implicit get request so the updated value is + * returned. + * + * If the device encounters an error handling the get or set request then the get/set reply packet + * will contain the tag HDHOMERUN_TAG_ERROR_MESSAGE. The format of the value is a sequence of + * bytes forming a null-terminated string, including the NULL. + * + * In the future additional tags may also be returned - unknown tags should be skipped and not + * treated as an error. + * + * Security note: The application should not rely on the NULL character being present. The + * application should write a NULL character based on the TLV length to protect the application + * from a potential attack. + * + * Firmware Upgrade: + * + * A firmware upgrade packet has a packet type of HDHOMERUN_TYPE_UPGRADE_REQ and has a fixed format: + * uint32_t Position in bytes from start of file. + * uint8_t[256] Firmware data (256 bytes) + * + * The data must be uploaded in 256 byte chunks and must be uploaded in order. + * The position number is in bytes so will increment by 256 each time. + * + * When all data is uploaded it should be signaled complete by sending another packet of type + * HDHOMERUN_TYPE_UPGRADE_REQ with payload of a single uint32_t with the value 0xFFFFFFFF. + */ + +#define HDHOMERUN_DISCOVER_UDP_PORT 65001 +#define HDHOMERUN_CONTROL_TCP_PORT 65001 + +#define HDHOMERUN_MAX_PACKET_SIZE 1460 +#define HDHOMERUN_MAX_PAYLOAD_SIZE 1452 + +#define HDHOMERUN_TYPE_DISCOVER_REQ 0x0002 +#define HDHOMERUN_TYPE_DISCOVER_RPY 0x0003 +#define HDHOMERUN_TYPE_GETSET_REQ 0x0004 +#define HDHOMERUN_TYPE_GETSET_RPY 0x0005 +#define HDHOMERUN_TYPE_UPGRADE_REQ 0x0006 +#define HDHOMERUN_TYPE_UPGRADE_RPY 0x0007 + +#define HDHOMERUN_TAG_DEVICE_TYPE 0x01 +#define HDHOMERUN_TAG_DEVICE_ID 0x02 +#define HDHOMERUN_TAG_GETSET_NAME 0x03 +#define HDHOMERUN_TAG_GETSET_VALUE 0x04 +#define HDHOMERUN_TAG_GETSET_LOCKKEY 0x15 +#define HDHOMERUN_TAG_ERROR_MESSAGE 0x05 +#define HDHOMERUN_TAG_TUNER_COUNT 0x10 +#define HDHOMERUN_TAG_DEVICE_AUTH_BIN 0x29 +#define HDHOMERUN_TAG_BASE_URL 0x2A +#define HDHOMERUN_TAG_DEVICE_AUTH_STR 0x2B + +#define HDHOMERUN_DEVICE_TYPE_WILDCARD 0xFFFFFFFF +#define HDHOMERUN_DEVICE_TYPE_TUNER 0x00000001 +#define HDHOMERUN_DEVICE_ID_WILDCARD 0xFFFFFFFF + +#define HDHOMERUN_MIN_PEEK_LENGTH 4 + +struct hdhomerun_pkt_t { + uint8_t *pos; + uint8_t *start; + uint8_t *end; + uint8_t *limit; + uint8_t buffer[3074]; +}; + +extern LIBTYPE struct hdhomerun_pkt_t *hdhomerun_pkt_create(void); +extern LIBTYPE void hdhomerun_pkt_destroy(struct hdhomerun_pkt_t *pkt); +extern LIBTYPE void hdhomerun_pkt_reset(struct hdhomerun_pkt_t *pkt); + +extern LIBTYPE uint8_t hdhomerun_pkt_read_u8(struct hdhomerun_pkt_t *pkt); +extern LIBTYPE uint16_t hdhomerun_pkt_read_u16(struct hdhomerun_pkt_t *pkt); +extern LIBTYPE uint32_t hdhomerun_pkt_read_u32(struct hdhomerun_pkt_t *pkt); +extern LIBTYPE size_t hdhomerun_pkt_read_var_length(struct hdhomerun_pkt_t *pkt); +extern LIBTYPE uint8_t *hdhomerun_pkt_read_tlv(struct hdhomerun_pkt_t *pkt, uint8_t *ptag, size_t *plength); +extern LIBTYPE void hdhomerun_pkt_read_mem(struct hdhomerun_pkt_t *pkt, void *mem, size_t length); + +extern LIBTYPE void hdhomerun_pkt_write_u8(struct hdhomerun_pkt_t *pkt, uint8_t v); +extern LIBTYPE void hdhomerun_pkt_write_u16(struct hdhomerun_pkt_t *pkt, uint16_t v); +extern LIBTYPE void hdhomerun_pkt_write_u32(struct hdhomerun_pkt_t *pkt, uint32_t v); +extern LIBTYPE void hdhomerun_pkt_write_var_length(struct hdhomerun_pkt_t *pkt, size_t v); +extern LIBTYPE void hdhomerun_pkt_write_mem(struct hdhomerun_pkt_t *pkt, const void *mem, size_t length); + +extern LIBTYPE bool_t hdhomerun_pkt_open_frame(struct hdhomerun_pkt_t *pkt, uint16_t *ptype); +extern LIBTYPE void hdhomerun_pkt_seal_frame(struct hdhomerun_pkt_t *pkt, uint16_t frame_type); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_sock.h b/hdhomerun_sock.h new file mode 100644 index 0000000..d14f6e4 --- /dev/null +++ b/hdhomerun_sock.h @@ -0,0 +1,59 @@ +/* + * hdhomerun_sock.h + * + * Copyright © 2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_local_ip_info_t { + uint32_t ip_addr; + uint32_t subnet_mask; +}; + +extern LIBTYPE int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count); + +#define HDHOMERUN_SOCK_INVALID -1 + +typedef int hdhomerun_sock_t; + +extern LIBTYPE hdhomerun_sock_t hdhomerun_sock_create_udp(void); +extern LIBTYPE hdhomerun_sock_t hdhomerun_sock_create_tcp(void); +extern LIBTYPE void hdhomerun_sock_destroy(hdhomerun_sock_t sock); + +extern LIBTYPE int hdhomerun_sock_getlasterror(void); +extern LIBTYPE uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock); +extern LIBTYPE uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock); +extern LIBTYPE uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock); +extern LIBTYPE uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name); + +extern LIBTYPE bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip); +extern LIBTYPE bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip); + +extern LIBTYPE bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse); +extern LIBTYPE bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout); + +extern LIBTYPE bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout); +extern LIBTYPE bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout); + +extern LIBTYPE bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout); +extern LIBTYPE bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/hdhomerun_sock_posix.c b/hdhomerun_sock_posix.c new file mode 100644 index 0000000..8846c5d --- /dev/null +++ b/hdhomerun_sock_posix.c @@ -0,0 +1,451 @@ +/* + * hdhomerun_sock_posix.c + * + * Copyright © 2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Implementation notes: + * + * API specifies timeout for each operation (or zero for non-blocking). + * + * It is not possible to rely on the OS socket timeout as this will fail to + * detect the command-response situation where data is sent successfully and + * the other end chooses not to send a response (other than the TCP ack). + * + * The select() cannot be used with high socket numbers (typically max 1024) + * so the code works as follows: + * - Use non-blocking sockets to allow operation without select. + * - Use select where safe (low socket numbers). + * - Poll with short sleep when select cannot be used safely. + */ + +#include "hdhomerun.h" + +#include +#include + +#ifndef SIOCGIFCONF +#include +#endif + +#ifndef _SIZEOF_ADDR_IFREQ +#define _SIZEOF_ADDR_IFREQ(x) sizeof(x) +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == HDHOMERUN_SOCK_INVALID) { + return -1; + } + + struct ifconf ifc; + size_t ifreq_buffer_size = 1024; + + while (1) { + ifc.ifc_len = ifreq_buffer_size; + ifc.ifc_buf = (char *)malloc(ifreq_buffer_size); + if (!ifc.ifc_buf) { + close(sock); + return -1; + } + + memset(ifc.ifc_buf, 0, ifreq_buffer_size); + + if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) { + free(ifc.ifc_buf); + close(sock); + return -1; + } + + if (ifc.ifc_len < ifreq_buffer_size) { + break; + } + + free(ifc.ifc_buf); + ifreq_buffer_size += 1024; + } + + char *ptr = ifc.ifc_buf; + char *end = ifc.ifc_buf + ifc.ifc_len; + + int count = 0; + while (ptr < end) { + struct ifreq *ifr = (struct ifreq *)ptr; + ptr += _SIZEOF_ADDR_IFREQ(*ifr); + + /* Flags. */ + if (ioctl(sock, SIOCGIFFLAGS, ifr) != 0) { + continue; + } + + if ((ifr->ifr_flags & IFF_UP) == 0) { + continue; + } + if ((ifr->ifr_flags & IFF_RUNNING) == 0) { + continue; + } + + /* Local IP address. */ + if (ioctl(sock, SIOCGIFADDR, ifr) != 0) { + continue; + } + + struct sockaddr_in *ip_addr_in = (struct sockaddr_in *)&(ifr->ifr_addr); + uint32_t ip_addr = ntohl(ip_addr_in->sin_addr.s_addr); + if (ip_addr == 0) { + continue; + } + + /* Subnet mask. */ + if (ioctl(sock, SIOCGIFNETMASK, ifr) != 0) { + continue; + } + + struct sockaddr_in *subnet_mask_in = (struct sockaddr_in *)&(ifr->ifr_addr); + uint32_t subnet_mask = ntohl(subnet_mask_in->sin_addr.s_addr); + + /* Report. */ + if (count < max_count) { + struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[count]; + ip_info->ip_addr = ip_addr; + ip_info->subnet_mask = subnet_mask; + } + + count++; + } + + free(ifc.ifc_buf); + close(sock); + return count; +} + +hdhomerun_sock_t hdhomerun_sock_create_udp(void) +{ + /* Create socket. */ + hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + return HDHOMERUN_SOCK_INVALID; + } + + /* Set non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + close(sock); + return HDHOMERUN_SOCK_INVALID; + } + + /* Allow broadcast. */ + int sock_opt = 1; + setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); + + /* Success. */ + return sock; +} + +hdhomerun_sock_t hdhomerun_sock_create_tcp(void) +{ + /* Create socket. */ + hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + return HDHOMERUN_SOCK_INVALID; + } + + /* Set non-blocking */ + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + close(sock); + return HDHOMERUN_SOCK_INVALID; + } + + /* Success. */ + return sock; +} + +void hdhomerun_sock_destroy(hdhomerun_sock_t sock) +{ + close(sock); +} + +int hdhomerun_sock_getlasterror(void) +{ + return errno; +} + +uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + socklen_t sockaddr_size = sizeof(sock_addr); + + if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohl(sock_addr.sin_addr.s_addr); +} + +uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + socklen_t sockaddr_size = sizeof(sock_addr); + + if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohs(sock_addr.sin_port); +} + +uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + socklen_t sockaddr_size = sizeof(sock_addr); + + if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohl(sock_addr.sin_addr.s_addr); +} + +uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *sock_info; + if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { + return 0; + } + + struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; + uint32_t addr = ntohl(sock_addr->sin_addr.s_addr); + + freeaddrinfo(sock_info); + return addr; +} + +bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = htonl(multicast_ip); + imr.imr_interface.s_addr = htonl(local_ip); + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = htonl(multicast_ip); + imr.imr_interface.s_addr = htonl(local_ip); + + if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse) +{ + int sock_opt = allow_reuse; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); + + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(local_addr); + sock_addr.sin_port = htons(local_port); + + if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + return FALSE; + } + + return TRUE; +} + +static bool_t hdhomerun_sock_wait_for_event(hdhomerun_sock_t sock, short event_type, uint64_t stop_time) +{ + uint64_t current_time = getcurrenttime(); + if (current_time >= stop_time) { + return FALSE; + } + + struct pollfd poll_event; + poll_event.fd = sock; + poll_event.events = event_type; + poll_event.revents = 0; + + uint64_t timeout = stop_time - current_time; + + if (poll(&poll_event, 1, (int)timeout) <= 0) { + return FALSE; + } + + if ((poll_event.revents & event_type) == 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) +{ + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(remote_addr); + sock_addr.sin_port = htons(remote_port); + + if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { + return FALSE; + } + } + + uint64_t stop_time = getcurrenttime() + timeout; + return hdhomerun_sock_wait_for_event(sock, POLLOUT, stop_time); +} + +bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + const uint8_t *ptr = (const uint8_t *)data; + + while (1) { + int ret = send(sock, ptr, length, MSG_NOSIGNAL); + if (ret <= 0) { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, POLLOUT, stop_time)) { + return FALSE; + } + continue; + } + + if (ret < (int)length) { + ptr += ret; + length -= ret; + continue; + } + + return TRUE; + } +} + +bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + const uint8_t *ptr = (const uint8_t *)data; + + while (1) { + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(remote_addr); + sock_addr.sin_port = htons(remote_port); + + int ret = sendto(sock, ptr, length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + if (ret <= 0) { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, POLLOUT, stop_time)) { + return FALSE; + } + continue; + } + + if (ret < (int)length) { + ptr += ret; + length -= ret; + continue; + } + + return TRUE; + } +} + +bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + + while (1) { + int ret = recv(sock, data, *length, 0); + if (ret < 0) { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, POLLIN, stop_time)) { + return FALSE; + } + continue; + } + + if (ret == 0) { + return FALSE; + } + + *length = ret; + return TRUE; + } +} + +bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + + while (1) { + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + socklen_t sockaddr_size = sizeof(sock_addr); + + int ret = recvfrom(sock, data, *length, 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + if (ret < 0) { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, POLLIN, stop_time)) { + return FALSE; + } + continue; + } + + if (ret == 0) { + return FALSE; + } + + *remote_addr = ntohl(sock_addr.sin_addr.s_addr); + *remote_port = ntohs(sock_addr.sin_port); + *length = ret; + return TRUE; + } +} diff --git a/hdhomerun_sock_windows.c b/hdhomerun_sock_windows.c new file mode 100644 index 0000000..3a80a5b --- /dev/null +++ b/hdhomerun_sock_windows.c @@ -0,0 +1,450 @@ +/* + * hdhomerun_sock_windows.c + * + * Copyright © 2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Implementation notes: + * + * API specifies timeout for each operation (or zero for non-blocking). + * + * It is not possible to rely on the OS socket timeout as this will fail to + * detect the command-response situation where data is sent successfully and + * the other end chooses not to send a response (other than the TCP ack). + * + * Windows supports select() however native WSA events are used to: + * - avoid problems with socket numbers above 1024. + * - wait without allowing other events handlers to run (important for use + * with win7 WMC). + */ + +#include "hdhomerun.h" +#include +#include + +int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) +{ + PIP_ADAPTER_INFO AdapterInfo; + ULONG AdapterInfoLength = sizeof(IP_ADAPTER_INFO) * 16; + + while (1) { + AdapterInfo = (IP_ADAPTER_INFO *)malloc(AdapterInfoLength); + if (!AdapterInfo) { + return -1; + } + + ULONG LengthNeeded = AdapterInfoLength; + DWORD Ret = GetAdaptersInfo(AdapterInfo, &LengthNeeded); + if (Ret == NO_ERROR) { + break; + } + + free(AdapterInfo); + + if (Ret != ERROR_BUFFER_OVERFLOW) { + return -1; + } + if (AdapterInfoLength >= LengthNeeded) { + return -1; + } + + AdapterInfoLength = LengthNeeded; + } + + int count = 0; + PIP_ADAPTER_INFO Adapter = AdapterInfo; + while (Adapter) { + IP_ADDR_STRING *IPAddr = &Adapter->IpAddressList; + while (IPAddr) { + uint32_t ip_addr = ntohl(inet_addr(IPAddr->IpAddress.String)); + uint32_t subnet_mask = ntohl(inet_addr(IPAddr->IpMask.String)); + + if (ip_addr == 0) { + IPAddr = IPAddr->Next; + continue; + } + + if (count < max_count) { + struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[count]; + ip_info->ip_addr = ip_addr; + ip_info->subnet_mask = subnet_mask; + } + + count++; + IPAddr = IPAddr->Next; + } + + if (count >= max_count) { + break; + } + + Adapter = Adapter->Next; + } + + free(AdapterInfo); + return count; +} + +hdhomerun_sock_t hdhomerun_sock_create_udp(void) +{ + /* Create socket. */ + hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + return HDHOMERUN_SOCK_INVALID; + } + + /* Set non-blocking */ + unsigned long mode = 1; + if (ioctlsocket(sock, FIONBIO, &mode) != 0) { + closesocket(sock); + return HDHOMERUN_SOCK_INVALID; + } + + /* Allow broadcast. */ + int sock_opt = 1; + setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); + + /* Success. */ + return sock; +} + +hdhomerun_sock_t hdhomerun_sock_create_tcp(void) +{ + /* Create socket. */ + hdhomerun_sock_t sock = (hdhomerun_sock_t)socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + return HDHOMERUN_SOCK_INVALID; + } + + /* Set non-blocking */ + unsigned long mode = 1; + if (ioctlsocket(sock, FIONBIO, &mode) != 0) { + closesocket(sock); + return HDHOMERUN_SOCK_INVALID; + } + + /* Success. */ + return sock; +} + +void hdhomerun_sock_destroy(hdhomerun_sock_t sock) +{ + closesocket(sock); +} + +int hdhomerun_sock_getlasterror(void) +{ + return WSAGetLastError(); +} + +uint32_t hdhomerun_sock_getsockname_addr(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + int sockaddr_size = sizeof(sock_addr); + + if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohl(sock_addr.sin_addr.s_addr); +} + +uint16_t hdhomerun_sock_getsockname_port(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + int sockaddr_size = sizeof(sock_addr); + + if (getsockname(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohs(sock_addr.sin_port); +} + +uint32_t hdhomerun_sock_getpeername_addr(hdhomerun_sock_t sock) +{ + struct sockaddr_in sock_addr; + int sockaddr_size = sizeof(sock_addr); + + if (getpeername(sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { + return 0; + } + + return ntohl(sock_addr.sin_addr.s_addr); +} + +uint32_t hdhomerun_sock_getaddrinfo_addr(hdhomerun_sock_t sock, const char *name) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo *sock_info; + if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { + return 0; + } + + struct sockaddr_in *sock_addr = (struct sockaddr_in *)sock_info->ai_addr; + uint32_t addr = ntohl(sock_addr->sin_addr.s_addr); + + freeaddrinfo(sock_info); + return addr; +} + +bool_t hdhomerun_sock_join_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = htonl(multicast_ip); + imr.imr_interface.s_addr = htonl(local_ip); + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_leave_multicast_group(hdhomerun_sock_t sock, uint32_t multicast_ip, uint32_t local_ip) +{ + struct ip_mreq imr; + memset(&imr, 0, sizeof(imr)); + imr.imr_multiaddr.s_addr = htonl(multicast_ip); + imr.imr_interface.s_addr = htonl(local_ip); + + if (setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_bind(hdhomerun_sock_t sock, uint32_t local_addr, uint16_t local_port, bool_t allow_reuse) +{ + int sock_opt = allow_reuse; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); + + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(local_addr); + sock_addr.sin_port = htons(local_port); + + if (bind(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_connect(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) +{ + /* Connect (non-blocking). */ + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(remote_addr); + sock_addr.sin_port = htons(remote_port); + + if (connect(sock, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FALSE; + } + } + + /* Wait for connect to complete (both success and failure will signal). */ + WSAEVENT wsa_event = WSACreateEvent(); + if (wsa_event == WSA_INVALID_EVENT) { + return FALSE; + } + + if (WSAEventSelect(sock, wsa_event, FD_WRITE | FD_CLOSE) == SOCKET_ERROR) { + WSACloseEvent(wsa_event); + return FALSE; + } + + DWORD ret = WaitForSingleObjectEx(wsa_event, (DWORD)timeout, FALSE); + WSACloseEvent(wsa_event); + if (ret != WAIT_OBJECT_0) { + return FALSE; + } + + /* Detect success/failure. */ + wsa_event = WSACreateEvent(); + if (wsa_event == WSA_INVALID_EVENT) { + return FALSE; + } + + if (WSAEventSelect(sock, wsa_event, FD_CLOSE) == SOCKET_ERROR) { + WSACloseEvent(wsa_event); + return FALSE; + } + + ret = WaitForSingleObjectEx(wsa_event, 0, FALSE); + WSACloseEvent(wsa_event); + if (ret == WAIT_OBJECT_0) { + return FALSE; + } + + return TRUE; +} + +static bool_t hdhomerun_sock_wait_for_event(hdhomerun_sock_t sock, long event_type, uint64_t stop_time) +{ + uint64_t current_time = getcurrenttime(); + if (current_time >= stop_time) { + return FALSE; + } + + WSAEVENT wsa_event = WSACreateEvent(); + if (wsa_event == WSA_INVALID_EVENT) { + return FALSE; + } + + if (WSAEventSelect(sock, wsa_event, event_type) == SOCKET_ERROR) { + WSACloseEvent(wsa_event); + return FALSE; + } + + DWORD ret = WaitForSingleObjectEx(wsa_event, (DWORD)(stop_time - current_time), FALSE); + WSACloseEvent(wsa_event); + + if (ret != WAIT_OBJECT_0) { + return FALSE; + } + + return TRUE; +} + +bool_t hdhomerun_sock_send(hdhomerun_sock_t sock, const void *data, size_t length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + const uint8_t *ptr = (uint8_t *)data; + + while (1) { + int ret = send(sock, (char *)ptr, (int)length, 0); + if (ret <= 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) { + return FALSE; + } + continue; + } + + if (ret < (int)length) { + ptr += ret; + length -= ret; + continue; + } + + return TRUE; + } +} + +bool_t hdhomerun_sock_sendto(hdhomerun_sock_t sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + const uint8_t *ptr = (uint8_t *)data; + + while (1) { + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = htonl(remote_addr); + sock_addr.sin_port = htons(remote_port); + + int ret = sendto(sock, (char *)ptr, (int)length, 0, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + if (ret <= 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, FD_WRITE | FD_CLOSE, stop_time)) { + return FALSE; + } + continue; + } + + if (ret < (int)length) { + ptr += ret; + length -= ret; + continue; + } + + return TRUE; + } +} + +bool_t hdhomerun_sock_recv(hdhomerun_sock_t sock, void *data, size_t *length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + + while (1) { + int ret = recv(sock, (char *)data, (int)(*length), 0); + if (ret < 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) { + return FALSE; + } + continue; + } + + if (ret == 0) { + return FALSE; + } + + *length = ret; + return TRUE; + } +} + +bool_t hdhomerun_sock_recvfrom(hdhomerun_sock_t sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) +{ + uint64_t stop_time = getcurrenttime() + timeout; + + while (1) { + struct sockaddr_in sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + int sockaddr_size = sizeof(sock_addr); + + int ret = recvfrom(sock, (char *)data, (int)(*length), 0, (struct sockaddr *)&sock_addr, &sockaddr_size); + if (ret < 0) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FALSE; + } + if (!hdhomerun_sock_wait_for_event(sock, FD_READ | FD_CLOSE, stop_time)) { + return FALSE; + } + continue; + } + + if (ret == 0) { + return FALSE; + } + + *remote_addr = ntohl(sock_addr.sin_addr.s_addr); + *remote_port = ntohs(sock_addr.sin_port); + *length = ret; + return TRUE; + } +} diff --git a/hdhomerun_types.h b/hdhomerun_types.h new file mode 100644 index 0000000..e2a888f --- /dev/null +++ b/hdhomerun_types.h @@ -0,0 +1,80 @@ +/* + * hdhomerun_types.h + * + * Copyright © 2008-2009 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define HDHOMERUN_STATUS_COLOR_NEUTRAL 0xFFFFFFFF +#define HDHOMERUN_STATUS_COLOR_RED 0xFFFF0000 +#define HDHOMERUN_STATUS_COLOR_YELLOW 0xFFFFFF00 +#define HDHOMERUN_STATUS_COLOR_GREEN 0xFF00C000 + +struct hdhomerun_device_t; +struct hdhomerun_device_allocation_t; + +struct hdhomerun_tuner_status_t { + char channel[32]; + char lock_str[32]; + bool_t signal_present; + bool_t lock_supported; + bool_t lock_unsupported; + unsigned int signal_strength; + unsigned int signal_to_noise_quality; + unsigned int symbol_error_quality; + uint32_t raw_bits_per_second; + uint32_t packets_per_second; +}; + +struct hdhomerun_tuner_vstatus_t { + char vchannel[32]; + char name[32]; + char auth[32]; + char cci[32]; + char cgms[32]; + bool_t not_subscribed; + bool_t not_available; + bool_t copy_protected; +}; + +struct hdhomerun_channelscan_program_t { + char program_str[64]; + uint16_t program_number; + uint16_t virtual_major; + uint16_t virtual_minor; + uint16_t type; + char name[32]; +}; + +#define HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT 64 + +struct hdhomerun_channelscan_result_t { + char channel_str[64]; + uint32_t channelmap; + uint32_t frequency; + struct hdhomerun_tuner_status_t status; + int program_count; + struct hdhomerun_channelscan_program_t programs[HDHOMERUN_CHANNELSCAN_MAX_PROGRAM_COUNT]; + bool_t transport_stream_id_detected; + bool_t original_network_id_detected; + uint16_t transport_stream_id; + uint16_t original_network_id; +}; + +struct hdhomerun_plotsample_t { + int16_t real; + int16_t imag; +}; diff --git a/hdhomerun_video.c b/hdhomerun_video.c new file mode 100644 index 0000000..aaca66d --- /dev/null +++ b/hdhomerun_video.c @@ -0,0 +1,383 @@ +/* + * hdhomerun_video.c + * + * Copyright © 2006-2010 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "hdhomerun.h" + +struct hdhomerun_video_sock_t { + pthread_mutex_t lock; + struct hdhomerun_debug_t *dbg; + hdhomerun_sock_t sock; + + volatile size_t head; + volatile size_t tail; + uint8_t *buffer; + size_t buffer_size; + size_t advance; + + pthread_t thread; + volatile bool_t terminate; + + volatile uint32_t packet_count; + volatile uint32_t transport_error_count; + volatile uint32_t network_error_count; + volatile uint32_t sequence_error_count; + volatile uint32_t overflow_error_count; + + volatile uint32_t rtp_sequence; + volatile uint8_t sequence[0x2000]; +}; + +static THREAD_FUNC_PREFIX hdhomerun_video_thread_execute(void *arg); + +struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool_t allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg) +{ + /* Create object. */ + struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)calloc(1, sizeof(struct hdhomerun_video_sock_t)); + if (!vs) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate video object\n"); + return NULL; + } + + vs->dbg = dbg; + vs->sock = HDHOMERUN_SOCK_INVALID; + pthread_mutex_init(&vs->lock, NULL); + + /* Reset sequence tracking. */ + hdhomerun_video_flush(vs); + + /* Buffer size. */ + vs->buffer_size = (buffer_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE; + if (vs->buffer_size == 0) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: invalid buffer size (%lu bytes)\n", (unsigned long)buffer_size); + goto error; + } + vs->buffer_size += VIDEO_DATA_PACKET_SIZE; + + /* Create buffer. */ + vs->buffer = (uint8_t *)malloc(vs->buffer_size); + if (!vs->buffer) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate buffer (%lu bytes)\n", (unsigned long)vs->buffer_size); + goto error; + } + + /* Create socket. */ + vs->sock = hdhomerun_sock_create_udp(); + if (vs->sock == HDHOMERUN_SOCK_INVALID) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate socket\n"); + goto error; + } + + /* Expand socket buffer size. */ + int rx_size = 1024 * 1024; + setsockopt(vs->sock, SOL_SOCKET, SO_RCVBUF, (char *)&rx_size, sizeof(rx_size)); + + /* Bind socket. */ + if (!hdhomerun_sock_bind(vs->sock, INADDR_ANY, listen_port, allow_port_reuse)) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to bind socket (port %u)\n", listen_port); + goto error; + } + + /* Start thread. */ + if (pthread_create(&vs->thread, NULL, &hdhomerun_video_thread_execute, vs) != 0) { + hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to start thread\n"); + goto error; + } + + /* Success. */ + return vs; + +error: + if (vs->sock != HDHOMERUN_SOCK_INVALID) { + hdhomerun_sock_destroy(vs->sock); + } + if (vs->buffer) { + free(vs->buffer); + } + free(vs); + return NULL; +} + +void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs) +{ + vs->terminate = TRUE; + pthread_join(vs->thread, NULL); + + hdhomerun_sock_destroy(vs->sock); + free(vs->buffer); + + free(vs); +} + +hdhomerun_sock_t hdhomerun_video_get_sock(struct hdhomerun_video_sock_t *vs) +{ + return vs->sock; +} + +uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs) +{ + uint16_t port = hdhomerun_sock_getsockname_port(vs->sock); + if (port == 0) { + hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_get_local_port: getsockname failed (%d)\n", hdhomerun_sock_getlasterror()); + return 0; + } + + return port; +} + +int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip) +{ + if (!hdhomerun_sock_join_multicast_group(vs->sock, multicast_ip, local_ip)) { + hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_join_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror()); + return -1; + } + + return 1; +} + +void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip) +{ + if (!hdhomerun_sock_leave_multicast_group(vs->sock, multicast_ip, local_ip)) { + hdhomerun_debug_printf(vs->dbg, "hdhomerun_video_leave_multicast_group: setsockopt failed (%d)\n", hdhomerun_sock_getlasterror()); + } +} + +static void hdhomerun_video_stats_ts_pkt(struct hdhomerun_video_sock_t *vs, uint8_t *ptr) +{ + uint16_t packet_identifier = ((uint16_t)(ptr[1] & 0x1F) << 8) | (uint16_t)ptr[2]; + if (packet_identifier == 0x1FFF) { + return; + } + + bool_t transport_error = ptr[1] >> 7; + if (transport_error) { + vs->transport_error_count++; + vs->sequence[packet_identifier] = 0xFF; + return; + } + + uint8_t sequence = ptr[3] & 0x0F; + + uint8_t previous_sequence = vs->sequence[packet_identifier]; + vs->sequence[packet_identifier] = sequence; + + if (previous_sequence == 0xFF) { + return; + } + if (sequence == ((previous_sequence + 1) & 0x0F)) { + return; + } + if (sequence == previous_sequence) { + return; + } + + vs->sequence_error_count++; +} + +static void hdhomerun_video_parse_rtp(struct hdhomerun_video_sock_t *vs, struct hdhomerun_pkt_t *pkt) +{ + pkt->pos += 2; + uint32_t rtp_sequence = hdhomerun_pkt_read_u16(pkt); + pkt->pos += 8; + + uint32_t previous_rtp_sequence = vs->rtp_sequence; + vs->rtp_sequence = rtp_sequence; + + /* Initial case - first packet received. */ + if (previous_rtp_sequence == 0xFFFFFFFF) { + return; + } + + /* Normal case - next sequence number. */ + if (rtp_sequence == ((previous_rtp_sequence + 1) & 0xFFFF)) { + return; + } + + /* Error case - sequence missed. */ + vs->network_error_count++; + + /* Restart pid sequence check after packet loss. */ + int i; + for (i = 0; i < 0x2000; i++) { + vs->sequence[i] = 0xFF; + } +} + +static THREAD_FUNC_PREFIX hdhomerun_video_thread_execute(void *arg) +{ + struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)arg; + struct hdhomerun_pkt_t pkt_inst; + + while (!vs->terminate) { + struct hdhomerun_pkt_t *pkt = &pkt_inst; + hdhomerun_pkt_reset(pkt); + + /* Receive. */ + size_t length = VIDEO_RTP_DATA_PACKET_SIZE; + if (!hdhomerun_sock_recv(vs->sock, pkt->end, &length, 25)) { + continue; + } + + pkt->end += length; + + if (length == VIDEO_RTP_DATA_PACKET_SIZE) { + hdhomerun_video_parse_rtp(vs, pkt); + length = (int)(pkt->end - pkt->pos); + } + + if (length != VIDEO_DATA_PACKET_SIZE) { + /* Data received but not valid - ignore. */ + continue; + } + + pthread_mutex_lock(&vs->lock); + + /* Store in ring buffer. */ + size_t head = vs->head; + uint8_t *ptr = vs->buffer + head; + memcpy(ptr, pkt->pos, length); + + /* Stats. */ + vs->packet_count++; + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 0); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 1); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 2); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 3); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 4); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 5); + hdhomerun_video_stats_ts_pkt(vs, ptr + TS_PACKET_SIZE * 6); + + /* Calculate new head. */ + head += length; + if (head >= vs->buffer_size) { + head -= vs->buffer_size; + } + + /* Check for buffer overflow. */ + if (head == vs->tail) { + vs->overflow_error_count++; + pthread_mutex_unlock(&vs->lock); + continue; + } + + vs->head = head; + + pthread_mutex_unlock(&vs->lock); + } + + return NULL; +} + +uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size) +{ + pthread_mutex_lock(&vs->lock); + + size_t head = vs->head; + size_t tail = vs->tail; + + if (vs->advance > 0) { + tail += vs->advance; + if (tail >= vs->buffer_size) { + tail -= vs->buffer_size; + } + + vs->tail = tail; + } + + if (head == tail) { + vs->advance = 0; + *pactual_size = 0; + pthread_mutex_unlock(&vs->lock); + return NULL; + } + + size_t size = (max_size / VIDEO_DATA_PACKET_SIZE) * VIDEO_DATA_PACKET_SIZE; + if (size == 0) { + vs->advance = 0; + *pactual_size = 0; + pthread_mutex_unlock(&vs->lock); + return NULL; + } + + size_t avail; + if (head > tail) { + avail = head - tail; + } else { + avail = vs->buffer_size - tail; + } + if (size > avail) { + size = avail; + } + vs->advance = size; + *pactual_size = size; + uint8_t *result = vs->buffer + tail; + + pthread_mutex_unlock(&vs->lock); + return result; +} + +void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs) +{ + pthread_mutex_lock(&vs->lock); + + vs->tail = vs->head; + vs->advance = 0; + + vs->rtp_sequence = 0xFFFFFFFF; + + int i; + for (i = 0; i < 0x2000; i++) { + vs->sequence[i] = 0xFF; + } + + vs->packet_count = 0; + vs->transport_error_count = 0; + vs->network_error_count = 0; + vs->sequence_error_count = 0; + vs->overflow_error_count = 0; + + pthread_mutex_unlock(&vs->lock); +} + +void hdhomerun_video_debug_print_stats(struct hdhomerun_video_sock_t *vs) +{ + struct hdhomerun_video_stats_t stats; + hdhomerun_video_get_stats(vs, &stats); + + hdhomerun_debug_printf(vs->dbg, "video sock: pkt=%u net=%u te=%u miss=%u drop=%u\n", + (unsigned int)stats.packet_count, (unsigned int)stats.network_error_count, + (unsigned int)stats.transport_error_count, (unsigned int)stats.sequence_error_count, + (unsigned int)stats.overflow_error_count + ); +} + +void hdhomerun_video_get_stats(struct hdhomerun_video_sock_t *vs, struct hdhomerun_video_stats_t *stats) +{ + memset(stats, 0, sizeof(struct hdhomerun_video_stats_t)); + + pthread_mutex_lock(&vs->lock); + + stats->packet_count = vs->packet_count; + stats->network_error_count = vs->network_error_count; + stats->transport_error_count = vs->transport_error_count; + stats->sequence_error_count = vs->sequence_error_count; + stats->overflow_error_count = vs->overflow_error_count; + + pthread_mutex_unlock(&vs->lock); +} diff --git a/hdhomerun_video.h b/hdhomerun_video.h new file mode 100644 index 0000000..d6485e1 --- /dev/null +++ b/hdhomerun_video.h @@ -0,0 +1,104 @@ +/* + * hdhomerun_video.h + * + * Copyright © 2006 Silicondust USA Inc. . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef __cplusplus +extern "C" { +#endif + +struct hdhomerun_video_sock_t; + +struct hdhomerun_video_stats_t { + uint32_t packet_count; + uint32_t network_error_count; + uint32_t transport_error_count; + uint32_t sequence_error_count; + uint32_t overflow_error_count; +}; + +#define TS_PACKET_SIZE 188 +#define VIDEO_DATA_PACKET_SIZE (188 * 7) +#define VIDEO_DATA_BUFFER_SIZE_1S (20000000 / 8) + +#define VIDEO_RTP_DATA_PACKET_SIZE ((188 * 7) + 12) + +/* + * Create a video/data socket. + * + * uint16_t listen_port: Port number to listen on. Set to 0 to auto-select. + * size_t buffer_size: Size of receive buffer. For 1 second of buffer use VIDEO_DATA_BUFFER_SIZE_1S. + * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL. + * + * Returns a pointer to the newly created control socket. + * + * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy. + */ +extern LIBTYPE struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool_t allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg); +extern LIBTYPE void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs); + +/* + * Get the port the socket is listening on. + * + * Returns 16-bit port with native endianness, or 0 on error. + */ +extern LIBTYPE uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs); + +/* + * Join/leave multicast group. + */ +extern LIBTYPE int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); +extern LIBTYPE void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); + +/* + * Read data from buffer. + * + * size_t max_size: The maximum amount of data to be returned. + * size_t *pactual_size: The caller-supplied pactual_size value will be updated to contain the amount + * of data available to the caller. + * + * Returns a pointer to the data, or NULL if no data is available. + * The data will remain valid until another call to hdhomerun_video_recv. + * + * The amount of data returned will always be a multiple of VIDEO_DATA_PACKET_SIZE (1316). + * Attempting to read a single TS frame (188 bytes) will not return data as it is less than + * the minimum size. + * + * The buffer is implemented as a ring buffer. It is possible for this function to return a small + * amount of data when more is available due to the wrap-around case. + */ +extern LIBTYPE uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size); + +/* + * Flush the buffer. + */ +extern LIBTYPE void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs); + +/* + * Debug print internal stats. + */ +extern LIBTYPE void hdhomerun_video_debug_print_stats(struct hdhomerun_video_sock_t *vs); +extern LIBTYPE void hdhomerun_video_get_stats(struct hdhomerun_video_sock_t *vs, struct hdhomerun_video_stats_t *stats); + +/* + * Internal use only. + */ +extern LIBTYPE hdhomerun_sock_t hdhomerun_video_get_sock(struct hdhomerun_video_sock_t *vs); + +#ifdef __cplusplus +} +#endif