Merge branch 'refactor'
authorSam Moore <[email protected]>
Thu, 12 Sep 2013 15:50:44 +0000 (23:50 +0800)
committerSam Moore <[email protected]>
Thu, 12 Sep 2013 15:50:44 +0000 (23:50 +0800)
Conflicts:
server/sensor.c
server/sensor.h

Also made the resulting code compile.

BUT...
There are memory errors in the FCGI code :S

server/common.h
server/data.c
server/fastcgi.c
server/fastcgi.h
server/run.sh
server/sensor.c
server/sensor.h
testing/qunit/index.html
testing/qunit/unit-tests.js

index 5b6be66..a690149 100644 (file)
@@ -32,4 +32,5 @@
 /**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
index 254b7bc..57b51ff 100644 (file)
@@ -33,6 +33,13 @@ void Data_Open(DataFile * df, const char * filename)
 
        // 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");
@@ -41,12 +48,7 @@ void Data_Open(DataFile * df, const char * filename)
                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));
-       }
+
 }
 
 /**
@@ -150,7 +152,7 @@ int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
  * @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);
@@ -218,7 +220,7 @@ void Data_Print(DataFile * df, int start_index, int end_index, DataFormat format
  * @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);
@@ -233,7 +235,7 @@ void Data_PrintTimes(DataFile * df, double start_time, double end_time, DataForm
        // 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;
        }
 
@@ -241,7 +243,7 @@ void Data_PrintTimes(DataFile * df, double start_time, double end_time, DataForm
        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);
 }
 
 /**
index 4f47584..b58ba79 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <fcgi_stdio.h>
 #include <openssl/sha.h>
+#include <stdarg.h>
 
 #include "common.h"
 #include "sensor.h"
@@ -37,23 +38,32 @@ struct FCGIContext {
  * @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++) {
@@ -64,7 +74,7 @@ static void IdentifyHandler(FCGIContext *context, char *params) {
                }
                FCGI_JSONValue("\n\t}");
        }
-       if (identActuators) {
+       if (ident_actuators) {
                FCGI_JSONKey("actuators");
                FCGI_JSONValue("{\n\t\t");
                for (i = 0; i < NUMACTUATORS; i++) {
@@ -189,6 +199,87 @@ char *FCGI_KeyPair(char *in, const char **key, const char **value)
        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.
@@ -200,20 +291,17 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
        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.
  */
@@ -270,6 +358,36 @@ void FCGI_EndJSON()
        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.
index f52a20a..965cf5c 100644 (file)
 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);
 
@@ -26,6 +44,7 @@ extern void FCGI_BeginControl(FCGIContext *context, bool force);
 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);
@@ -34,6 +53,7 @@ extern void FCGI_JSONBool(const char *key, bool 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);
 
index aafc5d1..a172c45 100755 (executable)
@@ -1,6 +1,6 @@
 #!/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
index 2e08a30..8914817 100644 (file)
@@ -258,13 +258,14 @@ void Sensor_Handler(FCGIContext *context, char * params)
        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},
@@ -289,8 +290,10 @@ void Sensor_Handler(FCGIContext *context, char * params)
        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))
@@ -304,7 +307,7 @@ void Sensor_Handler(FCGIContext *context, char * params)
        }
 
        // Get Sensor
-       Sensor * s = g_sensors[id];
+       Sensor * s = g_sensors+id;
        
        // Begin response
        Sensor_BeginResponse(context, id, format);
index a499b77..55b7d89 100644 (file)
@@ -47,7 +47,6 @@ typedef struct
        /** Most recently recorded data **/
        DataPoint newest_data;
 
-       
 } Sensor;
 
 
index faf4b51..765bf04 100644 (file)
        <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
index 12bb76a..476e145 100644 (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
@@ -39,7 +39,7 @@ function query(module, opts) {
     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);
@@ -48,86 +48,87 @@ function query(module, opts) {
 }
 
 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);
     }});
   });
@@ -135,7 +136,7 @@ QUnit.asyncTest("Setting actuator value", function () {
 
 $(document).ready(function(){
   $("#control").submit(function () {
-    ut.controlcb.fire();
+    ut.controlcb.resolve();
     return false;
   });
 });
\ No newline at end of file

UCC git Repository :: git.ucc.asn.au