| /* |
| * 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; |
| } |
| |