From 34ec9353b52472aabc2b42ec0861e004c50b76eb Mon Sep 17 00:00:00 2001 From: Jeremy Tan Date: Thu, 5 Sep 2013 21:18:32 +0800 Subject: [PATCH] Change @purpose to @brief (Doxygen warning) and work on unit tests --- server/common.h | 2 +- server/control.c | 4 + server/control.h | 5 + server/fastcgi.c | 37 ++++---- server/fastcgi.h | 28 ++++-- server/log.c | 2 +- server/log.h | 2 +- server/main.c | 2 +- server/options.h | 2 +- server/sensor.c | 5 +- server/sensor.h | 3 +- server/thread.c | 2 +- server/thread.h | 2 +- testing/qunit/base64.js | 176 ++++++++++++++++++++++++++++++++++++ testing/qunit/index.html | 28 +++++- testing/qunit/unit-tests.js | 98 ++++++++++++-------- 16 files changed, 323 insertions(+), 75 deletions(-) create mode 100644 testing/qunit/base64.js diff --git a/server/common.h b/server/common.h index eba4902..67ea195 100644 --- a/server/common.h +++ b/server/common.h @@ -1,6 +1,6 @@ /** * @file common.h - * @purpose Common header includes + * @brief Common header includes */ #ifndef _COMMON_H diff --git a/server/control.c b/server/control.c index 12e340a..59b5bf8 100644 --- a/server/control.c +++ b/server/control.c @@ -1,3 +1,7 @@ +/** + * @file control.c + * @brief Handles all client control requests (admin/actuator related) + */ #include "common.h" #include "control.h" diff --git a/server/control.h b/server/control.h index cc8f327..2878a88 100644 --- a/server/control.h +++ b/server/control.h @@ -1,6 +1,11 @@ +/** + * @file control.h + * @brief Header file for control functions + */ #ifndef _CONTROL_H #define _CONTROL_H +/**ID codes for all the actuators**/ typedef enum Actuators {ACT_NONE = -1, ACT_PREG = 0, ACT_SOLENOID1} Actuators; extern void Control_Handler(FCGIContext *context, char *params); diff --git a/server/fastcgi.c b/server/fastcgi.c index 2da4b12..cb12e85 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -1,6 +1,6 @@ /** * @file fastcgi.c - * @purpose Runs the FCGI request loop to handle web interface requests. + * @brief Runs the FCGI request loop to handle web interface requests. * * fcgi_stdio.h must be included before all else so the stdio function * redirection works ok. @@ -15,8 +15,10 @@ #include "control.h" #include "options.h" +/**The time period (in seconds) before the control key expires @ */ #define CONTROL_TIMEOUT 180 +/**Contextual information related to FCGI requests*/ struct FCGIContext { /**The time of last valid user access possessing the control key*/ time_t control_timestamp; @@ -177,7 +179,7 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code) * 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. * @param key The key of the JSON entry - * ¶m value The value associated with the key. + * @param value The value associated with the key. */ void FCGI_JSONPair(const char *key, const char *value) { @@ -224,19 +226,6 @@ void FCGI_JSONKey(const char *key) printf(",\r\n\t\"%s\" : ", key); } -/** - * Should be used to write out the value of a JSON key. This has - * the same format as the printf functions. Care should be taken to format - * the output in valid JSON. - */ -void FCGI_JSONValue(const char *format, ...) -{ - va_list list; - va_start(list, format); - vprintf(format, list); - va_end(list); -} - /** * Ends a JSON response that was initiated by FCGI_BeginJSON. */ @@ -261,7 +250,6 @@ void FCGI_RejectJSON(FCGIContext *context) * @param context The context to work in * @param status The status the return data should have. * @param description A short description of why the input was rejected. - * @param params The parameters that the module handler received. */ void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description) { @@ -278,6 +266,21 @@ void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *des FCGI_EndJSON(); } +/** + * Generates a response to the client as described by the format parameter and + * extra arguments (exactly like printf). To be used when none of the other + * predefined functions will work exactly as needed. Extra care should be taken + * to ensure the correctness of the output. + * @param format The format string + * @param ... Any extra arguments as required by the format string. + */ +void FCGI_PrintRaw(const char *format, ...) +{ + va_list list; + va_start(list, format); + vprintf(format, list); + va_end(list); +} /** * Main FCGI request loop that receives/responds to client requests. @@ -340,6 +343,4 @@ void * FCGI_RequestLoop (void *data) Thread_QuitProgram(false); // NOTE: Don't call pthread_exit, because this runs in the main thread. Just return. return NULL; - - } diff --git a/server/fastcgi.h b/server/fastcgi.h index c4d2ef5..2003fd0 100644 --- a/server/fastcgi.h +++ b/server/fastcgi.h @@ -1,16 +1,22 @@ /** * @file fastcgi.h - * @purpose Headers for the fastcgi web interface + * @brief Headers for the fastcgi web interface */ #ifndef _FASTCGI_H #define _FASTCGI_H -/**(HTTP) Status codes that fcgi module handlers can return**/ +/** + * Status codes that fcgi module handlers can return + * Success status codes have values > 0 + * Failure status codes have values <(=) 0 + * Note: 0 is counted as an error code to minimise confusion + * with in-browser JSON parsing error codes + */ typedef enum StatusCodes { - STATUS_OK = 200, - STATUS_ERROR = 400, - STATUS_UNAUTHORIZED = 401 + STATUS_OK = 1, + STATUS_ERROR = -1, + STATUS_UNAUTHORIZED = -2 } StatusCodes; typedef struct FCGIContext FCGIContext; @@ -26,12 +32,20 @@ extern void FCGI_JSONLong(const char *key, long value); extern void FCGI_JSONDouble(const char *key, double value); extern void FCGI_JSONBool(const char *key, bool value); extern void FCGI_JSONKey(const char *key); -extern void FCGI_JSONValue(const char *format, ...); +extern void FCGI_PrintRaw(const char *format, ...); extern void FCGI_EndJSON(); extern void FCGI_RejectJSON(FCGIContext *context); extern void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description); extern void * FCGI_RequestLoop (void *data); -#define FCGI_PrintRaw FCGI_JSONValue // Functionality is identical + +/** + * Custom formatting function for the JSON value. To be used in + * conjunction with FCGI_JSONKey. Care should be taken to ensure + * that valid JSON is produced. + * + * @see FCGI_PrintRaw for calling syntax + */ +#define FCGI_JSONValue FCGI_PrintRaw #endif diff --git a/server/log.c b/server/log.c index 7b67043..d964e61 100644 --- a/server/log.c +++ b/server/log.c @@ -1,6 +1,6 @@ /** * @file log.c - * @purpose Implement logging and error handling functions + * @brief Implement logging and error handling functions */ diff --git a/server/log.h b/server/log.h index 2969540..eec5e53 100644 --- a/server/log.h +++ b/server/log.h @@ -1,6 +1,6 @@ /** * @file log.h - * @purpose Declaration of functions for printing log messages and/or terminating program after a fatal error + * @brief Declaration of functions for printing log messages and/or terminating program after a fatal error */ #ifndef _LOG_H diff --git a/server/main.c b/server/main.c index ec21a25..b202418 100644 --- a/server/main.c +++ b/server/main.c @@ -1,6 +1,6 @@ /** * @file main.c - * @purpose main and its helper functions, signal handling and cleanup functions + * @brief main and its helper functions, signal handling and cleanup functions */ // --- Custom headers --- // diff --git a/server/options.h b/server/options.h index a15e14a..a20e0e1 100644 --- a/server/options.h +++ b/server/options.h @@ -1,6 +1,6 @@ /** * @file options.h - * @purpose Define the Options structure and the g_options variable + * @brief Define the Options structure and the g_options variable */ #ifndef _OPTIONS_H diff --git a/server/sensor.c b/server/sensor.c index 2e0749e..f58daa5 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -1,10 +1,9 @@ /** * @file sensor.c - * @purpose Implementation of sensor thread + * @brief Implementation of sensor thread * TODO: Finalise implementation */ - #include "common.h" #include "sensor.h" #include "options.h" @@ -12,7 +11,7 @@ /** Array of sensors, initialised by Sensor_Init **/ static Sensor g_sensors[NUMSENSORS]; //global to this file -static const char * g_sensor_names[] = {"analog_test0","analog_test1","digital_test0","digital_test1"}; +static const char * g_sensor_names[] = {"analog_test0", "analog_test1", "digital_test0", "digital_test1"}; /** * Read a data value from a sensor; block until value is read * @param sensor_id - The ID of the sensor diff --git a/server/sensor.h b/server/sensor.h index 8b20077..c6cfbe0 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -1,7 +1,6 @@ - /** * @file sensor.h - * @purpose Declarations for sensor thread related stuff + * @brief Declarations for sensor thread related stuff */ diff --git a/server/thread.c b/server/thread.c index 57473ea..f1d0bb3 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1,6 +1,6 @@ /** * @file thread.c - * @purpose Implementation of thread control + * @brief Implementation of thread control */ #include "thread.h" diff --git a/server/thread.h b/server/thread.h index 14ae785..1c14c9a 100644 --- a/server/thread.h +++ b/server/thread.h @@ -1,6 +1,6 @@ /** * @file thread.h - * @purpose Declarations for thread control related functions and variables + * @brief Declarations for thread control related functions and variables */ #ifndef _THREAD_H diff --git a/testing/qunit/base64.js b/testing/qunit/base64.js new file mode 100644 index 0000000..5baba0d --- /dev/null +++ b/testing/qunit/base64.js @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010 Nick Galbreath + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* base64 encode/decode compatible with window.btoa/atob + * + * window.atob/btoa is a Firefox extension to convert binary data (the "b") + * to base64 (ascii, the "a"). + * + * It is also found in Safari and Chrome. It is not available in IE. + * + * if (!window.btoa) window.btoa = base64.encode + * if (!window.atob) window.atob = base64.decode + * + * The original spec's for atob/btoa are a bit lacking + * https://developer.mozilla.org/en/DOM/window.atob + * https://developer.mozilla.org/en/DOM/window.btoa + * + * window.btoa and base64.encode takes a string where charCodeAt is [0,255] + * If any character is not [0,255], then an DOMException(5) is thrown. + * + * window.atob and base64.decode take a base64-encoded string + * If the input length is not a multiple of 4, or contains invalid characters + * then an DOMException(5) is thrown. + */ +var base64 = {}; +base64.PADCHAR = '='; +base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +base64.makeDOMException = function() { + // sadly in FF,Safari,Chrome you can't make a DOMException + var e, tmp; + + try { + return new DOMException(DOMException.INVALID_CHARACTER_ERR); + } catch (tmp) { + // not available, just passback a duck-typed equiv + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype + var ex = new Error("DOM Exception 5"); + + // ex.number and ex.description is IE-specific. + ex.code = ex.number = 5; + ex.name = ex.description = "INVALID_CHARACTER_ERR"; + + // Safari/Chrome output format + ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; }; + return ex; + } +} + +base64.getbyte64 = function(s,i) { + // This is oddly fast, except on Chrome/V8. + // Minimal or no improvement in performance by using a + // object with properties mapping chars to value (eg. 'A': 0) + var idx = base64.ALPHA.indexOf(s.charAt(i)); + if (idx === -1) { + throw base64.makeDOMException(); + } + return idx; +} + +base64.decode = function(s) { + // convert to string + s = '' + s; + var getbyte64 = base64.getbyte64; + var pads, i, b10; + var imax = s.length + if (imax === 0) { + return s; + } + + if (imax % 4 !== 0) { + throw base64.makeDOMException(); + } + + pads = 0 + if (s.charAt(imax - 1) === base64.PADCHAR) { + pads = 1; + if (s.charAt(imax - 2) === base64.PADCHAR) { + pads = 2; + } + // either way, we want to ignore this last block + imax -= 4; + } + + var x = []; + for (i = 0; i < imax; i += 4) { + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | + (getbyte64(s,i+2) << 6) | getbyte64(s,i+3); + x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff)); + } + + switch (pads) { + case 1: + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6); + x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff)); + break; + case 2: + b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12); + x.push(String.fromCharCode(b10 >> 16)); + break; + } + return x.join(''); +} + +base64.getbyte = function(s,i) { + var x = s.charCodeAt(i); + if (x > 255) { + throw base64.makeDOMException(); + } + return x; +} + +base64.encode = function(s) { + if (arguments.length !== 1) { + throw new SyntaxError("Not enough arguments"); + } + var padchar = base64.PADCHAR; + var alpha = base64.ALPHA; + var getbyte = base64.getbyte; + + var i, b10; + var x = []; + + // convert to string + s = '' + s; + + var imax = s.length - s.length % 3; + + if (s.length === 0) { + return s; + } + for (i = 0; i < imax; i += 3) { + b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2); + x.push(alpha.charAt(b10 >> 18)); + x.push(alpha.charAt((b10 >> 12) & 0x3F)); + x.push(alpha.charAt((b10 >> 6) & 0x3f)); + x.push(alpha.charAt(b10 & 0x3f)); + } + switch (s.length - imax) { + case 1: + b10 = getbyte(s,i) << 16; + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + + padchar + padchar); + break; + case 2: + b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8); + x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) + + alpha.charAt((b10 >> 6) & 0x3f) + padchar); + break; + } + return x.join(''); +} diff --git a/testing/qunit/index.html b/testing/qunit/index.html index e666fce..faf4b51 100644 --- a/testing/qunit/index.html +++ b/testing/qunit/index.html @@ -6,10 +6,36 @@ - + + + +
+ +
+

