mirror of
https://github.com/Netflix/dial-reference.git
synced 2026-06-08 10:59:59 +00:00
dial spec 1.7
This commit is contained in:
344
client/DialDiscovery.cpp
Normal file
344
client/DialDiscovery.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Netflix, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <curl/curl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "DialDiscovery.h"
|
||||
#include <assert.h>
|
||||
|
||||
#define SSDP_TIMEOUT 30
|
||||
#define SSDP_RESPONSE_TIMEOUT 3
|
||||
|
||||
using namespace std;
|
||||
|
||||
DialDiscovery *DialDiscovery::sDiscovery = 0;
|
||||
|
||||
//static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
|
||||
//static int my_port = 0;
|
||||
static struct sockaddr_in saddr;
|
||||
typedef struct
|
||||
{
|
||||
struct sockaddr_in saddr;
|
||||
int sock;
|
||||
socklen_t addrlen;
|
||||
}search_conn;
|
||||
|
||||
static pthread_mutex_t list_locker = PTHREAD_MUTEX_INITIALIZER;
|
||||
class ScopeLocker
|
||||
{
|
||||
public:
|
||||
ScopeLocker(pthread_mutex_t &m) : mLock(m)
|
||||
{ pthread_mutex_lock(&mLock); }
|
||||
virtual ~ScopeLocker()
|
||||
{ pthread_mutex_unlock(&mLock); }
|
||||
private:
|
||||
pthread_mutex_t mLock;
|
||||
};
|
||||
|
||||
static const char ssdp_msearch[] =
|
||||
"M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"MAN: \"ssdp:discover\"\r\n"
|
||||
"MX: 10\r\n"
|
||||
"ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
|
||||
|
||||
static string getLocation(char *pResponse)
|
||||
{
|
||||
string loc(pResponse);
|
||||
size_t prev = 0, index = loc.find("\r\n");
|
||||
string locUrl, retUrl;
|
||||
while( index != string::npos ) {
|
||||
locUrl = loc.substr(prev, index-prev);
|
||||
ATRACE("locUrl: ##%s## prev: %d index %d\n", locUrl.c_str(), (int)prev, (int)index );
|
||||
if( locUrl.find("LOCATION: ") != string::npos ) {
|
||||
index = locUrl.find("\r\n");
|
||||
retUrl = locUrl.substr( 10 );
|
||||
break;
|
||||
}
|
||||
|
||||
prev = index+2; // move past the "\r\n" token
|
||||
index = loc.find("\r\n", prev);
|
||||
}
|
||||
return retUrl;
|
||||
}
|
||||
|
||||
static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string parse((char*)ptr);
|
||||
if( parse.find("Application-URL: ") != string::npos ) {
|
||||
size_t index_start = parse.find(":");
|
||||
index_start += 2;
|
||||
size_t index_end = parse.find("\r\n");
|
||||
string *header = static_cast<string*>(userdata);
|
||||
header->append(parse.substr(index_start, index_end-index_start));
|
||||
ATRACE("Apps URL set to %s\n", header->c_str());
|
||||
#ifndef DEBUG
|
||||
}
|
||||
#else
|
||||
} else {
|
||||
ATRACE("%s: Dropping %s\n", __FUNCTION__, (char*)ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
if ((size * nmemb) != 0) {
|
||||
string *url = static_cast<string*>(userdata);
|
||||
url->append((char*)ptr);
|
||||
}
|
||||
return (size * nmemb);
|
||||
}
|
||||
|
||||
static void getServerInfo( const string &server, string& appsUrl, string& ddxml )
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode res = CURLE_OK;
|
||||
|
||||
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
|
||||
fprintf(stderr, "curl_global_init() failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((curl = curl_easy_init()) == NULL) {
|
||||
fprintf(stderr, "curl_easy_init() failed\n");
|
||||
curl_global_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
ATRACE("Sending ##%s##\n", server.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_URL, server.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &appsUrl);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ddxml);
|
||||
res = curl_easy_perform(curl);
|
||||
|
||||
(void)(res);
|
||||
curl_easy_cleanup(curl);
|
||||
//curl_global_cleanup();
|
||||
}
|
||||
|
||||
void DialDiscovery::updateServerList(string& server)
|
||||
{
|
||||
ServerMap::const_iterator it;
|
||||
it = mServerMap.find(server);
|
||||
if( it == mServerMap.end() ) {
|
||||
// not found, add it
|
||||
string appsUrl, ddxml;
|
||||
getServerInfo(server, appsUrl, ddxml);
|
||||
mServerMap[server] = new DialServer(server, appsUrl, ddxml);
|
||||
} else {
|
||||
// just mark that we found it
|
||||
(*it).second->setFound(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::processServer(char *pResponse)
|
||||
{
|
||||
if (strstr(pResponse, "ST: urn:dial-multiscreen-org:service:dial:1")) {
|
||||
string server;
|
||||
|
||||
ScopeLocker s(list_locker);
|
||||
// parse for LOCATION header
|
||||
server = getLocation(pResponse);
|
||||
ATRACE("FOUND server: %s\n", server.c_str());
|
||||
|
||||
// save the appURL in the server
|
||||
updateServerList(server);
|
||||
#ifndef DEBUG
|
||||
}
|
||||
#else
|
||||
} else {
|
||||
ATRACE("Dropping Server\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void *DialDiscovery::receiveResponses(void *p)
|
||||
{
|
||||
search_conn *pConn = (search_conn*)p;
|
||||
int bytes;
|
||||
char buf[4096] = {0,};
|
||||
while (1) {
|
||||
if (-1 == (bytes = recvfrom(pConn->sock, buf, sizeof(buf) - 1, 0,
|
||||
(struct sockaddr *)&pConn->saddr, &pConn->addrlen))) {
|
||||
perror("recvfrom");
|
||||
break;
|
||||
}
|
||||
buf[bytes] = 0;
|
||||
ATRACE("Received [%s:%d] %s\n", __FUNCTION__, __LINE__, buf);
|
||||
DialDiscovery::instance()->processServer(buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DialDiscovery::cleanServerList(void)
|
||||
{
|
||||
ScopeLocker s(list_locker);
|
||||
|
||||
ServerMap::const_iterator it;
|
||||
vector<string> removal;
|
||||
for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
|
||||
{
|
||||
if( !(*it).second->isFound() ) {
|
||||
removal.push_back((*it).second->getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
// now remove and delete
|
||||
vector<string>::iterator iter;
|
||||
for( iter = removal.begin(); iter != removal.end(); iter ++)
|
||||
{
|
||||
ATRACE("Removing Server: %s\n", (*iter).c_str());
|
||||
DialServer *p = mServerMap[(*iter)];
|
||||
delete (p);
|
||||
mServerMap.erase((*iter));
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::resetDiscovery(void)
|
||||
{
|
||||
ScopeLocker s(list_locker);
|
||||
ServerMap::const_iterator it;
|
||||
for( it = mServerMap.begin(); it != mServerMap.end(); it++ ) {
|
||||
(*it).second->setFound(false);
|
||||
}
|
||||
}
|
||||
|
||||
void * DialDiscovery::send_mcast(void *p)
|
||||
{
|
||||
int one = 1, my_sock;
|
||||
socklen_t addrlen;
|
||||
//struct ip_mreq mreq;
|
||||
char send_buf[strlen((char*)ssdp_msearch) + INET_ADDRSTRLEN + 256] = {0,};
|
||||
int send_size;
|
||||
pthread_attr_t attr;
|
||||
search_conn connection;
|
||||
|
||||
// send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch, ip_addr, my_port);
|
||||
send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch);
|
||||
ATRACE("[%s:%d] %s\n", __FUNCTION__, __LINE__, send_buf);
|
||||
|
||||
if (-1 == (my_sock = socket(AF_INET, SOCK_DGRAM, 0))) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
if (-1 == setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
|
||||
perror("reuseaddr");
|
||||
exit(1);
|
||||
}
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
|
||||
saddr.sin_port = htons(1900);
|
||||
|
||||
while (1) {
|
||||
addrlen = sizeof(saddr);
|
||||
ATRACE("Sending SSDP M-SEARCH to %s:%d\n",
|
||||
inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
|
||||
if (-1 == sendto(my_sock, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
|
||||
perror("sendto");
|
||||
continue;
|
||||
}
|
||||
|
||||
// set all servers to not found
|
||||
DialDiscovery::instance()->resetDiscovery();
|
||||
|
||||
// spawn a response thread
|
||||
connection.saddr = saddr;
|
||||
connection.sock = my_sock;
|
||||
connection.addrlen = addrlen;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
pthread_create(&DialDiscovery::instance()->_responseThread, &attr, DialDiscovery::receiveResponses, &connection);
|
||||
|
||||
// sleep SSDP_RESPONSE_TIMEOUT seconds to allow clients to response
|
||||
sleep(SSDP_RESPONSE_TIMEOUT);
|
||||
DialDiscovery::instance()->cleanServerList();
|
||||
|
||||
sleep(SSDP_TIMEOUT-SSDP_RESPONSE_TIMEOUT);
|
||||
pthread_cancel(DialDiscovery::instance()->_responseThread);
|
||||
}
|
||||
}
|
||||
|
||||
void DialDiscovery::init()
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
pthread_create(&_mcastThread, &attr, DialDiscovery::send_mcast, (void*)ssdp_msearch );
|
||||
}
|
||||
|
||||
void DialDiscovery::getServerList( vector<DialServer*>& list )
|
||||
{
|
||||
for( ServerMap::iterator it = mServerMap.begin(); it != mServerMap.end(); ++it ) {
|
||||
list.push_back( it->second );
|
||||
}
|
||||
}
|
||||
|
||||
bool DialDiscovery::getServer( const string& friendlyName, DialServer &server )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
DialDiscovery::DialDiscovery()
|
||||
{
|
||||
assert( DialDiscovery::sDiscovery == 0 );
|
||||
DialDiscovery::sDiscovery = this;
|
||||
}
|
||||
|
||||
DialDiscovery::~DialDiscovery()
|
||||
{
|
||||
assert( sDiscovery == this );
|
||||
sDiscovery = 0;
|
||||
}
|
||||
|
||||
DialDiscovery * DialDiscovery::create()
|
||||
{
|
||||
assert( sDiscovery == 0 );
|
||||
return new DialDiscovery();
|
||||
}
|
||||
|
||||
DialDiscovery * DialDiscovery::instance()
|
||||
{
|
||||
return DialDiscovery::sDiscovery;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user