/*****************************************************************************/ /* autobahn.c Autobahn test-bench client: http://autobahn.ws/testsuite A sophisticated, comprehensive and indepdendent (non-WASD) test suite powered by Python that validates WebSocket behaviour, particularly in corner cases. Used to exercise and validate the wsLIB.c library. Many thanks to the author, Tobias Oberstein for making this generally available. It has been invaluable in verifying and improving wsLIB behaviours. This description is not a tutorial on the use of Autobahn, just how it was used to validate the WASD WebSocket environment. For the (considerable) ins-and-outs of Autobahn see its own documentation. Autobahn has a Twisted package dependency which currently is not supported on VMS. For WASD development purposes the Python platform used was OS-X 10.7.5 The comments below are based on Autobahn v0.5.2 and my test-bench environment. INSTALL ------- Based on instructions at: http://autobahn.ws/testsuite/installation Installed on OS-X 10.7.5: git clone https://github.com/tavendo/AutobahnTestSuite.git cd ./AutobahnTestSuite/autobahntestsuite # alter timeouts for my test-bench perl -e "s/self.WAITSECS = 10/self.WAITSECS = 100/g;" -pi $(find ./autobahn/case/ -type f) sudo python setup.py install cd ./autobahntestsuite python wstest.py --help Then the server test-bench executed using: cd ~/AutobahnTestSuite/autobahntestsuite/autobahntestsuite # edit "servers" changes described below nano fuzzingclient.json wstest -m fuzzingclient -s fuzzingclient.json WSLIB AS CLIENT CODE -------------------- The wsLIB code can be tested using the Autobahn "fuzzing server" on the same or separate host using it as a command-line utility. $ AUTOBAHN == "$WASD_EXE:AUTOBAHN" To automatically run all of the Autobahn test suite $ AUTOBAHN To use where the Autobahn test server is located on another host $ AUTOBAHN the.other.host Any of the specific Autobahn tests can be run by providing the URL $ AUTOBAHN "ws://localhost:9000/the_test_uri" /CASE= runs the specified Autobahn test case. /FRAME= sets the maximum frame size (default is none). /MRS= allows the maximum record size of the socket to be specified controlling the maximum fragment size generated by wsLIB (default is 65535). /UPDATE the Autobahn "clients" report page. MODIFYING TIMEOUTS ------------------ The default timeouts for Autobahn tests are a bit conservative for my test bench setup and have required some tweaking. For a 2^20 x 1 octet test (yes, a million network datagrams each containing one octet!) and 100Mbps LAN and VMS $QIO this takes *much* longer. The timeout can be extended by modifying the test Python code. The following Perl command-lines did it for this author (at the OS X command-line). perl -e "s/self.WAITSECS = 10/self.WAITSECS = 100/g;" -pi $(find ~/Autobahn/lib/python/autobahn/case/ -type f) Ensure you change the timeout value before the cd ~/Autobahn/lib/python python setup.py install or remove all of the *.pyc and reinstall after doing so. WSLIB AS SERVER CODE -------------------- The wsLIB code can also be tested as a WebSocket application (server script) by placing it in the scripting directory and then using the "fuzzing test driver". The host and port may be specified other than localhost:9000 using the fuzzing_client_spec.json file. The following allowed the Autobahn client to be on a separate system to the WASD server and WebSocket test application running on 192.168.1.3. The agent should be adjusted to reflect accurately the environment. { "servers": [{ "agent":"WASDv10.2.0+1.0.4", "url":"ws://192.168.1.3:9000", "options": { "version": 17} }], "cases": ["*"] } Retaining port 9000 is only for convenience. The WASD server has a temporary service configured at that port with mapping to activate the test script in CGIplus mode for any WebSocket request on that service. # WASD_CONFIG_MAP if (websocket:) script+ /* /cgi-bin/autobahn/* map=once COPYRIGHT --------- Copyright (C) 2011-2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY (update SOFTWAREVN as well!) --------------- 23-SEP-2012 MGD v1.1.0, modify instructions for AutoBahn test suite v0.5.2 (no actual changes to code required) 13-AUG-2011 MGD v1.0.0, initial */ /*****************************************************************************/ #define SOFTWAREVN "1.1.0" #define SOFTWARENM "AUTOBAHN" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #define COPYRIGHT_DATES "2020" #define COPYRIGHT_FULL \ "Copyright (C) 2011-2020 Mark G.Daniel\n\ This program, comes with ABSOLUTELY NO WARRANTY.\n\ This is free software, and you are welcome to redistribute it under the\n\ conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.\n\ http://www.gnu.org/licenses/gpl.txt\n" /* standard C header files */ #include #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include /* wsLIB header file */ #include "wslib.h" #define BOOL int #define TRUE 1 #define FALSE 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define EXIT_LINE(status) { printf ("[AUTOBAHN:%d]", __LINE__); exit(status); } struct ConnectionStruct { int ReadBufferCount, ReadBufferSize, WriteBufferCount, WriteBufferSize; char *ReadBufferPtr, *WriteBufferPtr; struct WsLibStruct *WsLibPtr; }; char Utility [] = "AUTOBAHN"; BOOL CliUpdateReports, Debug, DoShowVersion; int CliCaseNumber, CliFrameMax, CliSocketMrs, ServerPort; char *RequestUriPtr; char CloseAdieu [128], CommandLine [256], ServerHost [256], RequestUri [256]; /* function prototypes */ void ClientRead (struct WsLibStruct*); void ClientReadAst (struct WsLibStruct*); void CloseConnection (struct WsLibStruct*); void ConnectToServer (char*, int); void EchoScriptReadAst (struct WsLibStruct*); void GetParameters (int, char**); void LaunchEchoScript (); void MessageCallback (struct WsLibStruct*); void ParseURL (); int strsame (char*, char*, int); /*****************************************************************************/ /* */ int main ( int argc, char *argv[] ) { int cnt, status, CaseCount; char *cptr, *sptr, *zptr; char AgentWasd [64], CaseBuffer [64]; /*********/ /* begin */ /*********/ if (getenv("AUTOBAHN$DBUG")) Debug = TRUE; GetParameters (argc, argv); if (DoShowVersion) { fprintf (stdout, "%%%s-I-VERSION, %s\n%s", Utility, SOFTWAREID, COPYRIGHT_FULL); exit (SS$_NORMAL); } sprintf (CloseAdieu, "%s bids adieu!", SOFTWAREID); sprintf (AgentWasd, "WASD-wsLIB-%s", strchr(WsLibVersion(),' ')+1); if (getenv ("HTTP$INPUT")) { /*****************/ /* server script */ /*****************/ do { /* wait for next request (if CGIplus) */ WsLibCgiVar (""); /* kick-off the echo listener */ LaunchEchoScript (); /* end of request (if CGIplus) */ WsLibCgiPlusEof (); } while (WsLibIsCgiPlus()); exit (SS$_NORMAL); } /**************/ /* CLI client */ /**************/ if (!RequestUriPtr) RequestUriPtr = "ws://localhost:9000/"; ParseURL (); if (CliCaseNumber) { /* send the case request to Autobahn server */ sprintf (RequestUri, "/runCase?case=%d&agent=%s", CliCaseNumber, AgentWasd); ConnectToServer (NULL, 0); sys$hiber (); } else if (CliUpdateReports) { /* send the update reports request to Autobahn server */ sprintf (RequestUri, "/updateReports?agent=%s", AgentWasd); ConnectToServer (NULL, 0); sys$hiber (); } else if (!RequestUri[0]) { /* run through full Autobahn test suite */ strcpy (RequestUri, "/getCaseCount"); ConnectToServer (CaseBuffer, sizeof(CaseBuffer)); sys$hiber (); CaseCount = atoi(CaseBuffer); if (Debug) fprintf (stdout, "CaseCount: %d\n", CaseCount); for (cnt = 1; cnt <= CaseCount; cnt++) { fprintf (stdout, "case: %d\n", cnt); sprintf (RequestUri, "/runCase?case=%d&agent=%s", cnt, AgentWasd); ConnectToServer (NULL, 0); sys$hiber (); sleep (1); } sprintf (RequestUri, "/updateReports?agent=%s", AgentWasd); ConnectToServer (NULL, 0); sys$hiber (); } else { /* send the CLI request to Autobahn server */ ConnectToServer (NULL, 0); sys$hiber (); } exit (SS$_NORMAL); } /*****************************************************************************/ /* Connect to the server asynchronously. */ void ConnectToServer ( char *ReadBuffer, int BufferSize ) { int status; struct ConnectionStruct *cxptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ConnectToServer()\n"); /* init connection structure */ cxptr = calloc (1, sizeof(struct ConnectionStruct)); if (!cxptr) EXIT_LINE (vaxc$errno); cxptr->WriteBufferSize = 1024; cxptr->WriteBufferPtr = calloc (1, cxptr->WriteBufferSize); if (!cxptr->WriteBufferPtr) EXIT_LINE (vaxc$errno); if (BufferSize) { /* use this supplied buffer */ cxptr->ReadBufferSize = BufferSize; cxptr->ReadBufferPtr = ReadBuffer; } else { /* use wsLIB dynamic buffers */ cxptr->ReadBufferSize = 0; cxptr->ReadBufferPtr = NULL; } cxptr->WsLibPtr = WsLibCreate (cxptr, &CloseConnection); if (CliSocketMrs) WsLibClSetSocketMrs (cxptr->WsLibPtr, CliSocketMrs); if (CliFrameMax) WsLibSetFrameMax (cxptr->WsLibPtr, CliFrameMax); if (Debug) cxptr->WsLibPtr->WatchScript = 1; WsLibSetRoleClient (cxptr->WsLibPtr); WsLibSetMsgCallback (cxptr->WsLibPtr, &MessageCallback); status = WsLibClConnect (cxptr->WsLibPtr, ServerHost, ServerPort, RequestUri, &ClientRead); if (Debug) fprintf (stdout, "WsLibClConnect() %%X%08.08X\n", status); } /****************************************************************************/ /* Request is concluded. */ void CloseConnection (struct WsLibStruct *wsptr) { int status; struct ConnectionStruct *cxptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CloseConnection()\n"); cxptr = WsLibGetUserData (wsptr); WsLibDestroy (cxptr->WsLibPtr); if (cxptr->WriteBufferPtr) free (cxptr->WriteBufferPtr); if (cxptr->ReadBufferPtr) free (cxptr->ReadBufferPtr); sys$wake (0, 0); free (cxptr); } /****************************************************************************/ /* CLI client read asynchronously from Autobahn test server. */ void ClientRead (struct WsLibStruct *wsptr) { int status; struct ConnectionStruct *cxptr; /*********/ /* begin */ /*********/ cxptr = WsLibGetUserData (wsptr); if (Debug) fprintf (stdout, "ClientRead()\n"); if (VMSnok (status = WsLibClSocketStatus (wsptr))) { fprintf (stdout, "%%%s-W-CONNECT, [%d] failed (%%X%08.08X)\n", Utility, __LINE__, status); sys$wake (0, 0); return; } /* read next frame */ WsLibRead (cxptr->WsLibPtr, cxptr->ReadBufferPtr, cxptr->ReadBufferSize, ClientReadAst); } /****************************************************************************/ /* CLI client read from Autobahn test server has completed. Echoes the message back to Autobahn test server. */ void ClientReadAst (struct WsLibStruct *wsptr) { int status, DataCount; char *DataPtr; struct ConnectionStruct *cxptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ClientReadAst()\n"); cxptr = WsLibGetUserData (wsptr); if (VMSnok (status = WsLibReadStatus(wsptr))) { /* WEBSOCKET_INPUT read error (can be EOF) */ if (Debug) fprintf (stdout, "%%X%08.08X\n", status); return; } DataPtr = WsLibReadData(wsptr); DataCount = WsLibReadCount(wsptr); if (WsLibReadIsText (wsptr)) { fprintf (stdout, "text: %d bytes\n", DataCount); WsLibSetUtf8 (wsptr); WsLibWrite (wsptr, DataPtr, DataCount, NULL); } else if (WsLibReadIsBinary (wsptr)) { fprintf (stdout, "binary: %d bytes\n", DataCount); WsLibSetBinary (wsptr); WsLibWrite (wsptr, DataPtr, DataCount, NULL); } else { WsLibClose (wsptr, 0, NULL); return; } ClientRead (wsptr); } /*****************************************************************************/ /* Create a connection structure and begin to read from the Autobahn test client. It's all asynchronous from there. */ void LaunchEchoScript () { int status; struct ConnectionStruct *cxptr; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "LauchEcho()\n"); /* init connection structure */ cxptr = calloc (1, sizeof(struct ConnectionStruct)); if (!cxptr) EXIT_LINE (vaxc$errno); cxptr->WsLibPtr = wsptr = WsLibCreate (cxptr, &CloseConnection); /* open the IPC to the WebSocket (mailboxes) */ status = WsLibOpen (wsptr); if (VMSnok (status)) exit (status); /* read wait is ten seconds */ WsLibSetReadSecs (wsptr, 10); /* no I/O interaction is 120 seconds */ WsLibSetIdleSecs (wsptr, 120); /* set so that text frames are not automatially converted from UTF-8 */ WsLibSetBinary (wsptr); /* read next frame (dynamically allocated buffer and no limit on size) */ WsLibRead (wsptr, NULL, 0, EchoScriptReadAst); } /****************************************************************************/ /* Read from Autobahn test client has completed, simply echo it back. */ void EchoScriptReadAst (struct WsLibStruct *wsptr) { int DataCount, UniCount; char *DataPtr, *UniPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "EchoScriptReadAst()\n"); if (VMSnok (WsLibReadStatus(wsptr))) return; DataPtr = WsLibReadData(wsptr); DataCount = WsLibReadCount(wsptr); if (WsLibReadIsText (wsptr)) { WsLibSetUtf8 (wsptr); WsLibWrite (wsptr, DataPtr, DataCount, NULL); } else if (WsLibReadIsBinary (wsptr)) { WsLibSetBinary (wsptr); WsLibWrite (wsptr, DataPtr, DataCount, NULL); } else { WsLibClose (wsptr, 0, NULL); return; } /* read next frame (dynamically allocated buffer and no limit on size) */ WsLibRead (wsptr, NULL, 0, EchoScriptReadAst); } /*****************************************************************************/ /* Parse the CLI specified URL into it's host-name, port and URI. */ void ParseURL () { int status; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ParseURL() |%s|\n", RequestUriPtr); cptr = RequestUriPtr; if (strsame (cptr, "ws://", 5)) cptr += 5; sptr = ServerHost; while (*cptr && *cptr != ':' && *cptr != '/') *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == ':') { cptr++; if (isdigit(*cptr)) ServerPort = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; } if (!ServerHost[0]) { /* assume the local host is the server */ gethostname (ServerHost, sizeof(ServerHost)); } sptr = RequestUri; while (*cptr) *sptr++ = *cptr++; *sptr = '\0'; if (!ServerPort) ServerPort = 9000; if (Debug) fprintf (stdout, "|%s|%d|%s|\n", ServerHost, ServerPort, RequestUri); } /****************************************************************************/ /* Display non-informational messages output by the wsLIB library. */ void MessageCallback (struct WsLibStruct *wsptr) { char *cptr; struct ConnectionStruct *cxptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MessageCallback()\n"); cxptr = WsLibGetUserData (wsptr); cptr = WsLibMsgString (wsptr); if (*cptr == 'I') return; fprintf (stdout, "%%%s-W-WSLIB, error callback (WSLIB line %d)\n \\%s\\\n", Utility, WsLibMsgLineNumber(wsptr), cptr); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. This function has been customized for this utility to allow it to be called multiple times during the one use. */ void GetParameters ( int argc, char *argv[] ) { static char CommandLine [256]; static unsigned long Flags = 0; int acnt, status; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters() %d\n", argc); /**************/ /* initialize */ /**************/ if (argc <= 1) return; /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); aptr = NULL; ch = *clptr; for (;;) { if (aptr != NULL && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; /**************/ /* parameters */ /**************/ if (strsame (aptr, "/DBUG", -1)) { Debug = TRUE; continue; } if (strsame (aptr, "/CASE=", 5)) { while (*aptr && !isdigit(*aptr)) aptr++; CliCaseNumber = atoi(aptr); continue; } if (strsame (aptr, "/FRAME=", 5)) { while (*aptr && !isdigit(*aptr)) aptr++; CliFrameMax = atoi(aptr); continue; } if (strsame (aptr, "/MRS=", 5)) { while (*aptr && !isdigit(*aptr)) aptr++; CliSocketMrs = atoi(aptr); continue; } if (strsame (aptr, "/UPDATE", -1)) { CliUpdateReports = TRUE; continue; } if (strsame (aptr, "/VERSION", 4)) { DoShowVersion = TRUE; continue; } if (*aptr == '/') { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (!RequestUriPtr) { RequestUriPtr = aptr; continue; } fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns TRUE if two strings are the same, or FALSE if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ BOOL strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (FALSE); if (count) if (!--count) return (TRUE); } if (*sptr1 || *sptr2) return (FALSE); else return (TRUE); } /****************************************************************************/