/**Takes the tv1-tv2 between two timevals and returns the result as a double*/
#define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-6 * ((tv1).tv_usec - (tv2).tv_usec))
+
#endif //_COMMON_H
// Set number of DataPoints
df->num_points = 0;
+
+ // Set write FILE*
+ df->write_file = fopen(filename, "w");
+ if (df->write_file == NULL)
+ {
+ Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
+ }
// Set read FILE*
df->read_file = fopen(filename, "r");
Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
}
- // Set write FILE*
- df->write_file = fopen(filename, "w");
- if (df->write_file == NULL)
- {
- Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
- }
+
}
/**
* @param end_index - Index to end at (inclusive)
* @param format - The format to use
*/
-void Data_Print(DataFile * df, int start_index, int end_index, DataFormat format)
+void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
{
assert(df != NULL);
assert(start_index >= 0);
* @param end_time - Time to end at (inclusive)
* @param format - The format to use
*/
-void Data_PrintTimes(DataFile * df, double start_time, double end_time, DataFormat format)
+void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
{
assert(df != NULL);
assert(start_time > 0);
// Start time is greater than most recent time stamp
if (start_index >= df->num_points-1 && closest.time_stamp < start_time)
{
- Data_Print(df, 0, 0, format); // Will print "empty" dataset
+ Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset
return;
}
int end_index = Data_FindByTime(df, end_time, &closest);
// Print data between the indexes
- Data_Print(df, start_index, end_index, format);
+ Data_PrintByIndexes(df, start_index, end_index, format);
}
/**
#include <fcgi_stdio.h>
#include <openssl/sha.h>
+#include <stdarg.h>
#include "common.h"
#include "sensor.h"
* @param params User specified paramters: [actuators, sensors]
*/
static void IdentifyHandler(FCGIContext *context, char *params) {
- bool identSensors = false, identActuators = false;
- const char *key, *value;
+ bool ident_sensors = false, ident_actuators = false;
+ //const char *key, *value;
+
int i;
- while ((params = FCGI_KeyPair(params, &key, &value))) {
+ FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
+ {"actuators", &ident_actuators, FCGI_BOOL_T}};
+
+ if (!FCGI_ParseRequest(context, params, values, 2))
+ return;
+
+ /*while ((params = FCGI_KeyPair(params, &key, &value))) {
if (!strcmp(key, "sensors")) {
- identSensors = !identSensors;
+ ident_sensors = !ident_sensors;
} else if (!strcmp(key, "actuators")) {
- identActuators = !identActuators;
+ ident_actuators = !ident_actuators;
}
- }
+ }*/
FCGI_BeginJSON(context, STATUS_OK);
FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
FCGI_JSONLong("api_version", API_VERSION);
- if (identSensors) {
+
+ //Sensor and actuator information
+ if (ident_sensors) {
FCGI_JSONKey("sensors");
FCGI_JSONValue("{\n\t\t");
for (i = 0; i < NUMSENSORS; i++) {
}
FCGI_JSONValue("\n\t}");
}
- if (identActuators) {
+ if (ident_actuators) {
FCGI_JSONKey("actuators");
FCGI_JSONValue("{\n\t\t");
for (i = 0; i < NUMACTUATORS; i++) {
return ptr;
}
+/**
+ * Aids in parsing request parameters. Expected keys along with their type
+ * and whether or not they're required are provided. This function will then
+ * parse the parameter string to find these keys.
+ * @param context The context to work in
+ * @param params The parameter string to be parsed
+ * @param values An array of FCGIValue's that specify expected keys
+ * @param count The number of elements in 'values'.
+ * @return true If the parameter string was parsed successfully, false otherwise.
+ * Modes of failure include: Invalid a parsing error on the value,
+ * an unknown key is specified,
+ * a key/value pair is specified more than once, or
+ * not all required keys were present.
+ * If this function returns false, it is guaranteed that FCGI_RejectJSON
+ * has already been called with the appropriate description message.
+ */
+bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count)
+{
+ const char *key, *value;
+ char buf[BUFSIZ], *ptr;
+ size_t i;
+
+ while ((params = FCGI_KeyPair(params, &key, &value))) {
+ for (i = 0; i < count; i++) {
+ if (!strcmp(key, values[i].key)) {
+ FCGIValue *val = &values[i];
+
+ if (FCGI_RECEIVED(val->flags)) {
+ snprintf(buf, BUFSIZ, "Value already specified for '%s'.", key);
+ FCGI_RejectJSON(context, buf);
+ return false;
+ }
+ val->flags |= FCGI_PARAM_RECEIVED;
+
+ switch(FCGI_TYPE(val->flags)) {
+ case FCGI_BOOL_T:
+ *((bool*) val->value) = true;
+ break;
+ case FCGI_LONG_T:
+ *((long*) val->value) = strtol(value, &ptr, 10);
+ if (!*value || *ptr) {
+ snprintf(buf, BUFSIZ, "Expected int for '%s' but got '%s'", key, value);
+ FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+ return false;
+ }
+ break;
+ case FCGI_DOUBLE_T:
+ *((double*) val->value) = strtod(value, &ptr);
+ if (!*value || *ptr) {
+ snprintf(buf, BUFSIZ, "Expected float for '%s' but got '%s'", key, value);
+ FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+ return false;
+ }
+ break;
+ case FCGI_STRING_T:
+ *((const char**) val->value) = value;
+ break;
+ default:
+ Fatal("Invalid type %d given", FCGI_TYPE(val->flags));
+ }
+ break; //No need to search any more
+ }
+ } //End for loop
+ if (i == count) {
+ snprintf(buf, BUFSIZ, "Unknown key '%s' specified", key);
+ FCGI_RejectJSON(context, FCGI_EscapeJSON(buf));
+ return false;
+ }
+ }
+
+ //Check that required parameters are received
+ for (i = 0; i < count; i++) {
+ if (FCGI_IS_REQUIRED(values[i].flags) && !FCGI_RECEIVED(values[i].flags)) {
+ snprintf(buf, BUFSIZ, "Key '%s' required, but was not given.", values[i].key);
+ FCGI_RejectJSON(context, buf);
+ return false;
+ }
+ }
+ return true;
+}
+
/**
* Begins a response to the client in JSON format.
* @param context The context to work in.
printf("{\r\n");
printf("\t\"module\" : \"%s\"", context->current_module);
FCGI_JSONLong("status", status_code);
-
- // Jeremy: Should we include a timestamp in the JSON; something like this?
- double start_time = g_options.start_time.tv_sec + 1e-6*(g_options.start_time.tv_usec);
+ //Time and running statistics
struct timeval now;
gettimeofday(&now, NULL);
- double current_time = now.tv_sec + 1e-6*(now.tv_usec);
- FCGI_JSONDouble("start_time", start_time);
- FCGI_JSONDouble("current_time", current_time);
- FCGI_JSONDouble("running_time", current_time - start_time);
+ FCGI_JSONDouble("start_time", TIMEVAL_TO_DOUBLE(g_options.start_time));
+ FCGI_JSONDouble("current_time", TIMEVAL_TO_DOUBLE(now));
+ FCGI_JSONDouble("running_time", TIMEVAL_DIFF(now, g_options.start_time));
}
/**
* Adds a key/value pair to a JSON response. The response must have already
- * been initiated by FCGI_BeginJSON. Note that characters are not escaped.
+ * been initiated by FCGI_BeginJSON. Special characters are not escaped.
* @param key The key of the JSON entry
* @param value The value associated with the key.
*/
printf("\r\n}\r\n");
}
+/**
+ * Escapes a string so it can be used as a JSON string value.
+ * Does not support unicode specifiers in the form of \uXXXX.
+ * @param buf The string to be escaped
+ * @return The escaped string (return value == buf)
+ */
+char *FCGI_EscapeJSON(char *buf)
+{
+ int length, i;
+ length = strlen(buf);
+
+ //Escape special characters. Must count down to escape properly
+ for (i = length - 1; i >= 0; i--) {
+ if (buf[i] < 0x20) { //Control characters
+ buf[i] = ' ';
+ } else if (buf[i] == '"') {
+ if (i-1 >= 0 && buf[i-1] == '\\')
+ i--;
+ else
+ buf[i] = '\'';
+ } else if (buf[i] == '\\') {
+ if (i-1 >= 0 && buf[i-1] == '\'')
+ i--;
+ else
+ buf[i] = ' ';
+ }
+ }
+ return buf;
+}
+
/**
* To be used when the input parameters are rejected. The return data
* will also have debugging information provided.
typedef enum StatusCodes {
STATUS_OK = 1,
STATUS_ERROR = -1,
- STATUS_UNAUTHORIZED = -2
+ STATUS_UNAUTHORIZED = -2,
+ STATUS_OUTOFRANGE = -3
} StatusCodes;
+#define FCGI_PARAM_REQUIRED (1 << 0)
+#define FCGI_PARAM_RECEIVED (1 << 1)
+#define FCGI_BOOL_T (1 << 2)
+#define FCGI_LONG_T (1 << 3)
+#define FCGI_DOUBLE_T (1 << 4)
+#define FCGI_STRING_T (1 << 5)
+#define FCGI_REQUIRED(x) ((x) | FCGI_PARAM_REQUIRED)
+#define FCGI_IS_REQUIRED(x) ((x) & FCGI_PARAM_REQUIRED)
+#define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED)
+#define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED))
+
+typedef struct FCGIValue {
+ const char *key;
+ void *value;
+ unsigned flags;
+} FCGIValue;
+
typedef struct FCGIContext FCGIContext;
typedef void (*ModuleHandler) (FCGIContext *context, char *params);
extern void FCGI_EndControl(FCGIContext *context);
extern bool FCGI_HasControl(FCGIContext *context, const char *key);
extern char *FCGI_KeyPair(char *in, const char **key, const char **value);
+extern bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count);
extern void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code);
extern void FCGI_JSONPair(const char *key, const char *value);
extern void FCGI_JSONLong(const char *key, long value);
extern void FCGI_JSONKey(const char *key);
extern void FCGI_PrintRaw(const char *format, ...);
extern void FCGI_EndJSON();
+extern char *FCGI_EscapeJSON(char *buf);
extern void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description);
extern void *FCGI_RequestLoop (void *data);
#!/bin/bash
# Use this to quickly test run the server in valgrind
-#spawn-fcgi -p9005 -n ./valgrind.sh
+spawn-fcgi -p9005 -n ./valgrind.sh
# Use this to run the server normally
#./stream &
-spawn-fcgi -p9005 -n ./server
+#spawn-fcgi -p9005 -n ./server
gettimeofday(&now, NULL);
double current_time = TIMEVAL_DIFF(now, g_options.start_time);
+ int id = 0;
double start_time = 0;
double end_time = current_time;
char * fmt_str;
// key/value pairs
FCGIValue values[] = {
- {"id", &id, FCGI_REQUIRED(FCGI_INT_T)},
+ {"id", &id, FCGI_REQUIRED(FCGI_LONG_T)},
{"format", &fmt_str, FCGI_STRING_T},
{"start_time", &start_time, FCGI_DOUBLE_T},
{"end_time", &end_time, FCGI_DOUBLE_T},
if (id < 0 || id >= NUMSENSORS)
{
Log(LOGERR, "Invalid id %d", id);
- FCGI_RejectJSON(); // Whoops, I do still need this!
+ FCGI_RejectJSON(context, "Invalid id"); // Whoops, I do still need this!
}
+
+ DataFormat format = JSON;
// Check if format type was specified
if (FCGI_RECEIVED(values[FORMAT].flags))
}
// Get Sensor
- Sensor * s = g_sensors[id];
+ Sensor * s = g_sensors+id;
// Begin response
Sensor_BeginResponse(context, id, format);
/** Most recently recorded data **/
DataPoint newest_data;
-
} Sensor;
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="unit-tests.js"></script>
-
- <div style="margin:1em auto; width: 20em;">
- <h3>Access control</h3>
- <form id="control" action="#">
- <table>
- <tr>
- <td>Username:</td>
- <td><input type="text" id="username"></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" id="password"></td>
- </tr>
- <tr>
- <td><input type="submit" value="Submit"></td>
- </tr>
- </table>
- </form>
- </div>
+
+ <hr>
+ <h3 style="margin: 1em auto;">
+ The control tests won't complete until the credentials are
+ supplied below.
+ </h3>
+ <div style="margin:1em auto; width: 20em;">
+
+ <h4>Access control</h4>
+ <form id="control" action="#">
+ <table>
+ <tr>
+ <td>Username:</td>
+ <td><input type="text" id="username"></td>
+ </tr>
+ <tr>
+ <td>Password:</td>
+ <td><input type="password" id="password"></td>
+ </tr>
+ <tr>
+ <td><input type="submit" value="Submit"></td>
+ </tr>
+ </table>
+ </form>
+ </div>
</body>
</html>
\ No newline at end of file
ut = {};
ut.api = location.protocol + "//" + location.host + "/api/";
-ut.ckey = undefined;
-ut.controlcb = $.Callbacks();
+ut.controlcb = $.Deferred();
/**
- * Sends an AJAX query to the API
+ * Sends a synchronous AJAX query to the API
+ * A synchronous request makes it easier for unit testing.
* @param {string} module The name of the module to be queried
* @param {Object} opts Object holding the parameters, username, password and
* callback. The parameters should be an object of key/value
dataType: 'json',
data: opts.params,
beforeSend: authfunc,
- async: opts.async
+ async: false
}).done(opts.callback)
.fail(function(jqXHR) {
ok(false, "Request failed: " + jqXHR.status.toString() + " " + jqXHR.statusText);
}
QUnit.module("API basics");
-QUnit.asyncTest("API Existence (identify)", function () {
- query("identify", {callback : function(data) {
- start();
- ok(data.status > 0, "Return status");
- ok(data.description, data.description);
- ok(data.build_date, data.build_date);
- }});
+QUnit.test("API Existence (identify)", function () {
+ query("identify", {params : {actuators : true, sensors : true},
+ callback : function(data) {
+ ok(data.status > 0, "Return status");
+ ok(data.description !== undefined, data.description);
+ ok(data.build_date !== undefined, data.build_date);
+ ok(data.api_version !== undefined, "API version: " + data.api_version);
+ ok(data.sensors !== undefined, "Sensors list");
+ ok(data.actuators !== undefined, "Actuators list");
+
+ var sl = "Sensors: ", al = "Actuators: ";
+ for (var id in data.sensors) {
+ sl += id + ":" + data.sensors[id] + " ";
+ }
+ for (var id in data.actuators) {
+ al += id + ":" + data.actuators[id] + " ";
+ }
+ ok(sl, sl);
+ ok(al, al);
+ }});
});
-QUnit.asyncTest("Invalid module", function () {
+QUnit.test("Invalid module", function () {
query("dontexist", {callback : function(data) {
- start();
ok(data.status < 0);
}});
});
QUnit.module("Sensors");
-QUnit.asyncTest("Existence", function() {
- query("sensors", {params : {id : 0}, callback : function(data) {
- start();
- ok(data.status > 0, "Return status");
- ok(data.data !== undefined, "Data field existence");
- var result = "Data: ";
- for (var i = 0; i < data.data.length; i++) {
- result += data.data[i][0] + ":" + data.data[i][1] + ", ";
- }
- ok(true, result);
- }});
-});
-
-QUnit.asyncTest("Invalid sensor id 1", function() {
- query("sensors", {params : {id : 999}, callback : function(data) {
- start();
- ok(data.status < 0, "Return status");
- }});
+QUnit.test("Existence", function() {
+ query("identify", {params : {sensors : 1}, callback : function(data) {
+ ok(data.status > 0, "Identification");
+ var hasSensor = false;
+ for (var id in data.sensors) {
+ hasSensor = true;
+ query("sensors", {params : {id : id}, callback : function(data) {
+ ok(data.status > 0, "Sensor " + id);
+ ok(data.data !== undefined, "Data field existence");
+ var result = "Data: ";
+ for (var i = 0; i < data.data.length; i++) {
+ result += data.data[i][0] + ":" + data.data[i][1] + ", ";
+ }
+ ok(true, result);
+ }});
+ }
+ ok(hasSensor, "Has at least one sensor");
+ }});
+
});
-QUnit.asyncTest("Invalid sensor id 2", function() {
+QUnit.test("Invalid sensor ids", function() {
query("sensors", {params : {id : ""}, callback : function(data) {
- start();
- ok(data.status < 0, "Return status");
+ ok(data.status < 0, "No id");
}});
-});
-
-QUnit.asyncTest("Out of bounds sensor id 1", function() {
+ query("sensors", {params : {id : 999}, callback : function(data) {
+ ok(data.status < 0, "Id too large");
+ }});
query("sensors", {params : {id : "-1"}, callback : function(data) {
- start();
- ok(data.status < 0, "Return status");
- }});
-});
-
-QUnit.asyncTest("Out of bounds sensor id 2", function() {
- query("sensors", {params : {id : "999"}, callback : function(data) {
- start();
- ok(data.status < 0, "Return status");
+ ok(data.status < 0, "Negative id");
}});
});
QUnit.module("Controls and access");
-QUnit.asyncTest("Gaining access", function() {
- ut.controlcb.add(function () {
+QUnit.asyncTest("Setting actuator value", function () {
+ $.when(ut.controlcb).done(function () {
+ start();
+ var key;
+
query("control", {params : {action : "start", force : true},
username : $("#username").val(), password : $("#password").val(),
async : false,
callback : function(data) {
- start();
- ok(data.status > 0, "Return status");
- ut.ckey = data.key;
- }});
- });
-});
-
-QUnit.asyncTest("Setting actuator value", function () {
- ut.controlcb.add(function () {
+ ok(data.status > 0, "Gaining access key");
+ ok(data.key, "Access key - " + data.key);
+ key = data.key;
+ }});
query("control", {params : {action : "set", id : 0,
username : $("#username").val(), password : $("#password").val(),
- value : 200, key : ut.ckey},
+ value : 200, key : key},
callback : function(data) {
- start();
- ok(data.status > 0, "Return status");
+ ok(data.status > 0, "Setting actuator");
ok(true, data.description);
}});
});
$(document).ready(function(){
$("#control").submit(function () {
- ut.controlcb.fire();
+ ut.controlcb.resolve();
return false;
});
});
\ No newline at end of file