blob: acf2163803f059d5615424e92da7e3e029156cf9 [file] [log] [blame]
/*
Copyright (c) 2012 250bpm s.r.o. All rights reserved.
Copyright (c) 2014 Wirebird Labs LLC. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
#include "../src/nn.h"
#include "../src/pair.h"
#include "../src/ws.h"
#include "../src/utils/int.h"
#include "testutil.h"
/* Stress tests the WebSocket transport using Autobahn Testsuite. */
#define FUZZING_SERVER_ADDRESS "ws://127.0.0.1:9002"
/* Control whether performances tests are run, which may add an additional
minute or longer to the test. */
#define NN_WS_STRESS_SKIP_PERF 1
static void nn_ws_launch_fuzzing_client (NN_UNUSED void)
{
FILE *fd;
char *perf_tests;
int rc;
if (NN_WS_STRESS_SKIP_PERF)
perf_tests = ", \"9.*\"";
else
perf_tests = "";
/* Create an Autobahn json config file in the same working directory
as where the call to wstest will be. */
fd = fopen ("fuzzingclient.json", "w+");
errno_assert (fd != NULL);
rc = fprintf (fd,
"{\n"
" \"servers\": [\n"
" {\n"
" \"agent\": \"nanomsg\",\n"
" \"url\" : \"%s\",\n"
" \"protocols\" : [\"x-nanomsg-pair\"]\n"
" }\n"
" ],\n"
" \"outdir\" : \"./reports/client\",\n"
" \"cases\" : [\"*\"],\n"
" \"exclude-cases\" : [\"6.4.3\", \"6.4.4\"%s],\n"
" \"exclude-agent-cases\" : {}\n"
"}\n",
FUZZING_SERVER_ADDRESS, perf_tests);
errno_assert (rc > 0);
rc = fclose (fd);
errno_assert (rc == 0);
/* The following call launches a fuzzing client in an async
process and assumes you have Autobahn Testsuite installed
as per http://autobahn.ws/testsuite/installation.html */
#if defined NN_HAVE_WINDOWS
rc = system (
"start wstest "
"--mode=fuzzingclient "
"--spec=fuzzingclient.json");
#else
rc = system (
"wstest "
"--mode=fuzzingclient "
"--spec=fuzzingclient.json &");
#endif
errno_assert (rc == 0);
}
static void nn_ws_launch_fuzzing_server (NN_UNUSED void)
{
FILE *fd;
char *perf_tests;
int rc;
if (NN_WS_STRESS_SKIP_PERF)
perf_tests = ", \"9.*\"";
else
perf_tests = "";
/* Create an Autobahn json config file in the same working directory
as where the call to wstest will be. */
fd = fopen ("fuzzingserver.json", "w+");
errno_assert (fd != NULL);
rc = fprintf (fd,
"{\n"
" \"url\": \"%s\",\n"
" \"protocols\" : [\"x-nanomsg-pair\"],\n"
" \"outdir\" : \"./reports/server\",\n"
" \"cases\" : [\"*\"],\n"
" \"exclude-cases\" : [\"6.4.3\", \"6.4.4\"%s],\n"
" \"exclude-agent-cases\" : {}\n"
"}\n",
FUZZING_SERVER_ADDRESS, perf_tests);
errno_assert (rc > 0);
rc = fclose (fd);
errno_assert (rc == 0);
/* The following call launches a fuzzing server in an async
process and assumes you have Autobahn Testsuite installed
as per http://autobahn.ws/testsuite/installation.html */
#if defined NN_HAVE_WINDOWS
rc = system (
"start wstest "
"--mode=fuzzingserver "
"--spec=fuzzingserver.json "
"--webport=0");
#else
rc = system (
"wstest "
"--mode=fuzzingserver "
"--spec=fuzzingserver.json "
"--webport=0 &");
#endif
errno_assert (rc == 0);
/* TODO: Failure to wait an arbitrarily-long-enough time for the
test server to load results in STATUS_CONNECTION_REFUSED on the
first query to interrogate number of cases. Why? Is this a
nanomsg problem, Autobahn problem...? */
nn_sleep (5000);
}
static void nn_ws_kill_autobahn (NN_UNUSED void)
{
int rc;
#if defined NN_HAVE_WINDOWS
rc = system ("taskkill /IM wstest.exe");
#else
rc = system ("pkill Python");
#endif
nn_assert (rc == 0);
}
static void nn_ws_autobahn_test (int socket, void *recv_buf, int test_id)
{
int performing_test;
int rc;
uint8_t ws_msg_type;
performing_test = 1;
/* Perform test until remote endpoint either initiates a Close
Handshake, or if this endpoint fails the connection based on
invalid input from the remote peer. */
while (performing_test) {
rc = nn_ws_recv (socket, recv_buf, NN_MSG, &ws_msg_type, 0);
errno_assert (rc >= 0);
printf ("Test %03d: Rx: 0x%02x (%d bytes)\n", test_id, ws_msg_type, rc);
switch (ws_msg_type) {
case NN_WS_MSG_TYPE_TEXT:
/* Echo text message verbatim. */
rc = nn_ws_send (socket, recv_buf, NN_MSG, ws_msg_type, 0);
break;
case NN_WS_MSG_TYPE_BINARY:
/* Echo binary message verbatim. */
rc = nn_ws_send (socket, recv_buf, NN_MSG, ws_msg_type, 0);
break;
case NN_WS_MSG_TYPE_PING:
/* As per RFC 6455 5.5.3, echo PING data payload as a PONG. */
rc = nn_ws_send (socket, recv_buf, NN_MSG,
NN_WS_MSG_TYPE_PONG, 0);
break;
case NN_WS_MSG_TYPE_PONG:
/* Silently ignore PONGs in this echo server. */
break;
case NN_WS_MSG_TYPE_CLOSE:
/* As per RFC 6455 5.5.1, repeat Close Code in message body. */
rc = nn_ws_send (socket, recv_buf, NN_MSG, ws_msg_type, 0);
performing_test = 0;
break;
case NN_WS_MSG_TYPE_GONE:
/* This indicates the remote peer has sent invalid data forcing
the local endpoint to fail the connection as per RFC 6455. */
performing_test = 0;
printf ("Test %03d: correctly prevented remote endpoint fuzz\n",
test_id);
break;
default:
/* The library delivered an unexpected message type. */
nn_assert (0);
break;
}
errno_assert (rc >= 0);
}
}
int main ()
{
int rc;
int autobahn_server;
int autobahn_client;
int i;
int cases;
int local_connected_ep;
int local_bound_ep;
int recv_timeout;
char autobahn_addr[64];
uint8_t ws_msg_type;
uint8_t *recv_buf = NULL;
void *hdr_buf = NULL;
autobahn_client = test_socket (AF_SP, NN_PAIR);
/* The longest intentional delay in a test as of Autobahn Testsuite v0.7.1
is nominally 2sec, so a 5000msec timeout gives a bit of headroom. With
performance tests enabled, some of those tests take 30sec or longer,
depending on platform. */
if (NN_WS_STRESS_SKIP_PERF)
recv_timeout = 5000;
else
recv_timeout = 60000;
nn_ws_launch_fuzzing_server ();
nn_setsockopt (autobahn_client, NN_SOL_SOCKET, NN_RCVTIMEO, &recv_timeout,
sizeof (recv_timeout));
memset (autobahn_addr, 0, sizeof (autobahn_addr));
sprintf (autobahn_addr, "%s/getCaseCount", FUZZING_SERVER_ADDRESS);
printf ("Connecting to %s\n\n", autobahn_addr);
local_connected_ep = test_connect (autobahn_client, autobahn_addr);
errno_assert (local_connected_ep >= 0);
printf ("Fetching cases...\n");
rc = nn_ws_recv (autobahn_client, &recv_buf, NN_MSG, &ws_msg_type, 0);
/* We expect nominally three ASCII digits [0-9] representing total
number of cases to run; as of Autobahn TestSuite v0.7.1,
521+ test cases. 1 digit is allowed, to allow development of
specific cases, and up to 4 digits, for potential future expansion
of the test suite. */
nn_assert (1 <= rc && rc <= 4);
nn_assert (ws_msg_type == NN_WS_MSG_TYPE_TEXT);
cases = 0;
for (i = 0; i < rc; ++i) {
nn_assert ('0' <= recv_buf [i] && recv_buf [i] <= '9');
cases *= 10;
cases += recv_buf [i] - '0';
}
rc = nn_freemsg (recv_buf);
errno_assert (rc == 0);
rc = nn_ws_recv (autobahn_client, &recv_buf, NN_MSG, &ws_msg_type, 0);
/* Autobahn Testsuite server sends a close handshake immediately
following the number of test cases. */
nn_assert (ws_msg_type == NN_WS_MSG_TYPE_CLOSE);
/* Currently the Autobahn Testsuite sends a zero-length payload on the
close message, but we're allowing values greater than zero in case
this changes in the future. */
errno_assert (rc >= 0);
/* As per RFC 6455 5.5.1, repeat Close Code in message body. */
rc = nn_ws_send (autobahn_client, &recv_buf, NN_MSG, ws_msg_type, 0);
errno_assert (rc == 0);
rc = nn_shutdown (autobahn_client, local_connected_ep);
errno_assert (rc == 0);
printf ("Preparing to run %d cases...\n", cases);
/* Notice, index starts with 1, not zero! */
for (i = 1; i <= cases; i++) {
sprintf (autobahn_addr, "%s/runCase?case=%d&agent=nanomsg",
FUZZING_SERVER_ADDRESS, i);
local_connected_ep = test_connect (autobahn_client, autobahn_addr);
errno_assert (local_connected_ep >= 0);
nn_ws_autobahn_test (autobahn_client, &recv_buf, i);
rc = nn_shutdown (autobahn_client, local_connected_ep);
errno_assert (rc == 0);
}
sprintf (autobahn_addr, "%s/updateReports?agent=nanomsg",
FUZZING_SERVER_ADDRESS);
printf ("Generating reports with %s ....\n", autobahn_addr);
local_connected_ep = test_connect (autobahn_client, autobahn_addr);
errno_assert (local_connected_ep >= 0);
/* Server sends close code as soon as the report is created. */
rc = nn_ws_recv (autobahn_client, &recv_buf, NN_MSG, &ws_msg_type, 0);
/* Currently the Autobahn Testsuite sends a zero-length payload on the
close message, but we're allowing values greater than zero in case
this changes in the future. */
errno_assert (rc >= 0);
nn_assert (ws_msg_type == NN_WS_MSG_TYPE_CLOSE);
/* As per RFC 6455 5.5.1, repeat Close Code in message body. */
rc = nn_ws_send (autobahn_client, &recv_buf, NN_MSG, ws_msg_type, 0);
errno_assert (rc == 0);
rc = nn_shutdown (autobahn_client, local_connected_ep);
errno_assert (rc == 0);
test_close (autobahn_client);
nn_ws_kill_autobahn ();
printf ("WebSocket client tests complete! Now, testing server...\n\n");
/* Create echo server for Autobahn Testsuite client fuzzer. */
autobahn_server = test_socket (AF_SP, NN_PAIR);
local_bound_ep = test_bind (autobahn_server, FUZZING_SERVER_ADDRESS);
errno_assert (local_bound_ep >= 0);
printf ("\n\nServer started on %s\n"
"Waiting for Autobahn fuzzing client...\n\n",
FUZZING_SERVER_ADDRESS);
nn_ws_launch_fuzzing_client ();
nn_setsockopt (autobahn_server, NN_SOL_SOCKET, NN_RCVTIMEO, &recv_timeout,
sizeof (recv_timeout));
/* The same number of cases are tested for servers as for clients. */
for (i = 1; i <= cases; i++) {
nn_ws_autobahn_test (autobahn_server, &recv_buf, i);
}
/* TODO: This hangs; why? It must be fixed. */
//test_close (sb);
return 0;
}