Access control

+
+ + + + + + + + + + + + +
Username:
Password:
+
+
\ No newline at end of file diff --git a/testing/qunit/unit-tests.js b/testing/qunit/unit-tests.js index 2bab3f8..12bb76a 100644 --- a/testing/qunit/unit-tests.js +++ b/testing/qunit/unit-tests.js @@ -2,12 +2,17 @@ * MCTX3420 2013 - Remote pressurised can experiment. * Unit testing for the server API. * These unit tests use the QUnit unit testing framework. - * @requires QUnit and jQuery + * @requires QUnit, jQuery, and base64.js * @date 28/8/13 * @author Jeremy Tan */ -var api = location.protocol + "//" + location.host + "/api/"; +//Namespace ut + +ut = {}; +ut.api = location.protocol + "//" + location.host + "/api/"; +ut.ckey = undefined; +ut.controlcb = $.Callbacks(); /** * Sends an AJAX query to the API @@ -15,54 +20,38 @@ var api = location.protocol + "//" + location.host + "/api/"; * @param {Object} opts Object holding the parameters, username, password and * callback. The parameters should be an object of key/value * pairs. - * @returns JSON data + * @returns jqXHR object (but calls callback with JSON data, or null on AJAX error) */ function query(module, opts) { - function buildQuery(opts) { - var result = "?"; - var first = true; - - for (key in opts) { - if (!first) - result += "&"; - else - first = false; - result += encodeURIComponent(key) + - ((opts[key] !== undefined) ? "=" + encodeURIComponent(opts[key]) : ""); - } - return result; - } - - var queryurl = api + module; - if (opts.params) - queryurl += buildQuery(opts.params); + var queryurl = ut.api + module; var authfunc; if (opts.username) { authfunc = function(xhr) { xhr.setRequestHeader("Authorization", - "Basic " + btoa(opts.username + ":" + opts.password)); + "Basic " + base64.encode(opts.username + ":" + opts.password)); }; } - $.ajax({ + return $.ajax({ url: queryurl, type: 'GET', dataType: 'json', - beforeSend: authfunc + data: opts.params, + beforeSend: authfunc, + async: opts.async }).done(opts.callback) .fail(function(jqXHR) { - alert("Request Failed!"); ok(false, "Request failed: " + jqXHR.status.toString() + " " + jqXHR.statusText); opts.callback(null); }); } QUnit.module("API basics"); -QUnit.asyncTest("Existence (identify)", function () { +QUnit.asyncTest("API Existence (identify)", function () { query("identify", {callback : function(data) { start(); - ok(data.status >= 0, "Return status"); + ok(data.status > 0, "Return status"); ok(data.description, data.description); ok(data.build_date, data.build_date); }}); @@ -79,7 +68,7 @@ QUnit.module("Sensors"); QUnit.asyncTest("Existence", function() { query("sensors", {params : {id : 0}, callback : function(data) { start(); - ok(data.status >= 0, "Return status"); + 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++) { @@ -103,15 +92,50 @@ QUnit.asyncTest("Invalid sensor id 2", function() { }}); }); +QUnit.asyncTest("Out of bounds sensor id 1", function() { + 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"); + }}); +}); + QUnit.module("Controls and access"); QUnit.asyncTest("Gaining access", function() { - query("control", {params : {action : "start", force : true}, - username : "mctxadmin", password : "admin", - callback : function(data) { - start(); - ok(data.status >= 0, "Return status"); - - var key = data.key; - - }}); + ut.controlcb.add(function () { + 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 () { + query("control", {params : {action : "set", id : 0, + username : $("#username").val(), password : $("#password").val(), + value : 200, key : ut.ckey}, + callback : function(data) { + start(); + ok(data.status > 0, "Return status"); + ok(true, data.description); + }}); + }); }); + +$(document).ready(function(){ + $("#control").submit(function () { + ut.controlcb.fire(); + return false; + }); +}); \ No newline at end of file -- 2.20.1