libhdhomerun/hdhomerun_discover.c
2025-05-12 13:54:01 -07:00

1804 lines
53 KiB
C

/*
* hdhomerun_discover.c
*
* Copyright © 2006-2022 Silicondust USA Inc. <www.silicondust.com>.
*
* 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(1, sizeof(struct hdhomerun_discover_sock_t));
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(1, sizeof(struct hdhomerun_discover_sock_t));
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(1, sizeof(struct hdhomerun_discover_sock_t));
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(1, sizeof(struct hdhomerun_discover_sock_t));
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 (device_if->ip_addr.ss_family == AF_INET) {
if (hdhomerun_sock_sockaddr_is_ipv4_localhost((const struct sockaddr *)&device_if->ip_addr)) {
return 1; /* localhost ipv4 */
}
if (!hdhomerun_sock_sockaddr_is_ipv4_autoip((const struct sockaddr *)&device_if->ip_addr)) {
return 2; /* dhcp or static ipv4 */
}
return 6; /* auto ip */
} else {
if (hdhomerun_sock_sockaddr_is_ipv6_localhost((const struct sockaddr *)&device_if->ip_addr)) {
return 0; /* localhost ipv6 = highest priority */
}
if (hdhomerun_sock_sockaddr_is_ipv6_global((const struct sockaddr *)&device_if->ip_addr)) {
return 3; /* global ipv6 */
}
if (!hdhomerun_sock_sockaddr_is_ipv6_linklocal((const struct sockaddr *)&device_if->ip_addr)) {
return 4; /* site ipv6 */
}
return 5; /* link-local ipv6 */
}
}
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(1, sizeof(struct hdhomerun_discover2_device_type_t));
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(1, sizeof(struct hdhomerun_discover2_device_t));
struct hdhomerun_discover2_device_if_t *device_if = (struct hdhomerun_discover2_device_if_t *)calloc(1, sizeof(struct hdhomerun_discover2_device_if_t));
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);
}