MythTV  0.26-pre
hdhomerun_discover.c
Go to the documentation of this file.
00001 /*
00002  * hdhomerun_discover.c
00003  *
00004  * Copyright © 2006-2010 Silicondust USA Inc. <www.silicondust.com>.
00005  *
00006  * This library is free software; you can redistribute it and/or 
00007  * modify it under the terms of the GNU Lesser General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 3 of the License, or (at your option) any later version.
00010  *
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public
00017  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
00018  * 
00019  * As a special exception to the GNU Lesser General Public License,
00020  * you may link, statically or dynamically, an application with a
00021  * publicly distributed version of the Library to produce an
00022  * executable file containing portions of the Library, and
00023  * distribute that executable file under terms of your choice,
00024  * without any of the additional requirements listed in clause 4 of
00025  * the GNU Lesser General Public License.
00026  * 
00027  * By "a publicly distributed version of the Library", we mean
00028  * either the unmodified Library as distributed by Silicondust, or a
00029  * modified version of the Library that is distributed under the
00030  * conditions defined in the GNU Lesser General Public License.
00031  */
00032 
00033 #include "hdhomerun.h"
00034 
00035 #define HDHOMERUN_DISOCVER_MAX_SOCK_COUNT 16
00036 
00037 struct hdhomerun_discover_sock_t {
00038         hdhomerun_sock_t sock;
00039         bool_t detected;
00040         uint32_t local_ip;
00041         uint32_t subnet_mask;
00042 };
00043 
00044 struct hdhomerun_discover_t {
00045         struct hdhomerun_discover_sock_t socks[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT];
00046         unsigned int sock_count;
00047         struct hdhomerun_pkt_t tx_pkt;
00048         struct hdhomerun_pkt_t rx_pkt;
00049 };
00050 
00051 static bool_t hdhomerun_discover_sock_add(struct hdhomerun_discover_t *ds, uint32_t local_ip, uint32_t subnet_mask)
00052 {
00053         unsigned int i;
00054         for (i = 1; i < ds->sock_count; i++) {
00055                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00056 
00057                 if ((dss->local_ip == local_ip) && (dss->subnet_mask == subnet_mask)) {
00058                         dss->detected = TRUE;
00059                         return TRUE;
00060                 }
00061         }
00062 
00063         if (ds->sock_count >= HDHOMERUN_DISOCVER_MAX_SOCK_COUNT) {
00064                 return FALSE;
00065         }
00066 
00067         /* Create socket. */
00068         hdhomerun_sock_t sock = hdhomerun_sock_create_udp();
00069         if (sock == HDHOMERUN_SOCK_INVALID) {
00070                 return FALSE;
00071         }
00072 
00073         /* Bind socket. */
00074         if (!hdhomerun_sock_bind(sock, local_ip, 0, FALSE)) {
00075                 hdhomerun_sock_destroy(sock);
00076                 return FALSE;
00077         }
00078 
00079         /* Write sock entry. */
00080         struct hdhomerun_discover_sock_t *dss = &ds->socks[ds->sock_count++];
00081         dss->sock = sock;
00082         dss->detected = TRUE;
00083         dss->local_ip = local_ip;
00084         dss->subnet_mask = subnet_mask;
00085 
00086         return TRUE;
00087 }
00088 
00089 struct hdhomerun_discover_t *hdhomerun_discover_create(void)
00090 {
00091         struct hdhomerun_discover_t *ds = (struct hdhomerun_discover_t *)calloc(1, sizeof(struct hdhomerun_discover_t));
00092         if (!ds) {
00093                 return NULL;
00094         }
00095 
00096         /* Create a routable socket (always first entry). */
00097         if (!hdhomerun_discover_sock_add(ds, 0, 0)) {
00098                 free(ds);
00099                 return NULL;
00100         }
00101 
00102         /* Success. */
00103         return ds;
00104 }
00105 
00106 void hdhomerun_discover_destroy(struct hdhomerun_discover_t *ds)
00107 {
00108         unsigned int i;
00109         for (i = 0; i < ds->sock_count; i++) {
00110                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00111                 hdhomerun_sock_destroy(dss->sock);
00112         }
00113 
00114         free(ds);
00115 }
00116 
00117 static void hdhomerun_discover_sock_detect(struct hdhomerun_discover_t *ds)
00118 {
00119         unsigned int i;
00120         for (i = 1; i < ds->sock_count; i++) {
00121                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00122                 dss->detected = FALSE;
00123         }
00124 
00125         struct hdhomerun_local_ip_info_t ip_info_list[HDHOMERUN_DISOCVER_MAX_SOCK_COUNT];
00126         int count = hdhomerun_local_ip_info(ip_info_list, HDHOMERUN_DISOCVER_MAX_SOCK_COUNT);
00127         if (count < 0) {
00128                 count = 0;
00129         }
00130 
00131         int index;
00132         for (index = 0; index < count; index++) {
00133                 struct hdhomerun_local_ip_info_t *ip_info = &ip_info_list[index];
00134                 hdhomerun_discover_sock_add(ds, ip_info->ip_addr, ip_info->subnet_mask);
00135         }
00136 
00137         struct hdhomerun_discover_sock_t *src = &ds->socks[1];
00138         struct hdhomerun_discover_sock_t *dst = &ds->socks[1];
00139         count = 1;
00140         for (i = 1; i < ds->sock_count; i++) {
00141                 if (!src->detected) {
00142                         hdhomerun_sock_destroy(src->sock);
00143                         src++;
00144                         continue;
00145                 }
00146                 if (dst != src) {
00147                         *dst = *src;
00148                 }
00149                 src++;
00150                 dst++;
00151                 count++;
00152         }
00153 
00154         ds->sock_count = count;
00155 }
00156 
00157 static bool_t hdhomerun_discover_send_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
00158 {
00159         struct hdhomerun_pkt_t *tx_pkt = &ds->tx_pkt;
00160         hdhomerun_pkt_reset(tx_pkt);
00161 
00162         hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_TYPE);
00163         hdhomerun_pkt_write_var_length(tx_pkt, 4);
00164         hdhomerun_pkt_write_u32(tx_pkt, device_type);
00165         hdhomerun_pkt_write_u8(tx_pkt, HDHOMERUN_TAG_DEVICE_ID);
00166         hdhomerun_pkt_write_var_length(tx_pkt, 4);
00167         hdhomerun_pkt_write_u32(tx_pkt, device_id);
00168         hdhomerun_pkt_seal_frame(tx_pkt, HDHOMERUN_TYPE_DISCOVER_REQ);
00169 
00170         return hdhomerun_sock_sendto(dss->sock, target_ip, HDHOMERUN_DISCOVER_UDP_PORT, tx_pkt->start, tx_pkt->end - tx_pkt->start, 0);
00171 }
00172 
00173 static bool_t hdhomerun_discover_send_wildcard_ip(struct hdhomerun_discover_t *ds, uint32_t device_type, uint32_t device_id)
00174 {
00175         bool_t result = FALSE;
00176 
00177         /*
00178          * Send subnet broadcast using each local ip socket.
00179          * This will work with multiple separate 169.254.x.x interfaces.
00180          */
00181         unsigned int i;
00182         for (i = 1; i < ds->sock_count; i++) {
00183                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00184                 uint32_t target_ip = dss->local_ip | ~dss->subnet_mask;
00185                 result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
00186         }
00187 
00188         /*
00189          * If no local ip sockets then fall back to sending a global broadcast letting the OS choose the interface.
00190          */
00191         if (!result) {
00192                 struct hdhomerun_discover_sock_t *dss = &ds->socks[0];
00193                 result = hdhomerun_discover_send_internal(ds, dss, 0xFFFFFFFF, device_type, device_id);
00194         }
00195 
00196         return result;
00197 }
00198 
00199 static bool_t hdhomerun_discover_send_target_ip(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
00200 {
00201         bool_t result = FALSE;
00202 
00203         /*
00204          * Send targeted packet from any local ip that is in the same subnet.
00205          * This will work with multiple separate 169.254.x.x interfaces.
00206          */
00207         unsigned int i;
00208         for (i = 1; i < ds->sock_count; i++) {
00209                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00210                 if ((target_ip & dss->subnet_mask) != (dss->local_ip & dss->subnet_mask)) {
00211                         continue;
00212                 }
00213 
00214                 result |= hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
00215         }
00216 
00217         /*
00218          * If target IP does not match a local subnet then fall back to letting the OS choose the gateway interface.
00219          */
00220         if (!result) {
00221                 struct hdhomerun_discover_sock_t *dss = &ds->socks[0];
00222                 result = hdhomerun_discover_send_internal(ds, dss, target_ip, device_type, device_id);
00223         }
00224 
00225         return result;
00226 }
00227 
00228 static bool_t hdhomerun_discover_send(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id)
00229 {
00230         if (target_ip == 0) {
00231                 return hdhomerun_discover_send_wildcard_ip(ds, device_type, device_id);
00232         } else {
00233                 return hdhomerun_discover_send_target_ip(ds, target_ip, device_type, device_id);
00234         }
00235 }
00236 
00237 static bool_t hdhomerun_discover_recv_internal(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_sock_t *dss, struct hdhomerun_discover_device_t *result)
00238 {
00239         struct hdhomerun_pkt_t *rx_pkt = &ds->rx_pkt;
00240         hdhomerun_pkt_reset(rx_pkt);
00241 
00242         uint32_t remote_addr;
00243         uint16_t remote_port;
00244         size_t length = rx_pkt->limit - rx_pkt->end;
00245         if (!hdhomerun_sock_recvfrom(dss->sock, &remote_addr, &remote_port, rx_pkt->end, &length, 0)) {
00246                 return FALSE;
00247         }
00248 
00249         rx_pkt->end += length;
00250 
00251         uint16_t type;
00252         if (hdhomerun_pkt_open_frame(rx_pkt, &type) <= 0) {
00253                 return FALSE;
00254         }
00255         if (type != HDHOMERUN_TYPE_DISCOVER_RPY) {
00256                 return FALSE;
00257         }
00258 
00259         result->ip_addr = remote_addr;
00260         result->device_type = 0;
00261         result->device_id = 0;
00262         result->tuner_count = 0;
00263 
00264         while (1) {
00265                 uint8_t tag;
00266                 size_t len;
00267                 uint8_t *next = hdhomerun_pkt_read_tlv(rx_pkt, &tag, &len);
00268                 if (!next) {
00269                         break;
00270                 }
00271 
00272                 switch (tag) {
00273                 case HDHOMERUN_TAG_DEVICE_TYPE:
00274                         if (len != 4) {
00275                                 break;
00276                         }
00277                         result->device_type = hdhomerun_pkt_read_u32(rx_pkt);
00278                         break;
00279 
00280                 case HDHOMERUN_TAG_DEVICE_ID:
00281                         if (len != 4) {
00282                                 break;
00283                         }
00284                         result->device_id = hdhomerun_pkt_read_u32(rx_pkt);
00285                         break;
00286 
00287                 case HDHOMERUN_TAG_TUNER_COUNT:
00288                         if (len != 1) {
00289                                 break;
00290                         }
00291                         result->tuner_count = hdhomerun_pkt_read_u8(rx_pkt);
00292                         break;
00293 
00294                 default:
00295                         break;
00296                 }
00297 
00298                 rx_pkt->pos = next;
00299         }
00300 
00301         /* Fixup for old firmware. */
00302         if (result->tuner_count == 0) {
00303                 switch (result->device_id >> 20) {
00304                 case 0x102:
00305                         result->tuner_count = 1;
00306                         break;
00307 
00308                 case 0x100:
00309                 case 0x101:
00310                 case 0x121:
00311                         result->tuner_count = 2;
00312                         break;
00313 
00314                 default:
00315                         break;
00316                 }
00317         }
00318 
00319         return TRUE;
00320 }
00321 
00322 static bool_t hdhomerun_discover_recv(struct hdhomerun_discover_t *ds, struct hdhomerun_discover_device_t *result)
00323 {
00324         unsigned int i;
00325         for (i = 0; i < ds->sock_count; i++) {
00326                 struct hdhomerun_discover_sock_t *dss = &ds->socks[i];
00327 
00328                 if (hdhomerun_discover_recv_internal(ds, dss, result)) {
00329                         return TRUE;
00330                 }
00331         }
00332 
00333         return FALSE;
00334 }
00335 
00336 static struct hdhomerun_discover_device_t *hdhomerun_discover_find_in_list(struct hdhomerun_discover_device_t result_list[], int count, struct hdhomerun_discover_device_t *lookup)
00337 {
00338         int index;
00339         for (index = 0; index < count; index++) {
00340                 struct hdhomerun_discover_device_t *entry = &result_list[index];
00341                 if (memcmp(lookup, entry, sizeof(struct hdhomerun_discover_device_t)) == 0) {
00342                         return entry;
00343                 }
00344         }
00345 
00346         return NULL;
00347 }
00348 
00349 int hdhomerun_discover_find_devices(struct hdhomerun_discover_t *ds, uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count)
00350 {
00351         hdhomerun_discover_sock_detect(ds);
00352 
00353         int count = 0;
00354         int attempt;
00355         for (attempt = 0; attempt < 2; attempt++) {
00356                 if (!hdhomerun_discover_send(ds, target_ip, device_type, device_id)) {
00357                         return -1;
00358                 }
00359 
00360                 uint64_t timeout = getcurrenttime() + 200;
00361                 while (1) {
00362                         struct hdhomerun_discover_device_t *result = &result_list[count];
00363                         memset(result, 0, sizeof(struct hdhomerun_discover_device_t));
00364 
00365                         if (!hdhomerun_discover_recv(ds, result)) {
00366                                 if (getcurrenttime() >= timeout) {
00367                                         break;
00368                                 }
00369                                 msleep_approx(10);
00370                                 continue;
00371                         }
00372 
00373                         /* Filter. */
00374                         if (device_type != HDHOMERUN_DEVICE_TYPE_WILDCARD) {
00375                                 if (device_type != result->device_type) {
00376                                         continue;
00377                                 }
00378                         }
00379                         if (device_id != HDHOMERUN_DEVICE_ID_WILDCARD) {
00380                                 if (device_id != result->device_id) {
00381                                         continue;
00382                                 }
00383                         }
00384 
00385                         /* Ensure not already in list. */
00386                         if (hdhomerun_discover_find_in_list(result_list, count, result)) {
00387                                 continue;
00388                         }
00389 
00390                         /* Add to list. */
00391                         count++;
00392                         if (count >= max_count) {
00393                                 return count;
00394                         }
00395                 }
00396         }
00397 
00398         return count;
00399 }
00400 
00401 int hdhomerun_discover_find_devices_custom(uint32_t target_ip, uint32_t device_type, uint32_t device_id, struct hdhomerun_discover_device_t result_list[], int max_count)
00402 {
00403         if (hdhomerun_discover_is_ip_multicast(target_ip)) {
00404                 return 0;
00405         }
00406 
00407         struct hdhomerun_discover_t *ds = hdhomerun_discover_create();
00408         if (!ds) {
00409                 return -1;
00410         }
00411 
00412         int ret = hdhomerun_discover_find_devices(ds, target_ip, device_type, device_id, result_list, max_count);
00413 
00414         hdhomerun_discover_destroy(ds);
00415         return ret;
00416 }
00417 
00418 bool_t hdhomerun_discover_validate_device_id(uint32_t device_id)
00419 {
00420         static uint32_t lookup_table[16] = {0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0};
00421 
00422         uint32_t checksum = 0;
00423 
00424         checksum ^= lookup_table[(device_id >> 28) & 0x0F];
00425         checksum ^= (device_id >> 24) & 0x0F;
00426         checksum ^= lookup_table[(device_id >> 20) & 0x0F];
00427         checksum ^= (device_id >> 16) & 0x0F;
00428         checksum ^= lookup_table[(device_id >> 12) & 0x0F];
00429         checksum ^= (device_id >> 8) & 0x0F;
00430         checksum ^= lookup_table[(device_id >> 4) & 0x0F];
00431         checksum ^= (device_id >> 0) & 0x0F;
00432 
00433         return (checksum == 0);
00434 }
00435 
00436 bool_t hdhomerun_discover_is_ip_multicast(uint32_t ip_addr)
00437 {
00438         return (ip_addr >= 0xE0000000) && (ip_addr < 0xF0000000);
00439 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends