ndt-dev - [ndt-dev] [ndt] r1219 committed - This patch adds websocket support to ndt....
Subject: NDT-DEV email list created
List archive
- From:
- To:
- Subject: [ndt-dev] [ndt] r1219 committed - This patch adds websocket support to ndt....
- Date: Mon, 23 Mar 2015 23:00:07 +0000
Revision: 1219
Author:
Date: Mon Mar 23 22:59:30 2015 UTC
Log: This patch adds websocket support to ndt.
It also adds a safe integer arithmetic library in third_party/safe_iop.h, and
it adds a unit testing framework in unit_testing.c. You can see the unit tests
get run via "make check".
This adds websocket capabilities to the server by having it sniff the first
incoming message to the control socket. If the first incoming messate is GET,
then we assume that the socket is a websocket and try to perform the websocket
handshake. otherwise, everything proceeds as normal.
Although this test required lots of changes to lots of places, no code changes
were required for the web100clt, and all existing clients should work as
before.
https://code.google.com/p/ndt/source/detail?r=1219
Added:
/branches/websockets/src/node_tests
/branches/websockets/src/node_tests/README
/branches/websockets/src/node_tests/ndt_client.js
/branches/websockets/src/testoptions_unit_tests.c
/branches/websockets/src/third_party
/branches/websockets/src/third_party/safe_iop.h
/branches/websockets/src/third_party/safe_iop_LICENSE
/branches/websockets/src/websocket.c
/branches/websockets/src/websocket.h
/branches/websockets/src/websocket_unit_tests.c
Modified:
/branches/websockets/src/Makefile.am
/branches/websockets/src/jsonutils.c
/branches/websockets/src/network.c
/branches/websockets/src/network.h
/branches/websockets/src/test_c2s_srv.c
/branches/websockets/src/test_meta_srv.c
/branches/websockets/src/test_mid_srv.c
/branches/websockets/src/test_s2c_srv.c
/branches/websockets/src/test_sfw_srv.c
/branches/websockets/src/testoptions.c
/branches/websockets/src/testoptions.h
/branches/websockets/src/unit_testing.c
/branches/websockets/src/unit_testing.h
/branches/websockets/src/web100-util.c
/branches/websockets/src/web100srv.c
/branches/websockets/src/web100srv.h
/branches/websockets/src/web100srv_unit_tests.c
=======================================
--- /dev/null
+++ /branches/websockets/src/node_tests/README Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,13 @@
+These tests use node.js and the ws library for node.js to run the client side
+of NDT tests that speak to a server using websockets.
+
+They can only be run on a system that has node.js, and the ws libraries. At
+last look, the documentation for ws can be found at:
+ http://einaros.github.io/ws/
+
+This code also serves as documentation for how to write your own javascript
+client for NDT.
+
+If you have node.js and the node package manager npm, then the ws library may
+be installed via:
+ npm install ws
=======================================
--- /dev/null
+++ /branches/websockets/src/node_tests/ndt_client.js Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,317 @@
+/* This is an NDT client, written in node.js. It speaks the websocket version
+ * of the NDT protocol. The NDT protocol is documented at:
+ * https://code.google.com/p/ndt/wiki/NDTProtocol
+ */
+
+/*jslint bitwise: true, node: true */
+/*global Uint8Array */
+
+'use strict';
+
+// Constants in use by the entire program, and a live connection to the server.
+var COMM_FAILURE = 0,
+ SRV_QUEUE = 1,
+ MSG_LOGIN = 2,
+ TEST_PREPARE = 3,
+ TEST_START = 4,
+ TEST_MSG = 5,
+ TEST_FINALIZE = 6,
+ MSG_ERROR = 7,
+ MSG_RESULTS = 8,
+ MSG_LOGOUT = 9,
+ MSG_WAITING = 10,
+ MSG_EXTENDED_LOGIN = 11,
+ msg_name = ["COMM_FAILURE", "SRV_QUEUE", "MSG_LOGIN", "TEST_PREPARE", "TEST_START", "TEST_MSG", "TEST_FINALIZE", "MSG_ERROR", "MSG_RESULTS", "MSG_LOGOUT", "MSG_WAITING", "MSG_EXTENDED_LOGIN"],
+ WebSocket = require('ws'),
+ server = process.argv[2],
+ port = Number(process.argv[3]),
+ test_url = "ws://" + server + ":" + port + "/ndt_after_user_privacy_agreement",
+ ws = new WebSocket(test_url, {protocol: 'ndt'});
+
+console.log("Running NDT test to " + server + " on port " + port);
+
+// A helper function that prints an error message and crashes things.
+function die(a1, a2, a3, a4) {
+ console.log.apply(
+ console.log, ["DIED: "].concat(Array.prototype.slice.call(arguments)));
+ process.exit(1);
+}
+
+// Makes a login message suitable for sending to the server. The login
+// messages specifies the tests to be run.
+function make_login_msg(desired_tests) {
+ var i = 0,
+ message = 'XXX { "msg": "Xv3.5.5" }',
+ NDT_LOGIN_MSG = new Uint8Array(message.length);
+ NDT_LOGIN_MSG[0] = MSG_EXTENDED_LOGIN; // MSG_EXTENDED_LOGIN
+ NDT_LOGIN_MSG[1] = 0; // Two bytes to represent packet length
+ NDT_LOGIN_MSG[2] = message.length - 3;
+ for (i = 3; i < message.length; i += 1) {
+ if (message.charAt(i) === 'X') {
+ // The mid-message X specifies the tests to run. We must support
+ // TEST_STATUS (16) as a 3.5.5+ client
+ NDT_LOGIN_MSG[i] = desired_tests | 16;
+ } else {
+ NDT_LOGIN_MSG[i] = message.charCodeAt(i);
+ }
+ }
+ return NDT_LOGIN_MSG;
+}
+
+// A generic message creation system. The output is an array of bytes suitable
+// for sending on a binary websocket.
+function make_ndt_msg(type, msg) {
+ var message_body, NDT_MSG, i;
+ message_body = '{ "msg": "' + msg + '" } ';
+ NDT_MSG = new Uint8Array(message_body.length + 3);
+ NDT_MSG[0] = type;
+ NDT_MSG[1] = (message_body.length >> 8) & 0xFF;
+ NDT_MSG[2] = message_body.length & 0xFF;
+ for (i = 0; i < message_body.length; i += 1) {
+ NDT_MSG[i + 3] = message_body.charCodeAt(i);
+ }
+ return NDT_MSG;
+}
+
+// Returns a closure that will process all messages for the META NDT test. The
+// closure will return the string "DONE" when the test is complete and the
+// closure should no longer be called.
+function ndt_meta_test(sock) {
+ var state = "WAIT_FOR_TEST_PREPARE";
+ return function (type, body) {
+ if (state === "WAIT_FOR_TEST_PREPARE" && type === TEST_PREPARE) {
+ state = "WAIT_FOR_TEST_START";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_START" && type === TEST_START) {
+ // Send one piece of meta data and then an empty meta data packet
+ sock.send(make_ndt_msg(TEST_MSG, "client.os.name:CLIWebsockets"), { binary: true, mask: true });
+ sock.send(make_ndt_msg(TEST_MSG, ""), { binary: true, mask: true });
+ state = "WAIT_FOR_TEST_FINALIZE";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_FINALIZE" && type === TEST_FINALIZE) {
+ console.log("ndt_meta_test is done");
+ return "DONE";
+ }
+ die("Bad state and message combo for META test: ", state, type, body.msg);
+ };
+}
+
+// Returns a closure that will process all messages for the S2C NDT test. The
+// closure will return the string "DONE" when the test is complete and the
+// closure should no longer be called.
+function ndt_s2c_test(sock) {
+ var state = "WAIT_FOR_TEST_PREPARE",
+ server_port,
+ test_connection,
+ TRANSMITTED_BYTES = 0,
+ test_start,
+ test_end;
+
+ // Function called on the opening of the s2c socket.
+ function on_open() {
+ console.log("OPENED S2C SUCCESFULLY!");
+ test_start = Date.now() / 1000;
+ }
+
+ // Function called for each message received on the s2c socket.
+ function on_msg(message) {
+ var hdr_size;
+ if (message.length < 126) {
+ hdr_size = 2;
+ } else if (message.length < 65536) {
+ hdr_size = 4;
+ } else {
+ hdr_size = 10;
+ }
+ TRANSMITTED_BYTES += (hdr_size + message.length);
+ }
+
+ // If there is an error on the s2c socket, then die.
+ function on_error(error, num) {
+ die(error, num);
+ }
+
+ // The closure that processes messages on the control socket for the s2c test.
+ return function (type, body) {
+ var TEST_DURATION_SECONDS,
+ THROUGHPUT_VALUE;
+ console.log("CALLED S2C with %d (%s) %s in state %s", type, msg_name[type], body.msg, state);
+ if (state === "WAIT_FOR_TEST_PREPARE" && type === TEST_PREPARE) {
+ server_port = Number(body.msg);
+ // bind a connection to that port
+ test_connection = new WebSocket("ws://" + server + ":" + server_port + "/ndt_after_user_privacy_agreement", {protocol: "s2c"});
+ test_connection.on('open', on_open);
+ test_connection.on('message', on_msg);
+ test_connection.on('error', on_error);
+ state = "WAIT_FOR_TEST_START";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_START" && type === TEST_START) {
+ state = "WAIT_FOR_FIRST_TEST_MSG";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_FIRST_TEST_MSG" && type === TEST_MSG) {
+ state = "WAIT_FOR_TEST_MSG_OR_TEST_FINISH";
+ if (test_end === undefined) {
+ test_end = Date.now() / 1000;
+ }
+ // All caps because that's how it is in the NDT spec
+ TEST_DURATION_SECONDS = test_end - test_start;
+ // Calculation per NDT spec
+ THROUGHPUT_VALUE = 8 * TRANSMITTED_BYTES / 1000 / TEST_DURATION_SECONDS;
+ sock.send(make_ndt_msg(TEST_MSG, String(THROUGHPUT_VALUE)), { binary: true, mask: true });
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_MSG_OR_TEST_FINISH" && type === TEST_MSG) {
+ console.log("Got results: ", body.msg);
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_MSG_OR_TEST_FINISH" && type === TEST_FINALIZE) {
+ console.log("Test is over! ", body.msg);
+ return "DONE";
+ }
+ die("S2C: State = " + state + " type = " + type + "(" + msg_name[type] + ") message = ", body);
+ };
+}
+
+
+function ndt_c2s_test() {
+ var state = "WAIT_FOR_TEST_PREPARE",
+ server_port,
+ test_connection,
+ TRANSMITTED_BYTES = 0,
+ data_to_send = new Uint8Array(8192 - 4),
+ i,
+ test_start,
+ test_end;
+
+ for (i = 0; i < data_to_send.length; i += 1) {
+ // All the characters must be printable, and the printable range of
+ // ASCII is from 32 to 126. 101 is because we need a prime number.
+ data_to_send[i] = 32 + (i * 101) % (126 - 32);
+ }
+
+ // A while loop, encoded as a setTimeout callback.
+ function keep_sending_data() {
+ test_connection.send(data_to_send);
+ TRANSMITTED_BYTES += 8192;
+ if (Date.now() / 1000 < test_start + 10) {
+ setTimeout(keep_sending_data, 0);
+ } else {
+ test_end = Date.now() / 1000;
+ }
+ }
+
+ return function (type, body) {
+ console.log("C2S type %d (%s)", type, msg_name[type], body);
+ if (state === "WAIT_FOR_TEST_PREPARE" && type === TEST_PREPARE) {
+ server_port = Number(body.msg);
+ test_connection = new WebSocket("ws://" + server + ":" + server_port + "/ndt_after_user_privacy_agreement", {protocol: "c2s"});
+ state = "WAIT_FOR_TEST_START";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_START" && type === TEST_START) {
+ test_start = Date.now() / 1000;
+ keep_sending_data();
+ state = "WAIT_FOR_TEST_MSG";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_MSG" && type === TEST_MSG) {
+ state = "WAIT_FOR_TEST_FINALIZE";
+ return "KEEP GOING";
+ }
+ if (state === "WAIT_FOR_TEST_FINALIZE" && type === TEST_FINALIZE) {
+ state = "DONE";
+ console.log("C2S rate: ", 8 * TRANSMITTED_BYTES / 1000 / (test_end - test_start));
+ return "DONE";
+ }
+ die("C2S: State = " + state + " type = " + type + "(" + msg_name[type] + ") message = ", body);
+ };
+}
+
+
+function ndt_coordinator(sock) {
+ var state = "",
+ active_test,
+ tests_to_run = [];
+
+ function on_open() {
+ console.log("OPENED CONNECTION");
+ // Sign up for every test except for TEST_MID and TEST_SFW - browsers can't
+ // open server sockets, which makes those tests impossible, because they
+ // require the server to open a connection to a port on the client.
+ sock.send(make_login_msg(2 | 4 | 32), { binary: true, mask: true });
+ state = "LOGIN_SENT";
+ }
+
+ function on_message(message) {
+ var type = message[0],
+ body = JSON.parse(message.slice(3)),
+ i,
+ tests;
+ console.log("type = %d (%s) body = '%s'", type, msg_name[type], body.msg);
+ if (active_test === undefined && tests_to_run.length > 0) {
+ active_test = tests_to_run.pop();
+ }
+ if (active_test !== undefined) {
+ // Pass the message to the sub-test
+ console.log("Calling a subtest");
+ if (active_test(type, body) === "DONE") {
+ active_test = undefined;
+ console.log("Subtest complete");
+ }
+ return;
+ }
+ // If there is an active test, hand off control to the test
+ // Otherwise, move the coordinator state forwards.
+ if (state === "LOGIN_SENT") {
+ // Response to NDT_LOGIN should be SRV_QUEUE messages until we get SRV_QUEUE("0")
+ if (type === SRV_QUEUE) {
+ if (body.msg === "9990") { // special keepalive message
+ sock.send(make_ndt_msg(MSG_WAITING, ""), { binary: true, mask: true });
+ } else if (body.msg === "9977") { // Test failed
+ die("server terminated test with SRV_QUEUE 9977");
+ }
+ console.log("Got SRV_QUEUE. Ignoring and waiting for MSG_LOGIN");
+ } else if (type === MSG_LOGIN) {
+ if (body.msg[0] !== "v") { die("Bad msg '%s'", body.msg); }
+ state = "WAIT_FOR_TEST_IDS";
+ } else {
+ die("Bad type when we wanted a srv_queue or msg_login ({%d, %d} vs %d)", SRV_QUEUE, MSG_LOGIN, message[0]);
+ }
+ } else if (state === "WAIT_FOR_TEST_IDS" && type === MSG_LOGIN) {
+ tests = body.msg.split(" ");
+ for (i = tests.length - 1; i >= 0; i -= 1) {
+ if (tests[i] === "2") {
+ tests_to_run.push(ndt_c2s_test());
+ } else if (tests[i] === "4") {
+ tests_to_run.push(ndt_s2c_test(sock));
+ } else if (tests[i] === "32") {
+ tests_to_run.push(ndt_meta_test(sock));
+ } else if (tests[i] !== '') {
+ die("Unknown test type: %d", tests[i], tests, body.msg);
+ }
+ }
+ state = "WAIT_FOR_MSG_RESULTS";
+ } else if (state === "WAIT_FOR_MSG_RESULTS" && type === MSG_RESULTS) {
+ console.log(body);
+ } else if (state === "WAIT_FOR_MSG_RESULTS" && type === MSG_LOGOUT) {
+ sock.close();
+ console.log("TESTS FINISHED SUCCESSFULLY!");
+ process.exit(0);
+ } else {
+ die("Didn't know what to do with message type %d in state %s", message[0], state);
+ }
+ }
+
+ sock.on('open', on_open);
+ sock.on('message', on_message);
+ sock.on('error', function (err_msg, code) {
+ console.error("Error: %s (%d)", err_msg, code);
+ process.exit(1);
+ });
+}
+
+ndt_coordinator(ws);
=======================================
--- /dev/null
+++ /branches/websockets/src/testoptions_unit_tests.c Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,175 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "logging.h"
+#include "network.h"
+#include "testoptions.h"
+#include "unit_testing.h"
+
+/**
+ * Runs data through initialize_tests and compares the received data with the
+ * expected data.
+ * @param data the data we are planning on sending
+ * @param data_len how many bytes of data
+ * @param expected the data we expect to see in response
+ * @param expected_len how many bytes of response
+ * @param test_options the options object that carries the stream status
+ * @param test_suite which NDT tests are being requested
+ * @param test_suite_strlen length of test_suite
+ * @param expected_return_value expected return value from initialize_tests
+ */
+void send_data_and_compare_response(const char* data, size_t data_len,
+ const char* expected, size_t expected_len,
+ TestOptions* test_options, char* test_suite,
+ size_t test_suite_strlen,
+ int expected_return_value) {
+ char end_msg[] = "THE END OF THE STREAM";
+ char* received_data;
+ pid_t child_pid;
+ int child_exit_code;
+ int sockets[2];
+ int child_socket, parent_socket;
+ int bytes_written;
+ int return_value;
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ child_socket = sockets[0];
+ parent_socket = sockets[1];
+ if ((child_pid = fork()) == 0) {
+ bytes_written = writen(child_socket, data, data_len);
+ ASSERT(bytes_written == data_len, "write wrote %d bytes, and not %d",
+ bytes_written, data_len);
+ if (expected_len) {
+ received_data = (char*)malloc(expected_len * sizeof(char));
+ CHECK(received_data != NULL);
+ CHECK(expected_len == readn(child_socket, received_data, expected_len));
+ CHECK(strncmp(received_data, expected, expected_len) == 0);
+ free(received_data);
+ }
+ received_data = (char*)malloc(sizeof(end_msg));
+ CHECK(received_data != NULL);
+ CHECK(sizeof(end_msg) ==
+ readn(child_socket, received_data, sizeof(end_msg)));
+ ASSERT(strncmp(received_data, end_msg, sizeof(end_msg)) == 0,
+ "Received_data (%s)", received_data);
+ free(received_data);
+ exit(0);
+ } else {
+ return_value = initialize_tests(parent_socket, test_options, test_suite,
+ test_suite_strlen);
+ ASSERT(return_value == expected_return_value,
+ "initialize_tests returned %d and not %d", return_value,
+ expected_return_value);
+ CHECK(sizeof(end_msg) == writen(parent_socket, end_msg, sizeof(end_msg)));
+ waitpid(child_pid, &child_exit_code, 0);
+ CHECK(WIFEXITED(child_exit_code) && WEXITSTATUS(child_exit_code) == 0);
+ }
+}
+
+/**
+ * Send a MSG_LOGIN to the server, expect the "KICK OFF old clients" response.
+ */
+void test_initialize_MSG_LOGIN_tests() {
+ // a MSG_LOGIN requesting only a meta test
+ char test_suite[16] = {0};
+ char data[] = {MSG_LOGIN, 0, 1, 32};
+ char expected[] = {'1', '2', '3', '4', '5', '6', ' ',
+ '6', '5', '4', '3', '2', '1'};
+ TestOptions test_options;
+ test_options.connection_flags = 0;
+ send_data_and_compare_response(data, sizeof(data), expected, sizeof(expected),
+ &test_options, test_suite, sizeof(test_suite),
+ 32);
+ CHECK(test_options.connection_flags == 0);
+}
+
+/**
+ * Send a MSG_EXTENDED_LOGIN to the server, expect the "KICK OFF old clients"
+ * response.
+ */
+void test_initialize_MSG_EXTENDED_LOGIN_tests() {
+ // a MSG_EXTENDED_LOGIN requesting only a meta test
+ char test_suite[16] = {0};
+ char expected[] = {'1', '2', '3', '4', '5', '6', ' ',
+ '6', '5', '4', '3', '2', '1'};
+ TestOptions test_options;
+ // The first char of msg is TEST_META|TEST_STATUS == 32|16 == 48 == '0'
+ char data[] = "XXX{ \"msg\": \"0v3.5.5\"}";
+ data[0] = MSG_EXTENDED_LOGIN;
+ data[1] = 0;
+ data[2] = strlen(data + 3);
+ test_options.connection_flags = 0;
+ send_data_and_compare_response(data, sizeof(data) - 1, expected,
+ sizeof(expected), &test_options, test_suite,
+ sizeof(test_suite), 16 | 32);
+ CHECK(test_options.connection_flags & JSON_SUPPORT);
+ CHECK(!(test_options.connection_flags & WEBSOCKET_SUPPORT));
+}
+
+/**
+ * Log in to the server with websockets. The server should properly sniff that
+ * the request was not a normal NDT message and then authenticate with the
+ * client.
+ */
+void test_initialize_websocket_tests() {
+ int i;
+ char test_suite[16] = {0};
+ TestOptions test_options;
+ const char request[] =
+ "GET /ndt_after_user_privacy_agreement HTTP/1.1\r\n"
+ "Host: server.example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: ndt, superchat\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n";
+ size_t request_len = sizeof(request) - 1;
+ const char response[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Protocol: ndt\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "\r\n";
+ size_t response_len = sizeof(response) - 1;
+ unsigned char websocket_header[] = {0x80 | 0x1, 0x80, 'M', 'A', 'S', 'K'};
+ size_t websocket_header_len = sizeof(websocket_header);
+ unsigned char websocket_payload[] = "XXX{ \"msg\": \"0v3.5.5\"}";
+ size_t websocket_payload_len = sizeof(websocket_payload) - 1;
+ char* message_to_send;
+ websocket_payload[0] = MSG_EXTENDED_LOGIN;
+ websocket_payload[1] = 0;
+ websocket_payload[2] = strlen((char*)&(websocket_payload[3]));
+ websocket_header[1] |= websocket_payload_len;
+ fprintf(stderr, "Predicted length = %d\n", websocket_header[1] & 0x7F);
+ for (i = 0; i < websocket_payload_len; i++) {
+ websocket_payload[i] ^= websocket_header[2 + (i % 4)];
+ }
+ message_to_send =
+ (char*)malloc(request_len + websocket_header_len + websocket_payload_len);
+ CHECK(message_to_send != NULL);
+ memcpy(message_to_send, request, request_len);
+ memcpy(message_to_send + request_len, websocket_header, websocket_header_len);
+ memcpy(message_to_send + request_len + websocket_header_len,
+ websocket_payload, websocket_payload_len);
+ test_options.connection_flags = 0;
+ send_data_and_compare_response(
+ message_to_send,
+ request_len + websocket_header_len + websocket_payload_len, response,
+ response_len, &test_options, test_suite, sizeof(test_suite), 16 | 32);
+ CHECK(test_options.connection_flags & JSON_SUPPORT);
+ CHECK(test_options.connection_flags & WEBSOCKET_SUPPORT);
+}
+
+int main() {
+ set_debuglvl(1024);
+ return RUN_TEST(test_initialize_MSG_LOGIN_tests) |
+ RUN_TEST(test_initialize_MSG_EXTENDED_LOGIN_tests) |
+ RUN_TEST(test_initialize_websocket_tests);
+}
=======================================
--- /dev/null
+++ /branches/websockets/src/third_party/safe_iop.h Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,2324 @@
+/* safe_iop
+ * License:: BSD (See safe_iop_LICENSE file)
+ * Author:: Will Drewry
<>
+ *
+ * See README for usage.
+ *
+ * Namespace: This library uses the prefix sop_.
+ *
+ * Known issues:
+ * - GCC < 4.3 warns when using a constant value as it compiles out always
+ * pass or fail tests (e.g., 1 > 0 with s8,s16)
+ *
+ * To Do:
+ * = next milestone (0.5.0)
+ * - Autogenerate test cases for all op-type-type combinations
+ * - Add while() and for() test cases for inc and dec
+ * = long term/never:
+ * - break up sop_safe_cast into smaller macros using suffix concatenation to
+ * minimize repeated code and make it easier to review
+ * - Consider ways to do safe casting with operator awareness to
+ * allow cases where an addition of a negative signed value may be safe
+ * as a subtraction, for example. (Perhaps using checked type promotion
+ * similarly to compilers)
+ *
+ * History:
+ * = [next milestone]
+ * - Use cpp concatenation to minimize code duplication
+ * -- E.g., sop_addx no longer expands sop_sadd and sop_uadd at each callsite
+ * - Re-namespaced to sop_
+ * - All tests pass under pcc
+ * - Added pointer type markup which allows for (e.g.) u64=u32+u32.
+ * - Refactored the code again due to massive increase in generated code size
+ * -- Rewrote to support passing consts and compilers without typeof()
+ * -- Added sop_<op>x -- primary interface
+ * -- Added sop_<op>x[num] - convenience interface
+ * -- Added sop_incx and sop_decx
+ * = 0.4.0 (never released)
+ * - Compiles under pcc (but tests fail due to max # calculations)
+ * - Refactored nearly all of the code
+ * - Removed -DSAFE_IOP_COMPAT
+ * - Add support for differently typed/signed operands in sop_iopf format
+ * - Added negative tests to add T_<op>_*()s
+ * - [BUG] Fixed signed addition. Two negatives were failing!
+ * - Extended sop_iopf to support more types. Still needs more testing.
+ * - Added more mixed interface tests
+ * - Added safe type casting (automagically)
+ * - Added basic speed tests (not accurate at all yet)
+ * - Added sop_inc/sop_dec
+ * - Licensed all subsequent work BSD for clarity of code ownership
+ * = 0.3.1
+ * - fixed typo/bug in sop_sadd (backported from 0.4.0/trunk above)
+ * = 0.3
+ * - solidified code into a smaller number of macros and functions
+ * - added typeless functions using gcc magic (typeof)
+ * - deprecrated old interfaces (-DSAFE_IOP_COMPAT)
+ * - discover size maximums automagically
+ * - separated test cases for easier understanding
+ * - significantly expanded test cases
+ * - derive type maximums and minimums internally (checked in testing)
+ * = 0.2
+ * - Removed dependence on twos complement arithmetic to allow macro-ized
+ * definitions
+ * - Added (s)size_t support
+ * - Added (u)int8,16,64 support
+ * - Added portable inlining
+ * - Added support for NULL result pointers
+ * - Added support for header-only use (sop_iop.c only needed for sop_iopf)
+ * = 0.1
+ * - Initial release
+ *
+ * Contributors & thanks:
+ * -
for his review, comments, and enthusiasm
+ * - Diego 'Flameeyes' Petteno for his analysis, use, and bug reporting
+ * - thanks to Google for contributing some work upstream earlier in the project
+ *
+ * Copyright (c) 2007,2008 Will Drewry
<>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This library supplies a set of standard functions for performing and
+ * checking safe integer operations. The code is based on examples from
+ * https://www.securecoding.cert.org/confluence/display/seccode/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
+ */
+#ifndef _SAFE_IOP_H
+#define _SAFE_IOP_H
+#include <assert.h> /* for convenience NULL check */
+#include <limits.h> /* for CHAR_BIT */
+#include <stdint.h> /* [u]int<bits>_t, [U]INT64_MAX */
+#include <sys/types.h> /* for [s]size_t */
+
+#define SAFE_IOP_VERSION "0.5.0rc1"
+
+/* sopf
+ *
+ * Takes in a character array which specifies the operations
+ * to perform on a given value. The value will be assumed to be
+ * of the type specified for each operation.
+ *
+ * Currently accepted format syntax is:
+ * [type_marker]operation[type_marker]...
+ * The type marker may be any of the following:
+ * - s[8,16,32,64] for signed of size 8-bit, etc
+ * - u[8,16,32,64] for unsigned of size 8-bit, etc
+ * If no type_marker is specified, it is assumed to be s32.
+ * If a left-hand side type-marker is given, then that will
+ * become the default for all remaining operands.
+ * E.g.,
+ * sop_iopf(&dst, "u16**+", a, b, c. d);
+ * is equivalent to ((a*b)*c)+d all of type u16.
+ * This function uses FIFO and not any other order of operations/precedence.
+ *
+ * The operation must be one of the following:
+ * - * -- multiplication
+ * - / -- division
+ * - - -- subtraction
+ * - + -- addition
+ * - % -- modulo (remainder)
+ * - << -- left shift
+ * - >> -- right shift
+ *
+ * Args:
+ * - pointer to the final result
+ * - array of format characters
+ * - all remaining arguments are derived from the format
+ * Output:
+ * - Returns 1 on success leaving the result in value
+ * - Returns 0 on failure leaving the contents of value *unknown*
+ * Caveats:
+ * - This function is only provided if sop_iop.c is compiled and linked
+ * into the source. Otherwise only macro-based functions are available.
+ */
+int sopf(void *result, const char *const fmt, ...);
+
+
+/* Type markup macros
+ * These macros are the user mechanism for marking up
+ * types without giving the exact type name. This
+ * serves primarily as short-hand for long type names,
+ * but also provides a simple mechanism for automatically
+ * getting whether a type is signed in a programmatic fashion.
+ *
+ * These are used _only_ with the generic compiler interfaces
+ * and not with the GNU C compiler interfaces. See the comment
+ * at the start of the "Generic (x) interface macros" section
+ * for example usage.
+ */
+#define sop_typeof_sop_s8(_a) int8_t
+#define sop_signed_sop_s8(_a) 1
+#define sop_valueof_sop_s8(_a) _a
+/* These macros are used internally for expanding the
+ * sign-less interface (sop_addx) to the correct function
+ * without needing a runtime check (is signed). In addition,
+ * it means that the macros for signed and unsigned operations
+ * won't be expanded everywhere.
+ */
+#define sop_add_sop_s8(_a) sop_sadd
+#define sop_sub_sop_s8(_a) sop_ssub
+#define sop_mul_sop_s8(_a) sop_smul
+#define sop_div_sop_s8(_a) sop_sdiv
+#define sop_mod_sop_s8(_a) sop_smod
+#define sop_shl_sop_s8(_a) sop_sshl
+#define sop_shr_sop_s8(_a) sop_sshr
+
+
+#define sop_typeof_sop_u8(_a) uint8_t
+#define sop_signed_sop_u8(_a) 0
+#define sop_valueof_sop_u8(_a) _a
+#define sop_add_sop_u8(_a) sop_uadd
+#define sop_sub_sop_u8(_a) sop_usub
+#define sop_mul_sop_u8(_a) sop_umul
+#define sop_div_sop_u8(_a) sop_udiv
+#define sop_mod_sop_u8(_a) sop_umod
+#define sop_shl_sop_u8(_a) sop_ushl
+#define sop_shr_sop_u8(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_s16(_a) int16_t
+#define sop_signed_sop_s16(_a) 1
+#define sop_valueof_sop_s16(_a) _a
+#define sop_add_sop_s16(_a) sop_sadd
+#define sop_sub_sop_s16(_a) sop_ssub
+#define sop_mul_sop_s16(_a) sop_smul
+#define sop_div_sop_s16(_a) sop_sdiv
+#define sop_mod_sop_s16(_a) sop_smod
+#define sop_shl_sop_s16(_a) sop_sshl
+#define sop_shr_sop_s16(_a) sop_sshr
+
+
+#define sop_typeof_sop_u16(_a) uint16_t
+#define sop_signed_sop_u16(_a) 0
+#define sop_valueof_sop_u16(_a) _a
+#define sop_add_sop_u16(_a) sop_uadd
+#define sop_sub_sop_u16(_a) sop_usub
+#define sop_mul_sop_u16(_a) sop_umul
+#define sop_div_sop_u16(_a) sop_udiv
+#define sop_mod_sop_u16(_a) sop_umod
+#define sop_shl_sop_u16(_a) sop_ushl
+#define sop_shr_sop_u16(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_s32(_a) int32_t
+#define sop_signed_sop_s32(_a) 1
+#define sop_valueof_sop_s32(_a) _a
+#define sop_add_sop_s32(_a) sop_sadd
+#define sop_sub_sop_s32(_a) sop_ssub
+#define sop_mul_sop_s32(_a) sop_smul
+#define sop_div_sop_s32(_a) sop_sdiv
+#define sop_mod_sop_s32(_a) sop_smod
+#define sop_shl_sop_s32(_a) sop_sshl
+#define sop_shr_sop_s32(_a) sop_sshr
+
+
+#define sop_typeof_sop_u32(_a) uint32_t
+#define sop_signed_sop_u32(_a) 0
+#define sop_valueof_sop_u32(_a) _a
+#define sop_add_sop_u32(_a) sop_uadd
+#define sop_sub_sop_u32(_a) sop_usub
+#define sop_mul_sop_u32(_a) sop_umul
+#define sop_div_sop_u32(_a) sop_udiv
+#define sop_mod_sop_u32(_a) sop_umod
+#define sop_shl_sop_u32(_a) sop_ushl
+#define sop_shr_sop_u32(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_s64(_a) int64_t
+#define sop_signed_sop_s64(_a) 1
+#define sop_valueof_sop_s64(_a) _a
+#define sop_add_sop_s64(_a) sop_sadd
+#define sop_sub_sop_s64(_a) sop_ssub
+#define sop_mul_sop_s64(_a) sop_smul
+#define sop_div_sop_s64(_a) sop_sdiv
+#define sop_mod_sop_s64(_a) sop_smod
+#define sop_shl_sop_s64(_a) sop_sshl
+#define sop_shr_sop_s64(_a) sop_sshr
+
+
+#define sop_typeof_sop_u64(_a) uint64_t
+#define sop_signed_sop_u64(_a) 0
+#define sop_valueof_sop_u64(_a) _a
+#define sop_add_sop_u64(_a) sop_uadd
+#define sop_sub_sop_u64(_a) sop_usub
+#define sop_mul_sop_u64(_a) sop_umul
+#define sop_div_sop_u64(_a) sop_udiv
+#define sop_mod_sop_u64(_a) sop_umod
+#define sop_shl_sop_u64(_a) sop_ushl
+#define sop_shr_sop_u64(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_sl(_a) signed long
+#define sop_signed_sop_sl(_a) 1
+#define sop_valueof_sop_sl(_a) _a
+#define sop_add_sop_sl(_a) sop_sadd
+#define sop_sub_sop_sl(_a) sop_ssub
+#define sop_mul_sop_sl(_a) sop_smul
+#define sop_div_sop_sl(_a) sop_sdiv
+#define sop_mod_sop_sl(_a) sop_smod
+#define sop_shl_sop_sl(_a) sop_sshl
+#define sop_shr_sop_sl(_a) sop_sshr
+
+
+#define sop_typeof_sop_ul(_a) unsigned long
+#define sop_signed_sop_ul(_a) 0
+#define sop_valueof_sop_ul(_a) _a
+#define sop_add_sop_ul(_a) sop_uadd
+#define sop_sub_sop_ul(_a) sop_usub
+#define sop_mul_sop_ul(_a) sop_umul
+#define sop_div_sop_ul(_a) sop_udiv
+#define sop_mod_sop_ul(_a) sop_umod
+#define sop_shl_sop_ul(_a) sop_ushl
+#define sop_shr_sop_ul(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_sll(_a) signed long long
+#define sop_signed_sop_sll(_a) 1
+#define sop_valueof_sop_sll(_a) _a
+#define sop_add_sop_sll(_a) sop_sadd
+#define sop_sub_sop_sll(_a) sop_ssub
+#define sop_mul_sop_sll(_a) sop_smul
+#define sop_div_sop_sll(_a) sop_sdiv
+#define sop_mod_sop_sll(_a) sop_smod
+#define sop_shl_sop_sll(_a) sop_sshl
+#define sop_shr_sop_sll(_a) sop_sshr
+
+
+#define sop_typeof_sop_ull(_a) unsigned long long
+#define sop_signed_sop_ull(_a) 0
+#define sop_valueof_sop_ull(_a) _a
+#define sop_add_sop_ull(_a) sop_uadd
+#define sop_sub_sop_ull(_a) sop_usub
+#define sop_mul_sop_ull(_a) sop_umul
+#define sop_div_sop_ull(_a) sop_udiv
+#define sop_mod_sop_ull(_a) sop_umod
+#define sop_shl_sop_ull(_a) sop_ushl
+#define sop_shr_sop_ull(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_si(_a) signed int
+#define sop_signed_sop_si(_a) 1
+#define sop_valueof_sop_si(_a) _a
+#define sop_add_sop_si(_a) sop_sadd
+#define sop_sub_sop_si(_a) sop_ssub
+#define sop_mul_sop_si(_a) sop_smul
+#define sop_div_sop_si(_a) sop_sdiv
+#define sop_mod_sop_si(_a) sop_smod
+#define sop_shl_sop_si(_a) sop_sshl
+#define sop_shr_sop_si(_a) sop_sshr
+
+
+#define sop_typeof_sop_ui(_a) unsigned int
+#define sop_signed_sop_ui(_a) 0
+#define sop_valueof_sop_ui(_a) _a
+#define sop_add_sop_ui(_a) sop_uadd
+#define sop_sub_sop_ui(_a) sop_usub
+#define sop_mul_sop_ui(_a) sop_umul
+#define sop_div_sop_ui(_a) sop_udiv
+#define sop_mod_sop_ui(_a) sop_umod
+#define sop_shl_sop_ui(_a) sop_ushl
+#define sop_shr_sop_ui(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_sc(_a) signed char
+#define sop_signed_sop_sc(_a) 1
+#define sop_valueof_sop_sc(_a) _a
+#define sop_add_sop_sc(_a) sop_sadd
+#define sop_sub_sop_sc(_a) sop_ssub
+#define sop_mul_sop_sc(_a) sop_smul
+#define sop_div_sop_sc(_a) sop_sdiv
+#define sop_mod_sop_sc(_a) sop_smod
+#define sop_shl_sop_sc(_a) sop_sshl
+#define sop_shr_sop_sc(_a) sop_sshr
+
+
+#define sop_typeof_sop_uc(_a) unsigned char
+#define sop_signed_sop_uc(_a) 0
+#define sop_valueof_sop_uc(_a) _a
+#define sop_add_sop_uc(_a) sop_uadd
+#define sop_sub_sop_uc(_a) sop_usub
+#define sop_mul_sop_uc(_a) sop_umul
+#define sop_div_sop_uc(_a) sop_udiv
+#define sop_mod_sop_uc(_a) sop_umod
+#define sop_shl_sop_uc(_a) sop_ushl
+#define sop_shr_sop_uc(_a) sop_ushr
+
+
+
+#define sop_typeof_sop_sszt(_a) ssize_t
+#define sop_signed_sop_sszt(_a) 1
+#define sop_valueof_sop_sszt(_a) _a
+#define sop_add_sop_sszt(_a) sop_sadd
+#define sop_sub_sop_sszt(_a) sop_ssub
+#define sop_mul_sop_sszt(_a) sop_smul
+#define sop_div_sop_sszt(_a) sop_sdiv
+#define sop_mod_sop_sszt(_a) sop_smod
+#define sop_shl_sop_sszt(_a) sop_sshl
+#define sop_shr_sop_sszt(_a) sop_sshr
+
+
+#define sop_typeof_sop_szt(_a) size_t
+#define sop_signed_sop_szt(_a) 0
+#define sop_valueof_sop_szt(_a) _a
+#define sop_add_sop_szt(_a) sop_uadd
+#define sop_sub_sop_szt(_a) sop_usub
+#define sop_mul_sop_szt(_a) sop_umul
+#define sop_div_sop_szt(_a) sop_udiv
+#define sop_mod_sop_szt(_a) sop_umod
+#define sop_shl_sop_szt(_a) sop_ushl
+#define sop_shr_sop_szt(_a) sop_ushr
+
+
+
+/* This allows NULL to be passed in to the generic
+ * interface macros without needing to mark them up
+ * with a type (like sop_blah(NULL)). Instead, NULL
+ * will work.
+ */
+#define sop_typeof_NULL intmax_t /* silences gcc complaints when the macos expand */
+#define sop_signed_NULL 0
+#define sop_valueof_NULL 0
+/* Macros for handling pointer presence/non-presence. */
+#define sop_safe_cast_NULL sop_safe_cast_np
+
+#define sop_safe_cast_sop_u8(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_s8(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_u16(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_s16(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_u32(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_s32(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_u64(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_s64(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_uc(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_sc(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_ui(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_si(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_ul(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_sl(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_ull(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_sll(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_szt(_X) sop_safe_cast_p
+#define sop_safe_cast_sop_sszt(_X) sop_safe_cast_p
+
+/* Since we detect NULLness with a conditional, just return 0 for this case */
+#define sop_add_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_sub_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_mul_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_div_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_mod_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_shl_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+#define sop_shr_NULL(_A,_B,_C,_D,_E,_F,_G,_H,_I) 0
+
+/*****************************************************************************
+ * Safe-checking Implementation Macros
+ *****************************************************************************
+ * The macros below are used for the implementation of the specific
+ * operation (along with helpers). Each operation will take the format:
+ * sop_[u|s]<op>(...)
+ * 'u' and 's' represent unsigned and signed checks, respectively. The macros
+ * take the sign and type for both operands, but only the sign and type of the
+ * first operand, 'a', is used for the operation. These macros assume
+ * type-casting is safe on the given operands when they are called. (The
+ * sop_safe_cast macro in this section performs just that test.) Despite this,
+ * the sign and type of the second operand, 'b', have been left in in case of
+ * future need.
+ */
+
+/* sop_assert
+ * An assert() wrapper which still performs the operation when NDEBUG called
+ * and is safe in if statements.
+ */
+#ifdef NDEBUG
+# define sop_assert(x) ((x) ? 1 : 0)
+#else
+# define sop_assert(x) (assert(x),1)
+#endif
+
+/* use a nice prefix :) */
+#define __sop(x) OPAQUE_SAFE_IOP_PREFIX_ ## x
+#define OPAQUE_SAFE_IOP_PREFIX_var(x) OPAQUE_SAFE_IOP_PREFIX_VARIABLE_ ## x
+#define OPAQUE_SAFE_IOP_PREFIX_m(x) OPAQUE_SAFE_IOP_PREFIX_MACRO_ ## x
+#define OPAQUE_SAFE_IOP_PREFIX_f(x) OPAQUE_SAFE_IOP_PREFIX_FN_ ## x
+
+
+/* Determine maximums and minimums for the platform dynamically
+ * without relying on a limits.h file. As a bonus, the compiler
+ * may be able to optimize the expression out at compile-time since
+ * it should resolve all values to fixed numbers.
+ */
+#define OPAQUE_SAFE_IOP_PREFIX_MACRO_smin(_type) \
+ (_type)((_type)(~0)<<(sizeof(_type)*CHAR_BIT-1))
+
+#define OPAQUE_SAFE_IOP_PREFIX_MACRO_smax(_type) \
+ (_type)(-(OPAQUE_SAFE_IOP_PREFIX_MACRO_smin(_type)+1))
+
+#define OPAQUE_SAFE_IOP_PREFIX_MACRO_umax(_type) ((_type)~0)
+
+
+/*** Same-type addition macros ***/
+#define sop_uadd(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((((_ptr_type)(_b) <= \
+ ((_ptr_type)(__sop(m)(umax)(_ptr_type) - (_ptr_type)(_a))) ? 1 : 0)) \
+ ? \
+ (((void *)(_ptr)) != NULL ? \
+ *((_ptr_type *)(_ptr)) = ((_ptr_type)(_a) + (_ptr_type)(_b)), 1 : 1) \
+ : 0)
+
+#define sop_sadd(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ (((((_ptr_type)(_b) > (_ptr_type)0) && \
+ ((_ptr_type)(_a) > (_ptr_type)0)) \
+ ? /*>0*/ \
+ ((_ptr_type)(_a) > \
+ (_ptr_type)(__sop(m)(smax)(_ptr_type) - \
+ (_ptr_type)(_b)) ? 0 : 1) \
+ : \
+ /* <0 */ \
+ ((!((_ptr_type)(_b) > (_ptr_type)0) && \
+ !((_ptr_type)(_a) > (_ptr_type)0)) ? \
+ (((_ptr_type)(_a) < \
+ (_ptr_type)(__sop(m)(smin)(_ptr_type) - \
+ (_ptr_type)(_b))) ? 0 : 1) : 1) \
+ ) \
+ ? /* Now assign if needed */ \
+ (((void *)(_ptr)) != NULL ? \
+ *((_ptr_type *)(_ptr)) = ((_ptr_type)(_a) + (_ptr_type)(_b)),\
+ 1 \
+ : \
+ 1 \
+ ) \
+ : \
+ 0 \
+ )
+
+/*** Same-type subtraction macros ***/
+#define sop_usub(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((_ptr_type)(_a) >= (_ptr_type)(_b) ? (((void *)(_ptr)) != NULL ? \
+ *((_ptr_type*)(_ptr)) = ((_ptr_type)(_a) - (_ptr_type)(_b)),1 : 1) : 0 )
+
+#define sop_ssub(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) ( \
+ (!((_ptr_type)(_b) <= 0 && \
+ (_ptr_type)(_a) > (__sop(m)(smax)(_ptr_type) + (_ptr_type)(_b))) && \
+ !((_ptr_type)(_b) > 0 && \
+ (_ptr_type)(_a) < (__sop(m)(smin)(_ptr_type) + (_ptr_type)(_b)))) \
+ ? \
+ (((void *)(_ptr)) != NULL ? *((_ptr_type *)(_ptr)) = \
+ ((_ptr_type)(_a) - (_ptr_type)(_b)), 1 : 1) \
+ : \
+ 0)
+
+
+/*** Same-type multiplication macros ***/
+#define sop_umul(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) ( \
+ (!(_ptr_type)(_b) || \
+ (_ptr_type)(_a) <= (__sop(m)(umax)(_ptr_type) / (_ptr_type)(_b))) \
+ ? \
+ ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ ((_ptr_type)(_a)) * ((_ptr_type)(_b)),1 : 1) \
+ : \
+ 0)
+
+#define sop_smul(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((((_ptr_type)(_a) > 0) ? /* a is positive */ \
+ (((_ptr_type)(_b) > 0) ? /* b and a are positive */ \
+ (((_ptr_type)(_a) > (__sop(m)(smax)(_ptr_type) / ((_ptr_type)(_b)))) ? 0 : 1) \
+ : /* a positive, b non-positive */ \
+ (((_ptr_type)(_b) < (__sop(m)(smin)(_ptr_type) / (_ptr_type)(_a))) ? 0 : 1)) \
+ : /* a is non-positive */ \
+ (((_ptr_type)(_b) > 0) ? /* a is non-positive, b is positive */ \
+ (((_ptr_type)(_a) < (__sop(m)(smin)(_ptr_type) / ((_ptr_type)(_b)))) ? 0 : 1) \
+ : /* a and b are non-positive */ \
+ ((((_ptr_type)(_a) != 0) && \
+ (((_ptr_type)(_b)) < (__sop(m)(smax)(_ptr_type) / (_ptr_type)(_a)))) ? \
+ 0 : 1) \
+ ) \
+ ) /* end if a and b are non-positive */ \
+ ? \
+ (((void *)(_ptr)) != NULL ? *((_ptr_type*)(_ptr)) = \
+ ((_ptr_type)(_a) * ((_ptr_type)(_b))),1 : 1) \
+ : 0)
+
+/*** Same-type division macros ***/
+
+/* div-by-zero is the only thing addressed */
+#define sop_udiv(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ (((_ptr_type)(_b) != 0) ? ((((void *)(_ptr)) != NULL) ? \
+ *((_ptr_type*)(_ptr)) = ((_ptr_type)(_a) / (_ptr_type)(_b)),1 : \
+ 1) \
+ : 0)
+
+/* Addreses div by zero and smin -1 */
+#define sop_sdiv(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((((_ptr_type)(_b) != 0) && \
+ (((_ptr_type)(_a) != __sop(m)(smin)(_ptr_type)) || \
+ /* GCC type-limits hack: \
+ * whines if b is unsigned even with a cast, but should extend fine. */ \
+ ((_b_type)(_b) != (_b_type)-1))) \
+ ? \
+ ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ ((_ptr_type)(_a) / (_ptr_type)(_b)),1 : 1) \
+ : \
+ 0 \
+ ) \
+
+
+/*** Same-type modulo macros ***/
+/* mod-by-zero is the only thing addressed */
+#define sop_umod(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ (((_ptr_type)(_b) != 0) ? ((((void *)(_ptr)) != NULL) ? \
+ *((_ptr_type*)(_ptr)) = ((_ptr_type)(_a) % (_ptr_type)(_b)),1 : 1) : 0)
+
+/* Addreses mod by zero and smin -1 */
+#define sop_smod(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((((_ptr_type)(_b) != 0) && \
+ (((_ptr_type)(_a) != __sop(m)(smin)(_ptr_type)) || \
+ /* GCC type-limits hack: */ \
+ ((_b_type)(_b) != (_b_type)-1))) \
+ ? \
+ ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ ((_ptr_type)(_a) % (_ptr_type)(_b)),1 : 1) \
+ : \
+ 0 \
+ ) \
+
+/*** Same-type left-shift macros ***/
+#define sop_sshl(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ /* GCC type-limit hack: \
+ * should just check if < 0 */ \
+ ((!((_ptr_type)(_a) > 0 || (_ptr_type)(_a) == 0) || \
+ !((_ptr_type)(_b) > 0 || (_ptr_type)(_b) == 0) || \
+ ((_ptr_type)(_b) >= sizeof(_ptr_type)*CHAR_BIT) || \
+ ((_ptr_type)(_a) > (__sop(m)(smax)(_ptr_type) >> ((_ptr_type)(_b))))) ? \
+ 0 \
+ : ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ (_ptr_type)(_a) << (_ptr_type)(_b),1 : 1))
+
+#define sop_ushl(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((((_ptr_type)(_b) >= (sizeof(_ptr_type)*CHAR_BIT)) || \
+ (((_ptr_type)(_a)) > (__sop(m)(umax)(_ptr_type) >> ((_ptr_type)(_b))))) \
+ ? \
+ 0 \
+ : \
+ ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ (_ptr_type)(_a) << (_ptr_type)(_b),1 : 1))
+
+/*** Same-type right-shift macros ***/
+/* XXX: CERT doesnt recommend failing on -a, but it is undefined */
+#define sop_sshr(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((!((_ptr_type)(_a) > 0 || (_ptr_type)(_a) == 0) || \
+ !((_ptr_type)(_b) > 0 || (_ptr_type)(_b) == 0) || \
+ ((_ptr_type)(_b) >= sizeof(_ptr_type)*CHAR_BIT)) ? \
+ 0 \
+ : \
+ ((((void *)(_ptr)) != NULL) ? *((_ptr_type*)(_ptr)) = \
+ (_ptr_type)(_a) >> (_ptr_type)(_b),1 : 1) \
+ )
+
+/* this doesn't complain if 0 >> n. */
+#define sop_ushr(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ (((_ptr_type)(_b) >= (_ptr_type)(sizeof(_ptr_type)*CHAR_BIT)) ? \
+ 0 : ((((void *)(_ptr)) != NULL) ? \
+ *((_ptr_type*)(_ptr)) = ((_ptr_type)(_a) >> (_ptr_type)(_b)),1 : 1))
+
+
+/* sop_safe_cast
+ * sop_safe_cast takes the signedness, type, and value of two variables. It
+ * then returns true if second variable can be safely cast to the first
+ * variable's type and sign without changing value.
+ *
+ * This function is used internally in safe-iop but is exposed to allow
+ * use if there is a need.
+ */
+#define sop_safe_cast(_a_sign, _a_type, _a, _b_sign, _b_type, _b) \
+ ((sizeof(_a_type) == sizeof(_b_type)) \
+ ? \
+ /* sign change */ \
+ ((!_a_sign && !_b_sign) ? \
+ 1 \
+ : \
+ ((_a_sign && _b_sign) \
+ ? \
+ 1 \
+ : \
+ ((!_a_sign && _b_sign) \
+ ? \
+ (((_b) > (_b_type)0 || (_b) == (_b_type)0) ? 1 : 0) \
+ : \
+ ((_a_sign && !_b_sign) \
+ ? \
+ /* since they are the same size, the comparison cast should be safe */ \
+ (((_b) < (_b_type)__sop(m)(smax)(_a_type) || \
+ (_b) == (_b_type)__sop(m)(smax)(_a_type)) ? 1: 0) \
+ : \
+ 0 \
+ ) \
+ ) \
+ ) \
+ ) \
+ : \
+ ((sizeof(_a_type) > sizeof(_b_type)) \
+ ? \
+ /* cast up: this allows -1, e.g., which means extension. */ \
+ ((!_a_sign && !_b_sign) \
+ ? \
+ 1 \
+ : \
+ ((_a_sign && _b_sign) \
+ ? \
+ 1 \
+ : \
+ ((!_a_sign && _b_sign) \
+ ? \
+ (((_b) == (_b_type)0 || (_b) > (_b_type)0) ? 1 : 0)\
+ : \
+ ((_a_sign && !_b_sign) \
+ ? \
+ /* this is true by default */ \
+ ((__sop(m)(smax)(_a_type) >= __sop(m)(umax)(_b_type)) \
+ ? \
+ 1 \
+ : \
+ /* This will safely truncate given that smax(a) <= umax(b) */ \
+ (((_b) < (_b_type)__sop(m)(smax)(_a_type) || \
+ (_b) == (_b_type)__sop(m)(smax)(_a_type)) \
+ ? \
+ 1 \
+ : \
+ 0 \
+ ) \
+ ) \
+ : \
+ 0 \
+ ) \
+ ) \
+ ) \
+ ) \
+ : \
+ ((sizeof(_a_type) < sizeof(_b_type)) \
+ ? \
+ /* cast down (loss of precision) */ \
+ ((!_a_sign && !_b_sign) \
+ ? \
+ (((_b) == (_b_type)__sop(m)(umax)(_a_type)) \
+ ? \
+ 1 \
+ : \
+ (((_b) < (_b_type)__sop(m)(umax)(_a_type)) ? 1 : 0) \
+ ) \
+ : \
+ ((_a_sign && _b_sign) \
+ ? \
+ ((((_b) > (_b_type)__sop(m)(smin)(_a_type) || \
+ (_b) == (_b_type)__sop(m)(smin)(_a_type)) && \
+ ((_b) < (_b_type)__sop(m)(smax)(_a_type) || \
+ (_b) == (_b_type)__sop(m)(smax)(_a_type))) \
+ ? \
+ 1 \
+ : \
+ 0 \
+ ) \
+ : \
+ ((!_a_sign && _b_sign) \
+ ? \
+ /* GCC type-limit hack: \
+ * XXX: need a test for this in case umax of a smaller \
+ * type could exceed smax of a larger type. The other direction \
+ * could truncate _b though so it's a challene either way. */ \
+ ((((_b) > (_b_type)0 || (_b) == (_b_type)0) && \
+ (((_b) < (_b_type)__sop(m)(umax)(_a_type)) || \
+ ((_b) == (_b_type)__sop(m)(umax)(_a_type)))) \
+ ? \
+ 1 \
+ : \
+ 0 \
+ ) \
+ : \
+ ((_a_sign && !_b_sign) \
+ ? \
+ /* this should safely extend */ \
+ (((_b) < (_b_type)__sop(m)(smax)(_a_type) || \
+ (_b) == (_b_type)__sop(m)(smax)(_a_type)) \
+ ? \
+ 1 \
+ : \
+ 0 \
+ ) \
+ : \
+ 0 \
+ ) \
+ ) \
+ ) \
+ ) \
+ : \
+ 0 \
+ ) \
+ ) \
+ )
+
+
+/* These functions allow compile-time resolution of whether _ptr is non-NULL.
+ * In the future, concatenation tactics like these and the ones used with the
+ * operations may be employed to compartmentalize the sop_safe_cast tests, but
+ * that pushes extra work on preprocessors tests for portability (CHAR_BIT == 8)
+ * is int and int32 or an int16, etc. */
+#define sop_safe_cast_np(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, \
+ _b_sign, _b_type, _b) \
+ sop_safe_cast(_a_sign, _a_type, _a, _b_sign, _b_type, _b)
+
+#define sop_safe_cast_p(_ptr_sign, _ptr_type, _ptr, \
+ _a_sign, _a_type, _a, \
+ _b_sign, _b_type, _b) \
+ sop_safe_cast(_ptr_sign, _ptr_type, _ptr, _a_sign, _a_type, _a) && \
+ sop_safe_cast(_ptr_sign, _ptr_type, _ptr, _b_sign, _b_type, _b)
+
+
+/*****************************************************************************
+ * Generic (x) interface macros
+ *****************************************************************************
+ * These macros are known to work with GCC as well as PCC and perhaps other C99
+ * compatible compilers. Due to the limitations of the C99 standard, these
+ * macros are _NOT_ side effect free and the arguments require a custom
+ * type-markup.
+ *
+ * Instead of requiring the specification of the type for each variable,
+ * short-hand macros are provided which provide a simple interface:
+ * uint32_t a = 100, b = 200;
+ * uint64_t c;
+ * if (!sop_mulx(sop_u64(&c), sop_u32(a), sop_u32(b)) abort();
+ * In addition, this interface automatically handles testing for cast-safety.
+ * All operands will be cast to the type/signedness of the left-most operand unless
+ * there is a destination pointer. If there is a pointer, as above, the values will
+ * be cast to that type, if possible, for the operations.
+ *
+ * Ιn the example above, that is a's type: uint32_t.
+ *
+ * The type markup macros available are listed at the top of the file.
+ *
+ * With respect to side effects, never call sop_<op>x[#] with a operand that
+ * may have side effects. For example:
+ * [BAD!] sop_addx(sio_u32(buf++), sop_s32(a--), sop_s16(--b));
+ *
+ */
+#define sop_addx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_add_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_add_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_subx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_sub_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_sub_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_mulx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_mul_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_mul_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_divx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_div_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_div_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_modx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_mod_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_mod_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_shlx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_shl_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_shl_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+#define sop_shrx(_ptr, _a, _b) \
+ (sop_safe_cast_##_ptr(\
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) ? \
+ /* PCC won't do an extra dereference here! */ \
+ (((void *)(sop_valueof_##_ptr)) ? \
+ sop_shr_##_ptr( \
+ sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b) \
+ : \
+ sop_shr_##_a( \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_ptr, \
+ sop_signed_##_a, sop_typeof_##_a, sop_valueof_##_a, \
+ sop_signed_##_b, sop_typeof_##_b, sop_valueof_##_b)) \
+ : 0)
+
+/* Generic interface convenience functions */
+
+/* sop_incx
+ * Increments the value stored in a variable by one.
+ * Example:
+ * int i;
+ * for (i = 0; i <= max && sop_incx(sop_s32(i); ) { ... }
+ * This will increment until i == max or the variable would overflow (i=INT_MAX).
+ */
+#define sop_incx(_p) \
+ sop_add_##_p(sop_signed_##_p, sop_typeof_##_p, &(sop_valueof_##_p), \
+ sop_signed_##_p, sop_typeof_##_p, sop_valueof_##_p, \
+ sop_signed_##_p, sop_typeof_##_p, 1)
+
+/* sop_decx
+ * Decrements the value stored in a variable by one.
+ * Example:
+ * unsigned int i = 1024;
+ * while (sop_decx(sop_u32(i)) { ... }
+ * This will decrement until the variable would underflow (i==0).
+ */
+#define sop_decx(_p) \
+ sop_sub_##_p(sop_signed_##_p, sop_typeof_##_p, &(sop_valueof_##_p), \
+ sop_signed_##_p, sop_typeof_##_p, sop_valueof_##_p, \
+ sop_signed_##_p, sop_typeof_##_p, 1)
+
+/* sop_<op>x[3-5]
+ * These functions allow for the easy repetition of the same operation.
+ * For instance, sop_mulx3 will multiply 3 integers together if they can
+ * be safely cast to the type of the destination pointer and do not result
+ * in an overflow or underflow.
+ *
+ * For example:
+ * if (!sop_mulx3(sop_u32(&image_sz), sop_u32(w), sop_u32(h), sop_u16(depth)))
+ * goto ERR_handle_bad_dimensions;
+ */
+#define sop_addx3(_ptr, _A, _B, _C) \
+ (sop_assert(((void *)(sop_valueof_##_ptr)) != NULL) \
+ ? \
+ (sop_safe_cast(sop_signed_##_ptr, sop_typeof_##_ptr, sop_valueof_##_ptr, \
+ sop_signed_##_A, sop_typeof_##_A, sop_valueof_##_A) && \
***The diff for this file has been truncated for email.***
=======================================
--- /dev/null
+++ /branches/websockets/src/third_party/safe_iop_LICENSE Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2007,2008 Will Drewry
<>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
=======================================
--- /dev/null
+++ /branches/websockets/src/websocket.c Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,538 @@
+#include <errno.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "network.h"
+#include "third_party/safe_iop.h"
+#include "websocket.h"
+
+#define OPCODE_CONTINUE 0x0
+#define OPCODE_TEXT 0x1
+#define OPCODE_BINARY 0x2
+#define OPCODE_CLOSE 0x8
+#define OPCODE_PING 0x9
+#define OPCODE_PONG 0xA
+
+#define FIN_BIT 0x80
+#define MASK_BIT 0x80
+
+// The length of the buffer required to hold a SHA1 digest.
+#define SHA_HASH_SIZE 20
+
+// The length of the buffer that will hold the base64 encoded sha digest.
+#define BASE64_SHA_DIGEST_LENGTH 25
+
+// The HTTP header specifying the websocket sub-protocol.
+const static char WS_PROTOCOL[] = "Sec-WebSocket-Protocol: ";
+
+/**
+ * Send a close packet. We don't care whether it gets there or not, so we don't
+ * check error conditions.
+ * @param socket_fd The socket to send the close on
+ */
+void websocket_close_response(int socket_fd) {
+ unsigned char close_packet[2] = {FIN_BIT | OPCODE_CLOSE, 0x00};
+ writen(socket_fd, close_packet, 2);
+}
+
+/**
+ * Respond to a ping message.
+ * @param socket_fd The socket to respond on
+ * @param len The length of the data that must be read
+ * @param mask true if the data is masked
+ * @param masking_key the mask to apply
+ * @returns 0 on success, or an error code.
+ */
+int websocket_ping_response(int socket_fd, uint64_t len, int mask,
+ unsigned char masking_key[4]) {
+ unsigned char scratch[8] = {0};
+ int i;
+ // Immediately respond with a PONG containing the same data, but
+ // unmasked.
+ scratch[0] = FIN_BIT | OPCODE_PONG;
+ if (writen(socket_fd, scratch, 1) != 1) return -EIO;
+ // Write the length
+ if (len < 126) {
+ scratch[0] = len & 0x7F;
+ if (writen(socket_fd, scratch, 1) != 1) return -EIO;
+ } else if (len < (1 << 16)) {
+ scratch[0] = 126;
+ if (writen(socket_fd, scratch, 1) != 1) return -EIO;
+ scratch[0] = (len >> 8) & 0xFF;
+ scratch[1] = len & 0xFF;
+ if (writen(socket_fd, scratch, 2) != 2) return -EIO;
+ } else {
+ scratch[0] = 127;
+ if (writen(socket_fd, scratch, 1) != 1) return -EIO;
+ scratch[0] = (len >> 56) & 0xFF;
+ scratch[1] = (len >> 48) & 0xFF;
+ scratch[2] = (len >> 40) & 0xFF;
+ scratch[3] = (len >> 32) & 0xFF;
+ scratch[4] = (len >> 24) & 0xFF;
+ scratch[5] = (len >> 16) & 0xFF;
+ scratch[6] = (len >> 8) & 0xFF;
+ scratch[7] = len & 0xFF;
+ if (writen(socket_fd, scratch, 8) != 8) return -EIO;
+ }
+ // Write the unmasked data.
+ for (i = 0; i < len; i++) {
+ if (readn(socket_fd, scratch, 1) != 1) return -EIO;
+ if (mask) scratch[0] ^= masking_key[i % 4];
+ if (writen(socket_fd, scratch, 1) != 1) return -EIO;
+ }
+ return 0;
+}
+
+/**
+ * Receive a websocket message. Receive the data into memory and unmask it.
+ * Websockets are defined in RFC 6455.
+ * @param socket_fd the socket on which the websocket data is arriving
+ * @param data the location to which data will be written. In the case of
+ * failure it may or may not be partially filled in. It should be a
+ * memory segment of at least max_len bytes.
+ * @param max_len the maximum number of bytes we are willing to accept. We are
+ * stricter than the standard and refuse to accept any packet
+ * that is larger than 2^62 bytes. That still allows individual
+ * frames to be 9 exabytes in size, which should be sufficient.
+ * @returns number of bytes read on success, error code otherwise. 0 means
+ * that we successfully read a message of length zero.
+ */
+int64_t recv_websocket_msg(int socket_fd, void* data, int64_t max_len) {
+ unsigned char scratch[8]; // 8 bytes of scratch space
+ int mask;
+ unsigned char fin = 0;
+ unsigned char opcode = 0;
+ uint64_t len = 0ULL;
+ unsigned char masking_key[4] = {0};
+ uint64_t i;
+ uint64_t current_offset = 0ULL;
+ uint64_t next_offset;
+ int first_frame = 1;
+ size_t extra_len_bytes = 0;
+ if (max_len < 0) return -EINVAL;
+ // Read frames until you find a FIN frame ending the message. The first
+ // frame may be (and in many cases probably is) the final frame.
+ // opcodes arrive only on the first frame, except for the control opcodes
+ // of CLOSE, PING, and PONG, which may be randomly interspersed with data
+ // frames.
+ while (1) {
+ // Framing protocol for websockets (from the RFC)
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-------+-+-------------+-------------------------------+
+ // |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ // |I|S|S|S| (4) |A| (7) | (16/64) |
+ // |N|V|V|V| |S| | (if payload len==126/127) |
+ // | |1|2|3| |K| | |
+ // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ // | Extended payload length continued, if payload len == 127 |
+ // + - - - - - - - - - - - - - - - +-------------------------------+
+ // | |Masking-key, if MASK set to 1 |
+ // +-------------------------------+-------------------------------+
+ // | Masking-key (continued) | Payload Data |
+ // +-------------------------------- - - - - - - - - - - - - - - - +
+ // : Payload Data continued ... :
+ // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ // | Payload Data continued ... |
+ // +---------------------------------------------------------------+
+ // Read the header all the way up to the beginning of Payload Data
+ // First read the 2-byte required header
+ if (readn(socket_fd, scratch, 2) != 2) return -EIO;
+ fin = scratch[0] & FIN_BIT;
+ opcode = scratch[0] & 0x0F;
+ mask = scratch[1] & MASK_BIT;
+ // Length is either 7 bits, 16 bits, or 64 bits.
+ len = scratch[1] & 0x7F;
+ // Check for the signal values of len
+ if (len == 126ULL) {
+ extra_len_bytes = 2;
+ } else if (len == 127ULL) {
+ extra_len_bytes = 8;
+ }
+ // Read the extra length bytes, if required.
+ if (extra_len_bytes > 0) {
+ if (readn(socket_fd, scratch, extra_len_bytes) != extra_len_bytes)
+ return -EIO;
+ len = 0ULL;
+ for (i = 0; i < extra_len_bytes; i++) {
+ len = (len << 8) + scratch[i];
+ }
+ }
+ // Make sure that integer operations will not overflow.
+ if (sop_addx(NULL, sop_u64(len), sop_u64(current_offset)))
+ next_offset = len + current_offset;
+ else
+ return -EOVERFLOW;
+ // Make sure the message will fit in the provided memory
+ if (next_offset > max_len) return -EMSGSIZE;
+ // Read the 4 byte mask, if required
+ if (mask) {
+ if (readn(socket_fd, masking_key, 4) != 4) return -EIO;
+ } else {
+ // According to RFC 6455 Sec. 5.1. "The server MUST close the connection
+ // upon receiving a frame that is not masked."
+ websocket_close_response(socket_fd);
+ return -ENOLINK;
+ }
+ // Do different things, depending on the received opcode.
+ if (opcode == OPCODE_CLOSE) {
+ // Immediately respond with a CLOSE. Socket should be closed immediately
+ // after this. Nothing more can be read.
+ websocket_close_response(socket_fd);
+ return -ENOLINK;
+ } else if (opcode == OPCODE_PING) {
+ if (websocket_ping_response(socket_fd, len, mask, masking_key) != 0)
+ return -EIO;
+ } else if (opcode == OPCODE_PONG) {
+ // Read the PONG. Ignore it.
+ for (i = 0; i < len; i++) {
+ if (readn(socket_fd, scratch, 1) != 1) return -EIO;
+ }
+ } else if ((first_frame &&
+ (opcode == OPCODE_TEXT || opcode == OPCODE_BINARY)) ||
+ (!first_frame && opcode == OPCODE_CONTINUE)) {
+ // opcodes only apply to the first frame of a fragmented message.
+ // Make sure it is safe to cast len to a size_t
+ if (!sop_addx(NULL, sop_szt(0), sop_u64(len))) return -EOVERFLOW;
+ // Read the frame data into memory
+ if (readn(socket_fd, &(((char*)data)[current_offset]), (size_t)len) !=
+ len)
+ return -EIO;
+ // Unmask the data, if required
+ if (mask) {
+ for (i = 0; i < len; i++) {
+ ((unsigned char*)data)[current_offset + i] ^= masking_key[i % 4];
+ }
+ }
+ // Increment our state
+ current_offset = next_offset;
+ first_frame = 0;
+ if (fin) break;
+ } else {
+ // No opcode and frame combination matched up. This should never occur if
+ // the client is using a correct websocket libary.
+ return -EINVAL;
+ }
+ }
+ if (sop_addx(NULL, sop_s64(0), sop_u64(current_offset)))
+ return (int64_t)current_offset;
+ else
+ return -EOVERFLOW;
+}
+
+/**
+ * Receives an NDT message sent over websockets. Arguments are modeled after
+ * recv_msg in network.h.
+ *
+ * @param socket_fd The socket on which to listen
+ * @param msg_type An outparam for the type of the message
+ * @param msg_value Where to put the contents of the message
+ * @param msg_len Both an outparam for the message length stored in msg_value,
+ * and, when the function is called, the maximum message length
+ * that can be stored in msg_value.
+ */
+int64_t recv_websocket_ndt_msg(int socket_fd, int* msg_type, char* msg_value,
+ int* msg_len) {
+ const unsigned int HEADER_SIZE = 3;
+ int64_t bytes;
+ char* message;
+ message = (char*)malloc(sizeof(char) * (*msg_len + HEADER_SIZE));
+ if (message == NULL) return -ENOMEM;
+ bytes = recv_websocket_msg(socket_fd, message, *msg_len + HEADER_SIZE);
+ if (bytes < 0) return bytes;
+ *msg_type = message[0];
+ *msg_len = (message[1] << 8) + message[2];
+ if (*msg_len + HEADER_SIZE > bytes) {
+ free(message);
+ return -EBADMSG;
+ }
+ strncpy(msg_value, &(message[HEADER_SIZE]), *msg_len);
+ free(message);
+ return 0;
+}
+
+/**
+ * Reads a line from the filedescriptor, up to maxlen long. Returns the length
+ * of the line on success, a negative number on failure. On success the string
+ * in dest will be null terminated.
+ * @param fd The file descriptor to read from
+ * @param dest The memory to put the read values
+ * @param max_len The max number of allowed characters to read
+ * @returns The number of characters in the line (not counting the terminating
+ * NULL), or an error code.
+ */
+int ws_readline(int fd, char* dest, unsigned int max_len) {
+ unsigned int count = 0;
+ for (count = 0; count < max_len; count++) {
+ if (readn(fd, &(dest[count]), 1) != 1) return -EIO;
+ if (dest[count] == '\n') {
+ // Be robust to UNIX and DOS newlines
+ if (count > 0 && dest[count - 1] == '\r') {
+ // null-terminate the string
+ dest[count - 1] = '\0';
+ return count - 1;
+ } else {
+ // null-terminate the string
+ dest[count] = '\0';
+ return count;
+ }
+ }
+ }
+ return -EMSGSIZE;
+}
+
+/**
+ * Calculate the sha1 of the key concatenated with the special websocket
+ * protocol string.
+ * @param key The key string received from the client
+ * @param len The length of the key in bytes
+ * @param dest a pointer to a 20 byte array where the resulting message digest
+ * will be written.
+ * @returns 0 on success, an error code otherwise.
+ */
+int websocket_sha(const char* key, unsigned int key_len, unsigned char* dest) {
+ const static char WS_SHA_STRING[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ SHA_CTX context;
+ if (SHA1_Init(&context) && SHA1_Update(&context, key, key_len) &&
+ SHA1_Update(&context, WS_SHA_STRING, strlen(WS_SHA_STRING)) &&
+ SHA1_Final(dest, &context)) {
+ return 0;
+ } else {
+ return EINVAL;
+ }
+}
+
+/**
+ * Send the 20 byte message digest base64encoded.
+ * @param socket_fd the socket to which we should write.
+ * @param message_digest the 20 byte digest to encode and send.
+ * @returns 0 on success, error code otherwise
+ */
+int send_digest_base64(int socket_fd, const unsigned char* message_digest) {
+ BIO* to_socket;
+ BIO* buffer;
+ BIO* b64;
+ int error = 0;
+ // Allocate all the BIO objects
+ to_socket = BIO_new(BIO_s_socket());
+ buffer = BIO_new(BIO_f_buffer());
+ b64 = BIO_new(BIO_f_base64());
+ // If the allocations succeeded
+ if (b64 && to_socket && buffer) {
+ // Make sure b64 doesn't put newlines all over the place
+ BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+ // Connect the BIO objects to the socket and to each other in series
+ BIO_set_fd(to_socket, socket_fd, BIO_NOCLOSE);
+ BIO_push(b64, buffer);
+ BIO_push(buffer, to_socket);
+ // Write the data
+ if (BIO_write(b64, message_digest, SHA_HASH_SIZE) != SHA_HASH_SIZE)
+ error = EIO;
+ BIO_flush(b64);
+ } else {
+ error = ENOMEM;
+ }
+ // Cleanup
+ if (b64) BIO_free(b64);
+ if (to_socket) BIO_free(to_socket);
+ if (buffer) BIO_free(buffer);
+ return error;
+}
+
+/**
+ * Reads the websocket header and determines whether it is well-formed.
+ * @param socket_fd The socket on which the connection is happening.
+ * @param skip_bytes How many bytes of the initial handshake have already
+ * been read and validated?
+ * @param expected_protocol The protocol name we are expecting from the client.
+ * May be NULL or "" if no protocol name is desired.
+ * @param key An outparam to hold the websocket key - must point to a string
+ * at least 25 chars long.
+ * @returns 0 on success, error code otherwise.
+ */
+int read_websocket_header(int socket_fd, unsigned int skip_bytes,
+ char* expected_protocol, char* key) {
+ // Neither of these limits is in the HTTP spec, but they are the limits
+ // enforced by the Apache defaults. In particular, it is recommended that
+ // there not be more than 8K of headers total, so these limits are generous.
+ // http://stackoverflow.com/questions/686217/maximum-on-http-header-values
+ const static unsigned int MAX_HEADER_COUNT = 1024;
+ const static unsigned int MAX_HEADER_LENGTH = 8192;
+ // String constants used when making a websocket connection.
+ const static char UPGRADE_HEADER[] = "Upgrade: websocket";
+ const static char CONNECTION_HEADER[] = "Connection: Upgrade";
+ const static char VERSION_HEADER[] = "Sec-WebSocket-Version: 13";
+ const static char WS_KEY[] = "Sec-WebSocket-Key: ";
+
+ char line[MAX_HEADER_LENGTH];
+ const char* first_line = "GET /ndt_after_user_privacy_agreement HTTP/1.1";
+ const char* suffix;
+ int validated_connection = 0;
+ int validated_upgrade = 0;
+ int validated_version = 0;
+ int validated_protocol = 0;
+ int i;
+ // You can only fastforward into the very first line
+ if (skip_bytes >= strlen(first_line)) return EINVAL;
+ // Read the first line.
+ if (ws_readline(socket_fd, line, MAX_HEADER_LENGTH) < 0) return EIO;
+ if (strcmp(line, &(first_line[skip_bytes])) != 0) return EINVAL;
+ // HTTP headers end with a blank line
+ // We only save the header values we care about. The headers we only need to
+ // check (i.e. have no data we need for later) are handled as they arrive.
+ if (ws_readline(socket_fd, line, MAX_HEADER_LENGTH) < 0) return EIO;
+ for (i = 0; i < MAX_HEADER_COUNT; i++) {
+ if (strcmp(line, UPGRADE_HEADER) == 0) {
+ validated_upgrade = 1;
+ } else if (strcmp(line, CONNECTION_HEADER) == 0) {
+ validated_connection = 1;
+ } else if (strcmp(line, VERSION_HEADER) == 0) {
+ validated_version = 1;
+ } else if (strncmp(line, WS_PROTOCOL, strlen(WS_PROTOCOL)) == 0) {
+ if (expected_protocol == NULL || strcmp(expected_protocol, "") == 0) {
+ validated_protocol = 1;
+ } else {
+ suffix = &(line[strlen(WS_PROTOCOL)]);
+ validated_protocol = (strstr(suffix, expected_protocol) != NULL);
+ }
+ } else if (strncmp(line, WS_KEY, strlen(WS_KEY)) == 0) {
+ suffix = &(line[strlen(WS_KEY)]);
+ if (strlen(suffix) + 1 != BASE64_SHA_DIGEST_LENGTH) return EBADMSG;
+ strncpy(key, suffix, BASE64_SHA_DIGEST_LENGTH);
+ }
+ if (ws_readline(socket_fd, line, MAX_HEADER_LENGTH) < 0) return EIO;
+ if (strcmp(line, "") == 0) break;
+ }
+ if (strcmp(line, "") == 0 && validated_connection && validated_upgrade &&
+ validated_version && validated_protocol) {
+ return 0;
+ } else {
+ return EBADMSG;
+ }
+}
+
+int write_websocket_header(int socket_fd, char* protocol, char* key) {
+ const static char WS_RESPONSE[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n";
+ const static char WS_ACCEPT[] = "Sec-WebSocket-Accept: ";
+ unsigned char message_digest[SHA_HASH_SIZE];
+ const size_t WS_RESPONSE_LEN = sizeof(WS_RESPONSE) - sizeof(char);
+ const size_t WS_ACCEPT_LEN = sizeof(WS_ACCEPT) - sizeof(char);
+ const size_t WS_PROTOCOL_LEN = sizeof(WS_PROTOCOL) - sizeof(char);
+ // Write the constant header
+ if (writen(socket_fd, WS_RESPONSE, WS_RESPONSE_LEN) != WS_RESPONSE_LEN) {
+ return EIO;
+ }
+ // Agree to the protocol (if necessary)
+ if (protocol != NULL && strcmp(protocol, "") != 0) {
+ if (writen(socket_fd, WS_PROTOCOL, WS_PROTOCOL_LEN) != WS_PROTOCOL_LEN) {
+ return EIO;
+ }
+ if (writen(socket_fd, protocol, strlen(protocol)) != strlen(protocol)) {
+ return EIO;
+ }
+ // End the WS_PROTOCOL header
+ if (writen(socket_fd, "\r\n", 2) != 2) return EIO;
+ }
+ // Send the WS_ACCEPT header and value (requires sha1 and base64enc)
+ if (writen(socket_fd, WS_ACCEPT, WS_ACCEPT_LEN) != WS_ACCEPT_LEN) {
+ return EIO;
+ }
+ websocket_sha(key, strlen(key), message_digest);
+ if (send_digest_base64(socket_fd, message_digest) != 0) return EIO;
+ // End the WS_ACCEPT header
+ if (writen(socket_fd, "\r\n", 2) != 2) return EIO;
+ // A blank line signals the end of the headers.
+ if (writen(socket_fd, "\r\n", 2) != 2) return EIO;
+ return 0;
+}
+
+/**
+ * Sets up a websocket connection. There are times when we don't determine it's
+ * a websocket connection until a few bytes have been read, so this function
+ * supports specifying the number of bytes that have already been read, and
+ * will try to fast-forward to that point in the handshake.
+ * @param socket_fd The socket on which the connection is happening.
+ * @param skip_bytes How many bytes of the initial handshake have already
+ * been read and validated?
+ * @param expected_protocol The protocol name we are expecting from the client.
+ * May be NULL or "" if no protocol name is desired.
+ * @returns 0 on success, error code otherwise.
+ */
+int initialize_websocket_connection(int socket_fd, unsigned int skip_bytes,
+ char* expected_protocol) {
+ // Keys are base64 encoded 16 byte values. So we need to allocate enough
+ // memory on the stack to hold the entire key and null terminator.
+ char key[BASE64_SHA_DIGEST_LENGTH] = {0};
+ int err;
+ err = read_websocket_header(socket_fd, skip_bytes, expected_protocol, key);
+ if (err != 0) return err;
+ // We have received a well-formed header. We should respond with a
+ // well-formed response of our own.
+ err = write_websocket_header(socket_fd, expected_protocol, key);
+ if (err != 0) return err;
+ return 0;
+}
+
+/**
+ * Sends a websocket frame from the server to a client. Messages sent from a
+ * server MUST NOT be masked, according to the RFC, and so this function does
+ * not support masking. The arguments are modeled after send_msg.
+ * @param socket_fd The socket on which the connection is happening.
+ * @param type The NDT message type
+ * @param msg A pointer to the data to be sent
+ * @param len The number of bytes to be sent
+ */
+int send_websocket_msg(int socket_fd, int type, const void* msg, uint64_t len) {
+ int websocket_hdr_len;
+ int err;
+ int i;
+ uint64_t websocket_msg_len;
+ char websocket_msg[10]; // Max websocket header size if there is no mask
+ if (sop_addx(NULL, sop_u64(len), sop_u64(3))) {
+ websocket_msg_len = len + 3; // NDT header is always 3 bytes
+ } else {
+ return EINVAL;
+ }
+ // Set up the websocket header
+ websocket_msg[0] = (char)0x82; // FIN_BIT | BINARY_OPCODE
+ if (websocket_msg_len < 126) {
+ websocket_hdr_len = 2;
+ if (websocket_msg == NULL) return ENOMEM;
+ // 7 bits for the length
+ websocket_msg[1] = (char)(websocket_msg_len & 0x7F);
+ } else if (websocket_msg_len < (1 << 16)) {
+ websocket_hdr_len = 4;
+ if (websocket_msg == NULL) return ENOMEM;
+ // Signal value for "2 byte length"
+ websocket_msg[1] = 126;
+ // 16 bits for the length
+ websocket_msg[2] = (websocket_msg_len >> 8) & 0xFF;
+ websocket_msg[3] = websocket_msg_len & 0xFF;
+ } else {
+ websocket_hdr_len = 10;
+ if (websocket_msg == NULL) return ENOMEM;
+ // Signal value for "8 byte length"
+ websocket_msg[1] = 127;
+ // 64 bits for the length
+ for (i = 0; i < 8; ++i) {
+ websocket_msg[i + 2] = (websocket_msg_len >> ((7 - i) * 8)) & 0xFF;
+ }
+ }
+ // Send the header
+ if (writen(socket_fd, websocket_msg, websocket_hdr_len) !=
+ websocket_hdr_len) {
+ return EIO;
+ }
+ // Websocket server -> client messages are not masked, so we can just send it
+ // like it's an NDT message.
+ err = send_msg(socket_fd, type, msg, (int)len);
+ return err;
+}
=======================================
--- /dev/null
+++ /branches/websockets/src/websocket.h Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,11 @@
+#ifndef SRC_WEBSOCKET_H
+#define SRC_WEBSOCKET_H
+
+int initialize_websocket_connection(int socket_fd, unsigned int skip_bytes,
+ char* protocol);
+int64_t recv_websocket_msg(int socket_fd, void* data, int64_t len);
+int64_t recv_websocket_ndt_msg(int socket_fd, int* msg_type, char* msg_value,
+ int* msg_len);
+
+int send_websocket_msg(int socket_fd, int type, const void* msg, uint64_t len);
+#endif // SRC_WEBSOCKET_H
=======================================
--- /dev/null
+++ /branches/websockets/src/websocket_unit_tests.c Mon Mar 23 22:59:30 2015 UTC
@@ -0,0 +1,386 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "logging.h"
+#include "network.h"
+#include "unit_testing.h"
+#include "websocket.h"
+
+// Functions defined in websocket.c that we would like to unit test, but do not
+// want to expose to the rest of the program.
+int websocket_sha(const char* key, unsigned long len, unsigned char* dest);
+int send_digest_base64(int fd, const unsigned char* digest);
+
+/* Creates a socket pair and forks a subprocess to send data in one end. After
+ * reading the data from the other end, reports whether all the data was the
+ * same.
+ * @param raw_data_to_send The data to send down the pipe
+ * @param raw_length The size of the data
+ * @param expected_data The data we expect to receive in the websocket message
+ * @param expected_length The length of the expected_data
+ * @param expected_error The error we expect from the function. Should be set
+ * to zero if we expect success.
+ */
+void compare_sent_and_received(const unsigned char* raw_data_to_send,
+ int raw_length,
+ const unsigned char* expected_data,
+ int64_t expected_length, int expected_error) {
+ unsigned char* received_data;
+ int64_t bytes_read;
+ int sockets[2];
+ pid_t writer_pid;
+ int writer_exit_code;
+ int bytes_written;
+ int child_sock, parent_sock;
+ int i;
+
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ parent_sock = sockets[0];
+ child_sock = sockets[1];
+ if ((writer_pid = fork()) == 0) {
+ bytes_written = writen(child_sock, raw_data_to_send, raw_length);
+ if (bytes_written == raw_length) {
+ exit(0);
+ } else {
+ FAIL("write returned %d and not %d", bytes_written, raw_length);
+ }
+ } else {
+ received_data = (unsigned char*)malloc(expected_length * sizeof(char));
+ CHECK(received_data != NULL);
+ bytes_read =
+ recv_websocket_msg(parent_sock, received_data, expected_length);
+ if (expected_error) {
+ ASSERT(bytes_read < 0, "Expected an error, but got success");
+ bytes_read *= -1;
+ ASSERT(bytes_read == expected_error,
+ "Expected an error code of %d (%s), but we received %d (%s)",
+ (int)bytes_read, strerror(bytes_read), expected_error,
+ strerror(expected_error));
+ } else {
+ ASSERT(bytes_read == expected_length,
+ "recv_websocket_msg returned %lld but we expected %lld",
+ bytes_read, expected_length);
+ ASSERT(bytes_read == expected_length,
+ "Bad frame data length: %lld vs %lld", bytes_read,
+ expected_length);
+ for (i = 0; i < bytes_read; i++) {
+ ASSERT(received_data[i] == expected_data[i],
+ "received_data[%d] != expected data[%d] ('%c' != '%c')", i, i,
+ received_data[i], expected_data[i]);
+ }
+ }
+ waitpid(writer_pid, &writer_exit_code, 0);
+ CHECK(WIFEXITED(writer_exit_code) && WEXITSTATUS(writer_exit_code) == 0);
+ free(received_data);
+ }
+}
+
+void test_recv_unmasked_msg_closes_connection() {
+ const unsigned char raw_data[] = {0x82, 0x0A, 'a', 'b', 'c', 'd',
+ 'e', '1', '2', '3', '4', '5'};
+ const unsigned char expected_data[10] = "abcde12345";
+ compare_sent_and_received(raw_data, sizeof(raw_data), expected_data,
+ sizeof(expected_data), ENOLINK);
+}
+
+void test_recv_masked_msg() {
+ // Actual raw data from sending 'abcde12345' using goog.net.WebSocket
+ const unsigned char raw_data[] = {0x81, 0x8A, 0xA9, 0x8D, 0x85, 0x31,
+ 0xC8, 0xEF, 0xE6, 0x55, 0xCC, 0xBC,
+ 0xB7, 0x02, 0x9D, 0xB8};
+ const unsigned char expected_data[10] = "abcde12345";
+ compare_sent_and_received(raw_data, sizeof(raw_data), expected_data,
+ sizeof(expected_data), 0);
+}
+
+void test_recv_large_msg() {
+ // Three size codepaths. Small data (< 8 bits required to express length),
+ // large (8-16 bits), and jumbo (16-64 bits). This code tests large.
+ unsigned char expected_data[700];
+ unsigned char raw_data[8 + 700] = {0x82, // FIN_BIT | BINARY_OPCODE
+ 0xFE, // MASK_BIT | "large" length flag
+ // The two bytes that make up uint16 700
+ 0x02, 0xBC,
+ // The four byte mask
+ 0x01, 0x01, 0x01, 0x01};
+ memset(expected_data, 'a', sizeof(expected_data));
+ memset(&(raw_data[8]), 'a' ^ 0x01, sizeof(expected_data));
+ compare_sent_and_received(raw_data, sizeof(raw_data), expected_data,
+ sizeof(expected_data), 0);
+}
+
+void test_recv_jumbo_msg() {
+ // Three size codepaths. Small data (< 8 bits required to express length),
+ // large (8-16 bits), and jumbo (16-64 bits). This code tests jumbo.
+ unsigned char expected_data[200000] = {'a'};
+ unsigned char raw_data[14 + 200000] = {
+ 0x82, // FIN_BIT | BINARY_OPCODE
+ 0xFF, // MASK | "jumbo" length flag
+ // The eight bytes that make uint64 200000
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0D, 0x40,
+ // The four byte mask
+ 0x01, 0x01, 0x01, 0x01};
+ memset(expected_data, 'a', sizeof(expected_data));
+ memset(&(raw_data[14]), 'a' ^ 0x01, sizeof(expected_data));
+ compare_sent_and_received(raw_data, sizeof(raw_data), expected_data,
+ sizeof(expected_data), 0);
+}
+
+// Examples in Sec 5.7 of the RFC.
+const unsigned char HELLO[5] = "Hello";
+void test_rfc_example_masked_hello() {
+ const unsigned char raw_data[] = {0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d,
+ 0x7f, 0x9f, 0x4d, 0x51, 0x58};
+ compare_sent_and_received(raw_data, sizeof(raw_data), HELLO, sizeof(HELLO),
+ 0);
+}
+
+void test_rfc_example_fragmented_hello() {
+ // A fragmented message!
+ const unsigned char raw_data[] = {
+ // First packet header
+ 0x01, 0x83, 0x04, 0x03, 0x02, 0x01,
+ // First packet contents: "Hel"
+ 'H' ^ 0x04, 'e' ^ 0x03, 'l' ^ 0x02,
+ // Second packet header
+ 0x80, 0x82, 0x04, 0x03, 0x02, 0x01,
+ // Second packet contents: "lo"
+ 'l' ^ 0x04, 'o' ^ 0x03};
+ compare_sent_and_received(raw_data, sizeof(raw_data), HELLO, sizeof(HELLO),
+ 0);
+}
+
+void test_rfc_example_fragmented_with_ping() {
+ // A fragmented message with a ping in the middle!
+ const unsigned char raw_data[] = {
+ 0x01, 0x83, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, // "Hel"
+ 0x89, 0x81, 0x00, 0x00, 0x00, 0x00, 'a', // ping "a"
+ 0x80, 0x82, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6f}; // "lo"
+ compare_sent_and_received(raw_data, sizeof(raw_data), HELLO, sizeof(HELLO),
+ 0);
+}
+
+void test_messages_too_large() {
+ pid_t child_pid;
+ int child_exit_code;
+ int sockets[2];
+ const int64_t MAX_INT64 = 0x7FFFFFFFFFFFFFFFll;
+ // This pair of fragmented messages should cause the whole thing to fail.
+ // They will try to make a message of size MAX_INT64 + 3. The last frame
+ // claims to be very long, but actually has no data. This is because we
+ // should fail before we try to read any data.
+ const unsigned char raw_data[] = {
+ // "Hello" frame, not marked as final
+ 0x01, 0x85,
+ // 4 byte mask
+ 0x01, 0x02, 0x03, 0x04,
+ // "Hello", with the mask applied
+ 'H' ^ 0x01, 'e' ^ 0x02, 'l' ^ 0x03, 'l' ^ 0x04, 'o' ^ 0x01,
+ // FIN frame, "jumbo" len value, MASK bit set
+ 0x80, 0xFF,
+ // 64 bits of int64 for the final frame length
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF,
+ // 4 byte mask to complete the header
+ 0x01, 0x02, 0x03, 0x04};
+ unsigned char expected[5]; // space for the Hello to be read
+ int64_t err;
+ char* err_str = "no error";
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ if ((child_pid = fork()) == 0) {
+ err = (int64_t)writen(sockets[0], raw_data, sizeof(raw_data));
+ ASSERT(err == sizeof(raw_data), "writen failed (%d)", (int)err);
+ exit(0);
+ } else {
+ err = recv_websocket_msg(sockets[1], expected, MAX_INT64);
+ if (err < 0) {
+ err = -err;
+ err_str = strerror(err);
+ } else {
+ FAIL("Bad error: %d", (int)err);
+ }
+ ASSERT(err == EOVERFLOW, "Error was %d (%s) and we wanted %d (%s)",
+ (int)err, err_str, EOVERFLOW, strerror(EOVERFLOW));
+ waitpid(child_pid, &child_exit_code, 0);
+ ASSERT(WIFEXITED(child_exit_code) && WEXITSTATUS(child_exit_code) == 0,
+ "Child exited with error code %d", child_exit_code);
+ }
+}
+
+/**
+ * Tests the receipt of a NDT message inside a websocket message.
+ */
+void test_recv_websocket_ndt_msg() {
+ const unsigned char raw_data[] = {// Single message, 8 bytes, masked
+ 0x82, 0x88,
+ // Masked with 0x00
+ 0x00, 0x00, 0x00, 0x00,
+ // end websocket header, begin NDT message
+ // NDT protocol MSG_RESULTS type
+ 0x08,
+ // 5 bytes of content
+ 0x00, 0x05,
+ // "HELLO"
+ 0x48, 0x65, 0x6c, 0x6c, 0x6f};
+ const int expected_type = 8;
+ // We expect the message to be hello.
+ const char expected_contents[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f};
+ const int expected_len = sizeof(expected_contents);
+ char received_contents[sizeof(expected_contents) * 10];
+ int received_len = sizeof(received_contents);
+ int received_type;
+ int64_t err;
+ int sockets[2];
+ pid_t child_pid;
+ int child_exit_code;
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ if ((child_pid = fork()) == 0) {
+ err = writen(sockets[0], raw_data, sizeof(raw_data));
+ ASSERT(err = sizeof(raw_data), "writen failed (%d)", (int)err);
+ exit(0);
+ } else {
+ err = recv_websocket_ndt_msg(sockets[1], &received_type, received_contents,
+ &received_len);
+ CHECK(err == 0);
+ ASSERT(received_len == sizeof(expected_contents), "%d != %d", received_len,
+ sizeof(expected_contents));
+ CHECK(received_len == expected_len);
+ CHECK(received_type == expected_type);
+ CHECK(strncmp(expected_contents, received_contents, expected_len) == 0);
+ waitpid(child_pid, &child_exit_code, 0);
+ ASSERT(WIFEXITED(child_exit_code) && WEXITSTATUS(child_exit_code) == 0,
+ "Child exited with error code %d", child_exit_code);
+ }
+}
+
+/**
+ * Sends the header to initialize the websocket, and then compares the response
+ * with what was received.
+ * @param header The headers of the HTTP request
+ * @param response The expected response
+ */
+void check_websocket_handshake(const char* header, const char* response) {
+ char scratch[1024];
+ int sockets[2];
+ int child_socket, parent_socket;
+ pid_t client_pid;
+ int client_exit_code;
+ int bytes_written, bytes_read;
+ int err;
+ int i;
+
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ child_socket = sockets[0];
+ parent_socket = sockets[1];
+ if ((client_pid = fork()) == 0) {
+ err = initialize_websocket_connection(child_socket, 0, "ndt");
+ ASSERT(err == 0, "Initialization failed with exit code %d (%s)", err,
+ strerror(err));
+ CHECK(writen(child_socket, "SIGNAL", 6) == 6);
+ exit(0); // Everything was fine.
+ } else {
+ // The writer uses the second socket.
+ bytes_written = writen(parent_socket, header, strlen(header));
+ ASSERT(bytes_written == strlen(header), "write returned %d and not %d",
+ bytes_written, strlen(header));
+ bytes_read = readn(parent_socket, scratch, strlen(response));
+ ASSERT(bytes_read == strlen(response), "readn returned %d, we wanted %d",
+ bytes_read, strlen(response));
+ for (i = 0; i < strlen(response); i++) {
+ ASSERT(scratch[i] == response[i], "Differ at char %d (%d vs %d)\n", i,
+ (int)scratch[i], (int)response[i]);
+ }
+ ASSERT(strncmp(scratch, response, strlen(response)) == 0,
+ "response differed from what was read. We read:\n'%s' but we "
+ "wanted:\n'%s'\n",
+ scratch, response);
+ CHECK(readn(parent_socket, scratch, 6) == 6);
+ CHECK(strncmp(scratch, "SIGNAL", 6) == 0);
+ waitpid(client_pid, &client_exit_code, 0);
+ CHECK(WIFEXITED(client_exit_code) && WEXITSTATUS(client_exit_code) == 0);
+ }
+}
+
+void test_websocket_handshake() {
+ const char* header =
+ "GET /ndt_after_user_privacy_agreement HTTP/1.1\r\n"
+ "Host: server.example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: ndt, superchat\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n";
+ const char* response =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Protocol: ndt\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "\r\n";
+ check_websocket_handshake(header, response);
+}
+
+void test_websocket_sha() {
+ const char key[] = "dGhlIHNhbXBsZSBub25jZQ==";
+ const unsigned char expected_digest[20] = {
+ 0xb3, 0x7a, 0x4f, 0x2c, 0xc0, 0x62, 0x4f, 0x16, 0x90, 0xf6,
+ 0x46, 0x06, 0xcf, 0x38, 0x59, 0x45, 0xb2, 0xbe, 0xc4, 0xea};
+ unsigned char digest[20];
+ CHECK(websocket_sha(key, sizeof(key) - sizeof(char), digest) == 0);
+ CHECK(memcmp(digest, expected_digest, 20) == 0);
+}
+
+void check_send_digest_base64(const unsigned char* digest,
+ const char* expected) {
+ pid_t child_pid;
+ int child_exit_code;
+ char scratch[1024];
+ int sockets[2];
+ int child_socket, parent_socket;
+ CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+ child_socket = sockets[0];
+ parent_socket = sockets[1];
+ if ((child_pid = fork()) == 0) {
+ // Send the data down the pipe.
+ CHECK(send_digest_base64(child_socket, digest) == 0);
+ CHECK(writen(child_socket, "SIGNAL", 6) == 6);
+ exit(0);
+ } else {
+ // Receive the data and verify that it matches expectations.
+ CHECK(readn(parent_socket, scratch, strlen(expected)) == strlen(expected));
+ CHECK(strncmp(expected, scratch, strlen(expected)) == 0);
+ CHECK(readn(parent_socket, scratch, 6) == 6);
+ CHECK(strncmp("SIGNAL", scratch, 6) == 0);
+ // Make sure the child exited successfully
+ waitpid(child_pid, &child_exit_code, 0);
+ CHECK(WIFEXITED(child_exit_code) && WEXITSTATUS(child_exit_code) == 0);
+ }
+}
+
+void test_send_digest_base64() {
+ const unsigned char digest[20] = {0xb3, 0x7a, 0x4f, 0x2c, 0xc0, 0x62, 0x4f,
+ 0x16, 0x90, 0xf6, 0x46, 0x06, 0xcf, 0x38,
+ 0x59, 0x45, 0xb2, 0xbe, 0xc4, 0xea};
+ const char expected[] = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
+ check_send_digest_base64(digest, expected);
+}
+
+int main() {
+ set_debuglvl(1024);
+ return RUN_TEST(test_messages_too_large) | RUN_TEST(test_recv_jumbo_msg) |
+ RUN_TEST(test_recv_large_msg) | RUN_TEST(test_recv_masked_msg) |
+ RUN_TEST(test_recv_unmasked_msg_closes_connection) |
+ RUN_TEST(test_recv_websocket_ndt_msg) |
+ RUN_TEST(test_rfc_example_fragmented_hello) |
+ RUN_TEST(test_rfc_example_fragmented_with_ping) |
+ RUN_TEST(test_rfc_example_masked_hello) |
+ RUN_TEST(test_send_digest_base64) |
+ RUN_TEST(test_websocket_handshake) | RUN_TEST(test_websocket_sha);
+}
=======================================
--- /branches/websockets/src/Makefile.am Wed Mar 11 09:44:29 2015 UTC
+++ /branches/websockets/src/Makefile.am Mon Mar 23 22:59:30 2015 UTC
@@ -29,7 +29,11 @@
bin_PROGRAMS =
sbin_PROGRAMS = $(ADD_FAKEWWW)
noinst_PROGRAMS =
-TESTS =
+TESTS =
+
+if HAVE_JANSSON
+TESTS += websocket_unit_tests testoptions_unit_tests
+endif
if HAVE_WEB100
bin_PROGRAMS += analyze viewtrace tr-mkmap genplot
@@ -39,7 +43,6 @@
if HAVE_PCAP_H
if HAVE_JANSSON
sbin_PROGRAMS += web100srv
-noinst_PROGRAMS += web100srv_unit_tests
TESTS += web100srv_unit_tests
endif
endif
@@ -57,10 +60,12 @@
endif
endif
+noinst_PROGRAMS += $(TESTS)
+
web100clt_SOURCES = web100clt.c network.c usage.c logging.c utils.c protocol.c runningtest.c ndtptestconstants.c \
test_sfw_clt.c test_mid_clt.c test_c2s_clt.c test_s2c_clt.c test_meta_clt.c strlutils.c \
- test_results_clt.c jsonutils.c
-web100clt_LDADD = $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB)
+ test_results_clt.c jsonutils.c websocket.c
+web100clt_LDADD = $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
web100clt_CPPFLAGS ='-DBASEDIR="$(ndtdir)"'
web100clt_DEPENDENCIES = $(I2UTILLIBDEPS)
@@ -78,32 +83,50 @@
if BUILD_FAKEWWW
fakewww_SOURCES = fakewww.c troute.c troute6.c tr-tree.c tr-tree6.c network.c usage.c logging.c \
- runningtest.c ndtptestconstants.c strlutils.c jsonutils.c
-fakewww_LDADD = $(I2UTILLIBDEPS) $(ZLIB) $(JSONLIB)
+ runningtest.c ndtptestconstants.c strlutils.c jsonutils.c websocket.c
+fakewww_LDADD = $(I2UTILLIBDEPS) $(ZLIB) $(JSONLIB) -lssl
fakewww_CPPFLAGS ='-DBASEDIR="$(ndtdir)"'
endif
web100srv_SOURCES = web100srv.c web100-util.c web100-pcap.c web100-admin.c runningtest.c \
network.c usage.c utils.c mrange.c logging.c testoptions.c ndtptestconstants.c \
protocol.c test_sfw_srv.c test_meta_srv.c ndt_odbc.c strlutils.c heuristics.c \
- test_c2s_srv.c test_s2c_srv.c test_mid_srv.c jsonutils.c
+ test_c2s_srv.c test_s2c_srv.c test_mid_srv.c jsonutils.c websocket.c
web100srv_LDFLAGS = $(NDTLDFLAGS) $(I2UTILLDFLAGS)
-web100srv_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB)
+web100srv_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
web100srv_CPPFLAGS ='-DBASEDIR="$(ndtdir)"' -DFORCE_WEB100
web100srv_DEPENDENCIES = $(I2UTILLIBDEPS)
web100srv_unit_tests_SOURCES = web100srv_unit_tests.c unit_testing.c
web100srv_unit_tests_LDFLAGS = $(NDTLDFLAGS) $(I2UTILLDFLAGS)
-web100srv_unit_tests_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB)
-web100srv_unit_tests_CPPFLAGS ='-DBASEDIR="$(ndtdir)"' -DFORCE_WEB100
+web100srv_unit_tests_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
+web100srv_unit_tests_CPPFLAGS ='-DBASEDIR="$(ndtdir)"' -DFORCE_WEB100 -Wall -Werror -Wno-unused-variable -Wno-unused-function
web100srv_unit_tests_DEPENDENCIES = $(I2UTILLIBDEPS)
+websocket_unit_tests_SOURCES = unit_testing.c websocket_unit_tests.c websocket.c \
+ network.c logging.c strlutils.c jsonutils.c ndtptestconstants.c runningtest.c
+websocket_unit_tests_LDFLAGS = $(NDTLDFLAGS) $(I2UTILLDFLAGS)
+websocket_unit_tests_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
+websocket_unit_tests_CPPFLAGS ='-DBASEDIR="$(ndtdir)"' -DFORCE_WEB100 -Wall -Werror -Wno-unused-variable -Wno-unused-function
+websocket_unit_tests_DEPENDENCIES = $(I2UTILLIBDEPS)
+
+testoptions_unit_tests_SOURCES = testoptions_unit_tests.c testoptions.c unit_testing.c \
+ heuristics.c jsonutils.c logging.c mrange.c ndt_odbc.c ndtptestconstants.c \
+ network.c protocol.c runningtest.c strlutils.c test_c2s_srv.c test_meta_srv.c \
+ test_mid_srv.c test_s2c_srv.c test_sfw_srv.c utils.c web100-pcap.c web100-util.c \
+ web100srv.c websocket.c
+testoptions_unit_tests_LDFLAGS = $(NDTLDFLAGS) $(I2UTILLDFLAGS)
+testoptions_unit_tests_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
+testoptions_unit_tests_CPPFLAGS ='-DBASEDIR="$(ndtdir)"' -DFORCE_WEB100 -DUSE_WEB100SRV_ONLY_AS_LIBRARY -Wall -Werror -Wno-unused-variable -Wno-unused-function
+
+testoptions_unit_tests_DEPENDENCIES = $(I2UTILLIBDEPS)
+
web10gsrv_SOURCES = web100srv.c web100-util.c web100-pcap.c web100-admin.c runningtest.c \
network.c usage.c utils.c mrange.c logging.c testoptions.c ndtptestconstants.c \
protocol.c test_sfw_srv.c test_meta_srv.c ndt_odbc.c strlutils.c heuristics.c \
- test_c2s_srv.c test_s2c_srv.c test_mid_srv.c web10g-util.c jsonutils.c
+ test_c2s_srv.c test_s2c_srv.c test_mid_srv.c web10g-util.c jsonutils.c websocket.c
web10gsrv_LDFLAGS = $(NDTLDFLAGS) $(I2UTILLDFLAGS)
-web10gsrv_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB)
+web10gsrv_LDADD = $(NDTLIBS) $(I2UTILLIBS) $(I2UTILLIBDEPS) -lpthread $(ZLIB) $(JSONLIB) -lssl
web10gsrv_CPPFLAGS = '-DBASEDIR="$(ndtdir)"'
web10gsrv_DEPENDENCIES = $(I2UTILLIBDEPS)
=======================================
--- /branches/websockets/src/jsonutils.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/jsonutils.c Mon Mar 23 22:59:30 2015 UTC
@@ -7,9 +7,11 @@
*
*/
+#include <ctype.h>
#include <jansson.h>
#include <string.h>
#include "jsonutils.h"
+#include "logging.h"
/**
* Creates string representing JSON object with single key:value pair
@@ -141,7 +143,7 @@
data = json_object_get(root, key);
if(data != NULL)
{
- char *value = json_string_value(data);
+ char *value = (char *)json_string_value(data);
return value;
}
=======================================
--- /branches/websockets/src/network.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/network.c Mon Mar 23 22:59:30 2015 UTC
@@ -12,8 +12,9 @@
#include <unistd.h>
#include "jsonutils.h"
+#include "logging.h"
#include "network.h"
-#include "logging.h"
+#include "websocket.h"
/**
* Create and bind socket.
@@ -374,8 +375,9 @@
* @param type type of the message
* @param msg message to send
* @param len length of the message
- * @param jsonSupport indicates if JSON format is supported by second side (if not
- * then msg is being sent as it is and
no JSON converting is done)
+ * @param connectionFlags indicates if JSON format is supported by the other side (connectionFlags & JSON_SUPPORT)
+ * and if websockets are supported by the other side (connectionFlags & WEBSOCKET_SUPPORT)
+ * It is expected, but not required, that WEBSOCKET_SUPPORT will always include JSON_SUPPORT.
* @param jsonConvertType defines how message converting should be handled:
* JSON_SINGLE_VALUE: single key/value pair is being created (using default key)
* with msg as value
@@ -403,14 +405,19 @@
* -4 - Cannot convert msg to JSON
*
*/
-int send_json_msg(int ctlSocket, int type, const char* msg, int len, int jsonSupport,
- int jsonConvertType, const char *keys, const char
*keysDelimiters,
- const char *values, char *valuesDelimiters) {
+int send_json_msg(int ctlSocket, int type, const char* msg, int len,
+ int connectionFlags, int jsonConvertType,
+ const char *keys, const char *keysDelimiters,
+ const char *values, char *valuesDelimiters) {
char* tempBuff;
int ret = 0;
// if JSON is not supported by second side, sends msg as it is
- if (!jsonSupport) {
- return send_msg(ctlSocket, type, msg, len);
+ if (!(connectionFlags & JSON_SUPPORT)) {
+ if (connectionFlags & WEBSOCKET_SUPPORT) {
+ return send_websocket_msg(ctlSocket, type, msg, len);
+ } else {
+ return send_msg(ctlSocket, type, msg, len);
+ }
}
switch(jsonConvertType) {
@@ -422,24 +429,32 @@
case JSON_KEY_VALUE_PAIRS:
tempBuff = json_create_from_key_value_pairs(msg); break;
default:
- return send_msg(ctlSocket, type, msg, len);
+ if (connectionFlags & WEBSOCKET_SUPPORT) {
+ return send_websocket_msg(ctlSocket, type, msg, len);
+ } else {
+ return send_msg(ctlSocket, type, msg, len);
+ }
}
if (!tempBuff) {
return -4;
}
- ret = send_msg(ctlSocket, type, tempBuff, strlen(tempBuff));
+ if (connectionFlags & WEBSOCKET_SUPPORT) {
+ ret = send_websocket_msg(ctlSocket, type, tempBuff, strlen(tempBuff));
+ } else {
+ ret = send_msg(ctlSocket, type, tempBuff, strlen(tempBuff));
+ }
free(tempBuff);
return ret;
}
/**
- * Shortest version of send_json_msg method. Uses default NULL values for JSON_MULTIPLE_VALUES
- * convert type specific parameters.
+ * Shortest version of send_json_msg method. Uses default NULL values for
+ * JSON_MULTIPLE_VALUES convert type specific parameters.
*/
-int send_json_message(int ctlSocket, int type, const char* msg, int len, int jsonSupport,
- int jsonConvertType) {
- return send_json_msg(ctlSocket, type, msg, len, jsonSupport, jsonConvertType,
+int send_json_message(int ctlSocket, int type, const char* msg, int len,
+ int connectionFlags, int jsonConvertType) {
+ return send_json_msg(ctlSocket, type, msg, len, connectionFlags, jsonConvertType,
NULL, NULL, NULL, NULL);
}
@@ -455,8 +470,6 @@
* -2 - Cannot complete writing full message data into socket
* -3 - Cannot write after retries
*/
-
-
int send_msg(int ctlSocket, int type, const void* msg, int len) {
unsigned char buff[3];
int rc, i;
@@ -511,20 +524,18 @@
/**
* Receive the protocol message from the control socket.
* @param ctlSocket control socket
- * @param typetarget place for type of the message
+ * @param type target place for type of the message
* @param msg target place for the message body
- * @param len target place for the length of the message
+ * @param len target place for the length of the message
* @returns 0 on success, error code otherwise.
* Error codes:
* -1 : Error reading from socket
* -2 : No of bytes received were lesser than expected byte count
* -3 : No of bytes received did not match expected byte count
*/
-
int recv_msg(int ctlSocket, int* type, void* msg, int* len) {
unsigned char buff[3];
int length;
-
char *msgtemp = (char*) msg;
assert(type);
@@ -557,6 +568,15 @@
return 0;
}
+
+int recv_any_msg(int ctlSocket, int* type, void* msg, int* len,
+ int connectionFlags) {
+ if (connectionFlags & WEBSOCKET_SUPPORT) {
+ return recv_websocket_ndt_msg(ctlSocket, type, msg, len);
+ } else {
+ return recv_msg(ctlSocket, type, msg, len);
+ }
+}
/**
* Write the given amount of data to the file descriptor.
@@ -599,8 +619,9 @@
* @return The amount of bytes read from the file descriptor
*/
-int readn(int fd, void* buf, int amount) {
- int received = 0, n, rc;
+size_t readn(int fd, void* buf, size_t amount) {
+ size_t received = 0;
+ int n, rc;
char* ptr = buf;
struct timeval sel_tv;
fd_set rfd;
=======================================
--- /branches/websockets/src/network.h Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/network.h Mon Mar 23 22:59:30 2015 UTC
@@ -10,6 +10,7 @@
#define SRC_NETWORK_H_
#include <I2util/util.h>
+#include "testoptions.h"
#define NDT_BACKLOG 5
#define BUFFSIZE 8192
@@ -20,15 +21,17 @@
I2Addr CreateListenSocket(I2Addr addr, char* serv, int options, int buf_size);
int CreateConnectSocket(int* sockfd, I2Addr local_addr, I2Addr server_addr,
int option, int buf_sizes);
-int send_json_msg(int ctlSocket, int type, const char* msg, int len, int jsonSupport,
- int jsonConvertType, const char *keys, const char *keysDelimiters,
+int send_json_msg(int ctlSocket, int type, const char* msg, int len,
+ int connectionFlags, int jsonConvertType,
+ const char *keys, const char *keysDelimiters,
const char *values, char *valuesDelimiters);
-int send_json_message(int ctlSocket, int type, const char* msg, int len, int jsonSupport,
- int jsonConvertType);
+int send_json_message(int ctlSocket, int type, const char* msg, int len,
+ int connectionFlags, int jsonConvertType);
int send_msg(int ctlSocket, int type, const void* msg, int len);
int recv_msg(int ctlSocket, int* type, void* msg, int* len);
+int recv_any_msg(int ctlSocket, int* type, void* msg, int* len, int connectionFlags);
int writen(int fd, const void* buf, int amount);
-int readn(int fd, void* buf, int amount);
+size_t readn(int fd, void* buf, size_t amount);
/* web100-util.c routine used in network. */
int KillHung(void);
=======================================
--- /branches/websockets/src/test_c2s_srv.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/test_c2s_srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -21,6 +21,7 @@
#include "network.h"
#include "mrange.h"
#include "jsonutils.h"
+#include "websocket.h"
/**
* Perform the C2S Throughput test. This test intends to measure throughput
@@ -151,7 +152,7 @@
sizeof(buff),
"Server (C2S throughput test): CreateListenSocket failed: %s",
strerror(errno));
- send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff), testOptions->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff), testOptions->connection_flags, JSON_SINGLE_VALUE);
return -1;
}
@@ -177,7 +178,7 @@
// send TEST_PREPARE message with ephemeral port detail, indicating start
// of tests
if ((msgretvalue = send_json_message(ctlsockfd, TEST_PREPARE, buff,
- strlen(buff), testOptions->json_support, JSON_SINGLE_VALUE)) < 0) {
+ strlen(buff), testOptions->connection_flags, JSON_SINGLE_VALUE)) < 0) {
return msgretvalue;
}
@@ -218,6 +219,14 @@
proctypeenum = CONNECT_TYPE;
protolog_procstatus(testOptions->child0, testids, proctypeenum,
procstatusenum, recvsfd);
+ // To preserve user privacy, make sure that the HTTP header
+ // processing is done prior to the start of packet capture, as many
+ // browsers have headers that uniquely identitfy a single user.
+ if (testOptions->connection_flags & WEBSOCKET_SUPPORT) {
+ if (initialize_websocket_connection(recvsfd, 0, "c2s") != 0) {
+ recvsfd = 0;
+ }
+ }
break;
}
// socket interrupted, wait some more
@@ -318,7 +327,7 @@
sleep(2);
// send empty TEST_START indicating start of the test
- send_json_message(ctlsockfd, TEST_START, "", 0, testOptions->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_START, "", 0, testOptions->connection_flags, JSON_SINGLE_VALUE);
/* alarm(30); */ // reset alarm() again, this 10 sec test should finish
// before this signal is generated.
@@ -374,7 +383,7 @@
testOptions->child0);
log_println(1, "%s", buff);
snprintf(buff, sizeof(buff), "%0.0f", *c2sspd);
- send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->connection_flags, JSON_SINGLE_VALUE);
// get receiver side Web100 stats and write them to the log file. close
// sockets
@@ -441,7 +450,7 @@
}
// An empty TEST_FINALIZE message is sent to conclude the test
- send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, testOptions->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, testOptions->connection_flags, JSON_SINGLE_VALUE);
// Close opened resources for packet capture
if (getuid() == 0) {
=======================================
--- /branches/websockets/src/test_meta_srv.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/test_meta_srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -19,6 +19,8 @@
#include "utils.h"
#include "testoptions.h"
#include "jsonutils.h"
+#include "strlutils.h"
+#include "websocket.h"
/**
* Performs the META test.
@@ -42,6 +44,7 @@
int test_meta_srv(int ctlsockfd, tcp_stat_agent* agent,
TestOptions* testOptions, int conn_options) {
int j;
+ int64_t err;
int msgLen, msgType;
char buff[BUFFSIZE + 1];
struct metaentry *new_entry = NULL;
@@ -61,7 +64,7 @@
// first message exchanged is am empty TEST_PREPARE message
j = send_json_message(ctlsockfd, TEST_PREPARE, "", 0,
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
if (j == -1 || j == -2) { // Cannot write message headers/data
log_println(6, "META Error!, Test start message not sent!");
return j;
@@ -69,7 +72,7 @@
// Now, transmit an empty TEST_START message
if (send_json_message(ctlsockfd, TEST_START, "", 0,
- testOptions->json_support, JSON_SINGLE_VALUE) < 0) {
+ testOptions->connection_flags, JSON_SINGLE_VALUE) < 0) {
log_println(6, "META test - Test-start message failed");
}
@@ -77,13 +80,18 @@
msgLen = sizeof(buff);
// Now read the meta data sent by client.
- if (recv_msg(ctlsockfd, &msgType, buff, &msgLen)) {
+ if (testOptions->connection_flags & WEBSOCKET_SUPPORT) {
+ err = recv_websocket_ndt_msg(ctlsockfd, &msgType, buff, &msgLen);
+ } else {
+ err = recv_msg(ctlsockfd, &msgType, buff, &msgLen);
+ }
+ if (err) {
// message reading error
log_println(0, "Protocol error!");
snprintf(buff, sizeof(buff),
"Server (META test): Invalid meta data received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return 1;
}
if (check_msg_type("META test", TEST_MSG, msgType, buff, msgLen)) {
@@ -92,7 +100,7 @@
snprintf(buff, sizeof(buff),
"Server (META test): Invalid meta data received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return 2;
}
if (msgLen < 0) {
@@ -101,12 +109,12 @@
snprintf(buff, sizeof(buff),
"Server (META test): Invalid meta data received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return 3;
}
buff[msgLen] = 0;
- if (testOptions->json_support) {
+ if (testOptions->connection_flags & JSON_SUPPORT) {
jsonMsgValue = json_read_map_value(buff, DEFAULT_KEY);
if (strlen(jsonMsgValue) == 0) {
free(jsonMsgValue);
@@ -128,7 +136,7 @@
snprintf(buff, sizeof(buff), "Server (META test): "
"Invalid meta data received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return 4;
}
*value = 0;
@@ -171,7 +179,7 @@
// Finalize test by sending appropriate message, and setting status
if (send_json_message(ctlsockfd, TEST_FINALIZE, "", 0,
- testOptions->json_support, JSON_SINGLE_VALUE) < 0) {
+ testOptions->connection_flags, JSON_SINGLE_VALUE) < 0) {
log_println(6, "META test - failed to send finalize message");
}
=======================================
--- /branches/websockets/src/test_mid_srv.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/test_mid_srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -157,7 +157,7 @@
"Server (Middlebox test): CreateListenSocket failed: %s",
strerror(errno));
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return -1;
}
@@ -169,7 +169,7 @@
// send this port number to client
snprintf(buff, sizeof(buff), "%d", options->midsockport);
if ((msgretvalue = send_json_message(ctlsockfd, TEST_PREPARE, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE))
+ options->connection_flags, JSON_SINGLE_VALUE))
< 0)
return msgretvalue;
@@ -254,7 +254,7 @@
tcp_stat_middlebox(midsfd, agent, conn, results_keys, sizeof(results_keys), buff, sizeof(buff));
// Transmit results in the form of a TEST_MSG message
- send_json_msg(ctlsockfd, TEST_MSG, buff, strlen(buff), options->json_support, JSON_MULTIPLE_VALUES,
+ send_json_msg(ctlsockfd, TEST_MSG, buff, strlen(buff), options->connection_flags, JSON_MULTIPLE_VALUES,
results_keys, ";", buff, ";");
// Expect client to send throughput as calculated at its end
@@ -267,7 +267,7 @@
sizeof(buff),
"Server (Middlebox test): Invalid CWND limited throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return 1;
}
if (check_msg_type("Middlebox test", TEST_MSG, msgType, buff,
@@ -277,11 +277,11 @@
sizeof(buff),
"Server (Middlebox test): Invalid CWND limited throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return 2;
}
buff[msgLen] = 0;
- if (options->json_support) {
+ if (options->connection_flags & JSON_SUPPORT) {
jsonMsgValue = json_read_map_value(buff, DEFAULT_KEY);
strlcpy(buff, jsonMsgValue, sizeof(buff));
msgLen = strlen(buff);
@@ -294,7 +294,7 @@
sizeof(buff),
"Server (Middlebox test): Invalid CWND limited throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return 3;
}
@@ -308,7 +308,7 @@
shutdown(midsfd, SHUT_WR);
close(midsfd);
close(options->midsockfd);
- send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, options->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, options->connection_flags, JSON_SINGLE_VALUE);
log_println(1, " <--------- %d ----------->", options->child0);
// log end of test into protocol doc, just to delimit.
=======================================
--- /branches/websockets/src/test_s2c_srv.c Wed Jun 18 05:44:40 2014 UTC
+++ /branches/websockets/src/test_s2c_srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -22,6 +22,7 @@
#include "network.h"
#include "mrange.h"
#include "jsonutils.h"
+#include "websocket.h"
extern pthread_mutex_t mainmutex;
extern pthread_cond_t maincond;
@@ -200,7 +201,7 @@
"Server (S2C throughput test): CreateListenSocket failed: %s",
strerror(errno));
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return -1;
}
@@ -217,7 +218,7 @@
// number
snprintf(buff, sizeof(buff), "%d", testOptions->s2csockport);
j = send_json_message(ctlsockfd, TEST_PREPARE, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
if (j == -1) {
log_println(6, "S2C %d Error!, Test start message not sent!",
testOptions->child0);
@@ -265,6 +266,14 @@
proctypeenum = CONNECT_TYPE;
protolog_procstatus(testOptions->child0, testids, proctypeenum,
procstatusenum, xmitsfd);
+ if (testOptions->connection_flags & WEBSOCKET_SUPPORT) {
+ // To preserve user privacy, make sure that the HTTP header
+ // processing is done prior to the start of packet capture, as many
+ // browsers have headers that uniquely identitfy a single user.
+ if (initialize_websocket_connection(xmitsfd, 0, "s2c") != 0) {
+ xmitsfd = 0;
+ }
+ }
break;
}
// socket interrupted, wait some more
@@ -374,9 +383,32 @@
k++;
buff[j] = (k++ & 0x7f);
}
+ if (testOptions->connection_flags & WEBSOCKET_SUPPORT) {
+ // Make sure the data has a websocket header
+ ((unsigned char*)buff)[0] = 0x82; // One frame of binary data
+ // Depending on BUFFSIZE, the websocket header will be 2, 4, or 10
+ // bytes big. This header is constructed to comply with RFC 6455.
+ if (BUFFSIZE < 126) {
+ buff[1] = (BUFFSIZE-2) & 0x7F;
+ } else if (BUFFSIZE < 65536) {
+ buff[1] = 126;
+ ((unsigned char*)buff)[2] = ((BUFFSIZE - 4) >> 8) & 0xFF;
+ ((unsigned char*)buff)[3] = (BUFFSIZE - 4) & 0xFF;
+ } else {
+ buff[1] = 127;
+ ((unsigned char*)buff)[2] = (((long long)BUFFSIZE - 10) >> 56) & 0xFF;
+ ((unsigned char*)buff)[3] = (((long long)BUFFSIZE - 10) >> 48) & 0xFF;
+ ((unsigned char*)buff)[4] = (((long long)BUFFSIZE - 10) >> 40) & 0xFF;
+ ((unsigned char*)buff)[5] = (((long long)BUFFSIZE - 10) >> 32) & 0xFF;
+ ((unsigned char*)buff)[6] = (((long long)BUFFSIZE - 10) >> 24) & 0xFF;
+ ((unsigned char*)buff)[7] = (((long long)BUFFSIZE - 10) >> 16) & 0xFF;
+ ((unsigned char*)buff)[8] = (((long long)BUFFSIZE - 10) >> 8) & 0xFF;
+ ((unsigned char*)buff)[9] = (BUFFSIZE - 10) & 0xFF;
+ }
+ }
// Send message to client indicating TEST_START
- if (send_json_message(ctlsockfd, TEST_START, "", 0, testOptions->json_support,
+ if (send_json_message(ctlsockfd, TEST_START, "", 0, testOptions->connection_flags,
JSON_SINGLE_VALUE) < 0)
log_println(6,
"S2C test - Test-start message failed for pid=%d",
@@ -488,8 +520,8 @@
// Send throughput, unsent byte count, total sent byte count to client
snprintf(buff, sizeof(buff), "%0.0f %d %0.0f", x2cspd, sndqueue,
bytes_written);
- if (testOptions->json_support) {
- if (send_json_msg(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->json_support,
+ if (testOptions->connection_flags & JSON_SUPPORT) {
+ if (send_json_msg(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->connection_flags,
JSON_MULTIPLE_VALUES, RESULTS_KEYS, " ", buff, " ") < 0)
log_println(6,
"S2C test - failed to send test message to pid=%d",
@@ -497,7 +529,7 @@
}
else {
if (send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE) < 0)
+ testOptions->connection_flags, JSON_SINGLE_VALUE) < 0)
log_println(6,
"S2C test - failed to send test message to pid=%d",
s2c_childpid);
@@ -594,13 +626,13 @@
#if USE_WEB100
// send web100 data to client
- ret = tcp_stat_get_data(tsnap, xmitsfd, ctlsockfd, agent, count_vars, testOptions->json_support);
+ ret = tcp_stat_get_data(tsnap, xmitsfd, ctlsockfd, agent, count_vars, testOptions);
web100_snapshot_free(tsnap);
// send tuning-related web100 data collected to client
- ret = tcp_stat_get_data(rsnap, xmitsfd, ctlsockfd, agent, count_vars, testOptions->json_support);
+ ret = tcp_stat_get_data(rsnap, xmitsfd, ctlsockfd, agent, count_vars, testOptions);
web100_snapshot_free(rsnap);
#elif USE_WEB10G
- ret = tcp_stat_get_data(snap, xmitsfd, ctlsockfd, agent, count_vars, testOptions->json_support);
+ ret = tcp_stat_get_data(snap, xmitsfd, ctlsockfd, agent, count_vars, testOptions);
estats_val_data_free(&snap);
#endif
@@ -609,7 +641,7 @@
log_println(6, "S2C - No web100 data received for pid=%d",
s2c_childpid);
snprintf(buff, sizeof(buff), "No Data Collected: 000000");
- send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->json_support,
+ send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff), testOptions->connection_flags,
JSON_SINGLE_VALUE);
}
@@ -617,14 +649,14 @@
// value
log_println(6, "S2CSPD reception starts");
msgLen = sizeof(buff);
- if (recv_msg(ctlsockfd, &msgType, buff, &msgLen)) {
+ if (recv_any_msg(ctlsockfd, &msgType, buff, &msgLen, testOptions->connection_flags)) {
log_println(0, "Protocol error!");
snprintf(
buff,
sizeof(buff),
"Server (S2C throughput test): Invalid S2C throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return -1;
}
if (check_msg_type("S2C throughput test", TEST_MSG, msgType, buff,
@@ -634,11 +666,11 @@
sizeof(buff),
"Server (S2C throughput test): Invalid S2C throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return -2;
}
buff[msgLen] = 0;
- if (testOptions->json_support) {
+ if (testOptions->connection_flags & JSON_SUPPORT) {
jsonMsgValue = json_read_map_value(buff, DEFAULT_KEY);
strlcpy(buff, jsonMsgValue, sizeof(buff));
msgLen = strlen(buff);
@@ -651,7 +683,7 @@
sizeof(buff),
"Server (S2C throughput test): Invalid S2C throughput received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- testOptions->json_support, JSON_SINGLE_VALUE);
+ testOptions->connection_flags, JSON_SINGLE_VALUE);
return -3;
}
*s2cspd = atoi(buff); // save Throughput value as seen by client
@@ -660,7 +692,7 @@
// send finalise message to client
close(xmitsfd);
if (send_json_message(ctlsockfd, TEST_FINALIZE, "", 0,
- testOptions->json_support, JSON_SINGLE_VALUE) < 0)
+ testOptions->connection_flags, JSON_SINGLE_VALUE) < 0)
log_println(6,
"S2C test - failed to send finalize message to pid=%d",
s2c_childpid);
=======================================
--- /branches/websockets/src/test_sfw_srv.c Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/test_sfw_srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -55,7 +55,7 @@
test_osfw_srv(void* vptr) {
int sfwsock;
struct sigaction new, old;
- int jsonSupport = *((int*) vptr);
+ TestOptions* options = (TestOptions*)vptr;
// ignore the alarm signal
memset(&new, 0, sizeof(new));
@@ -66,7 +66,7 @@
// connect to client and send TEST_MSG message containing a pre-defined string
if (CreateConnectSocket(&sfwsock, NULL, sfwcli_addr, 0, 0) == 0) {
send_json_message(sfwsock, TEST_MSG, "Simple firewall test", 20,
- jsonSupport, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
}
alarm(0);
@@ -85,10 +85,10 @@
* Wait for the every thread to conclude and finalize
* the SFW test.
* @param ctlsockfd Client control socket descriptor
- * @param jsonSupport Indicates if messages should be sent using JSON format
+ * @param options the options regarding how to talk to the client.
*/
-void finalize_sfw(int ctlsockfd, int jsonSupport) {
+void finalize_sfw(int ctlsockfd, TestOptions* options) {
enum TEST_ID thistestId = SFW;
enum TEST_STATUS_INT teststatusnow = NONE;
// wait for mutex to be released before attempting to finalize
@@ -99,7 +99,7 @@
}
// close the SFW test by sending a nil (0 length) message
- send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, jsonSupport, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_FINALIZE, "", 0, options->connection_flags, JSON_SINGLE_VALUE);
// log
teststatusnow = TEST_ENDED;
@@ -172,7 +172,7 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"CreateListenSocket failed: %s", strerror(errno));
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return -1;
}
@@ -226,7 +226,7 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"Cannot find connection");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
I2AddrFree(sfwsrv_addr);
return -1;
}
@@ -234,7 +234,7 @@
// try sending TEST_PREPARE msg with ephemeral port number to client.
// If unable to, return
- if (options->json_support) {
+ if (options->connection_flags & JSON_SUPPORT) {
snprintf(buff, sizeof(buff), "%s: %d\n%s: %d", EMPHERAL_PORT_NUMBER,sfwsockport,
TEST_TIME, testTime);
}
@@ -243,7 +243,7 @@
}
if ((rc = send_json_message(ctlsockfd, TEST_PREPARE, buff, strlen(buff),
- options->json_support, JSON_KEY_VALUE_PAIRS)) < 0)
+ options->connection_flags, JSON_KEY_VALUE_PAIRS)) < 0)
return (rc);
// Listen for TEST_MSG from client's port number sent as data.
@@ -255,7 +255,7 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"Invalid port number received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
I2AddrFree(sfwsrv_addr);
return 1;
}
@@ -265,12 +265,12 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"Invalid port number received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
I2AddrFree(sfwsrv_addr);
return 2;
}
buff[msgLen] = 0;
- if (options->json_support) {
+ if (options->connection_flags & JSON_SUPPORT) {
jsonMsgValue = json_read_map_value(buff, DEFAULT_KEY);
strlcpy(buff, jsonMsgValue, sizeof(buff));
msgLen = strlen(buff);
@@ -281,7 +281,7 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"Invalid port number received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
I2AddrFree(sfwsrv_addr);
return 3;
}
@@ -294,7 +294,7 @@
snprintf(buff, sizeof(buff), "Server (Simple firewall test): "
"Invalid port number received");
send_json_message(ctlsockfd, MSG_ERROR, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
I2AddrFree(sfwsrv_addr);
return 4;
}
@@ -306,7 +306,7 @@
// todo, this is the client address we cannot resolve?
log_println(0, "Unable to resolve server address");
send_json_message(ctlsockfd, TEST_FINALIZE, "", 0,
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
// log end
teststatusnow = TEST_ENDED;
@@ -319,10 +319,10 @@
log_println(1, " -- oport: %d", sfwport);
// send S->C side TEST_MSG
- send_json_message(ctlsockfd, TEST_START, "", 0, options->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, TEST_START, "", 0, options->connection_flags, JSON_SINGLE_VALUE);
// send the S->C default test message in a separate thread to client
- pthread_create(&threadId, NULL, &test_osfw_srv, &options->json_support);
+ pthread_create(&threadId, NULL, &test_osfw_srv, options);
FD_ZERO(&fds);
FD_SET(sfwsockfd, &fds);
@@ -338,8 +338,8 @@
log_println(0, "Simple firewall test: select exited with error");
snprintf(buff, sizeof(buff), "%d", SFW_UNKNOWN);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
- finalize_sfw(ctlsockfd, options->json_support);
+ options->connection_flags, JSON_SINGLE_VALUE);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 1;
@@ -348,8 +348,8 @@
testTime);
snprintf(buff, sizeof(buff), "%d", SFW_POSSIBLE);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
- finalize_sfw(ctlsockfd, options->json_support);
+ options->connection_flags, JSON_SINGLE_VALUE);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 2;
@@ -371,9 +371,9 @@
log_println(0, "Simple firewall test: unrecognized message");
snprintf(buff, sizeof(buff), "%d", SFW_UNKNOWN);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
close(sockfd);
- finalize_sfw(ctlsockfd, options->json_support);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 1;
@@ -383,15 +383,15 @@
// unexpected message type received
snprintf(buff, sizeof(buff), "%d", SFW_UNKNOWN);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
close(sockfd);
- finalize_sfw(ctlsockfd, options->json_support);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 1;
}
buff[msgLen] = 0;
- if (options->json_support) {
+ if (options->connection_flags & JSON_SUPPORT) {
jsonMsgValue = json_read_map_value(buff, DEFAULT_KEY);
strlcpy(buff, jsonMsgValue, sizeof(buff));
msgLen = strlen(buff);
@@ -402,9 +402,9 @@
log_println(0, "Simple firewall test: Improper message");
snprintf(buff, sizeof(buff), "%d", SFW_UNKNOWN);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
close(sockfd);
- finalize_sfw(ctlsockfd, options->json_support);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 1;
@@ -414,9 +414,9 @@
log_println(0, "Simple firewall test: Improper message");
snprintf(buff, sizeof(buff), "%d", SFW_UNKNOWN);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
close(sockfd);
- finalize_sfw(ctlsockfd, options->json_support);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
return 1;
@@ -425,9 +425,9 @@
// All messages were received correctly, hence no firewall
snprintf(buff, sizeof(buff), "%d", SFW_NOFIREWALL);
send_json_message(ctlsockfd, TEST_MSG, buff, strlen(buff),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
close(sockfd);
- finalize_sfw(ctlsockfd, options->json_support);
+ finalize_sfw(ctlsockfd, options);
I2AddrFree(sfwsrv_addr);
I2AddrFree(sfwcli_addr);
}
=======================================
--- /branches/websockets/src/testoptions.c Wed Feb 18 16:54:51 2015 UTC
+++ /branches/websockets/src/testoptions.c Mon Mar 23 22:59:30 2015 UTC
@@ -20,6 +20,7 @@
#include "runningtest.h"
#include "strlutils.h"
#include "jsonutils.h"
+#include "websocket.h"
// Worker thread characteristics used to record snaplog and Cwnd peaks
@@ -190,46 +191,103 @@
}
}
+#define KICK_SUCCESS 1
+#define KICK_FAILURE -1
+/**
+ * Kick off old clients. Send the special code which causes old clients to
+ * disconnect.
+ * @param ctlsockfd Control socket file descriptor.
+ */
+int kick_off_old_clients(int ctlsockfd) {
+ int retcode;
+ int i;
+ for (i = 0; i < RETRY_COUNT; i++) {
+ // the specially crafted data that kicks off the old clients
+ retcode = write(ctlsockfd, "123456 654321", 13);
+ if ((retcode == -1) && (errno == EINTR)) // interrupted, retry
+ continue;
+ if (retcode == 13) // 13 bytes correctly written, exit successfully
+ return KICK_SUCCESS;
+ if (retcode == -1) { // socket error hindering further retries
+ break;
+ }
+ }
+ log_println(1, "Initial contact with client failed errno=%d", errno);
+ return KICK_FAILURE;
+}
+
+/**
+ * Receive an initialization message, but the message may not be an NDT
+ * message, and instead it may be the beginning of the websocket handshake. In
+ * that case, set up the websocket handshake and then receive the message.
+ */
+int recv_msg_plus_websocket(int ctlsockfd, TestOptions* test_options,
+ int* msg_type, char* msg_value, int* msg_len) {
+ char header[3] = {0};
+ int64_t err;
+ int received_length;
+ if (readn(ctlsockfd, header, sizeof(header)) != sizeof(header)) {
+ return EIO;
+ }
+ if (strncmp(header, "GET", 3) == 0) {
+ // GET starts HTTP stuff, so try and perform the websocket handshake
+ err = initialize_websocket_connection(ctlsockfd, sizeof(header), "ndt");
+ if (err) return err;
+ test_options->connection_flags |= WEBSOCKET_SUPPORT;
+ err = recv_websocket_ndt_msg(ctlsockfd, msg_type, msg_value, msg_len);
+ return (err < 0) ? -err : 0;
+ } else {
+ // It didn't start with GET so it's not a websocket connection
+ test_options->connection_flags &= ~WEBSOCKET_SUPPORT;
+ *msg_type = header[0];
+ received_length = (header[1] << 8) + header[2];
+ if (received_length > *msg_len) return EMSGSIZE;
+ *msg_len = received_length;
+ if (readn(ctlsockfd, msg_value, *msg_len) != *msg_len) return EIO;
+ return 0;
+ }
+}
+
/**
* Initialize the tests for the client.
* @param ctlsockfd Client control socket descriptor
* @param options Test options
* @param buff Connection options
- * @return integer 0 on success,
- * >0 - error code.
+ * @return integer the test codes OR'd together on success
+ * 0 or less is an error.
* Error codes:
- * -1 message reading in error
+ * -1 message reading in error
* -2 Invalid test request
* -3 Invalid test suite request
* -4 client timed out
*
*/
-
int initialize_tests(int ctlsockfd, TestOptions* options, char * buff,
size_t buff_strlen) {
- unsigned char msgValue[CS_VERSION_LENGTH_MAX + 1] = {'\0', };
+ char msgValue[CS_VERSION_LENGTH_MAX + 1] = {'\0'};
unsigned char useropt = 0;
int msgType;
int msgLen = CS_VERSION_LENGTH_MAX + 1;
int first = 1;
char *invalid_test_suite = "Invalid test suite request.";
- char *client_timeout = "Client timeout.";
char *invalid_test = "Invalid test request.";
char *invalid_login_msg = "Invalid login message.";
char* jsonMsgValue;
// char remhostarr[256], protologlocalarr[256];
- // char *remhost_ptr = get_remotehostaddress();
+ // char *remhost_ptr = get_remotehost();
assert(ctlsockfd != -1);
assert(options);
memset(options->client_version, 0, sizeof(options->client_version));
- // read the test suite request
- if (recv_msg(ctlsockfd, &msgType, msgValue, &msgLen)) {
- send_msg(ctlsockfd, MSG_ERROR, invalid_test_suite,
- strlen(invalid_test_suite));
+ // read the test suite request, if we get what looks like a websocket HTTP
+ // request then set up the websocket, and then read the test suite request.
+ if (recv_msg_plus_websocket(ctlsockfd, options, &msgType, msgValue,
+ &msgLen)) {
+ send_msg(ctlsockfd, MSG_ERROR, invalid_test_suite,
+ strlen(invalid_test_suite));
return (-1);
}
if (msgLen == -1) {
@@ -237,29 +295,34 @@
return (-4);
}
+ if (!(options->connection_flags & WEBSOCKET_SUPPORT)) {
+ if (kick_off_old_clients(ctlsockfd) != KICK_SUCCESS) return -1;
+ }
/*
- * Expecting a MSG_LOGIN or MSG_EXTENDED_LOGIN with
- * payload byte indicating tests to be run and potentially
- * a US-ASCII string indicating the version number.
+ * Expecting a MSG_LOGIN or MSG_EXTENDED_LOGIN with payload byte indicating
+ * tests to be run and potentially a US-ASCII string indicating the version
+ * number.
* Three cases:
* 1: MSG_LOGIN: Check that msgLen is 1
- * 2: MSG_EXTENDED_LOGIN: Check that msgLen is >= 1 and
- * <= the maximum length of the client/server version
- * string (plus 1 to account for the initial useropt
- * and then copy the client version into client_version.
+ * 2: MSG_EXTENDED_LOGIN: Check that msgLen is >= 1 and <= the maximum
+ * length of the client/server version string (plus 1 to account for the
+ * initial useropt and then copy the client version into client_version.
* 3: Neither
- *
- * In case (1) or (2) we copy the 0th byte from the msgValue
- * into useropt so we'll do that in the fallthrough.
+ *
+ * In case (1) or (2) we copy the 0th byte from the msgValue into useropt so
+ * we'll do that in the fallthrough. In cases (1), (2), and (4) we should
+ * make sure to send the code which kicks off old clients. Old clients that
+ * tickle case (3) will get kicked off when they fail the websocket
+ * handshake.
*/
if (msgType == MSG_LOGIN) { /* Case 1 */
- options->json_support = 0;
+ options->connection_flags &= ~JSON_SUPPORT;
if (msgLen != 1) {
send_msg(ctlsockfd, MSG_ERROR, invalid_test, strlen(invalid_test));
return (-2);
}
} else if (msgType == MSG_EXTENDED_LOGIN) { /* Case 2 */
- options->json_support = 1;
+ options->connection_flags |= JSON_SUPPORT;
jsonMsgValue = json_read_map_value(msgValue, DEFAULT_KEY);
strlcpy(msgValue, jsonMsgValue, sizeof(msgValue));
msgLen = strlen(jsonMsgValue);
@@ -269,7 +332,7 @@
log_println(0, "Client version: %s-\n", options->client_version);
} else {
send_json_message(ctlsockfd, MSG_ERROR, invalid_test, strlen(invalid_test),
- options->json_support, JSON_SINGLE_VALUE);
+ options->connection_flags, JSON_SINGLE_VALUE);
return (-2);
}
} else { /* Case 3 */
@@ -286,21 +349,23 @@
"Client connect received from :IP %s to some server on socket %d",
get_remotehostaddress(), ctlsockfd);
- // set_protologfile(get_remotehostaddress(), protologlocalarr);
+ // set_protologfile(get_remotehost(), protologlocalarr);
if (!(useropt
& (TEST_MID | TEST_C2S | TEST_S2C | TEST_SFW | TEST_STATUS
| TEST_META))) {
// message received does not indicate a valid test!
send_json_message(ctlsockfd, MSG_ERROR, invalid_test_suite,
- strlen(invalid_test_suite), options->json_support, JSON_SINGLE_VALUE);
+ strlen(invalid_test_suite), options->connection_flags, JSON_SINGLE_VALUE);
return (-3);
}
// construct test suite request based on user options received
- if (useropt & TEST_MID) {
+ // Can't connect from server to client using browser websockets, so we can't
+ // support both websockets and TEST_MID and TEST_SFW
+ if (useropt & TEST_MID && !(options->connection_flags & WEBSOCKET_SUPPORT)) {
add_test_to_suite(&first, buff, buff_strlen, TEST_MID);
}
- if (useropt & TEST_SFW) {
+ if (useropt & TEST_SFW && !(options->connection_flags & WEBSOCKET_SUPPORT)) {
add_test_to_suite(&first, buff, buff_strlen, TEST_SFW);
}
if (useropt & TEST_C2S) {
=======================================
--- /branches/websockets/src/testoptions.h Wed May 28 11:17:18 2014 UTC
+++ /branches/websockets/src/testoptions.h Mon Mar 23 22:59:30 2015 UTC
@@ -18,13 +18,16 @@
#define RETRY_EXCEEDED_WAITING_DATA -102
#define SOCKET_STATUS_FAILED -1
+#define JSON_SUPPORT 1
+#define WEBSOCKET_SUPPORT 2
+
typedef struct testoptions {
int multiple; // multiples tests enabled
int mainport; // main port used for test
char client_version[CS_VERSION_LENGTH_MAX + 1]; // client version number.
- int json_support; // indicates if client supports JSON messages
+ int connection_flags; // indicates if client supports JSON messages and/or websockets
int midopt; // middlebox test to be perfomed?
int midsockfd; // socket file desc for middlebox test
@@ -44,7 +47,6 @@
pid_t child2;
int sfwopt; // Is firewall test to be performed?
- int State; // seems unused currently
int metaopt; // meta test to be perfomed?
} TestOptions;
=======================================
--- /branches/websockets/src/unit_testing.c Mon Dec 8 19:27:27 2014 UTC
+++ /branches/websockets/src/unit_testing.c Mon Mar 23 22:59:30 2015 UTC
@@ -9,15 +9,16 @@
/**
* The workhorse method of the test framework. Due to this framework's
- * minimality, the run_test() method must be explicitly called in main() for
- * each test you want to run. Also, there is no timeout for tests, which means
- * an infinite loop will hang the testing framework.
+ * minimality, the run_unit_test() method must be explicitly called in main()
+ * for each test you want to run. Also, there is no timeout for tests, which
+ * means an infinite loop will hang the testing framework.
*
* @param *test_name The displayed name of the test to be run
* @param *test_func A pointer to the void() test function
- * */
-int run_test(const char* test_name, const void (*test_func)()) {
+ */
+int run_unit_test(const char* test_name, void (*test_func)()) {
int test_exit_code;
+ int scratch;
pid_t child_test_pid;
fprintf(stderr, "Running test %s...\n", test_name);
// Run each child test in its own subprocess to help prevent tests from
@@ -41,6 +42,10 @@
fprintf(stderr, " ...TEST CRASHED (return code=%d, %s).\n",
test_exit_code, strerror(test_exit_code));
}
+ if (!WIFEXITED(test_exit_code)) {
+ kill(child_test_pid, SIGKILL);
+ waitpid(child_test_pid, &scratch, 0);
+ }
// Make sure at least one of the bottom 7 bits is set. Some systems only
// pay attention to the bottom 7 bits of the process return code.
return (test_exit_code & 127) ? test_exit_code : (1 | test_exit_code);
=======================================
--- /branches/websockets/src/unit_testing.h Mon Dec 8 19:27:27 2014 UTC
+++ /branches/websockets/src/unit_testing.h Mon Mar 23 22:59:30 2015 UTC
@@ -26,7 +26,7 @@
* directly to fprintf, which allows you to do things like:
* ASSERT(the_answer == 42, "Bad the_answer: %d", the_answer);
* in order to create nice error messages.
- * */
+ */
#define ASSERT(COND, ...) \
do { \
if (!(COND)) { \
@@ -41,7 +41,7 @@
* Asserts a condition using the text of the condition as the error message.
*
* @param COND The condition being asserted.
- * */
+ */
#define CHECK(COND) ASSERT(COND, #COND)
/**
@@ -50,9 +50,11 @@
*
* @param ... The message and arguments for fprintf indicating why the failure
* occurred
- * */
+ */
#define FAIL(...) ASSERT(0, __VA_ARGS__)
-int run_test(const char* test_name, const void (*test_func)());
+int run_unit_test(const char* test_name, void (*test_func)());
+
+#define RUN_TEST(FN) run_unit_test(#FN, &(FN))
#endif // SRC_UNIT_TESTING_H
=======================================
--- /branches/websockets/src/web100-util.c Wed Jun 4 10:51:11 2014 UTC
+++ /branches/websockets/src/web100-util.c Mon Mar 23 22:59:30 2015 UTC
@@ -591,11 +591,11 @@
* @param ctlsock integer socket file descriptor indicating data recipient
* @param agent pointer to a tcp_stat_agent
* @param count_vars integer number of tcp_stat_variables to get value of
- * @param jsonSupport indicates if messages should be sent usin JSON format
+ * @param testoptions the options that determine how the data should be sent
*
*/
int tcp_stat_get_data(tcp_stat_snap* snap, int testsock, int ctlsock,
- tcp_stat_agent* agent, int count_vars, int jsonSupport) {
+ tcp_stat_agent* agent, int count_vars, const struct testoptions* const testoptions) {
char line[256];
#if USE_WEB100
int i;
@@ -639,7 +639,7 @@
/* Why do we atoi after getting as text anyway ?? */
snprintf(line, sizeof(line), "%s: %d\n", web_vars[i].name,
atoi(web_vars[i].value));
- send_json_message(ctlsock, TEST_MSG, line, strlen(line), jsonSupport, JSON_SINGLE_VALUE);
+ send_json_message(ctlsock, TEST_MSG, line, strlen(line), testoptions->connection_flags, JSON_SINGLE_VALUE);
log_print(9, "%s", line);
}
log_println(6, "S2C test - Send web100 data to client pid=%d", getpid());
@@ -1027,9 +1027,9 @@
const char* web100_name = tcp_names[a].web100_name;
if (web100_name == NULL)
continue;
- int PktsIn = -1;
- int DataPktsIn = -1;
- int has_AckPktsIn = 0;
+ //int PktsIn = -1;
+ //int DataPktsIn = -1;
+ //int has_AckPktsIn = 0;
for (b = 0; b < count_vars; b++) {
if (strcmp(web_vars[b].name, web100_name) == 0) {
=======================================
--- /branches/websockets/src/web100srv.c Wed Feb 18 16:54:51 2015 UTC
+++ /branches/websockets/src/web100srv.c Mon Mar 23 22:59:30 2015 UTC
@@ -87,6 +87,7 @@
#include "heuristics.h"
#include "tests_srv.h"
#include "jsonutils.h"
+#include "websocket.h"
static char lgfn[FILENAME_SIZE]; // log file name
static char wvfn[FILENAME_SIZE]; // file name of web100-variables list
@@ -302,7 +303,7 @@
5,
"\tLooking for %d, curent queue Child %d, host: %s [%s], next=0x%x",
pid, child_proc1->pid, child_proc1->host, child_proc1->addr,
- (u_int64_t) child_proc1->next);
+ (uintptr_t) child_proc1->next);
if (child_proc1->pid == pid) {
log_println(4, "Main test process %d terminated, remove from queue",
pid);
@@ -329,7 +330,7 @@
while (child_proc1 != NULL) {
log_println(5, "\tChild %d, host: %s [%s], next=0x%x",
child_proc1->pid, child_proc1->host,
- child_proc1->addr, (u_int64_t) child_proc1->next);
+ child_proc1->addr, (uintptr_t) child_proc1->next);
if (child_proc1->next == NULL)
break;
child_proc1 = child_proc1->next;
@@ -395,7 +396,7 @@
}
child_proc1 = child_proc1->next;
log_println(6, "Looping through service queue ptr = 0x%x",
- (u_int64_t) child_proc1);
+ (uintptr_t) child_proc1);
}
}
if (sig17 > 0)
@@ -783,7 +784,7 @@
retcode = send_json_message(tmp_ptr->ctlsockfd, SRV_QUEUE,
SRV_QUEUE_HEARTBEAT_STR,
strlen(SRV_QUEUE_HEARTBEAT_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
log_println(6,
"send_msg() returned %d during zombie check on client %d",
retcode, tmp_ptr->pid);
@@ -973,13 +974,13 @@
// client needs to be version compatible. Send current version
snprintf(buff, sizeof(buff), "v%s", VERSION "-" TCP_STAT_NAME);
- send_json_message(ctlsockfd, MSG_LOGIN, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_LOGIN, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
// initiate test with MSG_LOGIN message.
log_println(3, "run_test() routine, asking for test_suite = %s",
test_suite);
send_json_message(ctlsockfd, MSG_LOGIN, test_suite, strlen(test_suite),
- testopt->json_support, JSON_SINGLE_VALUE);
+ testopt->connection_flags, JSON_SINGLE_VALUE);
/* if ((n = initialize_tests(ctlsockfd, &testopt, conn_options))) {
log_println(0, "ERROR: Tests initialization failed (%d)", n);
return;
@@ -1224,38 +1225,38 @@
snprintf(buff, sizeof(buff), "c2sData: %d\nc2sAck: %d\ns2cData: %d\n"
"s2cAck: %d\n", c2s_linkspeed_data, c2s_linkspeed_ack,
s2c_linkspeed_data, s2c_linkspeed_ack);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
snprintf(buff, sizeof(buff),
"half_duplex: %d\nlink: %d\ncongestion: %d\nbad_cable: %d\n"
"mismatch: %d\nspd: %0.2f\n", half_duplex, link, congestion,
bad_cable, mismatch, realthruput);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
snprintf(buff, sizeof(buff),
"bw: %0.2f\nloss: %0.9f\navgrtt: %0.2f\nwaitsec: %0.2f\n"
"timesec: %0.2f\norder: %0.4f\n", bw_theortcl, packetloss_s2c,
avgrtt, waitsec, timesec, oo_order);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
snprintf(buff, sizeof(buff),
"rwintime: %0.4f\nsendtime: %0.4f\ncwndtime: %0.4f\n"
"rwin: %0.4f\nswin: %0.4f\n", rwintime, sendtime, cwndtime, rwin,
swin);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
snprintf(buff, sizeof(buff),
"cwin: %0.4f\nrttsec: %0.6f\nSndbuf: %"VARtype"\naspd: %0.5f\n"
"CWND-Limited: %0.2f\n", cwin, rttsec, vars.Sndbuf, aspd, s2c2spd);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
snprintf(buff, sizeof(buff),
"minCWNDpeak: %d\nmaxCWNDpeak: %d\nCWNDpeaks: %d\n",
peaks.min, peaks.max, peaks.amount);
- send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_RESULTS, buff, strlen(buff), testopt->connection_flags, JSON_SINGLE_VALUE);
// Signal end of test results to client
- send_json_message(ctlsockfd, MSG_LOGOUT, "", 0, testopt->json_support, JSON_SINGLE_VALUE);
+ send_json_message(ctlsockfd, MSG_LOGOUT, "", 0, testopt->connection_flags, JSON_SINGLE_VALUE);
// Copy collected values into the meta data structures. This section
// seems most readable, easy to debug here.
@@ -1442,6 +1443,12 @@
return (0);
}
+/* web100srv.c contains both a main() that runs things, but is also a source of
+ * library of code run by other parts of the program. In order to test the
+ * other parts, we must to compile this file without the main() function. To
+ * do so, pass in -DUSE_WEB100SRV_ONLY_AS_LIBRARY as a compile-time option.
+ */
+#ifndef USE_WEB100SRV_ONLY_AS_LIBRARY
/**
* Initializes data structures,
* web100 structures and logging systems. Read/load configuration, get process
@@ -1478,7 +1485,6 @@
int debug = 0;
int j;
- char *name;
// variables used for protocol validation logs
// char startsrvmsg[256]; // used to log start of server process
@@ -1977,7 +1983,7 @@
/* if ((mchild->next == NULL) && (mchild->running == 0))
* mchild = tmp_ptr;
- * if (mchild != head_ptr) {
+ * if (mchild != head_ptr)
*/
tmp_ptr = mchild;
@@ -1993,7 +1999,7 @@
break;
if (i == (2 * max_clients)) {
rac = send_json_message(tmp_ptr->ctlsockfd, SRV_QUEUE, "1", 1,
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
log_println(
6,
"sent 45 sec update message to client %d on fd=%d, "
@@ -2002,7 +2008,7 @@
}
if (i == (3 * max_clients)) {
rac = send_json_message(tmp_ptr->ctlsockfd, SRV_QUEUE, "2", 1,
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
log_println(
6,
"sent 90 sec update message to client %d on fd=%d, "
@@ -2027,7 +2033,7 @@
send_json_message(head_ptr->ctlsockfd, SRV_QUEUE,
SRV_QUEUE_SERVER_BUSY_STR,
strlen(SRV_QUEUE_SERVER_BUSY_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
shutdown(head_ptr->ctlsockfd, SHUT_WR);
close(head_ptr->ctlsockfd);
tpid = head_ptr->pid;
@@ -2049,7 +2055,7 @@
send_json_message(head_ptr->ctlsockfd, SRV_QUEUE,
SRV_QUEUE_SERVER_BUSY_STR,
strlen(SRV_QUEUE_SERVER_BUSY_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
shutdown(head_ptr->ctlsockfd, SHUT_WR);
close(head_ptr->ctlsockfd);
tpid = head_ptr->pid;
@@ -2086,7 +2092,7 @@
send_json_message(head_ptr->ctlsockfd, SRV_QUEUE,
SRV_QUEUE_SERVER_BUSY_STR,
strlen(SRV_QUEUE_SERVER_BUSY_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
shutdown(head_ptr->ctlsockfd, SHUT_WR);
close(head_ptr->ctlsockfd);
tpid = head_ptr->pid;
@@ -2217,25 +2223,6 @@
protolog_procstatus(getpid(), getCurrentTest(), proctypeenum,
procstatusenum, ctlsockfd);
}
-
- // the specially crafted data that kicks off the old clients
- for (i = 0; i < RETRY_COUNT; i++) {
- retcode = write(ctlsockfd, "123456 654321", 13);
- if ((retcode == -1) && (errno == EINTR)) // interrupted, retry
- continue;
- if (retcode == 13) // 13 bytes correctly written, exit
- break;
- if (retcode == -1) { // socket error hindering further retries
- log_println(1,
- "Initial contact with client failed errno=%d",
- errno);
- close(chld_pipe[0]);
- close(chld_pipe[1]);
- shutdown(ctlsockfd, SHUT_WR);
- close(ctlsockfd);
- goto mainloop;
- }
- }
t_opts = initialize_tests(ctlsockfd, &testopt, test_suite,
sizeof(test_suite));
@@ -2293,7 +2280,7 @@
send_json_message(ctlsockfd, SRV_QUEUE,
SRV_QUEUE_SERVER_BUSY_STR,
strlen(SRV_QUEUE_SERVER_BUSY_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
close(chld_pipe[0]);
close(chld_pipe[1]);
shutdown(ctlsockfd, SHUT_WR);
@@ -2359,7 +2346,7 @@
send_json_message(new_child->ctlsockfd, SRV_QUEUE,
SRV_QUEUE_SERVER_BUSY_STR,
strlen(SRV_QUEUE_SERVER_BUSY_STR),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
close(chld_pipe[1]);
shutdown(new_child->ctlsockfd, SHUT_WR);
close(new_child->ctlsockfd);
@@ -2397,7 +2384,7 @@
while (tmp_ptr != NULL) {
log_println(4, "\tChild %d, host: %s [%s], next=0x%x",
tmp_ptr->pid, tmp_ptr->host, tmp_ptr->addr,
- (u_int64_t) tmp_ptr->next);
+ (uintptr_t) tmp_ptr->next);
if (tmp_ptr->next == NULL)
break;
tmp_ptr = tmp_ptr->next;
@@ -2418,7 +2405,7 @@
(waiting-1));
snprintf(tmpstr, sizeof(tmpstr), "%d", (waiting-1));
send_json_message(tmp_ptr->ctlsockfd, SRV_QUEUE, tmpstr, strlen(tmpstr),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
continue;
}
@@ -2429,7 +2416,7 @@
(waiting-max_clients), mchild->pid, xx);
snprintf(tmpstr, sizeof(tmpstr), "%d", xx);
send_json_message(mchild->ctlsockfd, SRV_QUEUE, tmpstr, strlen(tmpstr),
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
continue;
}
@@ -2469,7 +2456,7 @@
snprintf(tmpstr, sizeof(tmpstr), "%d", (waiting-j));
send_json_message(tmp_ptr->ctlsockfd, SRV_QUEUE, tmpstr,
- strlen(tmpstr), testopt.json_support, JSON_SINGLE_VALUE);
+ strlen(tmpstr), testopt.connection_flags, JSON_SINGLE_VALUE);
tmp_ptr = tmp_ptr->next;
j--;
}
@@ -2535,7 +2522,7 @@
tmpstr);
// test session starts now
send_json_message(mchild->ctlsockfd, SRV_QUEUE, "0", 1,
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
for (i = 0; i < 5; i++) {
retcode = write(mchild->pipe, tmpstr, strlen(tmpstr));
log_println(6, "write(%d) returned %d, errno=%d",
@@ -2567,7 +2554,7 @@
log_println(5, "sending 'GO' signal to client msg='%s'",
tmpstr);
send_json_message(head_ptr->ctlsockfd, SRV_QUEUE, "0", 1,
- testopt.json_support, JSON_SINGLE_VALUE);
+ testopt.connection_flags, JSON_SINGLE_VALUE);
for (i = 0; i < 5; i++) {
retcode = write(head_ptr->pipe, tmpstr, strlen(tmpstr));
if ((retcode == -1) && (errno == EINTR))
@@ -2767,6 +2754,7 @@
}
}
}
+#endif
/**
* Method to get remote host's address.
=======================================
--- /branches/websockets/src/web100srv.h Wed Feb 18 16:54:51 2015 UTC
+++ /branches/websockets/src/web100srv.h Mon Mar 23 22:59:30 2015 UTC
@@ -315,8 +315,10 @@
int autotune);/* Not used so no web10g version */
void tcp_stat_get_data_recv(int sock, tcp_stat_agent* agent,
tcp_stat_connection cn, int count_vars);
+
+struct testoptions; // declare it, but don't define it
int tcp_stat_get_data(tcp_stat_snap* snap, int testsock, int ctlsock,
- tcp_stat_agent* agent, int count_vars, int jsonSupport);
+ tcp_stat_agent* agent, int count_vars, const struct testoptions* const testoptions);
int CwndDecrease(char* logname,
u_int32_t *dec_cnt, u_int32_t *same_cnt, u_int32_t *inc_cnt);
=======================================
--- /branches/websockets/src/web100srv_unit_tests.c Mon Dec 8 19:27:27 2014 UTC
+++ /branches/websockets/src/web100srv_unit_tests.c Mon Mar 23 22:59:30 2015 UTC
@@ -3,43 +3,77 @@
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <time.h>
#include <unistd.h>
#include "unit_testing.h"
-/** Runs an end-to-end test of the server and client code. */
-void test_e2e() {
+/** Starts the web100srv process. */
+pid_t start_server(int port) {
pid_t server_pid;
- pid_t client_pid;
- int client_exit_code;
- char hostname[1024];
+ int server_return_value;
+ char port_string[] = "32767"; // max port
fprintf(stderr, "Starting the server\n");
+ sprintf(port_string, "%d", port);
if ((server_pid = fork()) == 0) {
- execl("./web100srv", "./web100srv", "--port=5555", NULL);
- perror("Server start error");
- exit(1); // Children can't FAIL
+ execl("./web100srv", "./web100srv", "--port", port_string, NULL);
+ perror("SERVER START ERROR: SHOULD NEVER HAPPEN");
+ FAIL("The server should never return - it should only be killed.");
+ exit(1);
}
// Wait until the server port (hopefully) becomes available
sleep(1);
+ return server_pid;
+}
+
+/** Runs an end-to-end test of the server and client code. */
+void test_e2e() {
+ pid_t server_pid;
+ int server_exit_code;
+ char hostname[1024];
+ char command_line[1024];
+ int port;
+ srandom(time(NULL));
+ port = (random() % 30000) + 1024;
+ server_pid = start_server(port);
// Find out the hostname. We can't use "localhost" because then the test
// won't go through the TCP stack and so web100 won't work.
- gethostname(hostname, (sizeof(hostname) / sizeof(char)) - 1);
- fprintf(stderr, "Starting the client, will attach to %s\n", hostname);
- if ((client_pid = fork()) == 0) {
- execl("./web100clt", "./web100clt", "--name", hostname, "--port=5555",
- NULL);
- perror("Client start error");
- exit(1); // Children can't FAIL
+ gethostname(hostname, sizeof(hostname) - 1);
+ fprintf(stderr, "Starting the client, will attach to %s:%d\n", hostname,
+ port);
+ sprintf(command_line, "./web100clt --name=%s --port=%d", hostname, port);
+ ASSERT(system(command_line) == 0, "%s did not exit with 0", command_line);
+ kill(server_pid, SIGKILL);
+ waitpid(server_pid, &server_exit_code, 0);
+}
+
+/** Runs an end-to-end test of the server over websockets. */
+void test_javascript() {
+ pid_t server_pid;
+ int server_exit_code;
+ char hostname[1024];
+ char command_line[1024];
+ int port;
+ char* nodejs_name = NULL;
+ if (system("nodejs -e 'process.exit(0);'") == 0) {
+ nodejs_name = "nodejs";
+ } else if (system("node -e 'process.exit(0);'") == 0) {
+ nodejs_name = "node";
+ } else {
+ fprintf(stderr, "Can NOT e2e test websockets - node.js was not found.");
+ return;
}
- waitpid(client_pid, &client_exit_code, 0);
+ srandom(time(NULL));
+ port = (random() % 30000) + 1024;
+ server_pid = start_server(port);
+ gethostname(hostname, sizeof(hostname) - 1);
+ sprintf(command_line, "%s node_tests/ndt_client.js %s %d", nodejs_name,
+ hostname, port);
+ ASSERT(system(command_line) == 0, "%s exited with non-zero exit code",
+ command_line);
kill(server_pid, SIGKILL);
- ASSERT(WIFEXITED(client_exit_code) && WEXITSTATUS(client_exit_code) == 0,
- "client exit code was %d", WEXITSTATUS(client_exit_code));
+ waitpid(server_pid, &server_exit_code, 0);
}
/** Runs each test, returns non-zero to the shell if any tests fail. */
-int main() {
- int success = 0;
- success |= run_test("end to end test", &test_e2e);
- return success;
-}
+int main() { return RUN_TEST(test_e2e) | RUN_TEST(test_javascript); }
- [ndt-dev] [ndt] r1219 committed - This patch adds websocket support to ndt...., ndt, 03/23/2015
Archive powered by MHonArc 2.6.16.