blob: 8929594ed16253dc25a3680da4ab1f4aee7a8fd4 [file] [log] [blame]
/*
* 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.
*/
#include "dial_server.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include "mongoose.h"
// TODO: Partners should define this port
#define DIAL_PORT (56789)
struct DIALApp_ {
struct DIALApp_ *next;
struct DIALAppCallbacks callbacks;
void *callback_data;
DIAL_run_t run_id;
DIALStatus state;
char *name;
char payload[DIAL_MAX_PAYLOAD];
};
typedef struct DIALApp_ DIALApp;
struct DIALServer_ {
struct mg_context *ctx;
struct DIALApp_ *apps;
pthread_mutex_t mux;
};
static void ds_lock(DIALServer *ds) {
pthread_mutex_lock(&ds->mux);
}
static void ds_unlock(DIALServer *ds) {
pthread_mutex_unlock(&ds->mux);
}
// finds an app and returns a pointer to the previous element's next pointer
// if not found, return a pointer to the last element's next pointer
static DIALApp **find_app(DIALServer *ds, const char *app_name) {
DIALApp *app;
DIALApp **ret = &ds->apps;
for (app = ds->apps; app != NULL; ret = &app->next, app = app->next) {
if (!strcmp(app_name, app->name)) {
break;
}
}
return ret;
}
/*
* A bad payload is defined to be an unprintable character or a
* non-ascii character.
*/
static int isBadPayload( const char* pPayload, int numBytes )
{
int i = 0;
fprintf( stderr, "Checking %d bytes\n", numBytes );
for( ; i < numBytes; i++)
{
// High order bit should not be set
// 0x7F is DEL (non-printable)
// Anything under 32 is non-printable
if( ((pPayload[i] & 0x80) == 0x80) ||
(pPayload[i] == 0x7F) ||
(pPayload[i] <= 0x1F) )
return 1;
}
return 0;
}
static void handle_app_start(struct mg_connection *conn,
const struct mg_request_info *request_info,
const char *app_name) {
char body[DIAL_MAX_PAYLOAD+2] = {0,};
DIALApp *app;
DIALServer *ds = request_info->user_data;
int nread;
ds_lock(ds);
app = *find_app(ds, app_name);
if (!app) {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
} else {
nread = mg_read(conn, body, sizeof(body));
// NUL-terminate it just in case
if( nread > DIAL_MAX_PAYLOAD ) {
mg_send_http_error(conn, 413, "413 Request Entity Too Large",
"413 Request Entity Too Large");
}
else if( isBadPayload( body, nread ) ){
mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
}
else {
app->state = app->callbacks.start_cb(ds, app_name, body, nread,
&app->run_id, app->callback_data);
if (app->state == kDIALStatusRunning) {
char laddr[INET6_ADDRSTRLEN];
const struct sockaddr_in *addr =
(struct sockaddr_in *)&request_info->local_addr;
inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
mg_printf(conn, "HTTP/1.1 201 Created\r\n"
"Content-Type: text/plain\r\n"
"Location: http://%s:%d/apps/%s/run\r\n"
"\r\n", laddr, DIAL_get_port(ds), app_name);
// copy the payload into the application struct
memset( app->payload, 0, DIAL_MAX_PAYLOAD );
memcpy( app->payload, body, nread );
} else {
mg_send_http_error(conn, 503, "Service Unavailable",
"Service Unavailable");
}
}
}
ds_unlock(ds);
}
static void handle_app_status(struct mg_connection *conn,
const struct mg_request_info *request_info,
const char *app_name) {
DIALApp *app;
int canStop = 0;
DIALServer *ds = request_info->user_data;
ds_lock(ds);
app = *find_app(ds, app_name);
if (!app) {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
} else {
app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
&canStop, app->callback_data);
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: application/xml\r\n"
"\r\n"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
"<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\">\r\n"
" <name>%s</name>\r\n"
" <options allowStop=\"%s\"/>\r\n"
" <state>%s</state>\r\n"
"%s"
"</service>\r\n", app->name, canStop ? "true":"false",
app->state ? "running" : "stopped",
app->state == kDIALStatusStopped ?
"" : " <link rel=\"run\" href=\"run\"/>\r\n" );
}
ds_unlock(ds);
}
static void handle_app_stop(struct mg_connection *conn,
const struct mg_request_info *request_info,
const char *app_name) {
DIALApp *app;
DIALServer *ds = request_info->user_data;
int canStop = 0;
ds_lock(ds);
app = *find_app(ds, app_name);
// update the application state
if( !app ) {
app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
&canStop, app->callback_data);
}
if (!app || app->state != kDIALStatusRunning) {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
} else {
app->callbacks.stop_cb(ds, app_name, app->run_id, app->callback_data);
app->state = kDIALStatusStopped;
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"\r\n");
}
ds_unlock(ds);
}
#define APPS_URI "/apps/"
#define RUN_URI "/run"
static void *request_handler(enum mg_event event,
struct mg_connection *conn,
const struct mg_request_info *request_info) {
if (event == MG_NEW_REQUEST) {
// URL ends with run
if (!strncmp(request_info->uri + strlen(request_info->uri)-4, RUN_URI, strlen(RUN_URI))) {
char app_name[256] = {0,}; // assuming the application name is not over 256 chars.
strncpy( app_name, request_info->uri + strlen(APPS_URI),
((strlen(request_info->uri)-4) - (sizeof(APPS_URI)-1)) );
// DELETE non-empty app name
if (app_name[0] != '\0' &&
!strcmp(request_info->request_method, "DELETE")) {
handle_app_stop(conn, request_info, app_name);
} else {
mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
}
}
// URI starts with "/apps/"
else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)) {
const char *app_name;
app_name = request_info->uri + sizeof(APPS_URI) - 1;
// start app
if (!strcmp(request_info->request_method, "POST")) {
handle_app_start(conn, request_info, app_name);
// get app status
} else if (!strcmp(request_info->request_method, "GET")) {
handle_app_status(conn, request_info, app_name);
} else {
mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
}
} else {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
}
return "done";
} else if (event == MG_EVENT_LOG) {
fprintf( stderr, "MG: %s\n", request_info->log_message);
return "done";
}
return NULL;
}
DIALServer *DIAL_start() {
DIALServer *ds = calloc(1, sizeof(DIALServer));
struct mg_context *ctx;
pthread_mutex_init(&ds->mux, NULL);
ctx = mg_start(&request_handler, ds, DIAL_PORT);
if (!ctx) {
free(ds);
return NULL;
}
ds->ctx = ctx;
return ds;
}
void DIAL_stop(DIALServer *ds) {
mg_stop(ds->ctx);
pthread_mutex_destroy(&ds->mux);
}
in_port_t DIAL_get_port(DIALServer *ds) {
struct sockaddr sa;
socklen_t len = sizeof(sa);
if (!mg_get_listen_addr(ds->ctx, &sa, &len)) {
return 0;
}
return ntohs(((struct sockaddr_in *)&sa)->sin_port);
}
int DIAL_register_app(DIALServer *ds, const char *app_name,
struct DIALAppCallbacks *callbacks,
void *user_data) {
DIALApp **ptr, *app;
int ret;
ds_lock(ds);
ptr = find_app(ds, app_name);
if (*ptr != NULL) { // app already registered
ds_unlock(ds);
ret = 0;
} else {
app = malloc(sizeof(DIALApp));
app->callbacks = *callbacks;
app->name = strdup(app_name);
app->next = *ptr;
app->state = kDIALStatusStopped;
app->callback_data = user_data;
*ptr = app;
ret = 1;
}
ds_unlock(ds);
return ret;
}
int DIAL_unregister_app(DIALServer *ds, const char *app_name) {
DIALApp **ptr, *app;
int ret;
ds_lock(ds);
ptr = find_app(ds, app_name);
if (*ptr == NULL) { // no such app
ret = 0;
} else {
app = *ptr;
*ptr = app->next;
free(app->name);
free(app);
ret = 1;
}
ds_unlock(ds);
return ret;
}
const char * DIAL_get_payload(DIALServer *ds, const char *app_name)
{
const char * pPayload = NULL;
DIALApp **ptr, *app;
// NOTE: Don't grab the mutex as we are calling this function from
// inside the application callback which already has the lock.
//ds_lock(ds);
ptr = find_app(ds, app_name);
if (*ptr != NULL)
{
app = *ptr;
pPayload = app->payload;
}
//ds_unlock(ds);
return pPayload;
}