Commit fdbae5c6 authored by Steve Johnson's avatar Steve Johnson

first commit

parents
This diff is collapsed.
#My Stuff Everywhere™ - mse-ios repository
iPhone iOS client application for My Stuff Everywhere. The native application uses C++ code to discover DIAL and Chromecast devices. The device information is sent to a web page in an embedded UIWebView. Everything else of interest happens in the web application (see the mse-html repository for more information).
##Important Notes
1. The Xcode project for the iOS application (mseclient/mseclient.xcodeproj) also contains two OS X targets. One is a simulator with basically the same functionality as the iOS application except that it only supports the discovery of DIAL Servers (no chromecast support). The other is a discovery proxy application that discovers DIAL Servers and uploads the discovered devices to a web service. There is a JavaScript library for querying this web service - mse-html/js/dial.js. This is not used at the moment but could be used to create a JavaScript client application that is not capable of native discovery.
2. The above OS X targets use a version of curl that is only available in Mavericks (OS X version 9) and above. If you wish to build these on Mountain Lion or below, you'll have to build and install a newer version of the curl library. You can download the source for this here: https://html5.cablelabs.com/mse/support/curl-7.33.0.tar.gz
3. The iOS target requires the GoogleCast framework and a libcurl binary. These are included in the repository for your convenience. You may in the future have to download a newer version of the framework and/or rebuild the curl library. You can download the source for the curl library here: https://html5.cablelabs.com/mse/support/ioscurl-7.36.0.tar.gz
##Folder descriptions
####discovery
Contains C++ sources for DIAL discovery, shared by all of the targets in the Xcode project.
####ios
Contains required iOS frameworks, libraries, and include files.
####mseclient
Contains the Xcode project and related files
/*
* Copyright (c) 2012 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.
*
* 2013-12-11 CableLabs: refactored to use Discovery base class
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <map>
#import "TargetConditionals.h"
#ifdef TARGET_OS_IPHONE
#include "../ios/include/curl/curl.h"
#else
#include <curl/curl.h>
#endif
#include <iostream>
#include <vector>
#include "DialDiscovery.h"
#include "DialServer.h"
#include <assert.h>
using namespace std;
// Callback to parse headers
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());
}
}
return (size * nmemb);
}
// Callback to receive device description
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);
}
UPnPDevice* DialDiscovery::getServerInfo(const string &server)
{
CURL *curl;
CURLcode res = CURLE_OK;
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return 0;
}
if ((curl = curl_easy_init()) == NULL) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return 0;
}
string appsUrl;
string ddxml;
//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);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); // Should be plenty of time on a LAN for a UPnPDevice to respond.
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res == 0) {
DialServer* dialServer = new DialServer(server, appsUrl, ddxml);
return dialServer;
}
else {
fprintf(stderr,"getServerInfo: CURL operation failed with error code: %d (fetch device description)", res);
}
return 0;
}
int DialDiscovery::startApplication(string& serverKey, string& applicationName, string& args, string& stopURL)
{
ScopeLocker s(list_locker);
DialServer* server = (DialServer*) getServer(serverKey);
if (server) {
fprintf(stderr, "DialDiscovery::startApplication: calling launchApplication on server object");
return server->launchApplication(applicationName, args, stopURL);
}
else {
fprintf(stderr, "DialDiscovery::startApplication: Server not found!! - %s", serverKey.c_str());
}
return MSE_FAILURE;
}
int DialDiscovery::stopApplication(string& serverKey, string& stopURL )
{
ScopeLocker s(list_locker);
DialServer* server = (DialServer*) getServer(serverKey);
if (server) {
return server->stopApplication(stopURL);
}
return MSE_FAILURE;
}
int DialDiscovery::stopRunningApplication(string& serverKey, string& applicationName)
{
ScopeLocker s(list_locker);
DialServer* server = (DialServer*) getServer(serverKey);
if (server) {
string stopURL;
if (server->applicationStopURL(applicationName, stopURL) == MSE_SUCCESS) {
return server->stopApplication(stopURL);
}
}
// Application was not running
return MSE_SUCCESS;
}
/*
* Copyright (c) 2012 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.
*/
#ifndef DIALDISCOVERY_H
#define DIALDISCOVERY_H
#include "Discovery.h"
#include "DialServer.h"
//#include <map>
using namespace std;
static string serviceType_dial = "urn:dial-multiscreen-org:service:dial:1";
class DialDiscovery : public Discovery
{
public:
DialDiscovery(IDiscoveryAPI* parent) : Discovery(parent, serviceType_dial) {}
UPnPDevice* getServerInfo(const string &server);
int startApplication(string& serverKey, string& applicationName, string& args, string& stopURL);
int stopApplication(string& serverKey, string& stopURL);
int stopRunningApplication(string& serverKey, string& applicationName);
};
#endif // DIALDISCOVERY_H
/*
* Copyright (c) 2012 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.
*
* 2013-12-11 - Modified by CableLabs to use base class UPnPDevice, which was refactored from
* the original DialServer class.
*/
#include "DialServer.h"
#import "TargetConditionals.h"
#ifdef TARGET_OS_IPHONE
#include "../ios/include/curl/curl.h"
#else
#include <curl/curl.h>
#endif
#include "XmlUtils.h"
using namespace std;
int DialServer::launchApplication( string &application, string &payload, string& stopURL )
{
string responseHeaders;
string responseBody;
ATRACE("%s: Launch %s\n", __FUNCTION__, application.c_str());
string appUrl = m_controlUrl;
string url = appUrl.append(application);
fprintf(stderr, "DialServer::launchApplication - calling sendCommand: %s", url.c_str());
sendCommand( url, HttpCommand::POST, payload, responseHeaders, responseBody);
if (responseHeaders.find("201 Created") == std::string::npos) {
fprintf(stderr, "DialServer: Failed to launch %s application. %s\n", application.c_str(), responseHeaders.c_str());
return MSE_FAILURE;
}
// Extract link required by stopApplication
string header = "LOCATION";
getHttpResponseHeader( responseHeaders, header, stopURL );
fprintf(stderr, "DialServer: Launched %s application. Stop URL: %s\n", application.c_str(), stopURL.c_str());
return MSE_SUCCESS;
}
int DialServer::getStatus( string &application, string &responseHeaders, string &responseBody )
{
ATRACE("%s: GetStatus %s\n", __FUNCTION__, application.c_str());
string emptyPayload;
string appUrl = m_controlUrl;
sendCommand( appUrl.append(application), HttpCommand::GET, emptyPayload, responseHeaders, responseBody );
if (responseHeaders.find("200 OK") == std::string::npos) {
return MSE_FAILURE;
}
return MSE_SUCCESS;
}
int DialServer::stopApplication( string &stopurl )
{
ATRACE("%s: Quit URL: %s\n", __FUNCTION__, stopurl.c_str());
string emptyPayload, responseHeaders, responseBody; // dropping this
sendCommand( stopurl, HttpCommand::DELETE, emptyPayload, responseHeaders, responseBody );
if (responseHeaders.find("200 OK") == std::string::npos) {
return MSE_FAILURE;
}
return MSE_SUCCESS;
}
int DialServer::applicationState(string &application)
{
string responseHeaders;
string responseBody;
getStatus(application, responseHeaders, responseBody);
if (responseHeaders.find("200 OK") == std::string::npos) {
return MSE_NOTFOUND;
}
else {
char* pos;
string state = XmlUtils::getElementValue(responseBody, "state", &pos);
if (state.compare("stopped") == 0) {
return MSE_STOPPED;
}
else if (state.compare("running") == 0) {
return MSE_RUNNING;
}
else if (state.compare("installable") == 0) {
return MSE_INSTALLABLE;
}
fprintf(stderr,"!!! Undefined state: %s for application: %s\n", state.c_str(), application.c_str());
return MSE_UNDEFINED;
}
}
// This is a CableLabs extension to the DIAL Specification to allow us to stop a zombie application.
int DialServer::applicationStopURL(string &application, string& stopURL)
{
string responseHeaders;
string responseBody;
getStatus(application, responseHeaders, responseBody);
if (responseHeaders.find("200 OK") == std::string::npos) {
stopURL = "";
return MSE_FAILURE;
}
else {
char* pos;
string state = XmlUtils::getElementValue(responseBody, "state", &pos);
if (state.compare("running") != 0) {
return MSE_FAILURE;
}
}
// Extract link required by stopApplication
string header = "LOCATION";
getHttpResponseHeader( responseHeaders, header, stopURL );
return MSE_SUCCESS;
}
bool DialServer::applicationExists(string &application)
{
return applicationState(application) > MSE_NOTFOUND;
}
bool DialServer::applicationIsRunning(string &application)
{
return applicationState(application) == MSE_RUNNING;
}
bool DialServer::applicationIsStopped(string &application)
{
return applicationState(application) == MSE_STOPPED;
}
bool DialServer::applicationIsInstallable(string &application)
{
return applicationState(application) == MSE_INSTALLABLE;
}
/*
* Copyright (c) 2012 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.
*/
#ifndef DIALSERVER_H
#define DIALSERVER_H
//#define DEBUG
#ifdef DEBUG
#define ATRACE(...) fprintf(stderr,__VA_ARGS__)
#else
#define ATRACE(...)
#endif
#include <string>
#include <vector>
#include "UPnPDevice.h"
enum {
MSE_UNDEFINED = -1,
MSE_NOTFOUND,
MSE_STOPPED,
MSE_RUNNING,
MSE_INSTALLABLE
};
enum {
MSE_ERROR = -1,
MSE_SUCCESS,
MSE_FAILURE
};
using namespace std;
class DialServer : public UPnPDevice
{
public:
/**
* Dial Server ctor
*
* @param[in] location dd.xml LOCATION header
* @param[in] appsUrl Parsed out Application URL
* @param[in] location dd.xml LOCATION header
* empty string to find any server
*
*/
DialServer( string location, string url, string dd_xml ) : UPnPDevice(location, (*url.rbegin() != '/') ? url.append("/") : url, dd_xml)
{}
~DialServer() { ATRACE("%s\n", __FUNCTION__); }
/**
* Launch a DIAL application
*
* @param[in] application Name of the application to launch
* @param[in] payload launch POST data
* @param[out] location Link used to stop application
*
* @return 0 if successful, !0 otherwise
*/
int launchApplication(
string &application,
string &payload,
string& location);
/**
* Get the status of a DIAL application
*
* @param[in] application Name of the application to query
* @param[out] responseHeaders Returns the HTTP response headers
* @param[out] responseBody Returns the HTTP response body
*
* @return 0 if successful, !0 otherwise
*/
int getStatus(
string &application,
string &responseHeaders,
string &responseBody );
/**
* Stop an application using a fully formed URL.
*
* @param[in] stopurl Full stop URL
*
* @return 0 if successful, !0 otherwise
*/
int stopApplication(
string &stopurl );
/**
* Fetch application state
*
* @param[in] application Name of the application to stop
*
* @return state:
* MSE_NOTFOUND
* MSE_STOPPED
* MSE_RUNNING
* MSE_INSTALLABLE
*/
int applicationState(string &application);
/**
* Fetch stopURL for running application
*
* NOTE: This is an extension to the DIAL specification to allow us to stop an already running application.
*
* @param[in] application Name of the application to query
* @param[out] stopurl Full stop URL
*
* @return 0 if successful (application was running), !0 otherwise
*/
int applicationStopURL(string &application, string& stopURL);
// Helper functions
bool applicationExists(string &application);
bool applicationIsRunning(string &application);
bool applicationIsStopped(string &application);
bool applicationIsInstallable(string &application);
};
#endif // DIALSERVER_H
/*
* Copyright (c) 2012 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