libhdhomerun/hdhomerun.h0000664000175000017500000000226713013646353014675 0ustar buildbuild/* * 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" libhdhomerun/hdhomerun_channels.c0000664000175000017500000003070213737525222016542 0ustar buildbuild/* * hdhomerun_channels.c * * Copyright © 2007-2014 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} }; /* TW antenna channels. */ static const struct hdhomerun_channelmap_range_t hdhomerun_channelmap_range_tw_bcast[] = { { 7, 13, 177000000, 6000000}, { 14, 69, 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, 36, 473000000, 6000000}, { 38, 51, 617000000, 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_tw_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; } libhdhomerun/hdhomerun_channels.h0000664000175000017500000000611713013646353016546 0ustar buildbuild/* * hdhomerun_channels.h * * Copyright © 2007-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_channel_entry_t; struct hdhomerun_channel_list_t; extern LIBHDHOMERUN_API const char *hdhomerun_channelmap_get_channelmap_from_country_source(const char *countrycode, const char *source, const char *supported); extern LIBHDHOMERUN_API const char *hdhomerun_channelmap_get_channelmap_scan_group(const char *channelmap); extern LIBHDHOMERUN_API uint16_t hdhomerun_channel_entry_channel_number(struct hdhomerun_channel_entry_t *entry); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_entry_frequency(struct hdhomerun_channel_entry_t *entry); extern LIBHDHOMERUN_API const char *hdhomerun_channel_entry_name(struct hdhomerun_channel_entry_t *entry); extern LIBHDHOMERUN_API struct hdhomerun_channel_list_t *hdhomerun_channel_list_create(const char *channelmap); extern LIBHDHOMERUN_API void hdhomerun_channel_list_destroy(struct hdhomerun_channel_list_t *channel_list); extern LIBHDHOMERUN_API struct hdhomerun_channel_entry_t *hdhomerun_channel_list_first(struct hdhomerun_channel_list_t *channel_list); extern LIBHDHOMERUN_API struct hdhomerun_channel_entry_t *hdhomerun_channel_list_last(struct hdhomerun_channel_list_t *channel_list); extern LIBHDHOMERUN_API struct hdhomerun_channel_entry_t *hdhomerun_channel_list_next(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry); extern LIBHDHOMERUN_API struct hdhomerun_channel_entry_t *hdhomerun_channel_list_prev(struct hdhomerun_channel_list_t *channel_list, struct hdhomerun_channel_entry_t *entry); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_list_total_count(struct hdhomerun_channel_list_t *channel_list); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_list_frequency_count(struct hdhomerun_channel_list_t *channel_list); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_frequency_round(uint32_t frequency, uint32_t resolution); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_frequency_round_normal(uint32_t frequency); extern LIBHDHOMERUN_API uint32_t hdhomerun_channel_number_to_frequency(struct hdhomerun_channel_list_t *channel_list, uint16_t channel_number); extern LIBHDHOMERUN_API uint16_t hdhomerun_channel_frequency_to_number(struct hdhomerun_channel_list_t *channel_list, uint32_t frequency); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_channelscan.c0000664000175000017500000002111313063620312017204 0ustar buildbuild/* * hdhomerun_channelscan.c * * Copyright © 2007-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" 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 *pchanged, bool *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 = (uint16_t)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 = (uint16_t)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 = (uint16_t)program_number; program.virtual_major = (uint16_t)virtual_major; program.virtual_minor = (uint16_t)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 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 (uint8_t) (scan->scanned_channels * 100 / (scan->scanned_channels + channels_remaining)); } libhdhomerun/hdhomerun_channelscan.h0000664000175000017500000000330213013646353017221 0ustar buildbuild/* * hdhomerun_channelscan.h * * Copyright © 2007-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 #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 LIBHDHOMERUN_API struct hdhomerun_channelscan_t *channelscan_create(struct hdhomerun_device_t *hd, const char *channelmap); extern LIBHDHOMERUN_API void channelscan_destroy(struct hdhomerun_channelscan_t *scan); extern LIBHDHOMERUN_API int channelscan_advance(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result); extern LIBHDHOMERUN_API int channelscan_detect(struct hdhomerun_channelscan_t *scan, struct hdhomerun_channelscan_result_t *result); extern LIBHDHOMERUN_API uint8_t channelscan_get_progress(struct hdhomerun_channelscan_t *scan); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_config.c0000664000175000017500000003740714357356374016236 0ustar buildbuild/* * hdhomerun_config.c * * Copyright © 2006-2022 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" static const char *appname; struct hdhomerun_device_t *hd; static int help(void) { printf("Usage:\n"); printf("\t%s discover [-4] [-6] [--dedupe] []\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 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 int discover_print(int argc, char *argv[]) { const char *target_ip_str = NULL; uint32_t flags = 0; bool dedupe = false; while (argc--) { char *param = *argv++; if (param[0] != '-') { target_ip_str = param; continue; } if (contains(param, "-4")) { flags |= HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; continue; } if (contains(param, "-6")) { flags |= HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; continue; } if (contains(param, "dedupe")) { dedupe = true; continue; } } if (flags == 0) { flags = HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; } struct hdhomerun_discover_t *ds = hdhomerun_discover_create(NULL); if (!ds) { fprintf(stderr, "resource error\n"); return -1; } /* Most applications will specify a list of specific types rather than wildcard */ uint32_t device_types[1]; device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; int ret; if (target_ip_str) { struct sockaddr_storage target_addr; if (!hdhomerun_sock_ip_str_to_sockaddr(target_ip_str , &target_addr)) { fprintf(stderr, "invalid ip address: %s\n", target_ip_str); hdhomerun_discover_destroy(ds); return -1; } ret = hdhomerun_discover2_find_devices_targeted(ds, (struct sockaddr *)&target_addr, device_types, 1); } else { ret = hdhomerun_discover2_find_devices_broadcast(ds, flags, device_types, 1); } if (ret < 0) { fprintf(stderr, "error sending discover request\n"); hdhomerun_discover_destroy(ds); return -1; } if (ret == 0) { printf("no devices found\n"); hdhomerun_discover_destroy(ds); return 0; } struct hdhomerun_discover2_device_t *device = hdhomerun_discover2_iter_device_first(ds); while (device) { uint32_t device_id = hdhomerun_discover2_device_get_device_id(device); if (device_id == 0) { device = hdhomerun_discover2_iter_device_next(device); continue; } struct hdhomerun_discover2_device_if_t *device_if = hdhomerun_discover2_iter_device_if_first(device); while (device_if) { struct sockaddr_storage ip_addr; hdhomerun_discover2_device_if_get_ip_addr(device_if, &ip_addr); char ip_str[64]; hdhomerun_sock_sockaddr_to_ip_str(ip_str, (struct sockaddr *)&ip_addr, true); printf("hdhomerun device %08X found at %s\n", device_id, ip_str); if (dedupe) { break; } device_if = hdhomerun_discover2_iter_device_if_next(device_if); } device = hdhomerun_discover2_iter_device_next(device); } hdhomerun_discover_destroy(ds); return 1; } 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) { char *new_buffer = (char *)realloc(buffer, pos + 1024); if (!new_buffer) { fprintf(stderr, "out of memory\n"); return -1; } buffer = new_buffer; 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(_WIN32) 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 = (uint32_t)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(_WIN32) /* Configure console for UTF-8. */ SetConsoleOutputCP(CP_UTF8); /* Initialize network socket support. */ WORD wVersionRequested = MAKEWORD(2, 0); WSADATA wsaData; (void)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")) { return discover_print(argc, argv); } /* 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; } libhdhomerun/hdhomerun_control.c0000664000175000017500000004035114357356374016441 0ustar buildbuild/* * hdhomerun_control.c * * Copyright © 2006-2022 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 40000 struct hdhomerun_control_sock_t { uint32_t desired_device_id; uint32_t actual_device_id; struct sockaddr_storage desired_device_addr; struct sockaddr_storage actual_device_addr; struct 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) { return; } hdhomerun_sock_destroy(cs->sock); cs->sock = NULL; } void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip) { struct sockaddr_in device_addr; memset(&device_addr, 0, sizeof(device_addr)); device_addr.sin_family = AF_INET; device_addr.sin_addr.s_addr = htonl(device_ip); hdhomerun_control_set_device_ex(cs, device_id, (const struct sockaddr *)&device_addr); } void hdhomerun_control_set_device_ex(struct hdhomerun_control_sock_t *cs, uint32_t device_id, const struct sockaddr *device_addr) { hdhomerun_control_close_sock(cs); cs->desired_device_id = device_id; cs->actual_device_id = 0; hdhomerun_sock_sockaddr_copy(&cs->desired_device_addr, device_addr); memset(&cs->actual_device_addr, 0, sizeof(cs->actual_device_addr)); } struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg) { struct sockaddr_in device_addr; memset(&device_addr, 0, sizeof(device_addr)); device_addr.sin_family = AF_INET; device_addr.sin_addr.s_addr = htonl(device_ip); return hdhomerun_control_create_ex(device_id, (const struct sockaddr *)&device_addr, dbg); } struct hdhomerun_control_sock_t *hdhomerun_control_create_ex(uint32_t device_id, const struct sockaddr *device_addr, 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; hdhomerun_control_set_device_ex(cs, device_id, device_addr); return cs; } void hdhomerun_control_destroy(struct hdhomerun_control_sock_t *cs) { hdhomerun_control_close_sock(cs); free(cs); } static bool hdhomerun_control_connect_sock_discover(struct hdhomerun_control_sock_t *cs) { struct hdhomerun_discover_t *ds = hdhomerun_discover_create(cs->dbg); if (!ds) { return false; } uint32_t device_types[1]; device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; uint32_t device_id = cs->desired_device_id; if (device_id == 0) { device_id = HDHOMERUN_DEVICE_ID_WILDCARD; } int ret; if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&cs->desired_device_addr)) { if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { ret = hdhomerun_discover2_find_devices_targeted(ds, (struct sockaddr *)&cs->desired_device_addr, device_types, 1); } else { ret = hdhomerun_discover2_find_device_id_targeted(ds, (struct sockaddr *)&cs->desired_device_addr, device_id); } } else { uint32_t flags = HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { ret = hdhomerun_discover2_find_devices_broadcast(ds, flags, device_types, 1); } else { ret = hdhomerun_discover2_find_device_id_broadcast(ds, flags, device_id); } } if (ret <= 0) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: device not found\n"); hdhomerun_discover_destroy(ds); return false; } struct hdhomerun_discover2_device_t *device = hdhomerun_discover2_iter_device_first(ds); cs->actual_device_id = hdhomerun_discover2_device_get_device_id(device); struct hdhomerun_discover2_device_if_t *device_if = hdhomerun_discover2_iter_device_if_first(device); hdhomerun_discover2_device_if_get_ip_addr(device_if, &cs->actual_device_addr); hdhomerun_discover_destroy(ds); return true; } static bool hdhomerun_control_connect_sock(struct hdhomerun_control_sock_t *cs) { if (cs->sock) { return true; } if ((cs->desired_device_id == 0) && !hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&cs->desired_device_addr)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: no device specified\n"); return false; } if (hdhomerun_sock_sockaddr_is_multicast((struct sockaddr *)&cs->desired_device_addr)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: cannot use multicast ip address for device operations\n"); return false; } /* Find device. */ if (!hdhomerun_control_connect_sock_discover(cs)) { return false; } /* Create socket. */ cs->sock = hdhomerun_sock_create_tcp_ex(cs->actual_device_addr.ss_family); if (!cs->sock) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_connect_sock: failed to create socket (%d)\n", hdhomerun_sock_getlasterror()); return false; } /* Initiate connection. */ hdhomerun_sock_sockaddr_set_port((struct sockaddr *)&cs->actual_device_addr, HDHOMERUN_CONTROL_TCP_PORT); if (!hdhomerun_sock_connect_ex(cs->sock, (struct sockaddr *)&cs->actual_device_addr, 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; } if (cs->actual_device_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *actual_device_addr_in = (struct sockaddr_in *)&cs->actual_device_addr; return ntohl(actual_device_addr_in->sin_addr.s_addr); } bool hdhomerun_control_get_device_addr(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) { if (!hdhomerun_control_connect_sock(cs)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_device_ip: connect failed\n"); memset(result, 0, sizeof(struct sockaddr_storage)); return false; } *result = cs->actual_device_addr; return hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)result); } 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) { if (cs->desired_device_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *desired_device_addr_in = (struct sockaddr_in *)&cs->desired_device_addr; return ntohl(desired_device_addr_in->sin_addr.s_addr); } bool hdhomerun_control_get_device_addr_requested(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) { *result = cs->desired_device_addr; return hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)result); } uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs) { struct sockaddr_storage local_addr; if (!hdhomerun_control_get_local_addr_ex(cs, &local_addr)) { return 0; } if (local_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *local_addr_in = (struct sockaddr_in *)&local_addr; return ntohl(local_addr_in->sin_addr.s_addr); } bool hdhomerun_control_get_local_addr_ex(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result) { if (!hdhomerun_control_connect_sock(cs)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: connect failed\n"); return false; } if (!hdhomerun_sock_getsockname_addr_ex(cs->sock, result)) { hdhomerun_debug_printf(cs->dbg, "hdhomerun_control_get_local_addr: getsockname failed (%d)\n", hdhomerun_sock_getlasterror()); return false; } return true; } static bool 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 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) { 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); size_t name_len = 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) { size_t value_len = 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; default: break; } 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 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; } libhdhomerun/hdhomerun_control.h0000664000175000017500000001276614357356374016457 0ustar buildbuild/* * hdhomerun_control.h * * Copyright © 2006-2022 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 LIBHDHOMERUN_API struct hdhomerun_control_sock_t *hdhomerun_control_create(uint32_t device_id, uint32_t device_ip, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_control_sock_t *hdhomerun_control_create_ex(uint32_t device_id, const struct sockaddr *device_addr, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_id(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_ip(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API bool hdhomerun_control_get_device_addr(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_id_requested(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API uint32_t hdhomerun_control_get_device_ip_requested(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API bool hdhomerun_control_get_device_addr_requested(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); extern LIBHDHOMERUN_API void hdhomerun_control_set_device(struct hdhomerun_control_sock_t *cs, uint32_t device_id, uint32_t device_ip); extern LIBHDHOMERUN_API void hdhomerun_control_set_device_ex(struct hdhomerun_control_sock_t *cs, uint32_t device_id, const struct sockaddr *device_addr); /* * 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 LIBHDHOMERUN_API uint32_t hdhomerun_control_get_local_addr(struct hdhomerun_control_sock_t *cs); extern LIBHDHOMERUN_API bool hdhomerun_control_get_local_addr_ex(struct hdhomerun_control_sock_t *cs, struct sockaddr_storage *result); /* * Low-level communication. */ extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_control_get(struct hdhomerun_control_sock_t *cs, const char *name, char **pvalue, char **perror); extern LIBHDHOMERUN_API int hdhomerun_control_set(struct hdhomerun_control_sock_t *cs, const char *name, const char *value, char **pvalue, char **perror); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_control_upgrade(struct hdhomerun_control_sock_t *cs, FILE *upgrade_file); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_debug.c0000664000175000017500000002334613117566021016035 0ustar buildbuild/* * hdhomerun_debug.c * * Copyright © 2007-2016 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; char buffer[2048]; }; struct hdhomerun_debug_t { thread_task_t thread; volatile bool enabled; volatile bool terminate; char *prefix; thread_mutex_t print_lock; thread_mutex_t queue_lock; thread_mutex_t send_lock; thread_cond_t queue_cond; 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; struct hdhomerun_sock_t *sock; }; static void 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; } thread_mutex_init(&dbg->print_lock); thread_mutex_init(&dbg->queue_lock); thread_mutex_init(&dbg->send_lock); thread_cond_init(&dbg->queue_cond); if (!thread_task_create(&dbg->thread, &hdhomerun_debug_thread_execute, dbg)) { free(dbg); return NULL; } return dbg; } void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg) { if (!dbg) { return; } dbg->terminate = true; thread_cond_signal(&dbg->queue_cond); thread_task_join(dbg->thread); 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_destroy(dbg->sock); } thread_cond_dispose(&dbg->queue_cond); thread_mutex_dispose(&dbg->print_lock); thread_mutex_dispose(&dbg->queue_lock); thread_mutex_dispose(&dbg->send_lock); 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_destroy(dbg->sock); dbg->sock = NULL; } } void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout) { if (!dbg) { return; } if (timeout > 0) { hdhomerun_debug_flush(dbg, timeout); } thread_mutex_lock(&dbg->send_lock); hdhomerun_debug_close_internal(dbg); dbg->connect_delay = 0; thread_mutex_unlock(&dbg->send_lock); } void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename) { if (!dbg) { return; } thread_mutex_lock(&dbg->send_lock); if (!filename && !dbg->file_name) { thread_mutex_unlock(&dbg->send_lock); return; } if (filename && dbg->file_name) { if (strcmp(filename, dbg->file_name) == 0) { thread_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); } thread_mutex_unlock(&dbg->send_lock); } void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix) { if (!dbg) { return; } thread_mutex_lock(&dbg->print_lock); if (dbg->prefix) { free(dbg->prefix); dbg->prefix = NULL; } if (prefix) { dbg->prefix = strdup(prefix); } thread_mutex_unlock(&dbg->print_lock); } void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg) { if (!dbg) { return; } if (dbg->enabled) { return; } dbg->enabled = true; thread_cond_signal(&dbg->queue_cond); } void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg) { if (!dbg) { return; } dbg->enabled = false; } bool 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) { thread_mutex_lock(&dbg->queue_lock); struct hdhomerun_debug_message_t *message = dbg->queue_head; thread_mutex_unlock(&dbg->queue_lock); if (!message) { return; } msleep_approx(16); } } 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; } struct hdhomerun_debug_message_t *message = (struct hdhomerun_debug_message_t *)malloc(sizeof(struct hdhomerun_debug_message_t)); if (!message) { return; } message->next = NULL; 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. */ thread_mutex_lock(&dbg->print_lock); if (dbg->prefix) { hdhomerun_sprintf(ptr, end, "%s ", dbg->prefix); ptr = strchr(ptr, 0); } thread_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. */ thread_mutex_lock(&dbg->queue_lock); if (dbg->queue_tail) { dbg->queue_tail->next = message; } else { dbg->queue_head = message; } dbg->queue_tail = message; dbg->queue_depth++; bool signal_thread = dbg->enabled || (dbg->queue_depth > 1024 + 100); thread_mutex_unlock(&dbg->queue_lock); if (signal_thread) { thread_cond_signal(&dbg->queue_cond); } } /* Send lock held by caller */ static bool 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 hdhomerun_debug_output_message_sock(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message) { if (!dbg->sock) { 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) { 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 hdhomerun_debug_output_message(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message) { thread_mutex_lock(&dbg->send_lock); bool ret; if (dbg->file_name) { ret = hdhomerun_debug_output_message_file(dbg, message); } else { ret = hdhomerun_debug_output_message_sock(dbg, message); } thread_mutex_unlock(&dbg->send_lock); return ret; } static void hdhomerun_debug_pop_and_free_message(struct hdhomerun_debug_t *dbg) { thread_mutex_lock(&dbg->queue_lock); struct hdhomerun_debug_message_t *message = dbg->queue_head; dbg->queue_head = message->next; if (!dbg->queue_head) { dbg->queue_tail = NULL; } dbg->queue_depth--; thread_mutex_unlock(&dbg->queue_lock); free(message); } static void hdhomerun_debug_thread_execute(void *arg) { struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)arg; while (!dbg->terminate) { thread_mutex_lock(&dbg->queue_lock); struct hdhomerun_debug_message_t *message = dbg->queue_head; uint32_t queue_depth = dbg->queue_depth; thread_mutex_unlock(&dbg->queue_lock); if (!message) { thread_cond_wait(&dbg->queue_cond); continue; } if (queue_depth > 1024) { hdhomerun_debug_pop_and_free_message(dbg); continue; } if (!dbg->enabled) { thread_cond_wait(&dbg->queue_cond); continue; } if (!hdhomerun_debug_output_message(dbg, message)) { msleep_approx(1000); continue; } hdhomerun_debug_pop_and_free_message(dbg); } } libhdhomerun/hdhomerun_debug.h0000664000175000017500000000437513063620312016035 0ustar buildbuild/* * hdhomerun_debug.h * * Copyright © 2007-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 */ /* * 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 LIBHDHOMERUN_API struct hdhomerun_debug_t *hdhomerun_debug_create(void); extern LIBHDHOMERUN_API void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix); extern LIBHDHOMERUN_API void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename); extern LIBHDHOMERUN_API void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API bool hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout); extern LIBHDHOMERUN_API void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout); extern LIBHDHOMERUN_API void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...); extern LIBHDHOMERUN_API void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_device.c0000664000175000017500000012452414357356374016225 0ustar buildbuild/* * hdhomerun_device.c * * Copyright © 2006-2022 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; struct sockaddr_storage multicast_addr; 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) { struct sockaddr_in device_addr; memset(&device_addr, 0, sizeof(device_addr)); device_addr.sin_family = AF_INET; device_addr.sin_addr.s_addr = htonl(device_ip); return hdhomerun_device_set_device_ex(hd, device_id, (const struct sockaddr *)&device_addr); } int hdhomerun_device_set_device_ex(struct hdhomerun_device_t *hd, uint32_t device_id, const struct sockaddr *device_addr) { if ((device_id == 0) && !hdhomerun_sock_sockaddr_is_addr(device_addr)) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: device not specified\n"); return -1; } if (hdhomerun_sock_sockaddr_is_multicast(device_addr)) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device: invalid address\n"); 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_ex(hd->cs, device_id, device_addr); if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { device_id = hdhomerun_control_get_device_id(hd->cs); } memset(&hd->multicast_addr, 0, sizeof(hd->multicast_addr)); 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) { struct sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); multicast_addr.sin_port = htons(multicast_port); return hdhomerun_device_set_multicast_ex(hd, (const struct sockaddr *)&multicast_addr); } int hdhomerun_device_set_multicast_ex(struct hdhomerun_device_t *hd, const struct sockaddr *multicast_addr) { if (!hdhomerun_sock_sockaddr_is_multicast(multicast_addr)) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_set_device_multicast: invalid address\n"); return -1; } uint16_t multicast_port = hdhomerun_sock_sockaddr_get_port(multicast_addr); 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; } hdhomerun_sock_sockaddr_copy(&hd->multicast_addr, multicast_addr); hd->device_id = 0; hd->tuner = 0; hd->lockkey = 0; hdhomerun_sprintf(hd->name, hd->name + sizeof(hd->name), "multicast:%u", (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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { 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 sockaddr_in device_addr; memset(&device_addr, 0, sizeof(device_addr)); device_addr.sin_family = AF_INET; device_addr.sin_addr.s_addr = htonl(device_ip); return hdhomerun_device_create_ex(device_id, (const struct sockaddr *)&device_addr, tuner, dbg); } struct hdhomerun_device_t *hdhomerun_device_create_ex(uint32_t device_id, const struct sockaddr *device_addr, unsigned int tuner, struct hdhomerun_debug_t *dbg) { if ((device_id != 0) && !hdhomerun_discover_validate_device_id(device_id)) { return NULL; } struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); if (!hd) { return NULL; } if ((device_id == 0) && !hdhomerun_sock_sockaddr_is_addr(device_addr) && (tuner == 0)) { return hd; } if (hdhomerun_device_set_device_ex(hd, device_id, device_addr) <= 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 sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); multicast_addr.sin_port = htons(multicast_port); return hdhomerun_device_create_multicast_ex((const struct sockaddr *)&multicast_addr, dbg); } struct hdhomerun_device_t *hdhomerun_device_create_multicast_ex(const struct sockaddr *multicast_addr, struct hdhomerun_debug_t *dbg) { struct hdhomerun_device_t *hd = hdhomerun_device_create_internal(dbg); if (!hd) { return NULL; } if (hdhomerun_device_set_multicast_ex(hd, multicast_addr) <= 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); } static bool hdhomerun_device_create_from_str_parse_device_id(const char *name, uint32_t *pdevice_id) { char *end; uint32_t device_id = (uint32_t)strtoul(name, &end, 16); if (end != name + 8) { return false; } if (*end != 0) { return false; } *pdevice_id = device_id; return true; } static bool hdhomerun_device_create_from_str_parse_dns(const char *name, struct sockaddr_storage *device_addr) { const char *ptr = name; if (*ptr == 0) { return false; } while (1) { char c = *ptr++; if (c == 0) { break; } if ((c >= '0') && (c <= '9')) { continue; } if ((c >= 'a') && (c <= 'z')) { continue; } if ((c >= 'A') && (c <= 'Z')) { continue; } if ((c == '.') || (c == '-')) { continue; } return false; } return hdhomerun_sock_getaddrinfo_addr_ex(AF_INET, name, device_addr); } static struct hdhomerun_device_t *hdhomerun_device_create_from_str_tail(const char *tail, uint32_t device_id, struct sockaddr_storage *device_addr, struct hdhomerun_debug_t *dbg) { const char *ptr = tail; if (*ptr == 0) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)device_addr, 0, dbg); } if (*ptr == ':') { ptr++; char *end; unsigned long port = strtoul(ptr + 1, &end, 10); if (*end != 0) { return NULL; } if ((port < 1024) || (port > 65535)) { return NULL; } if (device_addr->ss_family == AF_INET) { struct sockaddr_in *device_addr_in = (struct sockaddr_in *)device_addr; device_addr_in->sin_port = htons((uint16_t)port); return hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, dbg); } if (device_addr->ss_family == AF_INET6) { struct sockaddr_in6 *device_addr_in = (struct sockaddr_in6 *)device_addr; device_addr_in->sin6_port = htons((uint16_t)port); return hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, dbg); } return NULL; } if (*ptr == '-') { ptr++; char *end; unsigned int tuner_index = (unsigned int)strtoul(ptr, &end, 10); if (*end != 0) { return NULL; } return hdhomerun_device_create_ex(device_id, (struct sockaddr *)device_addr, tuner_index, dbg); } return NULL; } struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg) { char str[64]; if (!hdhomerun_sprintf(str, str + sizeof(str), "%s", device_str)) { return NULL; } uint32_t device_id = HDHOMERUN_DEVICE_ID_WILDCARD; struct sockaddr_storage device_addr; device_addr.ss_family = 0; char *ptr = str; bool framed = (*ptr == '['); if (framed) { ptr++; char *end = strchr(ptr, ']'); if (!end) { return NULL; } *end++ = 0; if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_create_from_str_tail(end, device_id, &device_addr, dbg); } return NULL; } char *dash = strchr(ptr, '-'); if (dash) { *dash = 0; if (hdhomerun_device_create_from_str_parse_device_id(ptr, &device_id)) { *dash = '-'; return hdhomerun_device_create_from_str_tail(dash, device_id, &device_addr, dbg); } if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { *dash = '-'; return hdhomerun_device_create_from_str_tail(dash, device_id, &device_addr, dbg); } *dash = '-'; if (hdhomerun_device_create_from_str_parse_dns(ptr, &device_addr)) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); } return NULL; } char *colon = strchr(ptr, ':'); if (colon) { char *second_colon = strchr(colon, ':'); if (second_colon) { if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); } return NULL; } *colon = 0; if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { *colon = ':'; return hdhomerun_device_create_from_str_tail(colon, device_id, &device_addr, dbg); } return NULL; } if (hdhomerun_device_create_from_str_parse_device_id(ptr, &device_id)) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); } if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); } if (hdhomerun_device_create_from_str_parse_dns(ptr, &device_addr)) { return hdhomerun_device_create_ex(device_id, (struct sockaddr *)&device_addr, 0, dbg); } return NULL; } 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) { struct sockaddr_storage device_addr; if (!hdhomerun_device_get_device_addr(hd, &device_addr)) { return 0; } if (device_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *device_addr_in = (struct sockaddr_in *)&device_addr; return ntohl(device_addr_in->sin_addr.s_addr); } bool hdhomerun_device_get_device_addr(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) { if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { *result = hd->multicast_addr; return true; } if (!hd->cs) { memset(result, 0, sizeof(struct sockaddr_storage)); return false; } return hdhomerun_control_get_device_addr(hd->cs, result); } uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd) { if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { return 0; } if (!hd->cs) { return 0; } return hdhomerun_control_get_device_id_requested(hd->cs); } uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd) { struct sockaddr_storage device_addr; if (!hdhomerun_device_get_device_addr_requested(hd, &device_addr)) { return 0; } if (device_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *device_addr_in = (struct sockaddr_in *)&device_addr; return ntohl(device_addr_in->sin_addr.s_addr); } bool hdhomerun_device_get_device_addr_requested(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) { if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { *result = hd->multicast_addr; return true; } if (!hd->cs) { memset(result, 0, sizeof(struct sockaddr_storage)); return false; } return hdhomerun_control_get_device_addr_requested(hd->cs, result); } 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 allow_port_reuse = false; struct sockaddr_storage listen_addr; memset(&listen_addr, 0, sizeof(listen_addr)); if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { listen_addr.ss_family = hd->multicast_addr.ss_family; hdhomerun_sock_sockaddr_set_port((struct sockaddr *)&listen_addr, hdhomerun_sock_sockaddr_get_port((struct sockaddr *)&hd->multicast_addr)); allow_port_reuse = true; } struct sockaddr_storage device_addr; if (!hdhomerun_control_get_device_addr(hd->cs, &device_addr)) { return NULL; } listen_addr.ss_family = device_addr.ss_family; hd->vs = hdhomerun_video_create_ex((struct sockaddr *)&listen_addr, 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) { struct sockaddr_storage local_addr; if (!hdhomerun_device_get_local_machine_addr_ex(hd, &local_addr)) { return 0; } if (local_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *local_addr_in = (struct sockaddr_in *)&local_addr; return ntohl(local_addr_in->sin_addr.s_addr); } bool hdhomerun_device_get_local_machine_addr_ex(struct hdhomerun_device_t *hd, struct sockaddr_storage *result) { if (!hd->cs) { memset(result, 0, sizeof(struct sockaddr_storage)); return false; } return hdhomerun_control_get_local_addr_ex(hd->cs, result); } 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; (void)sscanf(ptr + strlen(tag), "%u", &value); return (uint32_t)value; } static bool hdhomerun_device_get_tuner_status_lock_is_bcast(struct hdhomerun_tuner_status_t *status) { if (strcmp(status->lock_str, "8vsb") == 0) { return true; } if (strcmp(status->lock_str, "atsc3") == 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) { (void)sscanf(channel + 3, "%31s", status->channel); } char *lock = strstr(status_str, "lock="); if (lock) { (void)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 >= 35; 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) { (void)sscanf(channel + 3, "%31s", status->channel); } char *lock = strstr(status_str, "lock="); if (lock) { (void)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 >= 35; 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) { (void)sscanf(vch + 4, "%31s", vstatus->vchannel); } char *name = strstr(vstatus_str, "name="); if (name) { (void)sscanf(name + 5, "%31s", vstatus->name); } char *auth = strstr(vstatus_str, "auth="); if (auth) { (void)sscanf(auth + 5, "%31s", vstatus->auth); } char *cci = strstr(vstatus_str, "cci="); if (cci) { (void)sscanf(cci + 4, "%31s", vstatus->cci); } char *cgms = strstr(vstatus_str, "cgms="); if (cgms) { (void)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_plpinfo(struct hdhomerun_device_t *hd, char **pplpinfo) { if (!hd->cs) { hdhomerun_debug_printf(hd->dbg, "hdhomerun_device_get_tuner_plpinfo: device not set\n"); return -1; } char name[32]; hdhomerun_sprintf(name, name + sizeof(name), "/tuner%u/plpinfo", hd->tuner); return hdhomerun_control_get(hd->cs, name, pplpinfo, NULL); } 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 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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { 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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { 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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { 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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { 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; } hdhomerun_video_set_keepalive(hd->vs, 0, 0, 0); /* Set target. */ if (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { struct sockaddr local_ip; memset(&local_ip, 0, sizeof(local_ip)); int ret = hdhomerun_video_join_multicast_group_ex(hd->vs, (struct sockaddr *)&hd->multicast_addr, &local_ip); 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; } uint32_t remote_ip = hdhomerun_control_get_device_ip(hd->cs); hdhomerun_video_set_keepalive(hd->vs, remote_ip, 5004, hd->lockkey); } /* 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 (hdhomerun_sock_sockaddr_is_addr((struct sockaddr *)&hd->multicast_addr)) { struct sockaddr local_ip; memset(&local_ip, 0, sizeof(local_ip)); hdhomerun_video_leave_multicast_group_ex(hd->vs, (struct sockaddr *)&hd->multicast_addr, &local_ip); } 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); } libhdhomerun/hdhomerun_device.h0000664000175000017500000003720314357356374016227 0ustar buildbuild/* * hdhomerun_device.h * * Copyright © 2006-2022 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 LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create(uint32_t device_id, uint32_t device_ip, unsigned int tuner, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_ex(uint32_t device_id, const struct sockaddr *device_addr, unsigned int tuner, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_multicast(uint32_t multicast_ip, uint16_t multicast_port, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_multicast_ex(const struct sockaddr *multicast_addr, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_create_from_str(const char *device_str, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_device_destroy(struct hdhomerun_device_t *hd); /* * Get the device id, ip, or tuner of the device instance. */ extern LIBHDHOMERUN_API const char *hdhomerun_device_get_name(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_id(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_ip(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API bool hdhomerun_device_get_device_addr(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_id_requested(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_device_ip_requested(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API bool hdhomerun_device_get_device_addr_requested(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); extern LIBHDHOMERUN_API unsigned int hdhomerun_device_get_tuner(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API int hdhomerun_device_set_device(struct hdhomerun_device_t *hd, uint32_t device_id, uint32_t device_ip); extern LIBHDHOMERUN_API int hdhomerun_device_set_device_ex(struct hdhomerun_device_t *hd, uint32_t device_id, const struct sockaddr *device_addr); extern LIBHDHOMERUN_API int hdhomerun_device_set_multicast(struct hdhomerun_device_t *hd, uint32_t multicast_ip, uint16_t multicast_port); extern LIBHDHOMERUN_API int hdhomerun_device_set_multicast_ex(struct hdhomerun_device_t *hd, const struct sockaddr *multicast_addr); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner(struct hdhomerun_device_t *hd, unsigned int tuner); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API uint32_t hdhomerun_device_get_local_machine_addr(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API bool hdhomerun_device_get_local_machine_addr_ex(struct hdhomerun_device_t *hd, struct sockaddr_storage *result); /* * 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 LIBHDHOMERUN_API int hdhomerun_device_get_tuner_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_vstatus(struct hdhomerun_device_t *hd, char **pvstatus_str, struct hdhomerun_tuner_vstatus_t *vstatus); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_plpinfo(struct hdhomerun_device_t *hd, char **pplpinfo); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_streaminfo(struct hdhomerun_device_t *hd, char **pstreaminfo); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_channel(struct hdhomerun_device_t *hd, char **pchannel); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_vchannel(struct hdhomerun_device_t *hd, char **pvchannel); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_channelmap(struct hdhomerun_device_t *hd, char **pchannelmap); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_filter(struct hdhomerun_device_t *hd, char **pfilter); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_program(struct hdhomerun_device_t *hd, char **pprogram); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_target(struct hdhomerun_device_t *hd, char **ptarget); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount); extern LIBHDHOMERUN_API int hdhomerun_device_get_tuner_lockkey_owner(struct hdhomerun_device_t *hd, char **powner); extern LIBHDHOMERUN_API int hdhomerun_device_get_oob_status(struct hdhomerun_device_t *hd, char **pstatus_str, struct hdhomerun_tuner_status_t *status); extern LIBHDHOMERUN_API int hdhomerun_device_get_oob_plotsample(struct hdhomerun_device_t *hd, struct hdhomerun_plotsample_t **psamples, size_t *pcount); extern LIBHDHOMERUN_API int hdhomerun_device_get_ir_target(struct hdhomerun_device_t *hd, char **ptarget); extern LIBHDHOMERUN_API int hdhomerun_device_get_version(struct hdhomerun_device_t *hd, char **pversion_str, uint32_t *pversion_num); extern LIBHDHOMERUN_API int hdhomerun_device_get_supported(struct hdhomerun_device_t *hd, char *prefix, char **pstr); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_tuner_status_ss_color(struct hdhomerun_tuner_status_t *status); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_tuner_status_snq_color(struct hdhomerun_tuner_status_t *status); extern LIBHDHOMERUN_API uint32_t hdhomerun_device_get_tuner_status_seq_color(struct hdhomerun_tuner_status_t *status); extern LIBHDHOMERUN_API const char *hdhomerun_device_get_hw_model_str(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_set_tuner_channel(struct hdhomerun_device_t *hd, const char *channel); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_vchannel(struct hdhomerun_device_t *hd, const char *vchannel); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_channelmap(struct hdhomerun_device_t *hd, const char *channelmap); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_filter(struct hdhomerun_device_t *hd, const char *filter); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_filter_by_array(struct hdhomerun_device_t *hd, unsigned char filter_array[0x2000]); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_program(struct hdhomerun_device_t *hd, const char *program); extern LIBHDHOMERUN_API int hdhomerun_device_set_tuner_target(struct hdhomerun_device_t *hd, const char *target); extern LIBHDHOMERUN_API int hdhomerun_device_set_ir_target(struct hdhomerun_device_t *hd, const char *target); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_get_var(struct hdhomerun_device_t *hd, const char *name, char **pvalue, char **perror); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_tuner_lockkey_request(struct hdhomerun_device_t *hd, char **perror); extern LIBHDHOMERUN_API int hdhomerun_device_tuner_lockkey_release(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API int hdhomerun_device_tuner_lockkey_force(struct hdhomerun_device_t *hd); /* * Intended only for non persistent connections; eg, hdhomerun_config. */ extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_stream_start(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API uint8_t *hdhomerun_device_stream_recv(struct hdhomerun_device_t *hd, size_t max_size, size_t *pactual_size); extern LIBHDHOMERUN_API void hdhomerun_device_stream_flush(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API void hdhomerun_device_stream_stop(struct hdhomerun_device_t *hd); /* * Channel scan API. */ extern LIBHDHOMERUN_API int hdhomerun_device_channelscan_init(struct hdhomerun_device_t *hd, const char *channelmap); extern LIBHDHOMERUN_API int hdhomerun_device_channelscan_advance(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result); extern LIBHDHOMERUN_API int hdhomerun_device_channelscan_detect(struct hdhomerun_device_t *hd, struct hdhomerun_channelscan_result_t *result); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_upgrade(struct hdhomerun_device_t *hd, FILE *upgrade_file); /* * Low level accessor functions. */ extern LIBHDHOMERUN_API struct hdhomerun_control_sock_t *hdhomerun_device_get_control_sock(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API struct hdhomerun_video_sock_t *hdhomerun_device_get_video_sock(struct hdhomerun_device_t *hd); /* * Debug print internal stats. */ extern LIBHDHOMERUN_API void hdhomerun_device_debug_print_video_stats(struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API void hdhomerun_device_get_video_stats(struct hdhomerun_device_t *hd, struct hdhomerun_video_stats_t *stats); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_device_selector.c0000664000175000017500000004115414357356374020122 0ustar buildbuild/* * hdhomerun_device_selector.c * * Copyright © 2009-2016 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_discover_t *ds; 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 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); } if (hds->ds) { hdhomerun_discover_destroy(hds->ds); } free(hds); } 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; } } struct hdhomerun_device_t **hd_list = (struct hdhomerun_device_t **)realloc(hds->hd_list, (hds->hd_count + 1) * sizeof(struct hdhomerun_device_t *)); if (!hd_list) { hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_add_device: failed to allocate device list\n"); return; } hds->hd_list = hd_list; 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 device_id, const struct sockaddr *device_addr) { if (!hds->ds) { hds->ds = hdhomerun_discover_create(hds->dbg); if (!hds->ds) { return 0; } } uint32_t device_types[1]; device_types[0] = HDHOMERUN_DEVICE_TYPE_TUNER; if (device_id == 0) { device_id = HDHOMERUN_DEVICE_ID_WILDCARD; } int ret; if (hdhomerun_sock_sockaddr_is_addr(device_addr)) { if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { ret = hdhomerun_discover2_find_devices_targeted(hds->ds, device_addr, device_types, 1); } else { ret = hdhomerun_discover2_find_device_id_targeted(hds->ds, device_addr, device_id); } } else { uint32_t flags = HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; if (device_id == HDHOMERUN_DEVICE_ID_WILDCARD) { ret = hdhomerun_discover2_find_devices_broadcast(hds->ds, flags, device_types, 1); } else { ret = hdhomerun_discover2_find_device_id_broadcast(hds->ds, flags, device_id); } } if (ret <= 0) { hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_load_from_str_discover: device not found\n"); return 0; } struct hdhomerun_discover2_device_t *device = hdhomerun_discover2_iter_device_first(hds->ds); struct hdhomerun_discover2_device_if_t *device_if = hdhomerun_discover2_iter_device_if_first(device); device_id = hdhomerun_discover2_device_get_device_id(device); uint8_t tuner_count = hdhomerun_discover2_device_get_tuner_count(device); struct sockaddr_storage actual_ip_addr; hdhomerun_discover2_device_if_get_ip_addr(device_if, &actual_ip_addr); int count = 0; unsigned int tuner_index; for (tuner_index = 0; tuner_index < tuner_count; tuner_index++) { struct hdhomerun_device_t *hd = hdhomerun_device_create_ex(device_id, (struct sockaddr *)&actual_ip_addr, tuner_index, hds->dbg); if (!hd) { continue; } hdhomerun_device_selector_add_device(hds, hd); count++; } return count; } static bool hdhomerun_device_selector_load_from_str_parse_device_id(const char *name, uint32_t *pdevice_id) { char *end; uint32_t device_id = (uint32_t)strtoul(name, &end, 16); if (end != name + 8) { return false; } if (*end != 0) { return false; } *pdevice_id = device_id; return true; } static bool hdhomerun_device_selector_load_from_str_parse_dns(const char *name, struct sockaddr_storage *device_addr) { const char *ptr = name; if (*ptr == 0) { return false; } while (1) { char c = *ptr++; if (c == 0) { break; } if ((c >= '0') && (c <= '9')) { continue; } if ((c >= 'a') && (c <= 'z')) { continue; } if ((c >= 'A') && (c <= 'Z')) { continue; } if ((c == '.') || (c == '-')) { continue; } return false; } return hdhomerun_sock_getaddrinfo_addr_ex(AF_INET, name, device_addr); } static int hdhomerun_device_selector_load_from_str_tail(struct hdhomerun_device_selector_t *hds, const char *tail, uint32_t device_id, struct sockaddr_storage *device_addr) { const char *ptr = tail; if (*ptr == 0) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)device_addr); } if (*ptr == ':') { ptr++; char *end; unsigned long port = strtoul(ptr + 1, &end, 10); if (*end != 0) { return 0; } if ((port < 1024) || (port > 65535)) { return 0; } if (device_addr->ss_family == AF_INET) { struct sockaddr_in *device_addr_in = (struct sockaddr_in *)device_addr; device_addr_in->sin_port = htons((uint16_t)port); struct hdhomerun_device_t *hd = hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, hds->dbg); if (!hd) { return 0; } hdhomerun_device_selector_add_device(hds, hd); return 1; } if (device_addr->ss_family == AF_INET6) { struct sockaddr_in6 *device_addr_in = (struct sockaddr_in6 *)device_addr; device_addr_in->sin6_port = htons((uint16_t)port); struct hdhomerun_device_t *hd = hdhomerun_device_create_multicast_ex((struct sockaddr *)device_addr, hds->dbg); if (!hd) { return 0; } hdhomerun_device_selector_add_device(hds, hd); return 1; } return 0; } if (*ptr == '-') { ptr++; char *end; unsigned int tuner_index = (unsigned int)strtoul(ptr, &end, 10); if (*end != 0) { return 0; } struct hdhomerun_device_t *hd = hdhomerun_device_create_ex(device_id, (struct sockaddr *)device_addr, tuner_index, hds->dbg); if (!hd) { return 0; } hdhomerun_device_selector_add_device(hds, hd); return 1; } return 0; } int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str) { char str[64]; if (!hdhomerun_sprintf(str, str + sizeof(str), "%s", device_str)) { return 0; } uint32_t device_id = HDHOMERUN_DEVICE_ID_WILDCARD; struct sockaddr_storage device_addr; device_addr.ss_family = 0; char *ptr = str; bool framed = (*ptr == '['); if (framed) { ptr++; char *end = strchr(ptr, ']'); if (!end) { return 0; } *end++ = 0; if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_selector_load_from_str_tail(hds, end, device_id, &device_addr); } return 0; } char *dash = strchr(ptr, '-'); if (dash) { *dash = 0; if (hdhomerun_device_selector_load_from_str_parse_device_id(ptr, &device_id)) { *dash = '-'; return hdhomerun_device_selector_load_from_str_tail(hds, dash, device_id, &device_addr); } if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { *dash = '-'; return hdhomerun_device_selector_load_from_str_tail(hds, dash, device_id, &device_addr); } *dash = '-'; if (hdhomerun_device_selector_load_from_str_parse_dns(ptr, &device_addr)) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)&device_addr); } return 0; } char *colon = strchr(ptr, ':'); if (colon) { char *second_colon = strchr(colon, ':'); if (second_colon) { if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)&device_addr); } return 0; } *colon = 0; if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { *colon = ':'; return hdhomerun_device_selector_load_from_str_tail(hds, colon, device_id, &device_addr); } return 0; } if (hdhomerun_device_selector_load_from_str_parse_device_id(ptr, &device_id)) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)&device_addr); } if (hdhomerun_sock_ip_str_to_sockaddr(ptr, &device_addr)) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)&device_addr); } if (hdhomerun_device_selector_load_from_str_parse_dns(ptr, &device_addr)) { return hdhomerun_device_selector_load_from_str_discover(hds, device_id, (struct sockaddr *)&device_addr); } return 0; } 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(_WIN32) && !defined(_WINRT) 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 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 = NULL; 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. */ struct hdhomerun_sock_t *test_sock = hdhomerun_sock_create_udp(); if (!test_sock) { hdhomerun_debug_printf(hds->dbg, "hdhomerun_device_selector_choose_test: device %s in use, failed to create test sock\n", name); return false; } bool 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; } libhdhomerun/hdhomerun_device_selector.h0000664000175000017500000000775713063620312020115 0ustar buildbuild/* * hdhomerun_device_selector.h * * Copyright © 2009-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 /* * 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 LIBHDHOMERUN_API struct hdhomerun_device_selector_t *hdhomerun_device_selector_create(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_device_selector_destroy(struct hdhomerun_device_selector_t *hds, bool destroy_devices); /* * Get the number of devices in the list. */ extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str); extern LIBHDHOMERUN_API int hdhomerun_device_selector_load_from_file(struct hdhomerun_device_selector_t *hds, char *filename); #if defined(_WIN32) && !defined(_WINRT) extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API void hdhomerun_device_selector_add_device(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *hd); extern LIBHDHOMERUN_API 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 LIBHDHOMERUN_API 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 LIBHDHOMERUN_API struct hdhomerun_device_t *hdhomerun_device_selector_choose_and_lock(struct hdhomerun_device_selector_t *hds, struct hdhomerun_device_t *prefered); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_discover.c0000664000175000017500000015214514424512713016566 0ustar buildbuild/* * hdhomerun_discover.c * * Copyright © 2006-2022 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_discover2_device_type_t { struct hdhomerun_discover2_device_type_t *next; uint32_t device_type; }; struct hdhomerun_discover2_device_if_t { struct hdhomerun_discover2_device_if_t *next; struct sockaddr_storage ip_addr; char *base_url; char *lineup_url; char *storage_url; uint8_t priority; }; struct hdhomerun_discover2_device_t { struct hdhomerun_discover2_device_t *next; struct hdhomerun_discover2_device_if_t *if_list; struct hdhomerun_discover2_device_type_t *type_list; uint32_t device_id; uint8_t tuner_count; char *device_auth; char *storage_id; }; struct hdhomerun_discover_sock_t { struct hdhomerun_discover_sock_t *next; struct hdhomerun_discover_sock_t *recv_next; struct hdhomerun_sock_t *sock; struct sockaddr_storage local_ip; uint32_t ifindex; uint32_t ipv4_subnet_mask; bool active; }; struct hdhomerun_discover_t { struct hdhomerun_discover2_device_t *device_list; struct hdhomerun_discover_sock_t *ipv6_socks; struct hdhomerun_discover_sock_t *ipv4_socks; struct hdhomerun_discover_sock_t *ipv6_localhost; struct hdhomerun_discover_sock_t *ipv4_localhost; struct hdhomerun_pkt_t tx_pkt; struct hdhomerun_pkt_t rx_pkt; struct hdhomerun_debug_t *dbg; }; static uint8_t hdhomerun_discover_ipv6_linklocal_multicast_ip[16] = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x76 }; static uint8_t hdhomerun_discover_ipv6_sitelocal_multicast_ip[16] = { 0xFF, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x76 }; static void hdhomerun_discover_sock_free(struct hdhomerun_discover_sock_t *dss) { hdhomerun_sock_destroy(dss->sock); free(dss); } static uint16_t hdhomerun_discover_get_local_port(struct hdhomerun_discover_t *ds) { if (ds->ipv6_socks) { return hdhomerun_sock_getsockname_port(ds->ipv6_socks->sock); } if (ds->ipv4_socks) { return hdhomerun_sock_getsockname_port(ds->ipv4_socks->sock); } if (ds->ipv6_localhost) { return hdhomerun_sock_getsockname_port(ds->ipv6_localhost->sock); } if (ds->ipv4_localhost) { return hdhomerun_sock_getsockname_port(ds->ipv4_localhost->sock); } return 0; } static void hdhomerun_discover_sock_add_ipv6(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) { if (local_ip->sa_family != AF_INET6) { return; } struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)arg; struct sockaddr_in6 *local_ip_in6 = (struct sockaddr_in6 *)local_ip; char local_ip_str[64]; hdhomerun_sock_sockaddr_to_ip_str(local_ip_str, local_ip, true); if (hdhomerun_sock_sockaddr_is_addr(local_ip)) { hdhomerun_debug_printf(ds->dbg, "discover: local ip %s/%u\n", local_ip_str, cidr); } struct hdhomerun_discover_sock_t **pprev = &ds->ipv6_socks; struct hdhomerun_discover_sock_t *p = ds->ipv6_socks; while (p) { struct sockaddr_in6 *p_ip = (struct sockaddr_in6 *)&p->local_ip; if ((p->ifindex == ifindex) && (memcmp(p_ip->sin6_addr.s6_addr, local_ip_in6->sin6_addr.s6_addr, 16) == 0)) { p->active = true; return; } pprev = &p->next; p = p->next; } /* Create socket. */ struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); if (!dss) { hdhomerun_debug_printf(ds->dbg, "discover: resource error\n"); return; } dss->sock = hdhomerun_sock_create_udp_ex(AF_INET6); if (!dss->sock) { hdhomerun_debug_printf(ds->dbg, "discover: failed to allocate socket (%d)\n", hdhomerun_sock_getlasterror()); free(dss); return; } hdhomerun_sock_set_ttl(dss->sock, 64); /* Bind socket. */ local_ip_in6->sin6_port = htons(hdhomerun_discover_get_local_port(ds)); if (!hdhomerun_sock_bind_ex(dss->sock, local_ip, true)) { hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to local ip %s (%d)\n", local_ip_str, hdhomerun_sock_getlasterror()); hdhomerun_discover_sock_free(dss); return; } /* Success. */ memcpy(&dss->local_ip, local_ip_in6, sizeof(struct sockaddr_in6)); dss->ifindex = ifindex; dss->active = true; *pprev = dss; } static void hdhomerun_discover_sock_add_ipv4(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) { if (local_ip->sa_family != AF_INET) { return; } struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)arg; struct sockaddr_in *local_ip_in = (struct sockaddr_in *)local_ip; uint32_t detected_subnet_mask = 0xFFFFFFFF << (32 - cidr); char local_ip_str[64]; hdhomerun_sock_sockaddr_to_ip_str(local_ip_str, local_ip, true); if (hdhomerun_sock_sockaddr_is_addr(local_ip)) { hdhomerun_debug_printf(ds->dbg, "discover: local ip %s/%u\n", local_ip_str, cidr); } struct hdhomerun_discover_sock_t **pprev = &ds->ipv4_socks; struct hdhomerun_discover_sock_t *p = ds->ipv4_socks; while (p) { struct sockaddr_in *p_ip = (struct sockaddr_in *)&p->local_ip; if ((p->ifindex == ifindex) && (p_ip->sin_addr.s_addr == local_ip_in->sin_addr.s_addr) && (p->ipv4_subnet_mask == detected_subnet_mask)) { p->active = true; return; } pprev = &p->next; p = p->next; } /* Create socket. */ struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); if (!dss) { hdhomerun_debug_printf(ds->dbg, "discover: resource error\n"); return; } dss->sock = hdhomerun_sock_create_udp_ex(AF_INET); if (!dss->sock) { hdhomerun_debug_printf(ds->dbg, "discover: failed to allocate socket (%d)\n", hdhomerun_sock_getlasterror()); free(dss); return; } hdhomerun_sock_set_ttl(dss->sock, 64); /* Bind socket. */ local_ip_in->sin_port = htons(hdhomerun_discover_get_local_port(ds)); if (!hdhomerun_sock_bind_ex(dss->sock, local_ip, true)) { hdhomerun_debug_printf(ds->dbg, "discover: failed to bind to local ip %s (%d)\n", local_ip_str, hdhomerun_sock_getlasterror()); hdhomerun_discover_sock_free(dss); return; } /* Success. */ memcpy(&dss->local_ip, local_ip_in, sizeof(struct sockaddr_in)); dss->ifindex = ifindex; dss->ipv4_subnet_mask = detected_subnet_mask; dss->active = true; *pprev = dss; } static void hdhomerun_discover_device_if_free(struct hdhomerun_discover2_device_if_t *device_if) { if (device_if->base_url) { free(device_if->base_url); } if (device_if->lineup_url) { free(device_if->lineup_url); } if (device_if->storage_url) { free(device_if->storage_url); } free(device_if); } static void hdhomerun_discover_device_free(struct hdhomerun_discover2_device_t *device) { while (device->if_list) { struct hdhomerun_discover2_device_if_t *device_if = device->if_list; device->if_list = device_if->next; hdhomerun_discover_device_if_free(device_if); } while (device->type_list) { struct hdhomerun_discover2_device_type_t *device_type = device->type_list; device->type_list = device_type->next; free(device_type); } if (device->device_auth) { free(device->device_auth); } if (device->storage_id) { free(device->storage_id); } free(device); } static void hdhomerun_discover_free_device_list(struct hdhomerun_discover_t *ds) { while (ds->device_list) { struct hdhomerun_discover2_device_t *device = ds->device_list; ds->device_list = device->next; hdhomerun_discover_device_free(device); } } void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds) { hdhomerun_discover_free_device_list(ds); while (ds->ipv6_socks) { struct hdhomerun_discover_sock_t *dss = ds->ipv6_socks; ds->ipv6_socks = dss->next; hdhomerun_discover_sock_free(dss); } while (ds->ipv4_socks) { struct hdhomerun_discover_sock_t *dss = ds->ipv4_socks; ds->ipv4_socks = dss->next; hdhomerun_discover_sock_free(dss); } free(ds); } 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; return ds; } static void hdhomerun_discover_sock_detect_ipv6(struct hdhomerun_discover_t *ds) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv6_socks; if (!default_dss) { /* Create a routable socket (always first entry). */ struct sockaddr_in6 ipv6_addr; memset(&ipv6_addr, 0, sizeof(ipv6_addr)); ipv6_addr.sin6_family = AF_INET6; hdhomerun_discover_sock_add_ipv6(ds, 0, (const struct sockaddr *)&ipv6_addr, 0); default_dss = ds->ipv6_socks; if (!default_dss) { return; } } struct hdhomerun_discover_sock_t *p = default_dss->next; while (p) { p->active = false; p = p->next; } hdhomerun_local_ip_info2(AF_INET6, hdhomerun_discover_sock_add_ipv6, ds); } static void hdhomerun_discover_sock_detect_ipv4(struct hdhomerun_discover_t *ds) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv4_socks; if (!default_dss) { /* Create a routable socket (always first entry). */ struct sockaddr_in ipv4_addr; memset(&ipv4_addr, 0, sizeof(ipv4_addr)); ipv4_addr.sin_family = AF_INET; hdhomerun_discover_sock_add_ipv4(ds, 0, (const struct sockaddr *)&ipv4_addr, 0); default_dss = ds->ipv4_socks; if (!default_dss) { return; } } struct hdhomerun_discover_sock_t *p = default_dss->next; while (p) { p->active = false; p = p->next; } hdhomerun_local_ip_info2(AF_INET, hdhomerun_discover_sock_add_ipv4, ds); } static void hdhomerun_discover_sock_detect_ipv6_localhost(struct hdhomerun_discover_t *ds) { if (ds->ipv6_localhost) { return; } struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); if (!dss) { return; } dss->sock = hdhomerun_sock_create_udp_ex(AF_INET6); if (!dss->sock) { free(dss); return; } struct sockaddr_in6 *sock_addr = (struct sockaddr_in6 *)&dss->local_ip; sock_addr->sin6_family = AF_INET6; sock_addr->sin6_addr.s6_addr[15] = 1; sock_addr->sin6_port = htons(hdhomerun_discover_get_local_port(ds)); if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)sock_addr, true)) { hdhomerun_discover_sock_free(dss); return; } dss->active = true; ds->ipv6_localhost = dss; } static void hdhomerun_discover_sock_detect_ipv4_localhost(struct hdhomerun_discover_t *ds) { if (ds->ipv4_localhost) { return; } struct hdhomerun_discover_sock_t *dss = (struct hdhomerun_discover_sock_t *)calloc(sizeof(struct hdhomerun_discover_sock_t), 1); if (!dss) { return; } dss->sock = hdhomerun_sock_create_udp_ex(AF_INET); if (!dss->sock) { free(dss); return; } struct sockaddr_in *sock_addr = (struct sockaddr_in *)&dss->local_ip; sock_addr->sin_family = AF_INET; sock_addr->sin_addr.s_addr = htonl(0x7F000001); sock_addr->sin_port = htons(hdhomerun_discover_get_local_port(ds)); dss->ipv4_subnet_mask = 0xFF000000; if (!hdhomerun_sock_bind_ex(dss->sock, (const struct sockaddr *)sock_addr, true)) { hdhomerun_discover_sock_free(dss); return; } dss->active = true; ds->ipv4_localhost = dss; } static void hdhomerun_discover_sock_detect_all_from_flags(struct hdhomerun_discover_t *ds, uint32_t flags) { if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { hdhomerun_discover_sock_detect_ipv6_localhost(ds); } if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { hdhomerun_discover_sock_detect_ipv6(ds); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { hdhomerun_discover_sock_detect_ipv4_localhost(ds); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { hdhomerun_discover_sock_detect_ipv4(ds); } } static void hdhomerun_discover_sock_flush_list(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *list) { struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; hdhomerun_pkt_reset(rx_pkt); struct hdhomerun_discover_sock_t *dss = list; while (dss) { if (!dss->active) { dss = dss->next; continue; } while (1) { struct sockaddr_storage remote_addr; size_t length = rx_pkt->limit - rx_pkt->end; if (!hdhomerun_sock_recvfrom_ex(dss->sock, &remote_addr, rx_pkt->end, &length, 0)) { break; } } dss = dss->next; } } static void hdhomerun_discover_sock_recv_list_append_list(struct hdhomerun_discover_sock_t **recv_list, struct hdhomerun_discover_sock_t *list) { struct hdhomerun_discover_sock_t *dss = list; while (dss) { if (!dss->active) { dss = dss->next; continue; } dss->recv_next = *recv_list; *recv_list = dss; dss = dss->next; } } static bool hdhomerun_discover_send_request(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, const struct sockaddr *remote_addr, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) { if (device_types_count == 0) { return false; } struct hdhomerun_pkt_t *tx_pkt = &ds->tx_pkt; hdhomerun_pkt_reset(tx_pkt); if (device_types_count == 1) { 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_types[0]); } else { hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_MULTI_TYPE); hdhomerun_pkt_write_var_length(tx_pkt, device_types_count * 4); while (device_types_count--) { hdhomerun_pkt_write_u32(tx_pkt, *device_types++); } } if (device_id != HDHOMERUN_DEVICE_ID_WILDCARD) { 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); char local_ip_str[64]; char remote_ip_str[64]; hdhomerun_sock_sockaddr_to_ip_str(local_ip_str, (const struct sockaddr *)&dss->local_ip, true); hdhomerun_sock_sockaddr_to_ip_str(remote_ip_str, remote_addr, true); hdhomerun_debug_printf(ds->dbg, "discover: send to %s via %s\n", remote_ip_str, local_ip_str); return hdhomerun_sock_sendto_ex(dss->sock, remote_addr, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0); } static void hdhomerun_discover_send_ipv6_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv6_socks; if (!default_dss) { return; } hdhomerun_discover_sock_flush_list(ds, ds->ipv6_socks); hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv6_socks); struct sockaddr_in6 target_addr_in; memcpy(&target_addr_in, target_addr, sizeof(target_addr_in)); target_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); /* ipv6 linklocal - send out every interface if the scope id isn't provided */ if (hdhomerun_sock_sockaddr_is_ipv6_linklocal(target_addr) && (target_addr_in.sin6_scope_id == 0)) { struct hdhomerun_discover_sock_t *dss = default_dss->next; while (dss) { if (!dss->active) { dss = dss->next; continue; } if (!hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&dss->local_ip)) { dss = dss->next; continue; } struct sockaddr_in6 *local_addr_in = (struct sockaddr_in6 *)&dss->local_ip; target_addr_in.sin6_scope_id = local_addr_in->sin6_scope_id; if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id)) { dss = dss->next; continue; } dss = dss->next; } return; } hdhomerun_discover_send_request(ds, default_dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id); } static void hdhomerun_discover_send_ipv4_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv4_socks; if (!default_dss) { return; } hdhomerun_discover_sock_flush_list(ds, ds->ipv4_socks); hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv4_socks); struct sockaddr_in target_addr_in; memcpy(&target_addr_in, target_addr, sizeof(target_addr_in)); target_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); uint32_t target_ipv4 = ntohl(target_addr_in.sin_addr.s_addr); bool local_subnet_send = false; struct hdhomerun_discover_sock_t *dss = default_dss->next; while (dss) { if (!dss->active) { dss = dss->next; continue; } if (dss->ipv4_subnet_mask == 0) { dss = dss->next; continue; } struct sockaddr_in *local_ip_in = (struct sockaddr_in *)&dss->local_ip; uint32_t local_ipv4 = ntohl(local_ip_in->sin_addr.s_addr); if ((target_ipv4 & dss->ipv4_subnet_mask) != (local_ipv4 & dss->ipv4_subnet_mask)) { dss = dss->next; continue; } if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id)) { dss = dss->next; continue; } local_subnet_send = true; dss = dss->next; } if (local_subnet_send) { return; } if (hdhomerun_sock_sockaddr_is_ipv4_autoip(target_addr)) { return; } hdhomerun_discover_send_request(ds, default_dss, (const struct sockaddr *)&target_addr_in, device_types, device_types_count, device_id); } static void hdhomerun_discover_send_ipv6_multicast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv6_socks; if (!default_dss) { return; } hdhomerun_discover_sock_flush_list(ds, ds->ipv6_socks); hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv6_socks); if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL) { struct sockaddr_in6 sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin6_family = AF_INET6; memcpy(sock_addr_in.sin6_addr.s6_addr, hdhomerun_discover_ipv6_linklocal_multicast_ip, 16); sock_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); struct hdhomerun_discover_sock_t *dss = default_dss->next; while (dss) { if (!dss->active) { dss = dss->next; continue; } if (!hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&dss->local_ip)) { dss = dss->next; continue; } hdhomerun_sock_set_ipv6_multicast_ifindex(dss->sock, dss->ifindex); struct sockaddr_in6 *local_ip_in = (struct sockaddr_in6 *)&dss->local_ip; sock_addr_in.sin6_scope_id = local_ip_in->sin6_scope_id; if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id)) { dss = dss->next; continue; } dss = dss->next; } } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL) { struct sockaddr_in6 sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin6_family = AF_INET6; memcpy(sock_addr_in.sin6_addr.s6_addr, hdhomerun_discover_ipv6_sitelocal_multicast_ip, 16); sock_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); struct hdhomerun_discover_sock_t *dss = default_dss->next; while (dss) { if (!dss->active) { dss = dss->next; continue; } if (hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&dss->local_ip)) { dss = dss->next; continue; } hdhomerun_sock_set_ipv6_multicast_ifindex(dss->sock, dss->ifindex); struct sockaddr_in6 *local_ip_in = (struct sockaddr_in6 *)&dss->local_ip; sock_addr_in.sin6_scope_id = local_ip_in->sin6_scope_id; if (!hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id)) { dss = dss->next; continue; } dss = dss->next; } } } static void hdhomerun_discover_send_ipv4_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *default_dss = ds->ipv4_socks; if (!default_dss) { return; } hdhomerun_discover_sock_flush_list(ds, ds->ipv4_socks); hdhomerun_discover_sock_recv_list_append_list(recv_list, ds->ipv4_socks); struct sockaddr_in sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(0xFFFFFFFF); sock_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); struct hdhomerun_discover_sock_t *dss = default_dss->next; while (dss) { if (!dss->active) { dss = dss->next; continue; } #if defined(IP_ONESBCAST) struct sockaddr_in *local_ip_in = (struct sockaddr_in *)&dss->local_ip; uint32_t local_ip_val = ntohl(local_ip_in->sin_addr.s_addr); uint32_t subnet_broadcast = local_ip_val | ~dss->ipv4_subnet_mask; if ((subnet_broadcast == 0) || (subnet_broadcast >= 0xE0000000)) { dss = dss->next; continue; } hdhomerun_sock_set_ipv4_onesbcast(dss->sock, 1); sock_addr_in.sin_addr.s_addr = htonl(subnet_broadcast); #endif bool send_ok = hdhomerun_discover_send_request(ds, dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); hdhomerun_sock_set_ipv4_onesbcast(dss->sock, 0); if (!send_ok) { dss = dss->next; continue; } dss = dss->next; } } static void hdhomerun_discover_send_ipv6_localhost(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *localhost_dss = ds->ipv6_localhost; if (!localhost_dss) { return; } hdhomerun_discover_sock_flush_list(ds, localhost_dss); hdhomerun_discover_sock_recv_list_append_list(recv_list, localhost_dss); struct sockaddr_in6 sock_addr_in; if (target_addr) { memcpy(&sock_addr_in, target_addr, sizeof(sock_addr_in)); } else { memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin6_family = AF_INET6; sock_addr_in.sin6_addr.s6_addr[15] = 1; } sock_addr_in.sin6_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); hdhomerun_discover_send_request(ds, localhost_dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); } static void hdhomerun_discover_send_ipv4_localhost(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id, struct hdhomerun_discover_sock_t **recv_list) { struct hdhomerun_discover_sock_t *localhost_dss = ds->ipv4_localhost; if (!localhost_dss) { return; } hdhomerun_discover_sock_flush_list(ds, localhost_dss); hdhomerun_discover_sock_recv_list_append_list(recv_list, localhost_dss); struct sockaddr_in sock_addr_in; if (target_addr) { memcpy(&sock_addr_in, target_addr, sizeof(sock_addr_in)); } else { memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(0x7F000001); } sock_addr_in.sin_port = htons(HDHOMERUN_DISCOVER_UDP_PORT); hdhomerun_discover_send_request(ds, localhost_dss, (const struct sockaddr *)&sock_addr_in, device_types, device_types_count, device_id); } static uint8_t hdhomerun_discover_compute_device_if_priority(struct hdhomerun_discover2_device_if_t *device_if) { if (hdhomerun_sock_sockaddr_is_ipv6_localhost((const struct sockaddr *)&device_if->ip_addr)) { return 0; /* highest priority */ } if (hdhomerun_sock_sockaddr_is_ipv4_localhost((const struct sockaddr *)&device_if->ip_addr)) { return 1; } if (device_if->ip_addr.ss_family == AF_INET6) { if (hdhomerun_sock_sockaddr_is_ipv6_global((const struct sockaddr *)&device_if->ip_addr)) { return 2; } if (!hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr)) { return 3; } return 4; } if (!hdhomerun_sock_sockaddr_is_ipv4_autoip((const struct sockaddr *)&device_if->ip_addr)) { return 5; } return 6; } static void hdhomerun_discover_recv_internal_device_type(struct hdhomerun_discover2_device_t *device, struct hdhomerun_pkt_t *rx_pkt) { uint32_t device_type = hdhomerun_pkt_read_u32(rx_pkt); if ((device_type == 0) || (device_type == HDHOMERUN_DEVICE_TYPE_WILDCARD)) { return; } struct hdhomerun_discover2_device_type_t **pprev = &device->type_list; struct hdhomerun_discover2_device_type_t *p = device->type_list; while (p) { if (p->device_type >= device_type) { if (p->device_type > device_type) { break; } return; } pprev = &p->next; p = p->next; } struct hdhomerun_discover2_device_type_t *new_type = (struct hdhomerun_discover2_device_type_t *)calloc(sizeof(struct hdhomerun_discover2_device_type_t), 1); if (!new_type) { return; } new_type->device_type = device_type; new_type->next = *pprev; *pprev = new_type; } static void hdhomerun_discover_recv_internal_string(char **poutput, struct hdhomerun_pkt_t *rx_pkt, size_t len) { if (*poutput) { return; } char *str = (char *)malloc(len + 1); if (!str) { return; } hdhomerun_pkt_read_mem(rx_pkt, str, len); str[len] = 0; *poutput = str; } static void hdhomerun_discover_recv_internal_auth_bin(char **poutput, struct hdhomerun_pkt_t *rx_pkt, size_t len) { if (*poutput) { return; } if (len != 18) { return; } char *str = (char *)malloc(24 + 1); if (!str) { return; } int i; for (i = 0; i < 24; i += 4) { static const char hdhomerun_discover_recv_base64_encode_table[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 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; str[i + 0] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 18) & 0x3F]; str[i + 1] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 12) & 0x3F]; str[i + 2] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 6) & 0x3F]; str[i + 3] = hdhomerun_discover_recv_base64_encode_table[(raw24 >> 0) & 0x3F]; } str[24] = 0; *poutput = str; } static void hdhomerun_discover_recv_fixup_tuner_count(struct hdhomerun_discover_t *ds, struct hdhomerun_discover2_device_t *device) { if (!hdhomerun_discover2_device_is_type(device, HDHOMERUN_DEVICE_TYPE_TUNER)) { return; } if (device->tuner_count > 0) { return; } switch (device->device_id >> 20) { case 0x102: device->tuner_count = 1; break; case 0x100: case 0x101: case 0x121: device->tuner_count = 2; break; default: break; } } static void hdhomerun_discover_recv_fixup_base_url(struct hdhomerun_discover_t *ds, struct hdhomerun_discover2_device_t *device) { if (!hdhomerun_discover2_device_is_type(device, HDHOMERUN_DEVICE_TYPE_TUNER)) { return; } struct hdhomerun_discover2_device_if_t *device_if = device->if_list; if (device_if->base_url) { return; } if (device_if->ip_addr.ss_family != AF_INET) { return; } device_if->base_url = (char *)malloc(32); if (!device_if->base_url) { return; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; uint32_t ip = ntohl(sock_addr_in->sin_addr.s_addr); hdhomerun_sprintf(device_if->base_url, device_if->base_url + 32, "http://%u.%u.%u.%u:80", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, (ip >> 0) & 0xFF ); } static bool hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_pkt_t *rx_pkt, struct hdhomerun_discover2_device_t *device) { uint16_t type; if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) { return false; } if (type != HDHOMERUN_TYPE_DISCOVER_RPY) { return false; } struct hdhomerun_discover2_device_if_t *device_if = device->if_list; 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_DEVICE_TYPE: if (len != 4) { break; } hdhomerun_discover_recv_internal_device_type(device, rx_pkt); break; case HDHOMERUN_TAG_MULTI_TYPE: while (len >= 4) { hdhomerun_discover_recv_internal_device_type(device, rx_pkt); len -= 4; } break; case HDHOMERUN_TAG_DEVICE_ID: if (len != 4) { break; } device->device_id = hdhomerun_pkt_read_u32(rx_pkt); break; case HDHOMERUN_TAG_TUNER_COUNT: if (len != 1) { break; } device->tuner_count = hdhomerun_pkt_read_u8(rx_pkt); break; case HDHOMERUN_TAG_DEVICE_AUTH_STR: hdhomerun_discover_recv_internal_string(&device->device_auth, rx_pkt, len); break; case HDHOMERUN_TAG_DEVICE_AUTH_BIN_DEPRECATED: hdhomerun_discover_recv_internal_auth_bin(&device->device_auth, rx_pkt, len); break; case HDHOMERUN_TAG_BASE_URL: hdhomerun_discover_recv_internal_string(&device_if->base_url, rx_pkt, len); break; case HDHOMERUN_TAG_STORAGE_ID: hdhomerun_discover_recv_internal_string(&device->storage_id, rx_pkt, len); break; case HDHOMERUN_TAG_LINEUP_URL: hdhomerun_discover_recv_internal_string(&device_if->lineup_url, rx_pkt, len); break; case HDHOMERUN_TAG_STORAGE_URL: hdhomerun_discover_recv_internal_string(&device_if->storage_url, rx_pkt, len); break; default: break; } rx_pkt->pos = next; } if (!device->type_list) { return false; } /* * Fixup for old firmware. */ hdhomerun_discover_recv_fixup_tuner_count(ds, device); hdhomerun_discover_recv_fixup_base_url(ds, device); /* * Success */ device_if->priority = hdhomerun_discover_compute_device_if_priority(device_if); return true; } static void hdhomerun_discover_recv_merge_device_type(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_type_t *new_type) { struct hdhomerun_discover2_device_type_t **pprev = &device->type_list; struct hdhomerun_discover2_device_type_t *p = device->type_list; while (p) { if (p->device_type >= new_type->device_type) { if (p->device_type > new_type->device_type) { break; } free(new_type); return; } pprev = &p->next; p = p->next; } new_type->next = *pprev; *pprev = new_type; } static void hdhomerun_discover_recv_merge_device_types(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_type_t *type_list) { while (type_list) { struct hdhomerun_discover2_device_type_t *new_type = type_list; type_list = new_type->next; hdhomerun_discover_recv_merge_device_type(device, new_type); } } static void hdhomerun_discover_recv_merge_device_if(struct hdhomerun_discover2_device_t *device, struct hdhomerun_discover2_device_if_t *device_if) { struct hdhomerun_discover2_device_if_t **pprev = &device->if_list; struct hdhomerun_discover2_device_if_t *p = device->if_list; while (p) { if (p->priority < device_if->priority) { pprev = &p->next; p = p->next; continue; } if (p->priority > device_if->priority) { break; } char *p_base_url = p->base_url; char *device_if_base_url = device_if->base_url; if (!p_base_url) { p_base_url = ""; } if (!device_if_base_url) { device_if_base_url = ""; } int cmp = strcmp(p_base_url, device_if_base_url); if (cmp < 0) { pprev = &p->next; p = p->next; continue; } if (cmp > 0) { break; } /* Duplicate */ hdhomerun_discover_device_if_free(device_if); return; } device_if->next = *pprev; *pprev = device_if; } static void hdhomerun_discover_recv_merge_device(struct hdhomerun_discover_t *ds, struct hdhomerun_discover2_device_t *device) { struct hdhomerun_discover2_device_t **pprev = &ds->device_list; struct hdhomerun_discover2_device_t *p = ds->device_list; while (p) { if (p->device_id < device->device_id) { pprev = &p->next; p = p->next; continue; } if (p->device_id > device->device_id) { break; } char *p_storage_id = p->storage_id; char *device_storage_id = device->storage_id; if (!p_storage_id) { p_storage_id = ""; } if (!device_storage_id) { device_storage_id = ""; } int cmp = strcmp(p_storage_id, device_storage_id); if (cmp < 0) { pprev = &p->next; p = p->next; continue; } if (cmp > 0) { break; } /* Merge */ struct hdhomerun_discover2_device_type_t *type_list = device->type_list; device->type_list = NULL; hdhomerun_discover_recv_merge_device_types(p, type_list); struct hdhomerun_discover2_device_if_t *device_if = device->if_list; device->if_list = NULL; hdhomerun_discover_recv_merge_device_if(p, device_if); hdhomerun_discover_device_free(device); return; } device->next = *pprev; *pprev = device; } static bool hdhomerun_discover_recvfrom_match_flags(const struct sockaddr *remote_addr, uint32_t flags) { if (remote_addr->sa_family == AF_INET6) { if (hdhomerun_sock_sockaddr_is_ipv6_localhost(remote_addr)) { return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) != 0; } if (hdhomerun_sock_sockaddr_is_ipv6_linklocal(remote_addr)) { return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL) != 0; } return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL) != 0; } if (remote_addr->sa_family == AF_INET) { if (hdhomerun_sock_sockaddr_is_ipv4_localhost(remote_addr)) { return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) != 0; } return (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) != 0; } return false; } static bool hdhomerun_discover_recvfrom_match_device_type(struct hdhomerun_discover2_device_t *device, const uint32_t device_types_match[], size_t device_types_count) { if (device_types_count == 0) { return false; } if (device_types_match[0] == HDHOMERUN_DEVICE_TYPE_WILDCARD) { return true; } while (device_types_count--) { if (hdhomerun_discover2_device_is_type(device, *device_types_match++)) { return true; } } return false; } static bool hdhomerun_discover_recvfrom_match_device_id(struct hdhomerun_discover2_device_t *device, uint32_t device_id_match) { if (device_id_match == HDHOMERUN_DEVICE_ID_WILDCARD) { return true; } return (device->device_id == device_id_match); } static bool hdhomerun_discover_recvfrom(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t flags, const uint32_t device_types_match[], size_t device_types_count, uint32_t device_id_match) { struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt; hdhomerun_pkt_reset(rx_pkt); bool activity = false; struct sockaddr_storage remote_addr; size_t length = rx_pkt->limit - rx_pkt->end; if (!hdhomerun_sock_recvfrom_ex(dss->sock, &remote_addr, rx_pkt->end, &length, 0)) { return activity; } rx_pkt->end += length; activity = true; char local_ip_str[64]; char remote_ip_str[64]; hdhomerun_sock_sockaddr_to_ip_str(local_ip_str, (const struct sockaddr *)&dss->local_ip, true); hdhomerun_sock_sockaddr_to_ip_str(remote_ip_str, (const struct sockaddr *)&remote_addr, true); hdhomerun_debug_printf(ds->dbg, "discover: recv from %s via %s\n", remote_ip_str, local_ip_str); if (!hdhomerun_discover_recvfrom_match_flags((const struct sockaddr *)&remote_addr, flags)) { return activity; } struct hdhomerun_discover2_device_t *device = (struct hdhomerun_discover2_device_t *)calloc(sizeof(struct hdhomerun_discover2_device_t), 1); struct hdhomerun_discover2_device_if_t *device_if = (struct hdhomerun_discover2_device_if_t *)calloc(sizeof(struct hdhomerun_discover2_device_if_t), 1); if (!device || !device_if) { if (device) { free(device); } if (device_if) { free(device_if); } return activity; } device->if_list = device_if; device_if->ip_addr = remote_addr; if (!hdhomerun_discover_recv_internal(ds, rx_pkt, device)) { hdhomerun_discover_device_free(device); return activity; } if (!hdhomerun_discover_recvfrom_match_device_type(device, device_types_match, device_types_count)) { hdhomerun_discover_device_free(device); return activity; } if (!hdhomerun_discover_recvfrom_match_device_id(device, device_id_match)) { hdhomerun_discover_device_free(device); return activity; } hdhomerun_discover_recv_merge_device(ds, device); return activity; } static void hdhomerun_discover2_find_devices_debug_log_results(struct hdhomerun_discover_t *ds) { if (!ds->dbg) { return; } struct hdhomerun_discover2_device_t *device = ds->device_list; while (device) { struct hdhomerun_discover2_device_if_t *device_if = device->if_list; if (device->device_id) { hdhomerun_debug_printf(ds->dbg, "discover: found %08X %s\n", device->device_id, device_if->base_url); device = device->next; continue; } if (device->storage_id) { hdhomerun_debug_printf(ds->dbg, "discover: found %s %s\n", device->storage_id, device_if->base_url); device = device->next; continue; } hdhomerun_debug_printf(ds->dbg, "discover: found %s\n", device_if->base_url); device = device->next; } } static uint32_t hdhomerun_discover2_find_devices_targeted_flags(const struct sockaddr *target_addr) { if (target_addr->sa_family == AF_INET6) { if (hdhomerun_sock_sockaddr_is_ipv6_localhost(target_addr)) { return HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST; } if (hdhomerun_sock_sockaddr_is_ipv6_linklocal(target_addr)) { return HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL; } return HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL; } if (target_addr->sa_family == AF_INET) { if (hdhomerun_sock_sockaddr_is_ipv4_localhost(target_addr)) { return HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST; } return HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL; } return 0; } static int hdhomerun_discover2_find_devices_targeted_internal(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) { uint32_t flags = hdhomerun_discover2_find_devices_targeted_flags(target_addr); if (flags == 0) { return -1; } hdhomerun_discover_free_device_list(ds); hdhomerun_discover_sock_detect_all_from_flags(ds, flags); int attempt; for (attempt = 0; attempt < 2; attempt++) { struct hdhomerun_discover_sock_t *recv_list = NULL; if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { hdhomerun_discover_send_ipv6_localhost(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { hdhomerun_discover_send_ipv6_targeted(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { hdhomerun_discover_send_ipv4_localhost(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { hdhomerun_discover_send_ipv4_targeted(ds, target_addr, flags, device_types, device_types_count, device_id, &recv_list); } if (!recv_list) { return -1; } uint64_t timeout = getcurrenttime() + 200; while (1) { bool activity = false; struct hdhomerun_discover_sock_t *dss = recv_list; while (dss) { activity |= hdhomerun_discover_recvfrom(ds, dss, flags, device_types, device_types_count, device_id); dss = dss->recv_next; } if (ds->device_list) { hdhomerun_discover2_find_devices_debug_log_results(ds); return 1; } if (activity) { continue; } if (getcurrenttime() >= timeout) { break; } msleep_approx(16); } } hdhomerun_discover2_find_devices_debug_log_results(ds); return (ds->device_list) ? 1 : 0; } static int hdhomerun_discover2_find_devices_broadcast_internal(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count, uint32_t device_id) { hdhomerun_discover_free_device_list(ds); hdhomerun_discover_sock_detect_all_from_flags(ds, flags); int attempt; for (attempt = 0; attempt < 2; attempt++) { struct hdhomerun_discover_sock_t *recv_list = NULL; if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST) { hdhomerun_discover_send_ipv6_localhost(ds, NULL, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & (HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL)) { hdhomerun_discover_send_ipv6_multicast(ds, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST) { hdhomerun_discover_send_ipv4_localhost(ds, NULL, flags, device_types, device_types_count, device_id, &recv_list); } if (flags & HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL) { hdhomerun_discover_send_ipv4_broadcast(ds, flags, device_types, device_types_count, device_id, &recv_list); } if (!recv_list) { return -1; } uint64_t timeout = getcurrenttime() + 200; while (1) { bool activity = false; struct hdhomerun_discover_sock_t *dss = recv_list; while (dss) { activity |= hdhomerun_discover_recvfrom(ds, dss, flags, device_types, device_types_count, device_id); dss = dss->recv_next; } if ((device_id != HDHOMERUN_DEVICE_ID_WILDCARD) && ds->device_list) { hdhomerun_discover2_find_devices_debug_log_results(ds); return 1; } if (activity) { continue; } if (getcurrenttime() >= timeout) { break; } msleep_approx(16); } } hdhomerun_discover2_find_devices_debug_log_results(ds); return (ds->device_list) ? 1 : 0; } int hdhomerun_discover2_find_devices_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count) { return hdhomerun_discover2_find_devices_targeted_internal(ds, target_addr, device_types, device_types_count, HDHOMERUN_DEVICE_ID_WILDCARD); } int hdhomerun_discover2_find_devices_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, const uint32_t device_types[], size_t device_types_count) { return hdhomerun_discover2_find_devices_broadcast_internal(ds, flags, device_types, device_types_count, HDHOMERUN_DEVICE_ID_WILDCARD); } int hdhomerun_discover2_find_device_id_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t device_id) { if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { return -1; } if (!hdhomerun_discover_validate_device_id(device_id)) { return - 1; } uint32_t device_types[1]; device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; return hdhomerun_discover2_find_devices_targeted_internal(ds, target_addr, device_types, 1, device_id); } int hdhomerun_discover2_find_device_id_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t device_id) { if ((device_id == 0) || (device_id == HDHOMERUN_DEVICE_ID_WILDCARD)) { return -1; } if (!hdhomerun_discover_validate_device_id(device_id)) { return -1; } uint32_t device_types[1]; device_types[0] = HDHOMERUN_DEVICE_TYPE_WILDCARD; return hdhomerun_discover2_find_devices_broadcast_internal(ds, flags, device_types, 1, device_id); } struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_first(struct hdhomerun_discover_t *ds) { return ds->device_list; } struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_next(struct hdhomerun_discover2_device_t *device) { return device->next; } struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_first(struct hdhomerun_discover2_device_t *device) { return device->if_list; } struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_next(struct hdhomerun_discover2_device_if_t *device_if) { return device_if->next; } bool hdhomerun_discover2_device_is_legacy(struct hdhomerun_discover2_device_t *device) { switch (device->device_id >> 20) { case 0x100: /* TECH-US/TECH3-US */ return (device->device_id < 0x10040000); case 0x120: /* TECH3-EU */ return (device->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; } } bool hdhomerun_discover2_device_is_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type) { struct hdhomerun_discover2_device_type_t *p = device->type_list; while (p) { if (p->device_type == device_type) { return true; } p = p->next; } return false; } uint32_t hdhomerun_discover2_device_get_device_id(struct hdhomerun_discover2_device_t *device) { return device->device_id; } const char *hdhomerun_discover2_device_get_storage_id(struct hdhomerun_discover2_device_t *device) { return device->storage_id; } uint8_t hdhomerun_discover2_device_get_tuner_count(struct hdhomerun_discover2_device_t *device) { return device->tuner_count; } const char *hdhomerun_discover2_device_get_device_auth(struct hdhomerun_discover2_device_t *device) { return device->device_auth; } bool hdhomerun_discover2_device_if_addr_is_ipv4(struct hdhomerun_discover2_device_if_t *device_if) { return (device_if->ip_addr.ss_family == AF_INET); } bool hdhomerun_discover2_device_if_addr_is_ipv6_linklocal(struct hdhomerun_discover2_device_if_t *device_if) { return hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr); } uint32_t hdhomerun_discover2_device_if_get_ipv6_linklocal_scope_id(struct hdhomerun_discover2_device_if_t *device_if) { if (!hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr)) { return 0; } const struct sockaddr_in6 *ip_addr_in6 = (const struct sockaddr_in6 *)&device_if->ip_addr; return ip_addr_in6->sin6_scope_id; } void hdhomerun_discover2_device_if_get_ip_addr(struct hdhomerun_discover2_device_if_t *device_if, struct sockaddr_storage *ip_addr) { *ip_addr = device_if->ip_addr; } const char *hdhomerun_discover2_device_if_get_base_url(struct hdhomerun_discover2_device_if_t *device_if) { return device_if->base_url; } const char *hdhomerun_discover2_device_if_get_lineup_url(struct hdhomerun_discover2_device_if_t *device_if) { return device_if->lineup_url; } const char *hdhomerun_discover2_device_if_get_storage_url(struct hdhomerun_discover2_device_if_t *device_if) { return device_if->storage_url; } static void hdhomerun_discover_v2v3_strcpy(char *output, size_t size, const char *src) { if (!src) { output[0] = 0; return; } size_t src_len = strlen(src); if (src_len >= size) { output[0] = 0; return; } memcpy(output, src, src_len + 1); } static uint32_t hdhomerun_discover_v2v3_device_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type_match) { if (device_type_match == HDHOMERUN_DEVICE_TYPE_WILDCARD) { return device->type_list->device_type; } if (hdhomerun_discover2_device_is_type(device, device_type_match)) { return device_type_match; } return device->type_list->device_type; } int hdhomerun_discover_find_devices_v3(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count) { if (target_ip == 0) { target_ip = 0xFFFFFFFF; } if (device_type_match == 0) { device_type_match = HDHOMERUN_DEVICE_TYPE_WILDCARD; } if (device_id_match == 0) { device_id_match = HDHOMERUN_DEVICE_ID_WILDCARD; } if (!hdhomerun_discover_validate_device_id(device_id_match)) { return -1; } uint32_t device_types[1]; device_types[0] = device_type_match; int ret; if (target_ip == 0xFFFFFFFF) { ret = hdhomerun_discover2_find_devices_broadcast_internal(ds, HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL, device_types, 1, device_id_match); } else { struct sockaddr_in sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(target_ip); ret = hdhomerun_discover2_find_devices_targeted_internal(ds, (const struct sockaddr *)&sock_addr_in, device_types, 1, device_id_match); } if (ret <= 0) { return ret; } struct hdhomerun_discover_device_v3_t *result = result_list; struct hdhomerun_discover2_device_t *device = ds->device_list; int count = 0; while (device && (count < max_count)) { struct hdhomerun_discover2_device_if_t *device_if = device->if_list; while (device_if) { if (device_if->ip_addr.ss_family == AF_INET) { break; } device_if = device_if->next; } if (!device_if) { device = device->next; continue; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; result->ip_addr = ntohl(sock_addr_in->sin_addr.s_addr); result->device_type = hdhomerun_discover_v2v3_device_type(device, device_type_match); result->device_id = device->device_id; result->tuner_count = device->tuner_count; result->is_legacy = hdhomerun_discover2_device_is_legacy(device); hdhomerun_discover_v2v3_strcpy(result->storage_id, sizeof(result->storage_id), device->storage_id); hdhomerun_discover_v2v3_strcpy(result->device_auth, sizeof(result->device_auth), device->device_auth); hdhomerun_discover_v2v3_strcpy(result->base_url, sizeof(result->base_url), device_if->base_url); hdhomerun_discover_v2v3_strcpy(result->lineup_url, sizeof(result->lineup_url), device_if->lineup_url); hdhomerun_discover_v2v3_strcpy(result->storage_url, sizeof(result->storage_url), device_if->storage_url); count++; result++; device = device->next; } return count; } int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count) { if (target_ip == 0) { target_ip = 0xFFFFFFFF; } if (device_type_match == 0) { device_type_match = HDHOMERUN_DEVICE_TYPE_WILDCARD; } if (device_id_match == 0) { device_id_match = HDHOMERUN_DEVICE_ID_WILDCARD; } if (!hdhomerun_discover_validate_device_id(device_id_match)) { return -1; } uint32_t device_types[1]; device_types[0] = device_type_match; int ret; if (target_ip == 0xFFFFFFFF) { ret = hdhomerun_discover2_find_devices_broadcast_internal(ds, HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL, device_types, 1, device_id_match); } else { struct sockaddr_in sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(target_ip); ret = hdhomerun_discover2_find_devices_targeted_internal(ds, (const struct sockaddr *)&sock_addr_in, device_types, 1, device_id_match); } if (ret <= 0) { return ret; } struct hdhomerun_discover_device_t *result = result_list; struct hdhomerun_discover2_device_t *device = ds->device_list; int count = 0; while (device && (count < max_count)) { struct hdhomerun_discover2_device_if_t *device_if = device->if_list; while (device_if) { if (device_if->ip_addr.ss_family == AF_INET) { break; } device_if = device_if->next; } if (!device_if) { device = device->next; continue; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&device_if->ip_addr; result->ip_addr = ntohl(sock_addr_in->sin_addr.s_addr); result->device_type = hdhomerun_discover_v2v3_device_type(device, device_type_match); result->device_id = device->device_id; result->tuner_count = device->tuner_count; result->is_legacy = hdhomerun_discover2_device_is_legacy(device); hdhomerun_discover_v2v3_strcpy(result->device_auth, sizeof(result->device_auth), device->device_auth); hdhomerun_discover_v2v3_strcpy(result->base_url, sizeof(result->base_url), device_if->base_url); count++; result++; device = device->next; } return count; } int hdhomerun_discover_find_devices_custom_v3(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count) { struct hdhomerun_discover_t *ds = hdhomerun_discover_create(NULL); if (!ds) { return -1; } int ret = hdhomerun_discover_find_devices_v3(ds, target_ip, device_type_match, device_id_match, result_list, max_count); hdhomerun_discover_destroy(ds); return ret; } int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count) { 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_match, device_id_match, result_list, max_count); hdhomerun_discover_destroy(ds); return ret; } bool 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 hdhomerun_discover_is_ip_multicast(uint32_t ip_addr) { struct sockaddr_in addr_in; memset(&addr_in, 0, sizeof(struct sockaddr_in)); addr_in.sin_family = AF_INET; addr_in.sin_addr.s_addr = htonl(ip_addr); return hdhomerun_sock_sockaddr_is_multicast((const struct sockaddr *)&addr_in); } bool hdhomerun_discover_is_ip_multicast_ex(const struct sockaddr *ip_addr) { return hdhomerun_sock_sockaddr_is_multicast(ip_addr); } libhdhomerun/hdhomerun_discover.h0000664000175000017500000002116314357356374016604 0ustar buildbuild/* * hdhomerun_discover.h * * Copyright © 2006-2022 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_DISCOVER_FLAGS_IPV4_GENERAL (1 << 0) #define HDHOMERUN_DISCOVER_FLAGS_IPV4_LOCALHOST (1 << 1) #define HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL (1 << 2) #define HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL (1 << 3) #define HDHOMERUN_DISCOVER_FLAGS_IPV6_LOCALHOST (1 << 4) struct hdhomerun_discover2_device_t; struct hdhomerun_discover2_device_if_t; /* * Discover object. * * May be maintained and reused across the lifespan of the app or may be created and destroyed for each discover. * If the app polls discover the same discover instance should be reused to avoid burning through local IP ports. */ extern LIBHDHOMERUN_API struct hdhomerun_discover_t *hdhomerun_discover_create(struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds); /* * Discover API: * * Use hdhomerun_discover2_find_devices_broadcast() to find all devices on the local subnet. * flags: IPv4 only use HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL * flags: IPv4 and IPv6 use HDHOMERUN_DISCOVER_FLAGS_IPV4_GENERAL | HDHOMERUN_DISCOVER_FLAGS_IPV6_GENERAL * flags: Linklocal IPv6 requires special handling (scope id) - only include HDHOMERUN_DISCOVER_FLAGS_IPV6_LINKLOCAL if the app tracks scope id needed for linklocal IPv6. * device_types: do not use HDHOMERUN_DEVICE_TYPE_WILDCARD, instead provide an array of types the app wants to discover, for example: * uint32_t device_types[2]; * device_types[0] = HDHOMERUN_DEVICE_TYPE_TUNER; * device_types[1] = HDHOMERUN_DEVICE_TYPE_STORAGE; * Returns 1 when one or more devices are found. * Results 0 when no devices are found. * Returns -1 on error. */ extern LIBHDHOMERUN_API int hdhomerun_discover2_find_devices_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t const device_types[], size_t device_types_count); extern LIBHDHOMERUN_API int hdhomerun_discover2_find_devices_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, const uint32_t device_types[], size_t device_types_count); extern LIBHDHOMERUN_API int hdhomerun_discover2_find_device_id_broadcast(struct hdhomerun_discover_t *ds, uint32_t flags, uint32_t device_id); extern LIBHDHOMERUN_API int hdhomerun_discover2_find_device_id_targeted(struct hdhomerun_discover_t *ds, const struct sockaddr *target_addr, uint32_t device_id); /* * Discover result access API. * * Use hdhomerun_discover2_iter_device_first() and hdhomerun_discover2_iter_device_next() to iterate through discover results. * Use hdhomerun_discover2_device_xxx() APIs to query properties of the device. * * Use hdhomerun_discover2_iter_device_if_first() to select the first (best) set of device URLs. * Use hdhomerun_discover2_device_if_xxx() to query specific device URLs. * * In most cases hdhomerun_discover2_iter_device_if_next() will not be used as the app should use the first set of URLs. * * Results are available until a new discover is run on the same discover object or until the discover object is destroyed. Strings shoud be copied. */ extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_first(struct hdhomerun_discover_t *ds); extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_t *hdhomerun_discover2_iter_device_next(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_first(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API struct hdhomerun_discover2_device_if_t *hdhomerun_discover2_iter_device_if_next(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_is_legacy(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_is_type(struct hdhomerun_discover2_device_t *device, uint32_t device_type); extern LIBHDHOMERUN_API uint32_t hdhomerun_discover2_device_get_device_id(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_get_storage_id(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API uint8_t hdhomerun_discover2_device_get_tuner_count(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_get_device_auth(struct hdhomerun_discover2_device_t *device); extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_if_addr_is_ipv4(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API bool hdhomerun_discover2_device_if_addr_is_ipv6_linklocal(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API uint32_t hdhomerun_discover2_device_if_get_ipv6_linklocal_scope_id(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API void hdhomerun_discover2_device_if_get_ip_addr(struct hdhomerun_discover2_device_if_t *device_if, struct sockaddr_storage *ip_addr); extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_base_url(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_lineup_url(struct hdhomerun_discover2_device_if_t *device_if); extern LIBHDHOMERUN_API const char *hdhomerun_discover2_device_if_get_storage_url(struct hdhomerun_discover2_device_if_t *device_if); /* * 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 LIBHDHOMERUN_API bool 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 LIBHDHOMERUN_API bool hdhomerun_discover_is_ip_multicast(uint32_t ip_addr); extern LIBHDHOMERUN_API bool hdhomerun_discover_is_ip_multicast_ex(const struct sockaddr *ip_addr); /* * Legacy API - not for new applications. * * 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 unless max_count is 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. */ struct hdhomerun_discover_device_t { uint32_t ip_addr; uint32_t device_type; uint32_t device_id; uint8_t tuner_count; bool is_legacy; char device_auth[25]; char base_url[29]; }; struct hdhomerun_discover_device_v3_t { uint32_t ip_addr; uint32_t device_type; uint32_t device_id; uint8_t tuner_count; bool is_legacy; char device_auth[25]; char base_url[29]; char storage_id[37]; char lineup_url[128]; char storage_url[128]; }; extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_custom_v2(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_custom_v3(uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_v2(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_t result_list[], int max_count); extern LIBHDHOMERUN_API int hdhomerun_discover_find_devices_v3(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type_match, uint32_t device_id_match, struct hdhomerun_discover_device_v3_t result_list[], int max_count); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_os.h0000664000175000017500000000165613063620312015367 0ustar buildbuild/* * hdhomerun_os.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 */ #if defined(_WIN32) #include "hdhomerun_os_windows.h" #else #include "hdhomerun_os_posix.h" #endif libhdhomerun/hdhomerun_os_posix.c0000664000175000017500000001364114424512713016610 0ustar buildbuild/* * hdhomerun_os_posix.c * * Copyright © 2006-2017 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 static pthread_once_t clock_monotonic_once = PTHREAD_ONCE_INIT; static clock_serv_t clock_monotonic_clock_serv; static void clock_monotonic_init(void) { host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock_monotonic_clock_serv); } static inline void clock_monotonic_timespec(struct timespec *ts) { pthread_once(&clock_monotonic_once, clock_monotonic_init); struct mach_timespec mt; clock_get_time(clock_monotonic_clock_serv, &mt); ts->tv_nsec = mt.tv_nsec; ts->tv_sec = mt.tv_sec; } static inline void clock_realtime_timespec(struct timespec *ts) { struct timeval tv; gettimeofday(&tv, NULL); ts->tv_nsec = tv.tv_usec * 1000; ts->tv_sec = tv.tv_sec; } #else static inline void clock_monotonic_timespec(struct timespec *ts) { clock_gettime(CLOCK_MONOTONIC, ts); } static inline void clock_realtime_timespec(struct timespec *ts) { clock_gettime(CLOCK_REALTIME, ts); } #endif static pthread_once_t random_getbytes_once = PTHREAD_ONCE_INIT; static FILE *random_getbytes_fp = NULL; static void random_getbytes_init(void) { random_getbytes_fp = fopen("/dev/urandom", "rb"); } void random_getbytes(uint8_t *out, size_t length) { pthread_once(&random_getbytes_once, random_getbytes_init); if (!random_getbytes_fp) { exit(1); } if (fread(out, 1, length, random_getbytes_fp) != length) { exit(1); } } uint32_t random_get32(void) { uint32_t Result; random_getbytes((uint8_t *)&Result, 4); return Result; } uint64_t getcurrenttime(void) { struct timespec ts; clock_monotonic_timespec(&ts); return ((uint64_t)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); } uint64_t timer_get_hires_ticks(void) { struct timespec ts; clock_monotonic_timespec(&ts); return ((uint64_t)ts.tv_sec * 1000000) + (ts.tv_nsec / 1000); } uint64_t timer_get_hires_frequency(void) { return 1000000; } void msleep_approx(uint64_t ms) { uint64_t delay_s = ms / 1000; if (delay_s > 0) { sleep((unsigned int)delay_s); ms -= delay_s * 1000; } uint64_t delay_us = ms * 1000; if (delay_us > 0) { usleep((useconds_t)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); } } struct thread_task_execute_args_t { thread_task_func_t func; void *arg; }; static void *thread_task_execute(void *arg) { struct thread_task_execute_args_t *execute_args = (struct thread_task_execute_args_t *)arg; execute_args->func(execute_args->arg); free(execute_args); return NULL; } bool thread_task_create(thread_task_t *tid, thread_task_func_t func, void *arg) { struct thread_task_execute_args_t *execute_args = (struct thread_task_execute_args_t *)malloc(sizeof(struct thread_task_execute_args_t)); if (!execute_args) { return false; } execute_args->func = func; execute_args->arg = arg; if (pthread_create(tid, NULL, thread_task_execute, execute_args) != 0) { free(execute_args); return false; } return true; } void thread_task_join(thread_task_t tid) { pthread_join(tid, NULL); } void thread_mutex_init(thread_mutex_t *mutex) { pthread_mutex_init(mutex, NULL); } void thread_mutex_dispose(pthread_mutex_t *mutex) { } void thread_mutex_lock(thread_mutex_t *mutex) { pthread_mutex_lock(mutex); } void thread_mutex_unlock(thread_mutex_t *mutex) { pthread_mutex_unlock(mutex); } void thread_cond_init(thread_cond_t *cond) { cond->signaled = false; pthread_mutex_init(&cond->lock, NULL); pthread_cond_init(&cond->cond, NULL); } void thread_cond_dispose(thread_cond_t *cond) { } void thread_cond_signal(thread_cond_t *cond) { pthread_mutex_lock(&cond->lock); cond->signaled = true; pthread_cond_signal(&cond->cond); pthread_mutex_unlock(&cond->lock); } void thread_cond_wait(thread_cond_t *cond) { pthread_mutex_lock(&cond->lock); if (!cond->signaled) { pthread_cond_wait(&cond->cond, &cond->lock); } cond->signaled = false; pthread_mutex_unlock(&cond->lock); } bool thread_cond_wait_with_timeout(thread_cond_t *cond, uint64_t max_wait_time) { pthread_mutex_lock(&cond->lock); if (!cond->signaled) { struct timespec ts; clock_realtime_timespec(&ts); uint64_t tv_nsec = (uint64_t)ts.tv_nsec + (max_wait_time * 1000000); ts.tv_nsec = (long)(tv_nsec % 1000000000); ts.tv_sec += (time_t)(tv_nsec / 1000000000); if (pthread_cond_timedwait(&cond->cond, &cond->lock, &ts) != 0) { pthread_mutex_unlock(&cond->lock); return false; } } cond->signaled = false; pthread_mutex_unlock(&cond->lock); return true; } bool 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 hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...) { va_list ap; va_start(ap, fmt); bool result = hdhomerun_vsprintf(buffer, end, fmt, ap); va_end(ap); return result; } libhdhomerun/hdhomerun_os_posix.h0000664000175000017500000000631214424512713016612 0ustar buildbuild/* * hdhomerun_os_posix.h * * Copyright © 2006-2017 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 #include #include typedef void (*sig_t)(int); typedef void (*thread_task_func_t)(void *arg); typedef pthread_t thread_task_t; typedef pthread_mutex_t thread_mutex_t; typedef struct { volatile bool signaled; pthread_mutex_t lock; pthread_cond_t cond; } thread_cond_t; #define LIBHDHOMERUN_API #define LIBHDHOMERUN_PACKED(x) x __attribute__((packed)) #if !defined(alignas) && !defined(__cplusplus) #define alignas(n) __attribute__((aligned(n))) #endif #ifdef __cplusplus extern "C" { #endif extern LIBHDHOMERUN_API void random_getbytes(uint8_t *out, size_t length); extern LIBHDHOMERUN_API uint32_t random_get32(void); extern LIBHDHOMERUN_API uint64_t getcurrenttime(void); extern LIBHDHOMERUN_API uint64_t timer_get_hires_ticks(void); extern LIBHDHOMERUN_API uint64_t timer_get_hires_frequency(void); extern LIBHDHOMERUN_API void msleep_approx(uint64_t ms); extern LIBHDHOMERUN_API void msleep_minimum(uint64_t ms); extern LIBHDHOMERUN_API bool thread_task_create(thread_task_t *tid, thread_task_func_t func, void *arg); extern LIBHDHOMERUN_API void thread_task_join(thread_task_t tid); extern LIBHDHOMERUN_API void thread_mutex_init(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_dispose(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_lock(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_unlock(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_cond_init(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_dispose(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_signal(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_wait(thread_cond_t *cond); extern LIBHDHOMERUN_API bool thread_cond_wait_with_timeout(thread_cond_t *cond, uint64_t max_wait_time); extern LIBHDHOMERUN_API bool hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap); extern LIBHDHOMERUN_API bool hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_os_windows.c0000664000175000017500000000760414424512713017142 0ustar buildbuild/* * hdhomerun_os_windows.c * * Copyright © 2006-2017 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" void random_getbytes(uint8_t *out, size_t length) { BCryptGenRandom(NULL, out, (ULONG)length, BCRYPT_USE_SYSTEM_PREFERRED_RNG); } uint32_t random_get32(void) { uint32_t Result; random_getbytes((uint8_t *)&Result, 4); return Result; } uint64_t getcurrenttime(void) { return GetTickCount64(); } uint64_t timer_get_hires_ticks(void) { LARGE_INTEGER count; if (!QueryPerformanceCounter(&count)) { return 0; } return count.QuadPart; } uint64_t timer_get_hires_frequency(void) { LARGE_INTEGER count; if (!QueryPerformanceFrequency(&count)) { return 0; } return count.QuadPart; } 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); } } struct thread_task_execute_args_t { thread_task_func_t func; void *arg; }; static DWORD WINAPI thread_task_execute(void *arg) { struct thread_task_execute_args_t *execute_args = (struct thread_task_execute_args_t *)arg; execute_args->func(execute_args->arg); free(execute_args); return 0; } bool thread_task_create(thread_task_t *tid, thread_task_func_t func, void *arg) { struct thread_task_execute_args_t *execute_args = (struct thread_task_execute_args_t *)malloc(sizeof(struct thread_task_execute_args_t)); if (!execute_args) { return false; } execute_args->func = func; execute_args->arg = arg; *tid = CreateThread(NULL, 0, thread_task_execute, execute_args, 0, NULL); if (!*tid) { free(execute_args); return false; } return true; } void thread_task_join(thread_task_t tid) { WaitForSingleObject(tid, INFINITE); CloseHandle(tid); } void thread_mutex_init(thread_mutex_t *mutex) { *mutex = CreateMutex(NULL, false, NULL); } void thread_mutex_dispose(thread_mutex_t *mutex) { CloseHandle(*mutex); } void thread_mutex_lock(thread_mutex_t *mutex) { WaitForSingleObject(*mutex, INFINITE); } void thread_mutex_unlock(thread_mutex_t *mutex) { ReleaseMutex(*mutex); } void thread_cond_init(thread_cond_t *cond) { *cond = CreateEvent(NULL, false, false, NULL); } void thread_cond_dispose(thread_cond_t *cond) { CloseHandle(*cond); } void thread_cond_signal(thread_cond_t *cond) { SetEvent(*cond); } void thread_cond_wait(thread_cond_t *cond) { WaitForSingleObject(*cond, INFINITE); } bool thread_cond_wait_with_timeout(thread_cond_t *cond, uint64_t max_wait_time) { return (WaitForSingleObject(*cond, (DWORD)max_wait_time) == WAIT_OBJECT_0); } bool 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 hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...) { va_list ap; va_start(ap, fmt); bool result = hdhomerun_vsprintf(buffer, end, fmt, ap); va_end(ap); return result; } libhdhomerun/hdhomerun_os_windows.h0000664000175000017500000000732414424512713017146 0ustar buildbuild/* * hdhomerun_os_windows.h * * Copyright © 2006-2017 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 _WINRT #include #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT _WIN32_WINNT_VISTA #endif #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS #define _WINSOCK_DEPRECATED_NO_WARNINGS #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LIBHDHOMERUN_DLLEXPORT #define LIBHDHOMERUN_API __declspec(dllexport) #endif #ifdef LIBHDHOMERUN_DLLIMPORT #define LIBHDHOMERUN_API __declspec(dllimport) #endif #ifndef LIBHDHOMERUN_API #define LIBHDHOMERUN_API #endif #define LIBHDHOMERUN_PACKED(x) __pragma(pack(push, 1)) x __pragma( pack(pop)) #if !defined(__unused) #define __unused __pragma(warning(suppress: 4100 4101)) #endif #if !defined(alignas) && !defined(__cplusplus) #define alignas(n) __declspec(align(n)) #endif typedef void (*sig_t)(int); typedef void (*thread_task_func_t)(void *arg); typedef HANDLE thread_task_t; typedef HANDLE thread_mutex_t; typedef HANDLE thread_cond_t; #if !defined(va_copy) #define va_copy(x, y) x = y #endif #define atoll _atoi64 #define strdup _strdup #define strcasecmp _stricmp #define strncasecmp _strnicmp #define fseeko _fseeki64 #define ftello _ftelli64 #ifdef __cplusplus extern "C" { #endif extern LIBHDHOMERUN_API void random_getbytes(uint8_t *out, size_t length); extern LIBHDHOMERUN_API uint32_t random_get32(void); extern LIBHDHOMERUN_API uint64_t getcurrenttime(void); extern LIBHDHOMERUN_API uint64_t timer_get_hires_ticks(void); extern LIBHDHOMERUN_API uint64_t timer_get_hires_frequency(void); extern LIBHDHOMERUN_API void msleep_approx(uint64_t ms); extern LIBHDHOMERUN_API void msleep_minimum(uint64_t ms); extern LIBHDHOMERUN_API bool thread_task_create(thread_task_t *tid, thread_task_func_t func, void *arg); extern LIBHDHOMERUN_API void thread_task_join(thread_task_t tid); extern LIBHDHOMERUN_API void thread_mutex_init(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_dispose(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_lock(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_mutex_unlock(thread_mutex_t *mutex); extern LIBHDHOMERUN_API void thread_cond_init(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_dispose(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_signal(thread_cond_t *cond); extern LIBHDHOMERUN_API void thread_cond_wait(thread_cond_t *cond); extern LIBHDHOMERUN_API bool thread_cond_wait_with_timeout(thread_cond_t *cond, uint64_t max_wait_time); extern LIBHDHOMERUN_API bool hdhomerun_vsprintf(char *buffer, char *end, const char *fmt, va_list ap); extern LIBHDHOMERUN_API bool hdhomerun_sprintf(char *buffer, char *end, const char *fmt, ...); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_pkt.c0000664000175000017500000001253413013646353015544 0ustar buildbuild/* * hdhomerun_pkt.c * * Copyright © 2006-2014 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; } libhdhomerun/hdhomerun_pkt.h0000664000175000017500000001763414357356374015574 0ustar buildbuild/* * hdhomerun_pkt.h * * Copyright © 2006-2022 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_LINEUP_URL 0x27 #define HDHOMERUN_TAG_STORAGE_URL 0x28 #define HDHOMERUN_TAG_DEVICE_AUTH_BIN_DEPRECATED 0x29 #define HDHOMERUN_TAG_BASE_URL 0x2A #define HDHOMERUN_TAG_DEVICE_AUTH_STR 0x2B #define HDHOMERUN_TAG_STORAGE_ID 0x2C #define HDHOMERUN_TAG_MULTI_TYPE 0x2D #define HDHOMERUN_DEVICE_TYPE_WILDCARD 0xFFFFFFFF #define HDHOMERUN_DEVICE_TYPE_TUNER 0x00000001 #define HDHOMERUN_DEVICE_TYPE_STORAGE 0x00000005 #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 LIBHDHOMERUN_API struct hdhomerun_pkt_t *hdhomerun_pkt_create(void); extern LIBHDHOMERUN_API void hdhomerun_pkt_destroy(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API void hdhomerun_pkt_reset(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API uint8_t hdhomerun_pkt_read_u8(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API uint16_t hdhomerun_pkt_read_u16(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API uint32_t hdhomerun_pkt_read_u32(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API size_t hdhomerun_pkt_read_var_length(struct hdhomerun_pkt_t *pkt); extern LIBHDHOMERUN_API uint8_t *hdhomerun_pkt_read_tlv(struct hdhomerun_pkt_t *pkt, uint8_t *ptag, size_t *plength); extern LIBHDHOMERUN_API void hdhomerun_pkt_read_mem(struct hdhomerun_pkt_t *pkt, void *mem, size_t length); extern LIBHDHOMERUN_API void hdhomerun_pkt_write_u8(struct hdhomerun_pkt_t *pkt, uint8_t v); extern LIBHDHOMERUN_API void hdhomerun_pkt_write_u16(struct hdhomerun_pkt_t *pkt, uint16_t v); extern LIBHDHOMERUN_API void hdhomerun_pkt_write_u32(struct hdhomerun_pkt_t *pkt, uint32_t v); extern LIBHDHOMERUN_API void hdhomerun_pkt_write_var_length(struct hdhomerun_pkt_t *pkt, size_t v); extern LIBHDHOMERUN_API void hdhomerun_pkt_write_mem(struct hdhomerun_pkt_t *pkt, const void *mem, size_t length); extern LIBHDHOMERUN_API int hdhomerun_pkt_open_frame(struct hdhomerun_pkt_t *pkt, uint16_t *ptype); extern LIBHDHOMERUN_API void hdhomerun_pkt_seal_frame(struct hdhomerun_pkt_t *pkt, uint16_t frame_type); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_sock.c0000664000175000017500000003211114357356374015713 0ustar buildbuild/* * hdhomerun_sock.c * * Copyright © 2022 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" #if defined(_WIN32) #include #else #include #endif #if defined(_WINRT) static inline uint32_t if_nametoindex(const char *ifname) { return 0; } #endif bool hdhomerun_sock_sockaddr_is_addr(const struct sockaddr *addr) { if (addr->sa_family == AF_INET6) { static uint8_t hdhomerun_sock_ipv6_zero_bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; return (memcmp(addr_in6->sin6_addr.s6_addr, hdhomerun_sock_ipv6_zero_bytes, 16) != 0); } if (addr->sa_family == AF_INET) { const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; return (addr_in->sin_addr.s_addr != 0); } return false; } bool hdhomerun_sock_sockaddr_is_multicast(const struct sockaddr *ip_addr) { if (ip_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)ip_addr; return (addr_in6->sin6_addr.s6_addr[0] == 0xFF); } if (ip_addr->sa_family == AF_INET) { const struct sockaddr_in *addr_in = (const struct sockaddr_in *)ip_addr; uint32_t v = ntohl(addr_in->sin_addr.s_addr); return (v >= 0xE0000000) && (v < 0xF0000000); } return false; } bool hdhomerun_sock_sockaddr_is_ipv4_localhost(const struct sockaddr *ip_addr) { if (ip_addr->sa_family != AF_INET) { return false; } const struct sockaddr_in *sock_addr_in = (const struct sockaddr_in *)ip_addr; uint8_t *addr_bytes = (uint8_t *)&sock_addr_in->sin_addr.s_addr; return (addr_bytes[0] == 0x7F); } bool hdhomerun_sock_sockaddr_is_ipv4_autoip(const struct sockaddr *ip_addr) { if (ip_addr->sa_family != AF_INET) { return false; } const struct sockaddr_in *sock_addr_in = (const struct sockaddr_in *)ip_addr; uint8_t *addr_bytes = (uint8_t *)&sock_addr_in->sin_addr.s_addr; return (addr_bytes[0] == 169) && (addr_bytes[1] == 254); } bool hdhomerun_sock_sockaddr_is_ipv6_localhost(const struct sockaddr *ip_addr) { if (ip_addr->sa_family != AF_INET6) { return false; } const struct sockaddr_in6 *sock_addr_in6 = (const struct sockaddr_in6 *)ip_addr; uint8_t *addr_bytes = (uint8_t *)sock_addr_in6->sin6_addr.s6_addr; static uint8_t hdhomerun_discover_ipv6_localhost_bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; return (memcmp(addr_bytes, hdhomerun_discover_ipv6_localhost_bytes, 16) == 0); } bool hdhomerun_sock_sockaddr_is_ipv6_linklocal(const struct sockaddr *addr) { if (addr->sa_family != AF_INET6) { return false; } const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; uint8_t *addr_bytes = (uint8_t *)addr_in6->sin6_addr.s6_addr; return (addr_bytes[0] == 0xFE) && ((addr_bytes[1] & 0xC0) == 0x80); } bool hdhomerun_sock_sockaddr_is_ipv6_global(const struct sockaddr *ip_addr) { if (ip_addr->sa_family != AF_INET6) { return false; } const struct sockaddr_in6 *sock_addr_in6 = (const struct sockaddr_in6 *)ip_addr; uint8_t *addr_bytes = (uint8_t *)sock_addr_in6->sin6_addr.s6_addr; return ((addr_bytes[0] & 0xE0) == 0x20); } uint16_t hdhomerun_sock_sockaddr_get_port(const struct sockaddr *addr) { if (addr->sa_family == AF_INET6) { const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr; return ntohs(addr_in6->sin6_port); } if (addr->sa_family == AF_INET) { const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr; return ntohs(addr_in->sin_port); } return 0; } void hdhomerun_sock_sockaddr_set_port(struct sockaddr *addr, uint16_t port) { if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr; addr_in6->sin6_port = htons(port); return; } if (addr->sa_family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)addr; addr_in->sin_port = htons(port); return; } } void hdhomerun_sock_sockaddr_copy(struct sockaddr_storage *result, const struct sockaddr *addr) { memset(result, 0, sizeof(struct sockaddr_storage)); if (addr->sa_family == AF_INET6) { memcpy(result, addr, sizeof(struct sockaddr_in6)); } if (addr->sa_family == AF_INET) { memcpy(result, addr, sizeof(struct sockaddr_in)); } } void hdhomerun_sock_sockaddr_to_ip_str(char ip_str[64], const struct sockaddr *ip_addr, bool include_ipv6_scope_id) { size_t ip_str_size = 64; if (ip_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *ip_addr_in6 = (const struct sockaddr_in6 *)ip_addr; inet_ntop(AF_INET6, ip_addr_in6->sin6_addr.s6_addr, ip_str, ip_str_size); if (include_ipv6_scope_id && (ip_addr_in6->sin6_scope_id != 0)) { hdhomerun_sprintf(strchr(ip_str, 0), ip_str + ip_str_size, "%%%u", ip_addr_in6->sin6_scope_id); } return; } if (ip_addr->sa_family == AF_INET) { const struct sockaddr_in *ip_addr_in = (const struct sockaddr_in *)ip_addr; inet_ntop(AF_INET, &ip_addr_in->sin_addr.s_addr, ip_str, ip_str_size); return; } ip_str[0] = 0; } bool hdhomerun_sock_ip_str_to_sockaddr(const char *ip_str, struct sockaddr_storage *result) { memset(result, 0, sizeof(struct sockaddr_storage)); struct sockaddr_in *result_in = (struct sockaddr_in *)result; if (inet_pton(AF_INET, ip_str, &result_in->sin_addr) == 1) { result_in->sin_family = AF_INET; return true; } bool framed = (*ip_str == '['); bool ifname_present = (strchr(ip_str, '%') != NULL); if (!framed && !ifname_present) { struct sockaddr_in6 *result_in6 = (struct sockaddr_in6 *)result; if (inet_pton(AF_INET6, ip_str, &result_in6->sin6_addr) == 1) { result_in6->sin6_family = AF_INET6; return true; } return false; } if (framed) { ip_str++; } char ip_str_tmp[64]; if (!hdhomerun_sprintf(ip_str_tmp, ip_str_tmp + sizeof(ip_str_tmp), "%s", ip_str)) { return false; } if (framed) { char *end = strchr(ip_str_tmp, ']'); if (!end) { return false; } *end++ = 0; if (*end) { return false; } } char *ifname = NULL; if (ifname_present) { ifname = strchr(ip_str_tmp, '%'); *ifname++ = 0; } struct sockaddr_in6 *result_in6 = (struct sockaddr_in6 *)result; if (inet_pton(AF_INET6, ip_str_tmp, &result_in6->sin6_addr) != 1) { return false; } result_in6->sin6_family = AF_INET6; if (!ifname_present) { return true; } char *end; result_in6->sin6_scope_id = (uint32_t)strtoul(ifname, &end, 10); if ((result_in6->sin6_scope_id > 0) && (*end == 0)) { return true; } result_in6->sin6_scope_id = if_nametoindex(ifname); return (result_in6->sin6_scope_id > 0); } struct hdhomerun_local_ip_info_state_t { struct hdhomerun_local_ip_info_t *ip_info; int max_count; int count; }; static void hdhomerun_local_ip_info_callback(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr) { struct hdhomerun_local_ip_info_state_t *state = (struct hdhomerun_local_ip_info_state_t *)arg; if (state->count >= state->max_count) { state->count++; return; } const struct sockaddr_in *local_ip_in = (const struct sockaddr_in *)local_ip; state->ip_info->ip_addr = ntohl(local_ip_in->sin_addr.s_addr); state->ip_info->subnet_mask = 0xFFFFFFFF << (32 - cidr); state->ip_info++; state->count++; } int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count) { struct hdhomerun_local_ip_info_state_t state; state.ip_info = ip_info_list; state.max_count = max_count; state.count = 0; if (!hdhomerun_local_ip_info2(AF_INET, hdhomerun_local_ip_info_callback, &state)) { return -1; } return state.count; } struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void) { return hdhomerun_sock_create_udp_ex(AF_INET); } struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void) { return hdhomerun_sock_create_tcp_ex(AF_INET); } uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock) { struct sockaddr_storage sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); if (!hdhomerun_sock_getsockname_addr_ex(sock, &sock_addr)) { return 0; } if (sock_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; return ntohl(sock_addr_in->sin_addr.s_addr); } uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock) { struct sockaddr_storage sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); if (!hdhomerun_sock_getpeername_addr_ex(sock, &sock_addr)) { return 0; } if (sock_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; return ntohl(sock_addr_in->sin_addr.s_addr); } uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name) { struct sockaddr_storage sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); if (!hdhomerun_sock_getaddrinfo_addr_ex(AF_INET, name, &sock_addr)) { return 0; } if (sock_addr.ss_family != AF_INET) { return 0; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; return ntohl(sock_addr_in->sin_addr.s_addr); } bool hdhomerun_sock_getaddrinfo_addr_ex(int af, const char *name, struct sockaddr_storage *result) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; struct addrinfo *sock_info; if (getaddrinfo(name, NULL, &hints, &sock_info) != 0) { return false; } if ((size_t)sock_info->ai_addrlen > sizeof(struct sockaddr_storage)) { return false; } memcpy(result, sock_info->ai_addr, sock_info->ai_addrlen); freeaddrinfo(sock_info); return true; } bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) { struct sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(local_ip); return hdhomerun_sock_join_multicast_group_ex(sock, (const struct sockaddr *)&multicast_addr, (const struct sockaddr *)&local_addr); } bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip) { struct sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(local_ip); return hdhomerun_sock_leave_multicast_group_ex(sock, (const struct sockaddr *)&multicast_addr, (const struct sockaddr *)&local_addr); } bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse) { 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); return hdhomerun_sock_bind_ex(sock, (const struct sockaddr *)&sock_addr, allow_reuse); } bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout) { struct sockaddr_in sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(remote_addr); sock_addr_in.sin_port = htons(remote_port); return hdhomerun_sock_connect_ex(sock, (const struct sockaddr *)&sock_addr_in, timeout); } bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout) { struct sockaddr_in sock_addr_in; memset(&sock_addr_in, 0, sizeof(sock_addr_in)); sock_addr_in.sin_family = AF_INET; sock_addr_in.sin_addr.s_addr = htonl(remote_addr); sock_addr_in.sin_port = htons(remote_port); return hdhomerun_sock_sendto_ex(sock, (const struct sockaddr *)&sock_addr_in, data, length, timeout); } bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout) { struct sockaddr_storage sock_addr; memset(&sock_addr, 0, sizeof(sock_addr)); if (!hdhomerun_sock_recvfrom_ex(sock, &sock_addr, data, length, timeout)) { return false; } if (sock_addr.ss_family != AF_INET) { return false; } struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; *remote_addr = ntohl(sock_addr_in->sin_addr.s_addr); *remote_port = ntohs(sock_addr_in->sin_port); return true; } libhdhomerun/hdhomerun_sock.h0000664000175000017500000001563714357356374015736 0ustar buildbuild/* * hdhomerun_sock.h * * Copyright © 2010-2022 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 /* * Windows: * hdhomerun_sock.c * hdhomerun_sock_windows.c * * Linux/Android: * hdhomerun_sock.c * hdhomerun_sock_netlink.c * hdhomerun_sock_posix.c * * Mac/BSD: * hdhomerun_sock.c * hdhomerun_sock_getifaddrs.c * hdhomerun_sock_posix.c */ extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_addr(const struct sockaddr *addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_multicast(const struct sockaddr *ip_addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_ipv4_localhost(const struct sockaddr *addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_ipv4_autoip(const struct sockaddr *addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_ipv6_localhost(const struct sockaddr *addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_ipv6_linklocal(const struct sockaddr *addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_sockaddr_is_ipv6_global(const struct sockaddr *addr); extern LIBHDHOMERUN_API uint16_t hdhomerun_sock_sockaddr_get_port(const struct sockaddr *addr); extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_set_port(struct sockaddr *addr, uint16_t port); extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_copy(struct sockaddr_storage *result, const struct sockaddr *addr); extern LIBHDHOMERUN_API void hdhomerun_sock_sockaddr_to_ip_str(char ip_str[64], const struct sockaddr *ip_addr, bool include_ipv6_scope_id); extern LIBHDHOMERUN_API bool hdhomerun_sock_ip_str_to_sockaddr(const char *ip_str, struct sockaddr_storage *result); struct hdhomerun_local_ip_info_t { uint32_t ip_addr; uint32_t subnet_mask; }; typedef void (*hdhomerun_local_ip_info2_callback_t)(void *arg, uint32_t ifindex, const struct sockaddr *local_ip, uint8_t cidr); extern LIBHDHOMERUN_API int hdhomerun_local_ip_info(struct hdhomerun_local_ip_info_t ip_info_list[], int max_count); extern LIBHDHOMERUN_API bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg); struct hdhomerun_sock_t; extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_udp(void); extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_tcp(void); extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af); extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af); extern LIBHDHOMERUN_API void hdhomerun_sock_stop(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API void hdhomerun_sock_set_send_buffer_size(struct hdhomerun_sock_t *sock, size_t size); extern LIBHDHOMERUN_API void hdhomerun_sock_set_recv_buffer_size(struct hdhomerun_sock_t *sock, size_t size); extern LIBHDHOMERUN_API void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API void hdhomerun_sock_set_ttl(struct hdhomerun_sock_t *sock, uint8_t ttl); extern LIBHDHOMERUN_API void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v); extern LIBHDHOMERUN_API void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex); extern LIBHDHOMERUN_API int hdhomerun_sock_getlasterror(void); extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getsockname_addr(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getpeername_addr(struct hdhomerun_sock_t *sock); extern LIBHDHOMERUN_API bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result); extern LIBHDHOMERUN_API uint32_t hdhomerun_sock_getaddrinfo_addr(struct hdhomerun_sock_t *sock, const char *name); extern LIBHDHOMERUN_API bool hdhomerun_sock_getaddrinfo_addr_ex(int af, const char *name, struct sockaddr_storage *result); extern LIBHDHOMERUN_API bool hdhomerun_sock_join_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip); extern LIBHDHOMERUN_API bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_leave_multicast_group(struct hdhomerun_sock_t *sock, uint32_t multicast_ip, uint32_t local_ip); extern LIBHDHOMERUN_API bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API bool hdhomerun_sock_bind(struct hdhomerun_sock_t *sock, uint32_t local_addr, uint16_t local_port, bool allow_reuse); extern LIBHDHOMERUN_API bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse); extern LIBHDHOMERUN_API bool hdhomerun_sock_connect(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_sendto(struct hdhomerun_sock_t *sock, uint32_t remote_addr, uint16_t remote_port, const void *data, size_t length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_recvfrom(struct hdhomerun_sock_t *sock, uint32_t *remote_addr, uint16_t *remote_port, void *data, size_t *length, uint64_t timeout); extern LIBHDHOMERUN_API bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout); #ifdef __cplusplus } #endif libhdhomerun/hdhomerun_sock_getifaddrs.c0000664000175000017500000000756414357356374020125 0ustar buildbuild/* * hdhomerun_sock_getifaddrs.c * * Copyright © 2010-2022 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" #if defined(__APPLE__) #import #endif #include #include #if !defined(TARGET_OS_IPHONE) #include #endif #include static uint8_t hdhomerun_local_ip_netmask_to_cidr(uint8_t *subnet_mask, size_t len) { uint8_t result = 0; uint8_t *ptr = subnet_mask; uint8_t *end = subnet_mask + len; while (ptr < end) { uint8_t c = *ptr++; if (c == 0xFF) { result += 8; continue; } while (c & 0x80) { result++; c <<= 1; } break; } return result; } bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { int af6_sock = socket(AF_INET6, SOCK_DGRAM, 0); if (af6_sock == -1) { return -1; } struct ifaddrs *ifaddrs; if (getifaddrs(&ifaddrs) != 0) { close(af6_sock); return -1; } struct ifaddrs *ifa = ifaddrs; while (ifa) { if (ifa->ifa_addr == NULL) { ifa = ifa->ifa_next; continue; } if ((af != AF_UNSPEC) && (ifa->ifa_addr->sa_family != af)) { ifa = ifa->ifa_next; continue; } if (!hdhomerun_sock_sockaddr_is_addr(ifa->ifa_addr)) { ifa = ifa->ifa_next; continue; } if ((ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) != 0) { ifa = ifa->ifa_next; continue; } if ((ifa->ifa_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) { ifa = ifa->ifa_next; continue; } if ((ifa->ifa_addr->sa_family == AF_INET6) && ((ifa->ifa_flags & IFF_MULTICAST) == 0)) { ifa = ifa->ifa_next; continue; } uint32_t ifindex = if_nametoindex(ifa->ifa_name); if (ifindex == 0) { ifa = ifa->ifa_next; continue; } if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *netmask_in = (struct sockaddr_in *)ifa->ifa_netmask; uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr((uint8_t *)&netmask_in->sin_addr.s_addr, 4); if ((cidr == 0) || (cidr >= 32)) { ifa = ifa->ifa_next; continue; } callback(callback_arg, ifindex, ifa->ifa_addr, cidr); ifa = ifa->ifa_next; continue; } if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *netmask_in = (struct sockaddr_in6 *)ifa->ifa_netmask; uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr(netmask_in->sin6_addr.s6_addr, 16); if ((cidr == 0) || (cidr >= 128)) { ifa = ifa->ifa_next; continue; } #if !defined(TARGET_OS_IPHONE) struct in6_ifreq ifr6; memset(&ifr6, 0, sizeof(ifr6)); struct sockaddr_in6 *addr_in = (struct sockaddr_in6 *)ifa->ifa_addr; strcpy(ifr6.ifr_name, ifa->ifa_name); ifr6.ifr_addr = *addr_in; if (ioctl(af6_sock, SIOCGIFAFLAG_IN6, &ifr6) < 0) { ifa = ifa->ifa_next; continue; } uint32_t flags6 = ifr6.ifr_ifru.ifru_flags6; if (flags6 & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_TEMPORARY | IN6_IFF_DEPRECATED)) { ifa = ifa->ifa_next; continue; } #endif callback(callback_arg, ifindex, ifa->ifa_addr, cidr); ifa = ifa->ifa_next; continue; } ifa = ifa->ifa_next; } close(af6_sock); freeifaddrs(ifaddrs); return true; } libhdhomerun/hdhomerun_sock_netdevice.c0000664000175000017500000000623414357356374017750 0ustar buildbuild/* * hdhomerun_sock_getifaddrs.c * * Copyright © 2010-2022 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" #include #include static uint8_t hdhomerun_local_ip_netmask_to_cidr(uint8_t *subnet_mask, size_t len) { uint8_t result = 0; uint8_t *ptr = subnet_mask; uint8_t *end = subnet_mask + len; while (ptr < end) { uint8_t c = *ptr++; if (c == 0xFF) { result += 8; continue; } while (c & 0x80) { result++; c <<= 1; } break; } return result; } bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { if (af != AF_INET) { return false; } int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (sock == -1) { return false; } int ifreq_buffer_size = 128 * sizeof(struct ifreq); char *ifreq_buffer = (char *)calloc(ifreq_buffer_size, 1); if (!ifreq_buffer) { close(sock); return false; } struct ifconf ifc; ifc.ifc_len = ifreq_buffer_size; ifc.ifc_buf = ifreq_buffer; if (ioctl(sock, SIOCGIFCONF, &ifc) != 0) { free(ifreq_buffer); close(sock); return false; } if (ifc.ifc_len > ifreq_buffer_size) { ifc.ifc_len = ifreq_buffer_size; } char *ptr = ifc.ifc_buf; char *end = ifc.ifc_buf + ifc.ifc_len; while (ptr + sizeof(struct ifreq) <= end) { struct ifreq *ifr = (struct ifreq *)ptr; ptr += sizeof(struct ifreq); /* Local IP address. */ struct sockaddr_in ip_addr_in; memcpy(&ip_addr_in, &ifr->ifr_addr, sizeof(ip_addr_in)); if (!hdhomerun_sock_sockaddr_is_addr((const struct sockaddr *)&ip_addr_in)) { continue; } /* Flags. */ if (ioctl(sock, SIOCGIFFLAGS, ifr) != 0) { continue; } if ((ifr->ifr_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) != 0) { continue; } if ((ifr->ifr_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) { continue; } /* Subnet mask. */ if (ioctl(sock, SIOCGIFNETMASK, ifr) != 0) { continue; } struct sockaddr_in *netmask_in = (struct sockaddr_in *)&ifr->ifr_addr; uint8_t cidr = hdhomerun_local_ip_netmask_to_cidr((uint8_t *)&netmask_in->sin_addr.s_addr, 4); if ((cidr == 0) || (cidr >= 32)) { continue; } /* ifindex. */ if (ioctl(sock, SIOCGIFINDEX, ifr) != 0) { continue; } uint32_t ifindex = ifr->ifr_ifindex; if (ifindex == 0) { continue; } /* Result. */ callback(callback_arg, ifindex, (const struct sockaddr *)&ip_addr_in, cidr); } free(ifreq_buffer); close(sock); return true; } libhdhomerun/hdhomerun_sock_netlink.c0000664000175000017500000001250214357356374017441 0ustar buildbuild/* * hdhomerun_sock_netlink.c * * Copyright © 2022 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" #include #include #include #include #define HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE 32768 struct nlmsghdr_ifaddrmsg { struct nlmsghdr nlh; struct ifaddrmsg msg; }; static void hdhomerun_local_ip_info2_newaddr(int af_sock, struct nlmsghdr *hdr, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { struct ifaddrmsg *addrmsg = (struct ifaddrmsg *)NLMSG_DATA(hdr); if ((addrmsg->ifa_family != AF_INET) && (addrmsg->ifa_family != AF_INET6)) { return; } if ((addrmsg->ifa_family == AF_INET6) && (addrmsg->ifa_flags & IFA_F_TEMPORARY)) { return; /* skip temporary IPv6 addresses */ } /* ifindex */ uint32_t ifindex = addrmsg->ifa_index; if (ifindex == 0) { return; } /* interface flags */ struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (!if_indextoname(ifindex, ifr.ifr_name)) { return; } if (ioctl(af_sock, SIOCGIFFLAGS, &ifr) < 0) { return; } if ((ifr.ifr_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) != 0) { return; } if ((ifr.ifr_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) { return; } if ((addrmsg->ifa_family == AF_INET6) && ((ifr.ifr_flags & IFF_MULTICAST) == 0)) { return; } /* addresses */ size_t ifa_payload_length = IFA_PAYLOAD(hdr); struct rtattr *rta = IFA_RTA(addrmsg); while (1) { if (!RTA_OK(rta, ifa_payload_length)) { break; } if (rta->rta_type != IFA_ADDRESS) { rta = RTA_NEXT(rta, ifa_payload_length); continue; } uint8_t cidr = (uint8_t)addrmsg->ifa_prefixlen; uint8_t cidr_fail = (addrmsg->ifa_family == AF_INET6) ? 128 : 32; if ((cidr == 0) || (cidr >= cidr_fail)) { rta = RTA_NEXT(rta, ifa_payload_length); continue; } if (addrmsg->ifa_family == AF_INET) { struct sockaddr_in local_ip; memset(&local_ip, 0, sizeof(local_ip)); local_ip.sin_family = AF_INET; memcpy(&local_ip.sin_addr.s_addr, RTA_DATA(rta), 4); if (!hdhomerun_sock_sockaddr_is_addr((const struct sockaddr *)&local_ip)) { rta = RTA_NEXT(rta, ifa_payload_length); continue; } callback(callback_arg, ifindex, (const struct sockaddr *)&local_ip, cidr); } if (addrmsg->ifa_family == AF_INET6) { struct sockaddr_in6 local_ip; memset(&local_ip, 0, sizeof(local_ip)); local_ip.sin6_family = AF_INET6; memcpy(local_ip.sin6_addr.s6_addr, RTA_DATA(rta), 16); if (!hdhomerun_sock_sockaddr_is_addr((const struct sockaddr *)&local_ip)) { rta = RTA_NEXT(rta, ifa_payload_length); continue; } if ((local_ip.sin6_addr.s6_addr[0] == 0xFE) && ((local_ip.sin6_addr.s6_addr[1] & 0xC0) == 0x80)) { local_ip.sin6_scope_id = ifindex; } callback(callback_arg, ifindex, (const struct sockaddr *)&local_ip, cidr); } rta = RTA_NEXT(rta, ifa_payload_length); } } bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { uint8_t *nl_buffer = (uint8_t *)malloc(HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE); if (!nl_buffer) { return false; } int nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); int af_sock = socket(AF_INET, SOCK_DGRAM, 0); if ((nl_sock == -1) || (af_sock == -1)) { close(af_sock); close(nl_sock); free(nl_buffer); return false; } struct nlmsghdr_ifaddrmsg req; memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(req))); req.nlh.nlmsg_type = RTM_GETADDR; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; req.msg.ifa_family = af; if (send(nl_sock, &req, req.nlh.nlmsg_len, 0) != (ssize_t)req.nlh.nlmsg_len) { close(af_sock); close(nl_sock); free(nl_buffer); return false; } bool again = true; while (1) { struct pollfd poll_fds[1]; poll_fds[0].fd = nl_sock; poll_fds[0].events = POLLIN; poll_fds[0].revents = 0; int ret = poll(poll_fds, 1, 25); if (ret <= 0) { break; } if ((poll_fds[0].revents & POLLIN) == 0) { break; } int length = (int)recv(nl_sock, nl_buffer, HDHOMERUN_SOCK_NETLINK_BUFFER_SIZE, 0); if (length <= 0) { break; } struct nlmsghdr *hdr = (struct nlmsghdr *)nl_buffer; while (1) { if (!NLMSG_OK(hdr, (unsigned int)length)) { break; } if (hdr->nlmsg_type == NLMSG_DONE) { again = false; break; } if (hdr->nlmsg_type == NLMSG_ERROR) { again = false; break; } if (hdr->nlmsg_type == RTM_NEWADDR) { hdhomerun_local_ip_info2_newaddr(af_sock, hdr, callback, callback_arg); } hdr = NLMSG_NEXT(hdr, length); } if (!again) { break; } } close(af_sock); close(nl_sock); free(nl_buffer); return true; } libhdhomerun/hdhomerun_sock_posix.c0000664000175000017500000003225214403770163017127 0ustar buildbuild/* * hdhomerun_sock_posix.c * * Copyright © 2010-2022 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" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif struct hdhomerun_sock_t { int sock; int af; uint8_t ttl_set; }; static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int af, int protocol) { struct hdhomerun_sock_t *sock = (struct hdhomerun_sock_t *)calloc(1, sizeof(struct hdhomerun_sock_t)); if (!sock) { return NULL; } sock->af = af; /* Create socket. */ sock->sock = socket(af, protocol, 0); if (sock->sock == -1) { free(sock); return NULL; } /* Set non-blocking */ if (fcntl(sock->sock, F_SETFL, O_NONBLOCK) != 0) { hdhomerun_sock_destroy(sock); return NULL; } /* Configure socket not to generate pipe-error signal (BSD/OSX). */ #if defined(SO_NOSIGPIPE) int set = 1; setsockopt(sock->sock, SOL_SOCKET, SO_NOSIGPIPE, (char *)&set, sizeof(set)); #endif /* Set ipv6 */ if (af == AF_INET6) { int sock_opt_ipv6only = 1; setsockopt(sock->sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&sock_opt_ipv6only, sizeof(sock_opt_ipv6only)); } /* Success. */ return sock; } struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af) { struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(af, SOCK_DGRAM); if (!sock) { return NULL; } /* Allow broadcast. */ int sock_opt = 1; setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); /* Success. */ return sock; } struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af) { return hdhomerun_sock_create_internal(af, SOCK_STREAM); } void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock) { close(sock->sock); free(sock); } void hdhomerun_sock_stop(struct hdhomerun_sock_t *sock) { shutdown(sock->sock, SHUT_RDWR); } void hdhomerun_sock_set_send_buffer_size(struct hdhomerun_sock_t *sock, size_t size) { int size_opt = (int)size; setsockopt(sock->sock, SOL_SOCKET, SO_SNDBUF, (char *)&size_opt, sizeof(size_opt)); } void hdhomerun_sock_set_recv_buffer_size(struct hdhomerun_sock_t *sock, size_t size) { int size_opt = (int)size; setsockopt(sock->sock, SOL_SOCKET, SO_RCVBUF, (char *)&size_opt, sizeof(size_opt)); } void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock) { int sock_opt = 1; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); } void hdhomerun_sock_set_ttl(struct hdhomerun_sock_t *sock, uint8_t ttl) { if (sock->ttl_set == ttl) { return; } int sock_opt = (int)(unsigned int)ttl; if (sock->af == AF_INET) { setsockopt(sock->sock, IPPROTO_IP, IP_TTL, (char *)&sock_opt, sizeof(sock_opt)); setsockopt(sock->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&sock_opt, sizeof(sock_opt)); } if (sock->af == AF_INET6) { setsockopt(sock->sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&sock_opt, sizeof(sock_opt)); setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&sock_opt, sizeof(sock_opt)); } sock->ttl_set = ttl; } void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v) { #if defined(IP_ONESBCAST) setsockopt(sock->sock, IPPROTO_IP, IP_ONESBCAST, (char *)&v, sizeof(v)); #endif } void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex) { setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&ifindex, sizeof(ifindex)); } int hdhomerun_sock_getlasterror(void) { return errno; } bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { socklen_t sockaddr_size = sizeof(struct sockaddr_storage); return (getsockname(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock) { struct sockaddr_storage sock_addr; socklen_t sockaddr_size = sizeof(sock_addr); if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { return 0; } if (sock_addr.ss_family == AF_INET) { struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; return ntohs(sock_addr_in->sin_port); } if (sock_addr.ss_family == AF_INET6) { struct sockaddr_in6 *sock_addr_in = (struct sockaddr_in6 *)&sock_addr; return ntohs(sock_addr_in->sin6_port); } return 0; } bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { socklen_t sockaddr_size = sizeof(struct sockaddr_storage); return (getpeername(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (multicast_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; struct ipv6_mreq imr; memset(&imr, 0, sizeof(imr)); memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } if (multicast_addr->sa_family == AF_INET) { const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; struct ip_mreq imr; memset(&imr, 0, sizeof(imr)); imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } return false; } bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (multicast_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; struct ipv6_mreq imr; memset(&imr, 0, sizeof(imr)); memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } if (multicast_addr->sa_family == AF_INET) { const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; struct ip_mreq imr; memset(&imr, 0, sizeof(imr)); imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } return false; } bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse) { socklen_t local_addr_size; switch (local_addr->sa_family) { case AF_INET6: local_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: local_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } int sock_opt = allow_reuse; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); if (bind(sock->sock, (const struct sockaddr *)local_addr, local_addr_size) != 0) { return false; } return true; } bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout) { socklen_t remote_addr_size; switch (remote_addr->sa_family) { case AF_INET6: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } if (connect(sock->sock, remote_addr, remote_addr_size) != 0) { if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } } struct pollfd poll_event; poll_event.fd = sock->sock; poll_event.events = POLLOUT; poll_event.revents = 0; if (poll(&poll_event, 1, (int)timeout) <= 0) { return false; } if ((poll_event.revents & POLLOUT) == 0) { return false; } return true; } bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t length, uint64_t timeout) { const uint8_t *ptr = (const uint8_t *)data; ssize_t ret = send(sock->sock, ptr, length, MSG_NOSIGNAL); if (ret >= (ssize_t)length) { return true; } if ((ret < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } if (ret > 0) { ptr += ret; length -= ret; } uint64_t stop_time = getcurrenttime() + timeout; while (1) { struct pollfd poll_event; poll_event.fd = sock->sock; poll_event.events = POLLOUT; poll_event.revents = 0; if (poll(&poll_event, 1, (int)timeout) <= 0) { return false; } if ((poll_event.revents & POLLOUT) == 0) { return false; } ret = send(sock->sock, ptr, length, MSG_NOSIGNAL); if (ret >= (ssize_t)length) { return true; } if ((ret < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } if (ret > 0) { ptr += ret; length -= ret; } uint64_t current_time = getcurrenttime(); if (current_time >= stop_time) { return false; } timeout = stop_time - current_time; } } bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout) { socklen_t remote_addr_size; switch (remote_addr->sa_family) { case AF_INET6: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } const uint8_t *ptr = (const uint8_t *)data; ssize_t ret = sendto(sock->sock, ptr, length, 0, remote_addr, remote_addr_size); if (ret >= (ssize_t)length) { return true; } if ((ret < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } if (ret > 0) { ptr += ret; length -= ret; } uint64_t stop_time = getcurrenttime() + timeout; while (1) { struct pollfd poll_event; poll_event.fd = sock->sock; poll_event.events = POLLOUT; poll_event.revents = 0; if (poll(&poll_event, 1, (int)timeout) <= 0) { return false; } if ((poll_event.revents & POLLOUT) == 0) { return false; } ret = sendto(sock->sock, ptr, length, 0, remote_addr, remote_addr_size); if (ret >= (ssize_t)length) { return true; } if ((ret < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } if (ret > 0) { ptr += ret; length -= ret; } uint64_t current_time = getcurrenttime(); if (current_time >= stop_time) { return false; } timeout = stop_time - current_time; } } bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *length, uint64_t timeout) { ssize_t ret = recv(sock->sock, data, *length, 0); if (ret > 0) { *length = (size_t)ret; return true; } if (ret == 0) { return false; } if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } struct pollfd poll_event; poll_event.fd = sock->sock; poll_event.events = POLLIN; poll_event.revents = 0; if (poll(&poll_event, 1, (int)timeout) <= 0) { return false; } if ((poll_event.revents & POLLIN) == 0) { return false; } ret = recv(sock->sock, data, *length, 0); if (ret > 0) { *length = (size_t)ret; return true; } return false; } bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout) { socklen_t sockaddr_size = sizeof(struct sockaddr_storage); ssize_t ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { *length = (size_t)ret; return true; } if (ret == 0) { return false; } if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINPROGRESS)) { return false; } struct pollfd poll_event; poll_event.fd = sock->sock; poll_event.events = POLLIN; poll_event.revents = 0; if (poll(&poll_event, 1, (int)timeout) <= 0) { return false; } if ((poll_event.revents & POLLIN) == 0) { return false; } ret = recvfrom(sock->sock, data, *length, 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { *length = (size_t)ret; return true; } return false; } libhdhomerun/hdhomerun_sock_windows.c0000664000175000017500000003506514403770163017464 0ustar buildbuild/* * hdhomerun_sock_windows.c * * Copyright © 2010-2022 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" #include struct hdhomerun_sock_t { SOCKET sock; HANDLE event; long events_selected; int af; uint8_t ttl_set; }; bool hdhomerun_local_ip_info2(int af, hdhomerun_local_ip_info2_callback_t callback, void *callback_arg) { IP_ADAPTER_ADDRESSES *adapter_addresses; ULONG adapter_addresses_length = sizeof(IP_ADAPTER_ADDRESSES) * 16; while (1) { adapter_addresses = (IP_ADAPTER_ADDRESSES *)malloc(adapter_addresses_length); if (!adapter_addresses) { return false; } ULONG length_needed = adapter_addresses_length; DWORD ret = GetAdaptersAddresses(af, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapter_addresses, &length_needed); if (ret == NO_ERROR) { break; } free(adapter_addresses); if (ret != ERROR_BUFFER_OVERFLOW) { return false; } if (adapter_addresses_length >= length_needed) { return false; } adapter_addresses_length = length_needed; } IP_ADAPTER_ADDRESSES *adapter = adapter_addresses; while (adapter) { if (adapter->OperStatus != 1) { adapter = adapter->Next; continue; } if (adapter->PhysicalAddressLength != 6) { adapter = adapter->Next; continue; } uint32_t ifindex = adapter->IfIndex; if (ifindex == 0) { adapter = adapter->Next; continue; } IP_ADAPTER_UNICAST_ADDRESS *adapter_address = adapter->FirstUnicastAddress; while (adapter_address) { struct sockaddr *local_ip = adapter_address->Address.lpSockaddr; if (!hdhomerun_sock_sockaddr_is_addr(local_ip)) { adapter_address = adapter_address->Next; continue; } if (adapter_address->Flags & IP_ADAPTER_ADDRESS_TRANSIENT) { adapter_address = adapter_address->Next; continue; } uint8_t cidr = adapter_address->OnLinkPrefixLength; uint8_t cidr_fail = (local_ip->sa_family == AF_INET6) ? 128 : 32; if ((cidr == 0) || (cidr >= cidr_fail)) { adapter_address = adapter_address->Next; continue; } callback(callback_arg, ifindex, local_ip, cidr); adapter_address = adapter_address->Next; } adapter = adapter->Next; } free(adapter_addresses); return true; } static struct hdhomerun_sock_t *hdhomerun_sock_create_internal(int af, int protocol) { struct hdhomerun_sock_t *sock = (struct hdhomerun_sock_t *)calloc(1, sizeof(struct hdhomerun_sock_t)); if (!sock) { return NULL; } sock->af = af; /* Create socket. */ sock->sock = socket(af, protocol, 0); if (sock->sock == INVALID_SOCKET) { free(sock); return NULL; } /* Set non-blocking */ unsigned long mode = 1; if (ioctlsocket(sock->sock, FIONBIO, &mode) != 0) { hdhomerun_sock_destroy(sock); return NULL; } /* Set ipv6 */ if (af == AF_INET6) { int sock_opt_ipv6only = 1; setsockopt(sock->sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&sock_opt_ipv6only, sizeof(sock_opt_ipv6only)); } /* Event */ sock->event = CreateEvent(NULL, false, false, NULL); if (!sock->event) { hdhomerun_sock_destroy(sock); return NULL; } /* Success. */ return sock; } struct hdhomerun_sock_t *hdhomerun_sock_create_udp_ex(int af) { struct hdhomerun_sock_t *sock = hdhomerun_sock_create_internal(af, SOCK_DGRAM); if (!sock) { return NULL; } /* Allow broadcast. */ int sock_opt = 1; setsockopt(sock->sock, SOL_SOCKET, SO_BROADCAST, (char *)&sock_opt, sizeof(sock_opt)); /* Success. */ return sock; } struct hdhomerun_sock_t *hdhomerun_sock_create_tcp_ex(int af) { return hdhomerun_sock_create_internal(af, SOCK_STREAM); } void hdhomerun_sock_destroy(struct hdhomerun_sock_t *sock) { if (sock->event) { CloseHandle(sock->event); } closesocket(sock->sock); free(sock); } void hdhomerun_sock_stop(struct hdhomerun_sock_t *sock) { shutdown(sock->sock, SD_BOTH); } void hdhomerun_sock_set_send_buffer_size(struct hdhomerun_sock_t *sock, size_t size) { int size_opt = (int)size; setsockopt(sock->sock, SOL_SOCKET, SO_SNDBUF, (char *)&size_opt, sizeof(size_opt)); } void hdhomerun_sock_set_recv_buffer_size(struct hdhomerun_sock_t *sock, size_t size) { int size_opt = (int)size; setsockopt(sock->sock, SOL_SOCKET, SO_RCVBUF, (char *)&size_opt, sizeof(size_opt)); } void hdhomerun_sock_set_allow_reuse(struct hdhomerun_sock_t *sock) { int sock_opt = 1; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); } void hdhomerun_sock_set_ttl(struct hdhomerun_sock_t *sock, uint8_t ttl) { if (sock->ttl_set == ttl) { return; } int sock_opt = (int)(unsigned int)ttl; if (sock->af == AF_INET) { setsockopt(sock->sock, IPPROTO_IP, IP_TTL, (char *)&sock_opt, sizeof(sock_opt)); setsockopt(sock->sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&sock_opt, sizeof(sock_opt)); } if (sock->af == AF_INET6) { setsockopt(sock->sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&sock_opt, sizeof(sock_opt)); setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&sock_opt, sizeof(sock_opt)); } sock->ttl_set = ttl; } void hdhomerun_sock_set_ipv4_onesbcast(struct hdhomerun_sock_t *sock, int v) { } void hdhomerun_sock_set_ipv6_multicast_ifindex(struct hdhomerun_sock_t *sock, uint32_t ifindex) { setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&ifindex, sizeof(ifindex)); } int hdhomerun_sock_getlasterror(void) { return WSAGetLastError(); } bool hdhomerun_sock_getsockname_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { socklen_t sockaddr_size = sizeof(struct sockaddr_storage); return (getsockname(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } uint16_t hdhomerun_sock_getsockname_port(struct hdhomerun_sock_t *sock) { struct sockaddr_storage sock_addr; socklen_t sockaddr_size = sizeof(sock_addr); if (getsockname(sock->sock, (struct sockaddr *)&sock_addr, &sockaddr_size) != 0) { return 0; } if (sock_addr.ss_family == AF_INET) { struct sockaddr_in *sock_addr_in = (struct sockaddr_in *)&sock_addr; return ntohs(sock_addr_in->sin_port); } if (sock_addr.ss_family == AF_INET6) { struct sockaddr_in6 *sock_addr_in = (struct sockaddr_in6 *)&sock_addr; return ntohs(sock_addr_in->sin6_port); } return 0; } bool hdhomerun_sock_getpeername_addr_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *result) { socklen_t sockaddr_size = sizeof(struct sockaddr_storage); return (getpeername(sock->sock, (struct sockaddr *)result, &sockaddr_size) == 0); } bool hdhomerun_sock_join_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (multicast_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; struct ipv6_mreq imr; memset(&imr, 0, sizeof(imr)); memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } if (multicast_addr->sa_family == AF_INET) { const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; struct ip_mreq imr; memset(&imr, 0, sizeof(imr)); imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; if (setsockopt(sock->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } return false; } bool hdhomerun_sock_leave_multicast_group_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (multicast_addr->sa_family == AF_INET6) { const struct sockaddr_in6 *multicast_addr_in = (const struct sockaddr_in6 *)multicast_addr; struct ipv6_mreq imr; memset(&imr, 0, sizeof(imr)); memcpy(imr.ipv6mr_multiaddr.s6_addr, multicast_addr_in->sin6_addr.s6_addr, 16); imr.ipv6mr_interface = multicast_addr_in->sin6_scope_id; if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } if (multicast_addr->sa_family == AF_INET) { const struct sockaddr_in *multicast_addr_in = (const struct sockaddr_in *)multicast_addr; const struct sockaddr_in *local_addr_in = (const struct sockaddr_in *)local_addr; struct ip_mreq imr; memset(&imr, 0, sizeof(imr)); imr.imr_multiaddr.s_addr = multicast_addr_in->sin_addr.s_addr; imr.imr_interface.s_addr = (local_addr->sa_family == AF_INET) ? local_addr_in->sin_addr.s_addr : 0; if (setsockopt(sock->sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char *)&imr, sizeof(imr)) != 0) { return false; } return true; } return false; } bool hdhomerun_sock_bind_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *local_addr, bool allow_reuse) { socklen_t local_addr_size; switch (local_addr->sa_family) { case AF_INET6: local_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: local_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } int sock_opt = allow_reuse; setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&sock_opt, sizeof(sock_opt)); if (bind(sock->sock, (const struct sockaddr *)local_addr, local_addr_size) != 0) { return false; } return true; } static bool hdhomerun_sock_event_select(struct hdhomerun_sock_t *sock, long events) { if (sock->events_selected != events) { if (WSAEventSelect(sock->sock, sock->event, events) == SOCKET_ERROR) { return false; } sock->events_selected = events; } ResetEvent(sock->event); return true; } bool hdhomerun_sock_connect_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, uint64_t timeout) { socklen_t remote_addr_size; switch (remote_addr->sa_family) { case AF_INET6: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } if (!hdhomerun_sock_event_select(sock, FD_WRITE | FD_CLOSE)) { return false; } if (connect(sock->sock, remote_addr, remote_addr_size) != 0) { if (WSAGetLastError() != WSAEWOULDBLOCK) { return false; } } DWORD wait_ret = WaitForSingleObjectEx(sock->event, (DWORD)timeout, false); if (wait_ret != WAIT_OBJECT_0) { return false; } WSANETWORKEVENTS network_events; if (WSAEnumNetworkEvents(sock->sock, sock->event, &network_events) == SOCKET_ERROR) { return false; } if ((network_events.lNetworkEvents & FD_WRITE) == 0) { return false; } if (network_events.lNetworkEvents & FD_CLOSE) { return false; } return true; } bool hdhomerun_sock_send(struct hdhomerun_sock_t *sock, const void *data, size_t length, uint64_t timeout) { if (!hdhomerun_sock_event_select(sock, FD_WRITE | FD_CLOSE)) { return false; } uint64_t stop_time = getcurrenttime() + timeout; const uint8_t *ptr = (uint8_t *)data; while (1) { int ret = send(sock->sock, (char *)ptr, (int)length, 0); if (ret >= (int)length) { return true; } if ((ret == SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK)) { return false; } if (ret > 0) { ptr += ret; length -= ret; } uint64_t current_time = getcurrenttime(); if (current_time >= stop_time) { return false; } if (WaitForSingleObjectEx(sock->event, (DWORD)(stop_time - current_time), false) != WAIT_OBJECT_0) { return false; } } } bool hdhomerun_sock_sendto_ex(struct hdhomerun_sock_t *sock, const struct sockaddr *remote_addr, const void *data, size_t length, uint64_t timeout) { socklen_t remote_addr_size; switch (remote_addr->sa_family) { case AF_INET6: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in6); break; case AF_INET: remote_addr_size = (socklen_t)sizeof(struct sockaddr_in); break; default: return false; } if (!hdhomerun_sock_event_select(sock, FD_WRITE | FD_CLOSE)) { return false; } int ret = sendto(sock->sock, (char *)data, (int)length, 0, remote_addr, remote_addr_size); if (ret >= (int)length) { return true; } if (ret >= 0) { return false; } if (WSAGetLastError() != WSAEWOULDBLOCK) { return false; } if (WaitForSingleObjectEx(sock->event, (DWORD)timeout, false) != WAIT_OBJECT_0) { return false; } ret = sendto(sock->sock, (char *)data, (int)length, 0, remote_addr, remote_addr_size); if (ret >= (int)length) { return true; } return false; } bool hdhomerun_sock_recv(struct hdhomerun_sock_t *sock, void *data, size_t *length, uint64_t timeout) { if (!hdhomerun_sock_event_select(sock, FD_READ | FD_CLOSE)) { return false; } int ret = recv(sock->sock, (char *)data, (int)(*length), 0); if (ret > 0) { *length = ret; return true; } if (ret == 0) { return false; } if (WSAGetLastError() != WSAEWOULDBLOCK) { return false; } if (WaitForSingleObjectEx(sock->event, (DWORD)timeout, false) != WAIT_OBJECT_0) { return false; } ret = recv(sock->sock, (char *)data, (int)(*length), 0); if (ret > 0) { *length = ret; return true; } return false; } bool hdhomerun_sock_recvfrom_ex(struct hdhomerun_sock_t *sock, struct sockaddr_storage *remote_addr, void *data, size_t *length, uint64_t timeout) { if (!hdhomerun_sock_event_select(sock, FD_READ | FD_CLOSE)) { return false; } socklen_t sockaddr_size = sizeof(struct sockaddr_storage); int ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { *length = ret; return true; } if (ret == 0) { return false; } if (WSAGetLastError() != WSAEWOULDBLOCK) { return false; } if (WaitForSingleObjectEx(sock->event, (DWORD)timeout, false) != WAIT_OBJECT_0) { return false; } ret = recvfrom(sock->sock, (char *)data, (int)(*length), 0, (struct sockaddr *)remote_addr, &sockaddr_size); if (ret > 0) { *length = ret; return true; } return false; } libhdhomerun/hdhomerun_types.h0000664000175000017500000000436713063620312016114 0ustar buildbuild/* * hdhomerun_types.h * * Copyright © 2008-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 */ #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 signal_present; bool lock_supported; bool 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 not_subscribed; bool not_available; bool 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 transport_stream_id_detected; bool original_network_id_detected; uint16_t transport_stream_id; uint16_t original_network_id; }; struct hdhomerun_plotsample_t { int16_t real; int16_t imag; }; libhdhomerun/hdhomerun_video.c0000664000175000017500000003323414357356374016071 0ustar buildbuild/* * hdhomerun_video.c * * Copyright © 2006-2022 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 { thread_mutex_t lock; struct hdhomerun_debug_t *dbg; struct hdhomerun_sock_t *sock; uint32_t keepalive_lockkey; struct sockaddr_storage keepalive_addr; volatile bool keepalive_start; volatile size_t head; volatile size_t tail; uint8_t *buffer; size_t buffer_size; size_t advance; thread_task_t thread; volatile bool 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 void hdhomerun_video_thread_execute(void *arg); struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg) { struct sockaddr_in listen_addr_in; memset(&listen_addr_in, 0, sizeof(listen_addr_in)); listen_addr_in.sin_family = AF_INET; listen_addr_in.sin_port = htons(listen_port); return hdhomerun_video_create_ex((const struct sockaddr *)&listen_addr_in, allow_port_reuse, buffer_size, dbg); } struct hdhomerun_video_sock_t *hdhomerun_video_create_ex(const struct sockaddr *listen_addr, bool 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; thread_mutex_init(&vs->lock); /* 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_ex(listen_addr->sa_family); if (!vs->sock) { hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to allocate socket\n"); goto error; } /* Expand socket buffer size. */ hdhomerun_sock_set_recv_buffer_size(vs->sock, 1024 * 1024); /* Bind socket. */ if (!hdhomerun_sock_bind_ex(vs->sock, listen_addr, allow_port_reuse)) { hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to bind socket\n"); goto error; } /* Start thread. */ if (!thread_task_create(&vs->thread, &hdhomerun_video_thread_execute, vs)) { hdhomerun_debug_printf(dbg, "hdhomerun_video_create: failed to start thread\n"); goto error; } /* Success. */ return vs; error: if (vs->sock) { hdhomerun_sock_destroy(vs->sock); } if (vs->buffer) { free(vs->buffer); } thread_mutex_dispose(&vs->lock); free(vs); return NULL; } void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs) { vs->terminate = true; thread_task_join(vs->thread); hdhomerun_sock_destroy(vs->sock); thread_mutex_dispose(&vs->lock); free(vs->buffer); free(vs); } void hdhomerun_video_set_keepalive(struct hdhomerun_video_sock_t *vs, uint32_t remote_addr, uint16_t remote_port, uint32_t lockkey) { if ((remote_addr == 0) || (remote_port == 0)) { hdhomerun_video_set_keepalive_ex(vs, NULL, lockkey); return; } struct sockaddr_in remote_addr_in; memset(&remote_addr_in, 0, sizeof(remote_addr_in)); remote_addr_in.sin_family = AF_INET; remote_addr_in.sin_addr.s_addr = htonl(remote_addr); remote_addr_in.sin_port = htons(remote_port); hdhomerun_video_set_keepalive_ex(vs, (struct sockaddr *)&remote_addr_in, lockkey); } void hdhomerun_video_set_keepalive_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *remote_addr, uint32_t lockkey) { thread_mutex_lock(&vs->lock); memset(&vs->keepalive_addr, 0, sizeof(vs->keepalive_addr)); if (remote_addr && (remote_addr->sa_family == AF_INET6)) { memcpy(&vs->keepalive_addr, remote_addr, sizeof(struct sockaddr_in6)); } if (remote_addr && (remote_addr->sa_family == AF_INET)) { memcpy(&vs->keepalive_addr, remote_addr, sizeof(struct sockaddr_in)); } vs->keepalive_lockkey = lockkey; if (vs->keepalive_addr.ss_family) { vs->keepalive_start = true; } thread_mutex_unlock(&vs->lock); } struct 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) { struct sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(local_ip); return hdhomerun_video_join_multicast_group_ex(vs, (struct sockaddr *)&multicast_addr, (struct sockaddr *)&local_addr); } int hdhomerun_video_join_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (!hdhomerun_sock_join_multicast_group_ex(vs->sock, multicast_addr, local_addr)) { 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) { struct sockaddr_in multicast_addr; memset(&multicast_addr, 0, sizeof(multicast_addr)); multicast_addr.sin_family = AF_INET; multicast_addr.sin_addr.s_addr = htonl(multicast_ip); struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(local_ip); hdhomerun_video_leave_multicast_group_ex(vs, (struct sockaddr *)&multicast_addr, (struct sockaddr *)&local_addr); } void hdhomerun_video_leave_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr) { if (!hdhomerun_sock_leave_multicast_group_ex(vs->sock, multicast_addr, local_addr)) { 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; packet_identifier = (uint16_t)(ptr[1] & 0x1F) << 8; packet_identifier |= (uint16_t)ptr[2] << 0; bool transport_error = (ptr[1] & 0x80) != 0; if (transport_error) { vs->transport_error_count++; vs->sequence[packet_identifier] = 0xFF; return; } if (packet_identifier == 0x1FFF) { return; } bool payload_present = (ptr[3] & 0x10) != 0; if (!payload_present) { 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; } 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 void hdhomerun_video_thread_send_keepalive(struct hdhomerun_video_sock_t *vs) { thread_mutex_lock(&vs->lock); uint32_t keepalive_lockkey = vs->keepalive_lockkey; struct sockaddr_storage keepalive_addr; keepalive_addr = vs->keepalive_addr; vs->keepalive_start = false; thread_mutex_unlock(&vs->lock); if (keepalive_addr.ss_family == 0) { return; } struct hdhomerun_pkt_t pkt; hdhomerun_pkt_reset(&pkt); hdhomerun_pkt_write_u32(&pkt, keepalive_lockkey); hdhomerun_sock_sendto_ex(vs->sock, (struct sockaddr *)&keepalive_addr, pkt.start, pkt.end - pkt.start, 25); } static void hdhomerun_video_thread_execute(void *arg) { struct hdhomerun_video_sock_t *vs = (struct hdhomerun_video_sock_t *)arg; uint64_t send_time = getcurrenttime(); while (!vs->terminate) { uint64_t current_time = getcurrenttime(); if (vs->keepalive_start || (current_time >= send_time)) { hdhomerun_video_thread_send_keepalive(vs); send_time = current_time + 1000; } /* Receive. */ struct hdhomerun_pkt_t pkt; hdhomerun_pkt_reset(&pkt); 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 = pkt.end - pkt.pos; } if (length != VIDEO_DATA_PACKET_SIZE) { /* Data received but not valid - ignore. */ continue; } thread_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++; thread_mutex_unlock(&vs->lock); continue; } vs->head = head; thread_mutex_unlock(&vs->lock); } } uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size) { thread_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; thread_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; thread_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; thread_mutex_unlock(&vs->lock); return result; } void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs) { thread_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; thread_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)); thread_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; thread_mutex_unlock(&vs->lock); } libhdhomerun/hdhomerun_video.h0000664000175000017500000001145314357356374016075 0ustar buildbuild/* * hdhomerun_video.h * * Copyright © 2006-2022 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 LIBHDHOMERUN_API struct hdhomerun_video_sock_t *hdhomerun_video_create(uint16_t listen_port, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API struct hdhomerun_video_sock_t *hdhomerun_video_create_ex(const struct sockaddr *listen_addr, bool allow_port_reuse, size_t buffer_size, struct hdhomerun_debug_t *dbg); extern LIBHDHOMERUN_API void hdhomerun_video_destroy(struct hdhomerun_video_sock_t *vs); /* * Configure to send a keepalive packet every second. */ extern LIBHDHOMERUN_API void hdhomerun_video_set_keepalive(struct hdhomerun_video_sock_t *vs, uint32_t remote_addr, uint16_t remote_port, uint32_t lockkey); extern LIBHDHOMERUN_API void hdhomerun_video_set_keepalive_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *remote_addr, uint32_t lockkey); /* * Get the port the socket is listening on. * * Returns 16-bit port with native endianness, or 0 on error. */ extern LIBHDHOMERUN_API uint16_t hdhomerun_video_get_local_port(struct hdhomerun_video_sock_t *vs); /* * Join/leave multicast group. */ extern LIBHDHOMERUN_API int hdhomerun_video_join_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); extern LIBHDHOMERUN_API int hdhomerun_video_join_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); extern LIBHDHOMERUN_API void hdhomerun_video_leave_multicast_group(struct hdhomerun_video_sock_t *vs, uint32_t multicast_ip, uint32_t local_ip); extern LIBHDHOMERUN_API void hdhomerun_video_leave_multicast_group_ex(struct hdhomerun_video_sock_t *vs, const struct sockaddr *multicast_addr, const struct sockaddr *local_addr); /* * 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 LIBHDHOMERUN_API uint8_t *hdhomerun_video_recv(struct hdhomerun_video_sock_t *vs, size_t max_size, size_t *pactual_size); /* * Flush the buffer. */ extern LIBHDHOMERUN_API void hdhomerun_video_flush(struct hdhomerun_video_sock_t *vs); /* * Debug print internal stats. */ extern LIBHDHOMERUN_API void hdhomerun_video_debug_print_stats(struct hdhomerun_video_sock_t *vs); extern LIBHDHOMERUN_API void hdhomerun_video_get_stats(struct hdhomerun_video_sock_t *vs, struct hdhomerun_video_stats_t *stats); /* * Internal use only. */ extern LIBHDHOMERUN_API struct hdhomerun_sock_t *hdhomerun_video_get_sock(struct hdhomerun_video_sock_t *vs); #ifdef __cplusplus } #endif libhdhomerun/Makefile0000664000175000017500000000447114357356374014207 0ustar buildbuild ifneq ($(OS),Windows_NT) OS := $(shell uname -s) endif CC := $(CROSS_COMPILE)gcc STRIP := $(CROSS_COMPILE)strip CFLAGS += -O2 -Wall -Wextra -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wpointer-arith -Wno-unused-parameter LDFLAGS += -lpthread SHARED = -shared -Wl,-soname,libhdhomerun$(LIBEXT) IF_DETECT := getifaddrs BINEXT := LIBEXT := .so ifeq ($(OS),Windows_NT) IF_DETECT := netdevice BINEXT := .exe LIBEXT := .dll LDFLAGS += -liphlpapi endif ifeq ($(OS),Linux) IF_DETECT := netlink LDFLAGS += -lrt endif 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.c LIBSRCS += hdhomerun_sock_posix.c LIBSRCS += hdhomerun_sock_$(IF_DETECT).c LIBSRCS += hdhomerun_video.c ifeq ($(OS),Darwin) TARGET_X64 := -target x86_64-apple-macos10.11 TARGET_ARM64 := -target arm64-apple-macos11 all : hdhomerun_config libhdhomerun.dylib hdhomerun_config_x64 : hdhomerun_config.c $(LIBSRCS) $(CC) $(TARGET_X64) $(CFLAGS) $+ $(LDFLAGS) -o $@ $(STRIP) $@ hdhomerun_config_arm64 : hdhomerun_config.c $(LIBSRCS) $(CC) $(TARGET_ARM64) $(CFLAGS) $+ $(LDFLAGS) -o $@ $(STRIP) $@ hdhomerun_config : hdhomerun_config_x64 hdhomerun_config_arm64 lipo -create -output hdhomerun_config hdhomerun_config_x64 hdhomerun_config_arm64 libhdhomerun_x64.dylib : $(LIBSRCS) $(CC) $(TARGET_X64) $(CFLAGS) -DDLL_EXPORT -fPIC -dynamiclib $+ $(LDFLAGS) -o $@ libhdhomerun_arm64.dylib : $(LIBSRCS) $(CC) $(TARGET_ARM64) $(CFLAGS) -DDLL_EXPORT -fPIC -dynamiclib $+ $(LDFLAGS) -o $@ libhdhomerun.dylib : libhdhomerun_x64.dylib libhdhomerun_arm64.dylib lipo -create -output libhdhomerun.dylib libhdhomerun_x64.dylib libhdhomerun_arm64.dylib else all : hdhomerun_config$(BINEXT) libhdhomerun$(LIBEXT) hdhomerun_config$(BINEXT) : hdhomerun_config.c $(LIBSRCS) $(CC) $(CFLAGS) $+ $(LDFLAGS) -o $@ $(STRIP) $@ libhdhomerun$(LIBEXT) : $(LIBSRCS) $(CC) $(CFLAGS) -DDLL_EXPORT -fPIC $(SHARED) $+ $(LDFLAGS) -o $@ endif clean : -rm -f hdhomerun_config$(BINEXT) -rm -f libhdhomerun$(LIBEXT) distclean : clean %: @echo "(ignoring request to make $@)" .PHONY: all list clean distclean libhdhomerun/LICENSE0000664000175000017500000006353513013646353013545 0ustar buildbuild GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. (This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.) Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} 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 Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. {signature of Ty Coon}, 1 April 1990 Ty Coon, President of Vice That's all there is to it! libhdhomerun/README.md0000664000175000017500000000076714424512713014014 0ustar buildbuildCopyright © 2005-2023 Silicondust USA Inc. . This library implements the libhdhomerun protocol for use with Silicondust HDHomeRun TV tuners. To compile simply "make" - this will compile both the library and the hdhomerun_config command line utility suitable for sending commands or scripting control of a HDHomeRun. The top level API is hdhomerun_device - see hdhomerun_device.h for documentation. Additional libraries required: - pthread (osx, linux, bsd) - iphlpapi (windows)