/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* wsLIB.c WebSocket library for WASD persistent WebSocket applications (CGIplus scripts). https://www.rfc-editor.org/rfc/rfc6455.txt This library allows AST-driven, multi-client WebSocket applications. It also abstracts as much of the required functionality into callable functions using optional string descriptors so as to minimise dependency on the C language and on knowing the internals of the library data structure. Supplementary but not necessarily general-purpose client functionality is provided by WSLIBCL.C, and WSLIB.C contains some minor but necessary functionality to support not only the primary IPC via the mailboxes of the WASD CGIplus/WebSocket environment but also WebSocket comms via the TCP/IP socket established by the client connection functionality. The WebSocket read is best as a single thread (at least the client-driven logic) within the processing and the writes (and "WATCH:" callout) can support multiple, concurrent I/O per thread. Callout AST functions cannot themselves call WsLibWatchScript() or WsLibCallout() (i.e. a callout inside a callout). Check the application examples within the [SRC.WEBSOCKET] directory for illustrations of use. All sizes are unsigned longwords and allow ranges from 0 to 4294967295. The default content (and frame type) is 8 bit ASCII text which is implicitly converted to and from UTF-8 during reads and writes. This can also be explicitly set using WsLibSetAscii(). If the content is already UTF-8 or is required to remain UTF-8 then using WsLibSetUtf8() sets the conversion off. Of course binary content, set using WsLibSetBinary(), is opaque. The library contains WATCH points. Network [x]Data and [x]Script provide a useful combination of traffic data. The library function WsLibWatchScript() allows WebSocket applications (scripts) to provide additional WATCHable information via the [x]Script item. REQUEST SYNCHRONISATION ----------------------- wsLIB start-of-request synchronisation is based on the wait-process-EOF-wait loop model used by CGIlib, where WsLibCgiVar("") or WsLibCgiVarNull("") are used to wait and continue when a new request is available. The example WebSocket scripts all use this approach by default. wsLIB also provides an AST-driven model. The WsLibOnNextRequest() function should be called once during script initialisation specifying the address of the function to be notified of a new request. At that point the CGI variables become available and remain so until the script issues the CGIplus EOF sentinal, and the WsLibCgiVar..() family of functions can be used to retrieve the CGI variable values. This asynchronous and the synchronous WsLibCgiVar("") approaches to request synchronisation are mutually exclusive and once one is used using the other results in a bugcheck. CGI VARIABLES ------------- CGI variables using the WsLibCgiVar..() functions are only available after the next request has been indicated and before the CGIplus EOF sentinal is signalled. Any that are required for further processing need to be copied into separate storage from the pointer returned by the WsLibCgiVar..() functions. With the request synchronisation provided by WsLibCgiVar("") this is usually obvious enough. With the AST-driven request synchronisation provided by WsLibOnNextRequest() it might be less obvious. In any case the WsLibCgiVarAvailable() function can be used to test whether CGI variable are available at that time or not. WEBSOCKET MESSAGES ------------------ WebSocket data are transfered as autonomous messages that have text (UTF-8) or binary (opaque) content. Messages may be transported as multiple frames of data but this is transparent to the WebSocket application. By default, when writing WebSocket messages the data must remain available and unchanged for the duration of the transfer. With synchronous (blocking) writes this is implicit; for asynchronous (non-blocking) writes this must be assured. If WsLibSetBuffer()ed the message is copied (more expensive) to an internal buffer prior to being written, obviating the need to maintain a static source buffer. WsLibSetNoBuffer() resets this to the default. For WebSocket reads a fixed buffer may be provided, with a SS$_RESULTOVF error reported if the message is larger than the buffer provided. WsLibRead (, , , ); Alternatively, the wsLIB library can allocate a dynamic buffer appropriate to the size of the message being received. This allows considerable flexibility for applications. If the is NULL (zero) then dynamic message buffering occurs. WsLibRead (, NULL, 0, ); The default is for there to be no limit on the size of the buffered data (well, actually 2^32-1 but who's counting?) To provide an application limit on the size of the read buffer specify NULL for the and an integer for the maximum number of bytes allowed for the buffer. WsLibRead (, NULL, , ); For asynchronous I/O the allocated buffer is automatically freed unless WsLibReadGrab() is used during the AST delivery routine. For synchronous I/O a dynamic buffer remains allocated and must be grabbed and then explicitly freed by the in-line code after the I/O completes. BASE FRAMING PROTOCOL --------------------- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 Section 4.1 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/63) | |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 ... | +---------------------------------------------------------------+ FUNCTIONS --------- struct WsLibStruct* WsLibCreate (void *UserDataPtr, void *DestroyFunction) Allocates a websocket structure, sets the user data pointer to the supplied address, and returns a pointer to the new structure. All subsequent associated websocket processing requires that pointer. void WsLibDestroy (struct WsLibStruct *wsptr) Deprecated. (From v1.0.1 handled internally.) struct WsLibStruct* WsLibNext (struct WsLibStruct **WsLibCtx) Step through the list of structures. WsLibCtx is the address of pointer to WsLibStruct used to hold the context. Set to NULL to initialise. Returns non-null pointers for each structure in the list, then NULL when exhausted. Care must be exercised that multiple calls are not preempted by list modification (i.e. use within AST delivery or with ASTs disabled). int WsLibMsgDsc (struct WsLibStruct *wsptr, struct dsc$descriptor_s *MsgDsc); Sets the supplied descriptor to the latest error string. int WsLibMsgLineNumber (struct WsLibStruct *wsptr) Return the wsLIB source code line at which the last message occured. char* WsLibMsgString (struct WsLibStruct *wsptr) Returns a pointer to the latest error string. int WsLibOpen (struct WsLibStruct *wsptr, void *AstFunction) Using the device names from the CGI variables WEBSOCKET_INPUT and WEBSOCKET_OUTPUT assign channels in preparation for asynchronous I/O. The AST function is called at websocket close. int WsLibClose (struct WsLibStruct* wsptr, int StatusCode, char *StatusString) Send a close opcode and then shut the WebSocket down. Optional status code; if zero then a "normal" close status is added. Optional status string (description). int WsLibIsClosed (struct WsLibStruct* wsptr) Return true if the WebSocket has been closed. int WsLibConnected (struct WsLibStruct *wsptr) Return true if the input/output channels are connected. void WsLibFree (void *cptr) Free the allocated memory (currently C-RTL but may not remain so). To be used after WsLibReadGrab(). int WsLibRead (struct WsLibStruct *wsptr, char *DataPtr, int DataSize, void *AstFunction) Read data from the websocket into the supplied buffer. Function is non-blocking if AST function is supplied, otherwise blocking. When the read completes the status can be returned by WsLibReadStatus(), the read count by WsLibReadCount(), a pointer to the data buffer using WsLibReadData(), and a pointer to the internal read string descriptor using WsLibReadDataDsc(). int WsLibReadDsc (struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc, struct dsc$descriptor_s *ReadDsc, void *AstFunction) Read data from the websocket into the buffer described by DataDsc. Function is non-blocking if AST function is supplied, otherwise blocking. When completed ReadDsc (if supplied) is modified to reflect the read. int WsLibReadIsBinary (struct WsLibStruct *wsptr) Return true if the most recent read used the BINARY opcode, false otherwise. int WsLibReadIsText (struct WsLibStruct *wsptr) Return true if the most recent read used the TEXT opcode, false otherwise. int WsLibReadStatus (struct WsLibStruct *wsptr) Return the status value of the most recent read. int WsLibReadCount (struct WsLibStruct *wsptr) Return the number of bytes in the most recent read. char* WsLibReadData (struct WsLibStruct *wsptr) Return a pointer to char of the most recent read buffer. struct dsc$descriptor_s* WsLibReadDataDsc (struct WsLibStruct *wsptr) Return a pointer to the internal descriptor for the most recent read. char* WsLibReadGrab (struct WsLibStruct *wsptr) Return a pointer to char of the most recent read buffer. The read buffer is removed from the WebSocket structure and becomes the resource of the grabber. Must be WsLibFree()ed when no longer required. unsigned long* WsLibReadTotal (struct WsLibStruct *wsptr) Return a pointer to a quadword containing the total number of bytes read. void WsLibResetMsg (struct WsLibStruct *wsptr) Reset the latest message data. unsigned int WsLibTime (); Return the current wsLIB time in seconds (i.e. C-RTL, Unix time). int WsLibWrite (struct WsLibStruct *wsptr, char *DataPtr, int DataCount, void *AstFunction) Write the supplied data to the websocket. Function is non-blocking if AST function is supplied, otherwise blocking. If the AST function is supplied as (void*)-1 a non-blocking I/O is generated that does not require a target AST function. If DataPtr is NULL then a close is sent to the websocket. int WsLibWriteDsc (struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc, void *AstFunction) Write the data supplied by the descriptor to the websocket. Function is non-blocking if AST function is supplied, otherwise blocking. int WsLibWriteStatus (struct WsLibStruct *wsptr) Return the status value of the most recent write. int WsLibWriteCount (struct WsLibStruct *wsptr) Return the number of bytes in the most recent write. struct dsc$descriptor_s* WsLibWriteDataDsc (struct WsLibStruct *wsptr) Return a pointer to the internal descriptor for the most recent write. unsigned long* WsLibWriteTotal (struct WsLibStruct *wsptr) Return a pointer to a quadword containing the total number of bytes written. int WsLibPing (struct WsLibStruct *wsptr, char *DataPtr, int DataCount) Ping the remote end with 0 to 125 bytes of opaque data by char pointer. int WsLibPingDsc (struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc) Ping the remote end with 0 to 125 bytes of opaque data by string descr. int WsLibPong (struct WsLibStruct *wsptr, char *DataPtr, int DataCount) Ping the remote end with an unsolicted pong. int WsLibSetAscii (struct WsLibStruct *wsptr) Set the message content to 8 bit ASCII (and yes, I realise ASCII is only 7 bit but I needed a representative yet intuitive name for the function that's less easily confused with WedSocket 'text' content). A WebSocket set WsLibSetAscii() is implicitly converted to and from UTF-8 during writes and reads with WebSocket messages sent as 'text'. int WsLibSetBuffer (struct WsLibStruct *wsptr) Set the message content to be buffered internally before sending. This means the calling code does not need to maintain the underlying message buffer for the duration of the write. int WsLibSetNoBuffer (struct WsLibStruct *wsptr) Resets use of the internal buffer. int WsLibSetBinary (struct WsLibStruct *wsptr) Set the WebSocket message content to 'binary' and treat all reads and writes as opaque data. int WsLibSetUtf8 (struct WsLibStruct *wsptr) Set the WebSocket message content to 'text' and treat all reads and writes as UTF-8 data (no implicit conversions). Data must be legal UTF-8 or will/should be rejected by the local and/or remote end. void WsLibSetUserData (struct WsLibStruct *wsptr, void *UserDataPtr) Set the websocket structure to point to the supplied user data. void* WsLibGetUserData (struct WsLibStruct *wsptr) Return the current pointer to the supplied user data. void* WsLibSetCallout (struct WsLibStruct *wsptr, void *AstFunction) Set/reset the callout response AST. Returns the previous callout pointer. void* WsLibSetMsgCallback (struct WsLibStruct *wsptr, void *CallbackFunction) Set/reset the wsLIB error reporting callback. Returns the previous callback pointer. The error callback function is activated with a pointer to the WsLib data structure that has the MsgLineNumber and timestamp set and provides a descriptive string. int WsLibSetFrameMax (struct WsLibStruct *wsptr, int FrameMax); Set the maximum sized frame before a message is fragmented. void WsLibSetCloseSecs (struct WsLibStruct *wsptr, int CloseSecs); Set number of seconds before an unresponded-to WebSocket close is considered closed and disconnected. If a WebSocket is not specified then set global value. void WsLibSetIdleSecs (struct WsLibStruct *wsptr, int IdleSecs); Set number of seconds before a WebSocket is considered idle and closed. If a WebSocket is not specified then set global value. void WsLibSetLifeSecs (int LifeSecs); Set number of seconds before the application is considered idle and exited. void WsLibSetPingSecs (struct WsLibStruct *wsptr, int IdleSecs); Set number of seconds for periodic ping (heartbeat) of remote end. If a WebSocket is not specified then set global value. Remote end received a string containing and unsigned int ping number (1..n) followed by an unsigned int time in seconds (U*x epoch). void WsLibSetReadSecs (struct WsLibStruct *wsptr, int IdleSecs); Set number of seconds to wait for a read from a WebSocket before close. If a WebSocket is not specified then set global value. void* WsLibSetWakeCallback (struct WsLibStruct *wsptr, void *CallbackFunction, int WakesSecs) Set/reset the wsLIB periodic wakeup callback. Returns the previous callback pointer. Set the number of seconds between wakeup calls (zero defaults). int WsLibFromUtf8 (char *UtfPtr, int UtfCount, char SubsChar) Given a buffer of UTF-8 convert in-situ to 8 bit ASCII. Ignore non- 8 bit ASCII characters. End-of-string is indicated by text-length not a null-character, however the resultant string is nulled. If supplied the 8 bit character 'SubsChar' is substituted for any non 8 bit code in the string. Return the number of converted characters or -1 if there is an error. The input string is mangled if an error. int WsLibFromUtf8Dsc (struct dsc$descriptor_s *InDsc, struct dsc$descriptor_s *OutDsc, char SubsChar) Given a descriptor of UTF-8 convert in-situ to 8 bit ASCII. The output descriptor must be provided even if it points to the same storage as the input descriptor. Return the length of the converted string or -1 to indicated a conversion error. The input string is mangled if an error. int WsLibToUtf8 (char *InPtr, int InLength, char *OutPtr, int SizeOfOut) Given a buffer of 8 bit ASCII text convert it to UTF-8. This can be done in-situ with the worst-case the buffer space needs to be two times in size (i.e. every character has the hi bit set requiring a leading UTF-8 byte). End-of-string is indicated by text-length not a null-character, however the resultant string is nulled. Return the length of the converted strin or -1 if the insufficient buffer. int WsLibToUtf8Dsc (struct dsc$descriptor_s *InDsc, struct dsc$descriptor_s *OutDsc) Given a descriptor of 8 bit ASCII convert to 8 bit ASCII. Can be done in-situ. The output descriptor must be provided even if it points to the same storage as the input descriptor. Return the length of the converted string or -1 to indicated a conversion error. void WsLibWatchScript (struct WsLibStruct *wsptr, char *SourceModuleName, int SourceLineNumber, char *FormatString, ...) Outputs the $FAO-formatted string to the WASD WATCH [x]Script item. COPYRIGHT --------- Copyright (C) 2010-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. Function Utf8Legal() contains code ... Copyright (c) 2008-2009 Bjoern Hoehrmann See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. VERSION HISTORY --------------- 20-JAN-2021 MGD v1.2.0, VAX dependecies removed WASD_WSLIB_WATCH required to WATCH_SCRIPT 10-DEC-2016 MGD v1.1.1, bugfix; WsLibCreate() watch log file open bugfix; WsLibWatchScript() multiple 18-AUG-2014 MGD v1.1.0, WsLibOnNextRequest() asynchronous next request, WsLibSetBuffer(), WsLibSetNoBuffer() set the message to be buffered internally before sending (obviating he need to maintain a static buffer) 27-JUL-2013 MGD v1.0.7, bugfix; sigh! $setast() around critical I/Os 14-JUL-2013 MGD v1.0.6, add queueing to read operations bugfix; WriteAst() next message dequeue 02-JAN-2013 MGD v1.0.5, Shut() limit call count before $CANCEL bugfix; WsLibWrite(),WsLib_WriteAst() multiple concurrent messages (each may require multiple frames) need to be queued so that the frames (and messages) arrive in-order! 08-DEC-2012 MGD tidied some #includes 23-SEP-2012 MGD v1.0.4, "clean"-up response to client close 15-AUG-2012 MGD v1.0.3, refine WRITEOF and channel destruction bugfix; remove WATCH from Shut() :-} 21-JUL-2012 MGD v1.0.2, refine shut and destruction sequence add WASD_WSLIB_WATCH_LOG mechanism 26-NOV-2011 MGD v1.0.1, refine I/O removing all $QIOW WsLibClose() on all I/O non-success status 30-SEP-2011 MGD v1.0.0, draft-ietf-hybi-thewebsocketprotocol-17 31-AUG-2011 MGD draft-ietf-hybi-thewebsocketprotocol-13 23-AUG-2011 MGD draft-ietf-hybi-thewebsocketprotocol-11 11-JUL-2011 MGD draft-ietf-hybi-thewebsocketprotocol-10 13-JUN-2011 MGD draft-ietf-hybi-thewebsocketprotocol-09 07-JUN-2011 MGD draft-ietf-hybi-thewebsocketprotocol-07 (BANG!) 25-FEB-2011 MGD draft-ietf-hybi-thewebsocketprotocol-06 05-FEB-2011 MGD draft-ietf-hybi-thewebsocketprotocol-05 11-JAN-2011 MGD draft-ietf-hybi-thewebsocketprotocol-04 17-OCT-2010 MGD draft-ietf-hybi-thewebsocketprotocol-03 24-SEP-2010 MGD draft-ietf-hybi-thewebsocketprotocol-02 26-AUG-2010 MGD draft-ietf-hybi-thewebsocketprotocol-01 (initial) */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #define SOFTWAREVN "1.2.0" #define SOFTWARENM "WSLIB" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # error VAX no longer implemented #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wslib.h" /* from libmsg.h */ #define LIB$_INVSTRDES 1409572 #ifndef EFN$C_ENF # define EFN$C_ENF 128 /* Event No Flag (no stored state) */ #endif #define AGN$M_READONLY 0x01 #define AGN$M_WRITEONLY 0x02 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define MAX16BIT 65535 #define MAX32BIT 4294967295 #define WSLIB_STRUCT_BUFFER_SIZE 4096 #define FI_LI "WSLIB", __LINE__ #if 1 #define WATCH_WSLIB if(wsptr->WatchScript)WsLibWatchScript #else #define WATCH_WSLIB if(0)WsLibWatchScript #endif /* used by WSLIBCL.C */ static int EfnWait, EfnNoWait; static int CalloutDone = 0, CgiPlusEofLength = 0, CgiPlusEotLength = 0, CgiPlusEscLength = 0, CgiVariablesAvailable = 0, StructBufferLength = 0, StructBufferSize = 0, WwwPrefix = 0; static unsigned short CgiPlusInChannel; static unsigned long CurrentTime, WatchDogWakeTime; static unsigned long CurrentBinTime [2]; #define DEFAULT_WATCHDOG_CLOSE_SECS 5 #define DEFAULT_WATCHDOG_IDLE_SECS 120 #define DEFAULT_WATCHDOG_LIFE_SECS 120 #define DEFAULT_WATCHDOG_PING_SECS 600 #define DEFAULT_WATCHDOG_READ_SECS 60 #define DEFAULT_WATCHDOG_WAKE_SECS 60 static unsigned long WatchDogCloseSecs = DEFAULT_WATCHDOG_CLOSE_SECS, WatchDogIdleSecs = DEFAULT_WATCHDOG_IDLE_SECS, WatchDogLifeSecs = DEFAULT_WATCHDOG_LIFE_SECS, WatchDogPingSecs = DEFAULT_WATCHDOG_PING_SECS, WatchDogReadSecs = DEFAULT_WATCHDOG_READ_SECS, WatchDogWakeSecs = DEFAULT_WATCHDOG_WAKE_SECS; static char *CgiPlusEofPtr = NULL, *CgiPlusEotPtr = NULL, *CgiPlusEscPtr = NULL, *NextVarNamePtr = NULL, *StructBufferPtr = NULL; static struct WsLibStruct *ListHead; static void (*RequestSynchronisation)() = NULL, (*PongCallbackFunction)() = NULL, (*WakeCallbackFunction)() = NULL; static char SoftwareID [] = SOFTWAREID; #define SYI$_VERSION 4096 int sys$getsyi(__unknown_params); /*****************************************************************************/ /* Initialise the library. */ void WsLibInit () { static char GetSyiVer [8]; static struct { unsigned short buf_len; unsigned short item; void *buf_addr; void *ret_len; } SyiItem [] = { { sizeof(GetSyiVer)-1, SYI$_VERSION, &GetSyiVer, 0 }, { 0,0,0,0 } }; int status, VersionInteger; /*********/ /* begin */ /*********/ /* just the once! */ if (EfnWait) return; status = sys$getsyiw (0, 0, 0, &SyiItem, 0, 0, 0); if (VMSnok (status)) WsLibExit (NULL, FI_LI, status); VersionInteger = ((GetSyiVer[1]-48) * 100) + ((GetSyiVer[3]-48) * 10); if (GetSyiVer[4] == '-') VersionInteger += GetSyiVer[5]-48; if (VersionInteger >= 700) EfnWait = EfnNoWait = EFN$C_ENF; else { if (VMSnok (status = lib$get_ef (&EfnWait))) WsLibExit (NULL, FI_LI, status);; if (VMSnok (status = lib$get_ef (&EfnNoWait))) WsLibExit (NULL, FI_LI, status);; } WatchDog (); } /*****************************************************************************/ /* Return a pointer to the wsLIB version string. */ char* WsLibVersion () { /*********/ /* begin */ /*********/ return (SoftwareID); } /*****************************************************************************/ /* Sanity check the incoming request. Provide error or continue/upgrade response. Allocate a WebSocket I/O structure and set the internal user data storage. Insert at the head of the list. Return a pointer to the allocated structure. Can only WATCH_WSLIB after WsLibOpen(). */ struct WsLibStruct* WsLibCreate ( void *UserDataPtr, void *DestroyFunction ) { int astatus, SecWebSocketVersion; char *cptr, *sptr; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ if (!EfnWait) WsLibInit (); astatus = sys$setast (0); wsptr = (struct WsLibStruct*) calloc (1, sizeof(struct WsLibStruct)); if (!wsptr) WsLibExit (NULL, FI_LI, vaxc$errno); if ((cptr = getenv ("WASD_WSLIB_WATCH_LOG")) != NULL) if ((wsptr->WatchLog = fopen (cptr, "w", "shr=get")) == NULL) WsLibExit (NULL, FI_LI, vaxc$errno); /* if a scripting application running under the server */ if (WsLibCgiVarNull ("SERVER_SOFTWARE")) { if (cptr = WsLibCgiVarNull("WEBSOCKET_INPUT_MRS")) wsptr->InputMrs = atoi(cptr); else WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (cptr = WsLibCgiVarNull("WEBSOCKET_OUTPUT_MRS")) wsptr->OutputMrs = atoi(cptr); else WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (!(cptr = WsLibCgiVarNull("HTTP_SEC_WEBSOCKET_VERSION"))) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); SecWebSocketVersion = atoi(cptr); if (SecWebSocketVersion <= 0) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); /* this logical name is also detected by the WASD server */ if (!(sptr = getenv("WASD_WEBSOCKET_VERSION"))) /* this is the wsLIB header string */ sptr = WSLIB_WEBSOCKET_VERSION; while (*sptr) { if (atoi(sptr) == SecWebSocketVersion) break; while (isdigit(*sptr)) sptr++; while (*sptr && !isdigit(*sptr)) sptr++; } if (!*sptr) { /* WebSockets version not supported */ fprintf (stdout, "Status: 426 Upgrade Required\r\n\ Sec-Websocket-Version: %s\r\n\ \r\n", WSLIB_WEBSOCKET_VERSION); fflush (stdout); free (wsptr); return (NULL); } wsptr->WebSocketVersion = SecWebSocketVersion; /* connection acceptance response */ fprintf (stdout, "Status: 101 Switching Protocols\r\n\r\n"); fflush (stdout); } else { /* first number listed in the macro should be the current version */ wsptr->WebSocketVersion = atoi (WSLIB_WEBSOCKET_VERSION); /* the maximum socket record size is the maximum $QIO size */ wsptr->InputMrs = wsptr->OutputMrs = MAX16BIT; } wsptr->FrameMaxSize = MAX32BIT; wsptr->UserDataPtr = UserDataPtr; wsptr->DestroyAstFunction = DestroyFunction; wsptr->NextPtr = ListHead; ListHead = wsptr; if (astatus == SS$_WASSET) sys$setast (1); return (wsptr); } /*****************************************************************************/ /* Deprecated. Now is just a stub. */ void* WsLibDestroy (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->UserDataPtr); } /*****************************************************************************/ /* Remove from the list and free the allocated memory. Return any user data. */ static void Destroy (struct WsLibStruct *wsptr) { int astatus, cnt, status; struct WsLibStruct *wslptr; void *UserDataPtr; FILE *WatchLog; /*********/ /* begin */ /*********/ if (!wsptr) return; WATCH_WSLIB (wsptr, FI_LI, "DESTROY"); WatchLog = wsptr->WatchLog; astatus = sys$setast (0); UserDataPtr = wsptr->UserDataPtr; if (wsptr->InBufferSize) free (wsptr->InBufferPtr); if (wsptr->OutBufferSize) free (wsptr->OutBufferPtr); if (wsptr->MsgStringSize) free (wsptr->MsgStringPtr); if (wsptr->ClientHeaderSize) { /* free WLIBCL.C storage */ free (wsptr->ClientHeaderPtr); if (wsptr->ClientAcceptSize) free (wsptr->ClientAcceptPtr); if (wsptr->ClientKeySize) free (wsptr->ClientKeyPtr); if (wsptr->ClientServerSize) free (wsptr->ClientServerPtr); if (wsptr->ClientUriSize) free (wsptr->ClientUriPtr); } if ((wslptr = ListHead) == wsptr) ListHead = wslptr->NextPtr; else { while (wslptr->NextPtr != wsptr) wslptr = wslptr->NextPtr; wslptr->NextPtr = wsptr->NextPtr; } if (!wsptr->SocketChannel && wsptr->OutputChannel) sys$dassgn (wsptr->OutputChannel); free (wsptr); if (astatus == SS$_WASSET) sys$setast (1); if (WatchLog) fclose (WatchLog); } /*****************************************************************************/ /* Step through the list of structures. WsLibCtx is the address of a pointer used to hold the context. Set to NULL to initialise. Returns non-null pointers for each structure in the list, then NULL when list exhausted, to begin non-null pointers again. Care must be exercised that multiple calls are not preempted by a list modification (i.e. use within AST delivery or with ASTs disabled). */ struct WsLibStruct* WsLibNext (struct WsLibStruct **WsLibCtx) { int astatus; struct WsLibStruct *wsptr, *wslptr; /*********/ /* begin */ /*********/ astatus = sys$setast (0); /* let's be overcautious and make sure it's still in the list! */ if (wsptr = *WsLibCtx) { for (wslptr = ListHead; wslptr; wslptr = wslptr->NextPtr) if (wslptr == wsptr) break; if (!wslptr) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); *WsLibCtx = wsptr->NextPtr; } else *WsLibCtx = ListHead; if (astatus == SS$_WASSET) sys$setast (1); return (*WsLibCtx); } /*****************************************************************************/ /* Using the device names from the CGI variables WEBSOCKET_INPUT and WEBSOCKET_OUTPUT assign channels in preparation for asynchronous I/O. The AST function is called at WebSocket close. Default data is 8 bit ASCII (that requires implicit UTF-8 encoding). */ int WsLibOpen (struct WsLibStruct *wsptr) { int status; char *cptr, *sptr, *zptr; $DESCRIPTOR (MbxDsc, ""); /*********/ /* begin */ /*********/ wsptr->InputDataDsc.dsc$b_class = wsptr->OutputDataDsc.dsc$b_class = DSC$K_CLASS_S; wsptr->InputDataDsc.dsc$b_dtype = wsptr->OutputDataDsc.dsc$b_dtype = DSC$K_DTYPE_T; if (!(cptr = WsLibCgiVarNull("WEBSOCKET_INPUT"))) return (SS$_BUGCHECK); zptr = (sptr = wsptr->InputDevName) + sizeof(wsptr->InputDevName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; wsptr->InputDevDsc.dsc$b_class = DSC$K_CLASS_S; wsptr->InputDevDsc.dsc$b_dtype = DSC$K_DTYPE_T; wsptr->InputDevDsc.dsc$a_pointer = wsptr->InputDevName; wsptr->InputDevDsc.dsc$w_length = sptr - wsptr->InputDevName; if (!(cptr = WsLibCgiVarNull("WEBSOCKET_OUTPUT"))) return (SS$_BUGCHECK); zptr = (sptr = wsptr->OutputDevName) + sizeof(wsptr->OutputDevName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; wsptr->OutputDevDsc.dsc$b_class = DSC$K_CLASS_S; wsptr->OutputDevDsc.dsc$b_dtype = DSC$K_DTYPE_T; wsptr->OutputDevDsc.dsc$a_pointer = wsptr->OutputDevName; wsptr->OutputDevDsc.dsc$w_length = sptr - wsptr->OutputDevName; status = sys$assign (&wsptr->InputDevDsc, &wsptr->InputChannel, 0, 0, AGN$M_READONLY); if (VMSnok (status)) return (status); status = sys$assign (&wsptr->OutputDevDsc, &wsptr->OutputChannel, 0, 0, AGN$M_WRITEONLY); if (VMSnok (status)) { sys$dassgn (wsptr->InputChannel); wsptr->InputChannel = 0; return (status); } if (VMSnok (status)) { sys$dassgn (wsptr->InputChannel); sys$dassgn (wsptr->OutputChannel); wsptr->InputChannel = wsptr->OutputChannel = 0; return (status); } /* default data is 8 bit "ASCII" text (requiring implicit UTF-8 encoding) */ wsptr->SetAscii = 1; if (!(wsptr->WatchScript = (wsptr->WatchLog != NULL))) if (getenv ("WASD_WSLIB_WATCH") || getenv ("WASD_WSLIB_WATCH_LOG")) wsptr->WatchScript = (WsLibCgiVarNull("WATCH_SCRIPT") != NULL); if (wsptr->WatchDogPingSecs) wsptr->WatchDogPingTime = CurrentTime + wsptr->WatchDogPingSecs; WATCH_WSLIB (wsptr, FI_LI, "OPEN !AZ", SOFTWAREID); return (SS$_NORMAL); } /*****************************************************************************/ /* Initiate a close from the application end. Default status code is 1000 (normal closure). To suppress any status code delivery specify -1. WatchDog will shut if close doesn't happen in course. */ void WsLibClose ( struct WsLibStruct *wsptr, int StatusCode, char *StatusString ) { static char DummyBuffer [125]; int astatus, status, FramePayload; unsigned char *aptr, *cptr, *sptr, *zptr; struct WsLibFrmStruct *frmptr; struct WsLibMsgStruct *msgptr; /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "CLOSE closed:!UL code:!SL \"!AZ\"", wsptr->WebSocketClosed, StatusCode, StatusString ? StatusString : "(null)"); if (wsptr->WebSocketClosed) { Shut (wsptr); return; } wsptr->WebSocketClosed = 1; /* allocate a pointer plus a structure (freed by OutputFreeAst()) */ aptr = calloc (1, sizeof(struct WsLibStruct*) + sizeof(struct WsLibFrmStruct)); if (!aptr) WsLibExit (wsptr, FI_LI, vaxc$errno); *(struct WsLibStruct**)aptr = wsptr; frmptr = (struct WsLibFrmStruct*)(aptr + sizeof(struct WsLibStruct*)); if (!StatusCode) StatusCode = WSLIB_CLOSE_NORMAL; else if (StatusCode == WSLIB_CLOSE_BANG) StatusCode = 0; if (!StatusString) { switch (StatusCode) { case 0 : break; case 1000 : StatusString = "normal closure"; break; case 1001 : StatusString = "bye-bye"; break; case 1002 : StatusString = "protocol error"; break; case 1003 : StatusString = "received data unacceptable"; break; case 1004 : StatusString = "RESERVED"; break; case 1005 : StatusString = "RESERVED"; break; case 1006 : StatusString = "RESERVED"; break; case 1007 : StatusString = "received data inconsistency"; break; case 1008 : StatusString = "policy violation"; break; case 1009 : StatusString = "received message too big"; break; case 1010 : StatusString = "expected extention negotiation"; break; case 1011 : StatusString = "unexpected condition"; break; default : StatusString = "unknown opcode"; } } FramePayload = 0; frmptr->FrameHeader[0] = WSLIB_BIT_FIN | WSLIB_OPCODE_CLOSE; if (wsptr->RoleClient) { /* little messier with masking required */ MaskingKey (frmptr); frmptr->FrameHeader[2] = frmptr->MaskingKey[0]; frmptr->FrameHeader[3] = frmptr->MaskingKey[1]; frmptr->FrameHeader[4] = frmptr->MaskingKey[2]; frmptr->FrameHeader[5] = frmptr->MaskingKey[3]; if (StatusCode) { int kcnt = 0; char *kptr = (char*)frmptr->MaskingKey; frmptr->FrameHeader[6] = ((StatusCode & 0xff00) >> 8) ^ kptr[kcnt++&0x3]; frmptr->FrameHeader[7] = (StatusCode & 0xff) ^ kptr[kcnt++&0x3]; FramePayload = 2; if (StatusString) { zptr = (sptr = frmptr->FrameHeader + 8) + 123; for (cptr = (unsigned char*)StatusString; *cptr && sptr < zptr; *sptr++ = *cptr++ ^ kptr[kcnt++&0x3]) FramePayload++; } } frmptr->FrameHeader[1] = frmptr->FrameMaskBit | FramePayload; FramePayload += 6; } else { if (StatusCode) { frmptr->FrameHeader[2] = (StatusCode & 0xff00) >> 8; frmptr->FrameHeader[3] = StatusCode & 0xff; FramePayload = 2; if (StatusString) { zptr = (sptr = frmptr->FrameHeader + 4) + 123; for (cptr = (unsigned char*)StatusString; *cptr && sptr < zptr; *sptr++ = *cptr++) FramePayload++; } } frmptr->FrameHeader[1] = FramePayload; FramePayload += 2; } astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputFreeAst, aptr, frmptr->FrameHeader, FramePayload, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); if (StatusCode == WSLIB_CLOSE_NORMAL || StatusCode == WSLIB_CLOSE_BYEBYE || StatusCode == WSLIB_CLOSE_POLICY) { /************************************/ /* receive any close response frame */ /************************************/ msgptr = calloc (1, sizeof(struct WsLibMsgStruct)); if (!msgptr) WsLibExit (wsptr, FI_LI, vaxc$errno); msgptr->WsLibPtr = wsptr; msgptr->DataMax = MAX32BIT; msgptr->DataPtr = DummyBuffer; msgptr->DataSize = sizeof(DummyBuffer); msgptr->AstFunction = DummyClose; ReadFrame (msgptr); } else { /*********************/ /* significant error */ /*********************/ /* e.g. protocol error, do not try to continue */ Shut (wsptr); return; } } /*****************************************************************************/ /* Essentially just a dummy target for the dummy read initiated following an application WebSocket close, a read that allows any client response close frame to be received and processed. */ static void DummyClose (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "CLOSE response %X!8XL", wsptr->InputStatus); } /*****************************************************************************/ /* Respond to a close opcode from the client. */ static void Close (struct WsLibFrmStruct *frmptr) { int status, CloseStatus, FramePayload; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = frmptr->WsLibMsgPtr->WsLibPtr; if (frmptr->DataCount >= 2) { CloseStatus = ((unsigned char)frmptr->DataPtr[0] << 8) + (unsigned char)frmptr->DataPtr[1]; frmptr->DataCount -= 2; } else CloseStatus = 0; WATCH_WSLIB (wsptr, FI_LI, "CLOSE code:!UL!AZ!#AZ", CloseStatus, frmptr->DataCount ? " " : "", frmptr->DataCount, frmptr->DataPtr+2); if (frmptr->DataCount) MsgCallback (wsptr, __LINE__, SS$_SHUT, "CLOSE !UL !#AZ", CloseStatus, frmptr->DataCount, frmptr->DataPtr+2); else MsgCallback (wsptr, __LINE__, SS$_SHUT, "CLOSE 0"); if (!wsptr->WebSocketClosed) { /* send the close opcode */ wsptr->WebSocketClosed = 1; WATCH_WSLIB (wsptr, FI_LI, "CLOSE response"); /* allocate a frame structure (freed by CloseFreeAst()) */ frmptr = (struct WsLibFrmStruct*)calloc(1,sizeof(struct WsLibFrmStruct)); if (!frmptr) WsLibExit (wsptr, FI_LI, vaxc$errno); /* close frame header */ frmptr->FrameHeader[0] = WSLIB_BIT_FIN | WSLIB_OPCODE_CLOSE; frmptr->FrameHeader[1] = 0; /* send the opcode asynchronously delivering to a specific AST */ status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, CloseFreeAst, frmptr, frmptr->FrameHeader, 2, 0, 0, 0, 0); /* immediately do the WebSocket shutdown, resulting a "clean" close */ } Shut (wsptr); } /*****************************************************************************/ /* Just deallocate the memory containing the close frame from Close(). */ void CloseFreeAst (struct WsLibFrmStruct *frmptr) { /*********/ /* begin */ /*********/ free (frmptr); } /*****************************************************************************/ /* Return true if the WebSocket has been closed. */ int WsLibIsClosed (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->WebSocketClosed); } /*****************************************************************************/ /* Shutdown the websocket (this is different to the close handshake). Cancel any outstanding I/O and deassign channels. Call any supplied closure AST function. This function call be called multiple times before full shutdown accomplished. Returns success status when finally shut, abort if not (fully) shut (yet). */ int Shut (struct WsLibStruct *wsptr) { int astatus, status; /*********/ /* begin */ /*********/ if (wsptr->WatchLog) WATCH_WSLIB (wsptr, FI_LI, "Shut() destroy:!8XL(!UL) shut:!UL closed:!UL input:!UL output:!UL(!UL)", wsptr->DestroyAstFunction, wsptr->DestroyAstFunction == &Destroy, wsptr->WebSocketShut, wsptr->WebSocketClosed, wsptr->QueuedInput, wsptr->QueuedOutput, wsptr->ShutCount); if (wsptr->DestroyAstFunction == &Destroy) return (SS$_NORMAL); if (!wsptr->WebSocketShut) { if (wsptr->QueuedInput) sys$cancel (wsptr->InputChannel); if (!wsptr->SocketChannel) { /* let the server know the client is being disconnected */ astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITEOF | IO$M_NORSWAIT, 0, WriteEofAst, wsptr, 0, 0, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); } /* can be shut without having been closed (e.g. network error) */ wsptr->WebSocketShut = wsptr->WebSocketClosed = 1; } else if (wsptr->QueuedOutput) if (wsptr->ShutCount++ > wsptr->QueuedOutput) sys$cancel (wsptr->OutputChannel); /* if outstanding I/O */ if (wsptr->QueuedInput || wsptr->QueuedOutput) return (SS$_ABORT); if (wsptr->SocketChannel) { /* client interface in use */ sys$dassgn (wsptr->SocketChannel); wsptr->InputChannel = wsptr->OutputChannel = wsptr->SocketChannel = 0; } else { /* deassign of output channel is handled during destroy */ sys$dassgn (wsptr->InputChannel); wsptr->InputChannel = 0; } /* first queue any client's destruction code */ if (wsptr->DestroyAstFunction) sys$dclast (wsptr->DestroyAstFunction, wsptr, 0, 0); /* then queue the wsLIB structure destruction */ sys$dclast ((wsptr->DestroyAstFunction = &Destroy), wsptr, 0, 0); return (SS$_NORMAL); } /*****************************************************************************/ /* Decrement the queued I/O counter and then recall the shut function. */ static void WriteEofAst (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ if (wsptr->QueuedOutput) wsptr->QueuedOutput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); Shut (wsptr); } /*****************************************************************************/ /* Send a ping to the client using the (optional) data pointed to by the supplied string descriptor. For the responding pong to be notified a WsLibSetPongCallback() must previously have been set. */ int WsLibPingDsc ( struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc ) { int status; /*********/ /* begin */ /*********/ if (DataDsc == NULL) status = WsLibPing (wsptr, NULL, 0); else if (DataDsc->dsc$b_class != DSC$K_CLASS_S && DataDsc->dsc$b_dtype != DSC$K_DTYPE_T) status = LIB$_INVSTRDES; else status = PingPong (wsptr, DataDsc->dsc$a_pointer, DataDsc->dsc$w_length, WSLIB_OPCODE_PING); return (status); } /*****************************************************************************/ /* Wrapper for PingPong() to send a ping. */ int WsLibPing ( struct WsLibStruct *wsptr, char *DataPtr, int DataCount ) { /*********/ /* begin */ /*********/ return (PingPong (wsptr, DataPtr, DataCount, WSLIB_OPCODE_PING)); } /*****************************************************************************/ /* Wrapper for PingPong() to send an unsolicted pong. */ int WsLibPong ( struct WsLibStruct *wsptr, char *DataPtr, int DataCount ) { /*********/ /* begin */ /*********/ return (PingPong (wsptr, DataPtr, DataCount, WSLIB_OPCODE_PONG)); } /*****************************************************************************/ /* Send a ping/pong to the client using the (optional) data supplied. For the responding pong to be notified a SetPongCallback() must have been set. */ static int PingPong ( struct WsLibStruct *wsptr, char *DataPtr, int DataCount, int OpCode ) { int astatus, cnt, hcnt, status; char *aptr; struct WsLibFrmStruct *frmptr; /*********/ /* begin */ /*********/ if (OpCode == WSLIB_OPCODE_PING) WATCH_WSLIB (wsptr, FI_LI, "PING"); else WATCH_WSLIB (wsptr, FI_LI, "PONG"); if (wsptr->WebSocketClosed) { MsgCallback (wsptr, __LINE__, SS$_SHUT, "can't ping; closed"); return (SS$_SHUT); } if (DataCount > 125) DataCount = 125; /* allocate a pointer plus a structure (freed by OutputFreeAst()) */ aptr = calloc (1, sizeof(struct WsLibStruct*) + sizeof(struct WsLibFrmStruct)); if (!aptr) WsLibExit (wsptr, FI_LI, vaxc$errno); *(struct WsLibStruct**)aptr = wsptr; frmptr = (struct WsLibFrmStruct*)(aptr + sizeof(struct WsLibStruct*)); hcnt = 0; frmptr->FrameHeader[hcnt++] = WSLIB_BIT_FIN | OpCode; if (wsptr->RoleClient) { MaskingKey (frmptr); frmptr->FrameHeader[hcnt++] = frmptr->FrameMaskBit | DataCount; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[0]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[1]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[2]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[3]; for (cnt = 0; cnt < DataCount; cnt++) frmptr->FrameHeader[hcnt+cnt] = DataPtr[cnt] ^ frmptr->MaskingKey[frmptr->MaskCount++&0x3]; } else { frmptr->FrameHeader[hcnt++] = DataCount; for (cnt = 0; cnt < DataCount; cnt++) frmptr->FrameHeader[hcnt+cnt] = DataPtr[cnt]; } astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputFreeAst, aptr, frmptr->FrameHeader, hcnt+cnt, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); return (status); } /*****************************************************************************/ /* A ping header has been detected. Return a pong frame. */ static void Pong (struct WsLibFrmStruct *frmptr) { int astatus, cnt, hcnt, status, DataCount; char *aptr, *DataPtr; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = frmptr->WsLibMsgPtr->WsLibPtr; WATCH_WSLIB (wsptr, FI_LI, "PONG !UL", frmptr->DataCount); if (wsptr->WebSocketClosed) { MsgCallback (wsptr, __LINE__, SS$_SHUT, "can't pong; closed"); return; } /* retrieve any pinged data (max is 125 bytes) */ DataPtr = frmptr->DataPtr; if ((DataCount = frmptr->DataCount) > 125) DataCount = 125; /* allocate a pointer plus a structure (freed by OutputFreeAst()) */ aptr = calloc (1, sizeof(struct WsLibStruct*) + sizeof(struct WsLibFrmStruct)); if (!aptr) WsLibExit (wsptr, FI_LI, vaxc$errno); *(struct WsLibStruct**)aptr = wsptr; frmptr = (struct WsLibFrmStruct*)(aptr + sizeof(struct WsLibStruct*)); hcnt = 0; frmptr->FrameHeader[hcnt++] = WSLIB_BIT_FIN | WSLIB_OPCODE_PONG; if (wsptr->RoleClient) { MaskingKey (frmptr); frmptr->FrameHeader[hcnt++] = frmptr->FrameMaskBit | DataCount; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[0]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[1]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[2]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[3]; for (cnt = 0; cnt < DataCount; cnt++) frmptr->FrameHeader[hcnt+cnt] = DataPtr[cnt] ^ frmptr->MaskingKey[frmptr->MaskCount++&0x3]; } else { frmptr->FrameHeader[hcnt++] = DataCount; for (cnt = 0; cnt < DataCount; cnt++) frmptr->FrameHeader[hcnt+cnt] = DataPtr[cnt]; } astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputFreeAst, aptr, frmptr->FrameHeader, hcnt+cnt, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); if (VMSnok(status)) { MsgCallback (wsptr, __LINE__, status, "pong"); return; } } /*****************************************************************************/ /* Return true if both input and output channel connected. */ int WsLibConnected (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputChannel && wsptr->OutputChannel); } /*****************************************************************************/ /* Read data from the WebSocket client using a descriptor to describe the input buffer. If supplied the 'ReadDsc' is populated (pointer and length adjusted) on a successful read. If 'DataDsc' is NULL then a dynamic buffer with a maximum size of 65535 will be allocated as per description in WsLibRead(). */ int WsLibReadDsc ( struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc, struct dsc$descriptor_s *ReadDsc, void *AstFunction ) { int status; struct dsc$descriptor_s ScratchDsc; /*********/ /* begin */ /*********/ if (!DataDsc) { DataDsc = &ScratchDsc; DataDsc->dsc$b_class = DSC$K_CLASS_S; DataDsc->dsc$b_dtype = DSC$K_DTYPE_T; DataDsc->dsc$a_pointer = NULL; DataDsc->dsc$w_length = MAX16BIT; } if (DataDsc->dsc$b_class != DSC$K_CLASS_S && DataDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); if (ReadDsc->dsc$b_class != DSC$K_CLASS_S && ReadDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); wsptr->ReadDscPtr = ReadDsc; status = WsLibRead (wsptr, DataDsc->dsc$a_pointer, DataDsc->dsc$w_length, AstFunction); return (status); } /*****************************************************************************/ /* Read a message from WEBSOCKET_INPUT. A message can comprise of multiple frames if the message is fragmented. If an AST function is supplied then the read is asynchronous, otherwise blocking. When the read is complete the status can be returned by WsLibReadStatus(), the read count by WsLibReadCount(), a pointer to the data buffer using WsLibReadData(), and a pointer to the read string descriptor with WsLibReadDataDsc(). A data pointer to NULL can be supplied in which case data size becomes the maximum allowed frame size (if zero there is no limit) and an appropriately sized buffer is dynamically allocated (and deallocated) with each frame read. */ int WsLibRead ( struct WsLibStruct *wsptr, char *DataPtr, int DataSize, void *AstFunction ) { struct WsLibMsgStruct *msgptr, *nsgptr; /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "READ size:!UL", DataSize); if (wsptr->WebSocketClosed) { MsgCallback (wsptr, __LINE__, SS$_SHUT, "can't read; closed"); wsptr->InputStatus = SS$_SHUT; if (AstFunction) ((void(*)())AstFunction)(wsptr); return (SS$_SHUT); } msgptr = calloc (1, sizeof(struct WsLibMsgStruct)); if (!msgptr) WsLibExit (wsptr, FI_LI, vaxc$errno); msgptr->WsLibPtr = wsptr; if (DataPtr) msgptr->DataMax = 0; else { /* the maxiumum sized message allowed to be buffered */ if (DataSize) msgptr->DataMax = DataSize; else msgptr->DataMax = MAX32BIT; } msgptr->DataPtr = DataPtr; msgptr->DataSize = DataSize; msgptr->AstFunction = AstFunction; if (wsptr->MsgReadQuePtr) { /* append this one to the read queue */ for (nsgptr = wsptr->MsgReadQuePtr; nsgptr->MsgQuePtr; nsgptr = nsgptr->MsgQuePtr); nsgptr->MsgQuePtr = msgptr; } else { /* only this one in the queue */ wsptr->MsgReadQuePtr = msgptr; /* either kick-off asynchronous I/O or return after blocking I/O */ ReadFrame (msgptr); } return (wsptr->InputStatus); } /*****************************************************************************/ /* Read a frame (can be a fragment). Read six bytes. See ReadHeader1Ast() for breakdown. */ static void ReadFrame (struct WsLibMsgStruct *msgptr) { struct WsLibFrmStruct *frmptr; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = msgptr->WsLibPtr; WATCH_WSLIB (wsptr, FI_LI, "READ frame"); frmptr = &msgptr->FrameData; if (frmptr->IOsb.iosb$w_status) memset (frmptr, 0, sizeof(struct WsLibFrmStruct)); frmptr->WsLibMsgPtr = msgptr; /* if the client interface is in use then it's TCP/IP */ if (wsptr->SocketChannel) /* not really needed on a TCP connection but seems to do no harm! */ frmptr->IoRead = IO$_READLBLK | IO$M_WRITERCHECK; else frmptr->IoRead = IO$_READLBLK | IO$M_STREAM | IO$M_WRITERCHECK; if (wsptr->RoleClient) frmptr->ReadSize = 2; else frmptr->ReadSize = 6; frmptr->IOsb.iosb$w_bcnt = 0; frmptr->IOsb.iosb$w_status = SS$_NORMAL; wsptr->QueuedInput++; ReadHeader1Ast (frmptr); } /*****************************************************************************/ /* First two or six bytes of the frame have been read. 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/63) | |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 ... | +---------------------------------------------------------------+ These can be ... 1) the 9+7 header bits, includes payload length if less than 126 bytes. 2) the 9+7 header bits, includes payload length if less than 126 bytes, plus any 32 bit masking-key. 3) the 9+7 header bits, plus the word of the 16 bit extended payload length, plus the first two bytes of any masking-key. 4) the 9+7 header bits, plus the first 4 bytes of the 64 bit extended payload length. If the header payload length is less than 126 then the header is complete, if 126 then read another 4 bytes to get the masking-key, if 127 then read another 10 bytes to complete the extended (64 bit) payload length and to get the masking-key. Perform header sanity checking. */ static void ReadHeader1Ast (struct WsLibFrmStruct *frmptr) { int astatus, status; struct WsLibStruct *wsptr; struct WsLibMsgStruct *msgptr; /*********/ /* begin */ /*********/ msgptr = frmptr->WsLibMsgPtr; wsptr = msgptr->WsLibPtr; if (wsptr->QueuedInput) wsptr->QueuedInput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WatchDogReadSecs) wsptr->WatchDogReadTime = CurrentTime + wsptr->WatchDogReadSecs; if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; /* loop until the required number of bytes have been read */ while (VMSok (frmptr->IOsb.iosb$w_status)) { /* if data is not available mailbox $QIO stream reads return less */ frmptr->FrameCount += frmptr->IOsb.iosb$w_bcnt; frmptr->ReadSize -= frmptr->IOsb.iosb$w_bcnt; if (!frmptr->ReadSize) break; if (msgptr->AstFunction) { /* a loop with an intermediate AST delivery! */ astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, ReadHeader1Ast, frmptr, frmptr->FrameHeader+frmptr->FrameCount, frmptr->ReadSize, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedInput++; if (astatus == SS$_WASSET) sys$setast (1); return; } sys$qiow (EfnWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, 0, 0, frmptr->FrameHeader+frmptr->FrameCount, frmptr->ReadSize, 0, 0, 0, 0); } if (VMSnok (frmptr->IOsb.iosb$w_status)) { wsptr->QueuedInput++; ReadDataAst (frmptr); return; } /* check if frame data is masked */ frmptr->FrameMaskBit = frmptr->FrameHeader[1] & 0x80; if (wsptr->RoleClient) { /* if the server is indicating it's masking frames */ if (frmptr->FrameMaskBit && frmptr->FrameCount == 2) { /* need four more octets!! */ frmptr->ReadSize = 4; wsptr->QueuedInput++; ReadHeader1Ast (frmptr); return; } } else { if (!frmptr->FrameMaskBit) { /* all client->server frames must be masked */ WATCH_WSLIB (wsptr, FI_LI, "CLIENT frame not masked 0x!2XL!2XL", frmptr->FrameHeader[0], frmptr->FrameHeader[1]); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "client frame not masked 0x!2XL!2XL", frmptr->FrameHeader[0], frmptr->FrameHeader[1]); strcpy (msgptr->CloseMsg, "client frame not masked"); goto ProtocolError; } } /* process rest of frame header */ frmptr->FrameFinBit = frmptr->FrameHeader[0] & 0x80; frmptr->FrameRsv = frmptr->FrameHeader[0] & 0x70; frmptr->FrameOpcode = frmptr->FrameHeader[0] & 0x0f; frmptr->FramePayload = frmptr->FrameHeader[1] & 0x7f; if (frmptr->FrameRsv) { /* reserve bits set */ WATCH_WSLIB (wsptr, FI_LI, "RESERVE bit 0x!2XL", frmptr->FrameRsv); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "reserve bit 0x!2XL", frmptr->FrameRsv); sprintf (msgptr->CloseMsg, "reserve bit 0x%02x", frmptr->FrameRsv); goto ProtocolError; } switch (frmptr->FrameOpcode) { case WSLIB_OPCODE_CONTIN : break; case WSLIB_OPCODE_TEXT : break; case WSLIB_OPCODE_BINARY : break; case WSLIB_OPCODE_CLOSE : break; case WSLIB_OPCODE_PING : break; case WSLIB_OPCODE_PONG : break; default : { /* unknown opcode */ WATCH_WSLIB (wsptr, FI_LI, "OPCODE unknown 0x!2XL", frmptr->FrameOpcode); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "unknown opcode 0x!2XL", frmptr->FrameOpcode); sprintf (msgptr->CloseMsg, "unknown opcode 0x%02x", frmptr->FrameOpcode); goto ProtocolError; } } if (frmptr->FrameOpcode & 0x8) { /* control opcode */ if (!frmptr->FrameFinBit) { WATCH_WSLIB (wsptr, FI_LI, "CONTROL frame fragmented"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "control frame fragmented"); strcpy (msgptr->CloseMsg, "control frame fragmented"); goto ProtocolError; } if (frmptr->FramePayload > 125) { WATCH_WSLIB (wsptr, FI_LI, "CONTROL payload > 125 bytes"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "control payload > 125 bytes"); strcpy (msgptr->CloseMsg, "control payload > 125 bytes"); goto ProtocolError; } } else if (frmptr->FrameFinBit) { /* FIN bit set */ if (frmptr->FrameOpcode) { if (msgptr->MsgOpcode) { /* must not have an opcode */ WATCH_WSLIB (wsptr, FI_LI, "FRAGMENT with opcode"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "fragment with opcode"); strcpy (msgptr->CloseMsg, "fragment with opcode"); goto ProtocolError; } msgptr->MsgOpcode = frmptr->FrameOpcode; } else { if (!msgptr->MsgOpcode) { /* must have an opcode */ WATCH_WSLIB (wsptr, FI_LI, "FRAME without opcode"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "frame without opcode"); strcpy (msgptr->CloseMsg, "frame without opcode"); goto ProtocolError; } } } else { /* FIN bit reset */ if (msgptr->MsgOpcode) { if (frmptr->FrameOpcode) { /* subsequent fragments must not have an opcode */ WATCH_WSLIB (wsptr, FI_LI, "FRAGMENT with opcode"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "fragment with opcode"); strcpy (msgptr->CloseMsg, "fragment with opcode"); goto ProtocolError; } } else { if (!frmptr->FrameOpcode) { /* fragments must have an initial opcode */ WATCH_WSLIB (wsptr, FI_LI, "FRAGMENT without opcode"); MsgCallback (wsptr, __LINE__, SS$_PROTOCOL, "fragment without opcode"); strcpy (msgptr->CloseMsg, "fragment without opcode"); goto ProtocolError; } msgptr->MsgOpcode = frmptr->FrameOpcode; } } if (frmptr->FrameCount == 6) { /* essentially a longword integer in network byte order */ frmptr->MaskingKey[0] = frmptr->FrameHeader[2]; frmptr->MaskingKey[1] = frmptr->FrameHeader[3]; frmptr->MaskingKey[2] = frmptr->FrameHeader[4]; frmptr->MaskingKey[3] = frmptr->FrameHeader[5]; } /* prepare to read more */ frmptr->IOsb.iosb$w_bcnt = 0; frmptr->IOsb.iosb$w_status = SS$_NORMAL; wsptr->QueuedInput++; if (frmptr->FramePayload == 126) { /* read more of the header */ frmptr->ReadSize = 2; ReadHeader2Ast (frmptr); return; } if (frmptr->FramePayload == 127) { /* read more of the header */ frmptr->ReadSize = 8; ReadHeader2Ast (frmptr); return; } /* frame length is 125 bytes or less, begin reading data */ ReadDataAst (frmptr); return; ProtocolError: frmptr->IOsb.iosb$w_bcnt = 0; frmptr->IOsb.iosb$w_status = SS$_PROTOCOL; wsptr->QueuedInput++; ReadDataAst (frmptr); return; } /*****************************************************************************/ /* The data $QIOed by ReadHeader1Ast() has been read. For frames less than 126 bytes this function will not be called. */ static void ReadHeader2Ast (struct WsLibFrmStruct *frmptr) { int astatus, cnt, status; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = frmptr->WsLibMsgPtr->WsLibPtr; if (wsptr->QueuedInput) wsptr->QueuedInput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WatchDogReadSecs) wsptr->WatchDogReadTime = CurrentTime + wsptr->WatchDogReadSecs; if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; /* loop until the required number of bytes have been read */ while (VMSok (frmptr->IOsb.iosb$w_status)) { /* if data is not available mailbox $QIO stream reads return less */ frmptr->FrameCount += frmptr->IOsb.iosb$w_bcnt; frmptr->ReadSize -= frmptr->IOsb.iosb$w_bcnt; if (!frmptr->ReadSize) break; if (frmptr->WsLibMsgPtr->AstFunction) { /* a loop with an intermediate AST delivery! */ astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, ReadHeader2Ast, frmptr, frmptr->FrameHeader+frmptr->FrameCount, frmptr->ReadSize, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedInput++; if (astatus == SS$_WASSET) sys$setast (1); return; } sys$qiow (EfnWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, 0, 0, frmptr->FrameHeader+frmptr->FrameCount, frmptr->ReadSize, 0, 0, 0, 0); } if (VMSnok (frmptr->IOsb.iosb$w_status)) { wsptr->QueuedInput++; ReadDataAst (frmptr); return; } if (frmptr->FramePayload == 126) { /* word integer in network byte order */ frmptr->FramePayload = (frmptr->FrameHeader[2] << 8) + frmptr->FrameHeader[3]; if (frmptr->FrameCount == 8) { /* essentially a longword integer in network byte order */ frmptr->MaskingKey[0] = frmptr->FrameHeader[4]; frmptr->MaskingKey[1] = frmptr->FrameHeader[5]; frmptr->MaskingKey[2] = frmptr->FrameHeader[6]; frmptr->MaskingKey[3] = frmptr->FrameHeader[7]; } } else if (frmptr->FramePayload == 127) { /* protocol octaword integer in network byte order */ if (frmptr->FrameHeader[2] || frmptr->FrameHeader[3] || frmptr->FrameHeader[4] || frmptr->FrameHeader[5]) { /* if >2^32 then something's probably wrong */ MsgCallback (wsptr, __LINE__, SS$_BUGCHECK, "frame length sanity check"); frmptr->IOsb.iosb$w_bcnt = 0; frmptr->IOsb.iosb$w_status = SS$_BUGCHECK; wsptr->QueuedInput++; ReadDataAst (frmptr); return; } /* quadword integer in network byte order (lowest 32 bits anyway) */ frmptr->FramePayload = (frmptr->FrameHeader[6] << 24) + (frmptr->FrameHeader[7] << 16) + (frmptr->FrameHeader[8] << 8) + frmptr->FrameHeader[9]; if (frmptr->FrameCount == 14) { /* essentially a longword integer in network byte order */ frmptr->MaskingKey[0] = frmptr->FrameHeader[10]; frmptr->MaskingKey[1] = frmptr->FrameHeader[11]; frmptr->MaskingKey[2] = frmptr->FrameHeader[12]; frmptr->MaskingKey[3] = frmptr->FrameHeader[13]; } } else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); /* the frame length is now known, begin reading data */ frmptr->IOsb.iosb$w_bcnt = 0; frmptr->IOsb.iosb$w_status = SS$_NORMAL; wsptr->QueuedInput++; ReadDataAst (frmptr); } /*****************************************************************************/ /* Called when the length of the frame has been determined. This repeatedly reads mailbox-sized chunks of the frame (if necessary), asynchronously or synchronously as required, until the frame is completely read (or an error occurs). The '->AstFunction' or blocking caller should drive the client-end business logic. */ static void ReadDataAst (struct WsLibFrmStruct *frmptr) { int astatus, cnt, status, DataCount, DataSize; char *DataPtr; struct WsLibStruct *wsptr; struct WsLibMsgStruct *msgptr; /*********/ /* begin */ /*********/ msgptr = frmptr->WsLibMsgPtr; wsptr = frmptr->WsLibMsgPtr->WsLibPtr; if (wsptr->QueuedInput) wsptr->QueuedInput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WatchDogReadSecs) wsptr->WatchDogReadTime = CurrentTime + wsptr->WatchDogReadSecs; if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; while (VMSok (frmptr->IOsb.iosb$w_status)) { if (!frmptr->DataPtr) { /* first call */ WATCH_WSLIB (wsptr, FI_LI, "READ header:!UL opcode:!2XL(!AZ) payload:!UL fin:!UL mask:!UL", frmptr->FrameCount, frmptr->FrameOpcode, OpCodeName(frmptr->FrameOpcode), frmptr->FramePayload, frmptr->FrameFinBit ? 1 : 0, frmptr->FrameMaskBit ? 1 : 0); /* establish buffer */ if (frmptr->FramePayload <= 125) { /* use internal frame buffer */ frmptr->DataPtr = (char*)frmptr->FrameHeader + frmptr->FrameCount; frmptr->DataSize = 125; } else { /* allocated frame data buffer */ frmptr->DataSize = frmptr->FramePayload; /* ensure that even for zero payload some memory is allocated */ frmptr->DataPtr = calloc (1, frmptr->DataSize+16); if (!frmptr->DataPtr) WsLibExit (wsptr, FI_LI, vaxc$errno); } } if (frmptr->IOsb.iosb$w_bcnt) { DataPtr = frmptr->DataPtr + frmptr->DataCount; if (msgptr->MsgOpcode == WSLIB_OPCODE_TEXT) { /* will also apply masking key if required */ if (!Utf8Legal (frmptr)) { WATCH_WSLIB (wsptr, FI_LI, "UTF-8 illegal (fast fail)"); frmptr->IOsb.iosb$w_status = SS$_BADESCAPE; strcpy (msgptr->CloseMsg, "UTF-8 illegal"); /* deliver the status to the application */ break; } } else if (frmptr->FrameMaskBit) { /* apply masking key */ for (cnt = 0; cnt < frmptr->IOsb.iosb$w_bcnt; cnt++) DataPtr[cnt] ^= frmptr->MaskingKey[frmptr->MaskCount++&0x3]; } frmptr->DataCount += frmptr->IOsb.iosb$w_bcnt; } WATCH_WSLIB (wsptr, FI_LI, "READ inque:!UL payload:!UL/!UL !AZ", wsptr->QueuedInput, frmptr->DataCount, frmptr->FramePayload, frmptr->DataCount >= frmptr->FramePayload ? "COMPLETE" : "in-progress"); if (frmptr->DataCount >= frmptr->FramePayload) break; DataPtr = frmptr->DataPtr + frmptr->DataCount; if (frmptr->FramePayload - frmptr->DataCount <= wsptr->InputMrs) DataCount = frmptr->FramePayload - frmptr->DataCount; else DataCount = wsptr->InputMrs; if (frmptr->DataCount + DataCount > frmptr->DataSize) DataCount = frmptr->DataSize - frmptr->DataCount; if (msgptr->AstFunction) { /* asynchronous read */ astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, ReadDataAst, frmptr, DataPtr, DataCount, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedInput++; if (astatus == SS$_WASSET) sys$setast (1); return; } /* synchronous read */ sys$qiow (EfnWait, wsptr->InputChannel, frmptr->IoRead, &frmptr->IOsb, 0, 0, DataPtr, DataCount, 0, 0, 0, 0); } WATCH_WSLIB (wsptr, FI_LI, "READ %X!8XL", frmptr->IOsb.iosb$w_status); if (VMSnok (frmptr->IOsb.iosb$w_status) && frmptr->IOsb.iosb$w_status != SS$_LINKDISCON && !wsptr->MsgStringLength) MsgCallback (wsptr, __LINE__, frmptr->IOsb.iosb$w_status, "frame read"); /******************/ /* frame complete */ /******************/ if (VMSok (frmptr->IOsb.iosb$w_status)) { if (frmptr->FrameOpcode == WSLIB_OPCODE_PING) { Pong (frmptr); /* restart the read of the data */ ReadFrame (msgptr); return; } if (frmptr->FrameOpcode == WSLIB_OPCODE_PONG) { /* process any (solicited or unsolicited) pong callback */ if (wsptr->PongCallbackFunction) (*wsptr->PongCallbackFunction)(wsptr); else if (PongCallbackFunction) (*PongCallbackFunction)(wsptr); else MsgCallback (wsptr, __LINE__, SS$_NOTMODIFIED, "no pong callback"); /* restart the read of the data */ ReadFrame (msgptr); return; } if (frmptr->FrameOpcode == WSLIB_OPCODE_CLOSE) { Close (frmptr); frmptr->IOsb.iosb$w_status = SS$_SHUT; /* deliver closed status to the application */ } } /*****************/ /* build message */ /*****************/ msgptr->MsgStatus = frmptr->IOsb.iosb$w_status; if (VMSok (msgptr->MsgStatus)) { /* accumulate total message data transfered */ *(int64*)wsptr->InputCount = *(int64*)wsptr->InputCount + (int32)frmptr->DataCount; if (!(DataSize = msgptr->DataMax)) DataSize = msgptr->DataSize; if (msgptr->DataCount + frmptr->DataCount > (unsigned)DataSize) { MsgCallback (wsptr, __LINE__, SS$_RESULTOVF, "message !UL bytes > buffer !UL bytes", msgptr->DataCount + frmptr->DataCount, DataSize); msgptr->MsgStatus = SS$_RESULTOVF; msgptr->DataCount = 0; } else if (msgptr->DataMax) { /* dynamic buffer */ if (msgptr->DataPtr) { if (frmptr->DataCount) { /* append this fragment to previously buffered data */ msgptr->DataPtr = realloc (msgptr->DataPtr, msgptr->DataCount + frmptr->DataCount); if (!msgptr->DataPtr) WsLibExit (wsptr, FI_LI, vaxc$errno); memcpy (msgptr->DataPtr + msgptr->DataCount, frmptr->DataPtr, frmptr->DataCount); msgptr->DataCount += frmptr->DataCount; } } else { /* ensure that even for zero payload some memory is allocated */ msgptr->DataPtr = calloc (1, frmptr->DataCount+16); if (!msgptr->DataPtr) WsLibExit (wsptr, FI_LI, vaxc$errno); memcpy (msgptr->DataPtr, frmptr->DataPtr, frmptr->DataCount); msgptr->DataCount = frmptr->DataCount; } } else { /* supplied buffer */ memcpy (msgptr->DataPtr + msgptr->DataCount, frmptr->DataPtr, frmptr->DataCount); msgptr->DataCount += frmptr->DataCount; } /* dispose any allocated frame data buffer */ if (frmptr->FramePayload > 125) free (frmptr->DataPtr); if (VMSok (msgptr->MsgStatus)) { /* if not the final fragment */ if (!frmptr->FrameFinBit) { /* read the next fragment frame */ ReadFrame (msgptr); return; } } } if (VMSok (msgptr->MsgStatus)) { if (msgptr->MsgOpcode == WSLIB_OPCODE_TEXT) { /* ensure any code-point is complete */ frmptr->IOsb.iosb$w_bcnt = 0; if (!Utf8Legal (frmptr)) { WATCH_WSLIB (wsptr, FI_LI, "UTF-8 illegal (fast fail)"); msgptr->MsgStatus = SS$_BADESCAPE; strcpy (msgptr->CloseMsg, "UTF-8 illegal"); /* deliver the status to the application */ } } } /****************************/ /* deliver to read function */ /****************************/ if (VMSok (msgptr->MsgStatus)) { *(int64*)wsptr->InputMsgCount = *(int64*)wsptr->InputMsgCount + 1; if (msgptr->MsgOpcode == WSLIB_OPCODE_TEXT) { /* for text always better if it's null-terminated */ if (msgptr->DataMax || msgptr->DataCount < msgptr->DataSize) msgptr->DataPtr[msgptr->DataCount] = '\0'; else MsgCallback (wsptr, __LINE__, SS$_BUFFEROVF, "no space for \\0"); if (wsptr->SetAscii) { /* convert from UTF-8 to 8 bit "ASCII" */ WATCH_WSLIB (wsptr, FI_LI, "UTF-8 decode"); cnt = WsLibFromUtf8 (msgptr->DataPtr, msgptr->DataCount, 0); if (cnt >= 0) msgptr->DataCount = cnt; else { /* error in UTF-8 to ASCII conversion */ WATCH_WSLIB (wsptr, FI_LI, "UTF-8 decode ERROR"); MsgCallback (wsptr, __LINE__, SS$_DATALOST, "UTF-8 decode error"); msgptr->MsgStatus = SS$_DATALOST; msgptr->DataCount = 0; } } } } if (msgptr->DataMax) { /* the ->MsgDataPtr is only used by WsLibReadGrab() for sanity check */ wsptr->InputDataPtr = wsptr->MsgDataPtr = msgptr->DataPtr; } wsptr->InputStatus = msgptr->MsgStatus; wsptr->InputOpcode = msgptr->MsgOpcode; wsptr->InputDataCount = msgptr->DataCount; wsptr->InputDataPtr = wsptr->InputDataDsc.dsc$a_pointer = msgptr->DataPtr; if (msgptr->DataCount > MAX16BIT) wsptr->InputDataDsc.dsc$w_length = MAX16BIT; else wsptr->InputDataDsc.dsc$w_length = msgptr->DataCount; if (msgptr->DataMax) wsptr->InputDataMax = msgptr->DataMax; else wsptr->InputDataMax = msgptr->DataSize; if (wsptr->ReadDscPtr) { wsptr->ReadDscPtr->dsc$a_pointer = msgptr->DataPtr; if (msgptr->DataCount > MAX16BIT) wsptr->ReadDscPtr->dsc$w_length = MAX16BIT; else wsptr->ReadDscPtr->dsc$w_length = msgptr->DataCount; } if (wsptr->InputStatus == SS$_PROTOCOL) WsLibClose (wsptr, WSLIB_CLOSE_PROTOCOL, msgptr->CloseMsg); else if (wsptr->InputStatus == SS$_BADESCAPE) WsLibClose (wsptr, WSLIB_CLOSE_DATA, msgptr->CloseMsg); else if (VMSnok (wsptr->InputStatus)) WsLibClose (wsptr, WSLIB_CLOSE_BANG, NULL); /* do not molest the buffer pointers if synchronous I/O */ if (msgptr->AstFunction) { msgptr->AstFunction (wsptr); if (!DataPtr && msgptr->DataMax) { /* if the dynamic message buffer has not been grabbed then free it */ if (wsptr->InputDataPtr) free (wsptr->InputDataPtr); /* reset the rest */ wsptr->InputDataPtr = wsptr->MsgDataPtr = NULL; wsptr->InputDataCount = wsptr->InputDataMax = 0; wsptr->InputDataDsc.dsc$a_pointer = NULL; wsptr->InputDataDsc.dsc$w_length = 0; wsptr->ReadDscPtr = NULL; } } free (msgptr); /* message dequeue */ if (msgptr = wsptr->MsgReadQuePtr = msgptr->MsgQuePtr) { /* kick-off the next queued read */ ReadFrame (msgptr); } wsptr->WatchDogReadTime = 0; if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; if (wsptr->WatchDogWakeSecs) wsptr->WatchDogWakeTime = CurrentTime + wsptr->WatchDogWakeSecs; if (wsptr->WebSocketShut) Shut (wsptr); } /*****************************************************************************/ /* When using dynamic message data buffer grab the allocated memory returning a pointer or NULL. The data count and other detail can be obtained using WsLibReadCount(), WsLibReadIsText(), etc. This should subsequently be freed using WsLibFree() when no longer required. */ char* WsLibReadGrab (struct WsLibStruct *wsptr) { char *cptr; /*********/ /* begin */ /*********/ /* sanity check the call */ if (wsptr->InputDataPtr && wsptr->InputDataPtr == wsptr->MsgDataPtr) { cptr = wsptr->InputDataPtr; wsptr->InputDataPtr = wsptr->MsgDataPtr = NULL; wsptr->InputDataCount = wsptr->InputDataMax = 0; wsptr->InputDataDsc.dsc$a_pointer = NULL; wsptr->InputDataDsc.dsc$w_length = 0; return (cptr); } WATCH_WSLIB (wsptr, FI_LI, "GRAB sanity check"); MsgCallback (wsptr, __LINE__, SS$_BUGCHECK, "GRAB sanity check (!AZ)", wsptr->InputDataPtr ? "null" : "data"); return (NULL); } /*****************************************************************************/ /* Return true if the most recent read was BINARY opcode. */ int WsLibReadIsBinary (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputOpcode == WSLIB_OPCODE_BINARY); } /*****************************************************************************/ /* Return true if the most recent read was TEXT opcode. */ int WsLibReadIsText (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputOpcode == WSLIB_OPCODE_TEXT); } /*****************************************************************************/ /* Return the read status value. */ int WsLibReadStatus (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputStatus); } /*****************************************************************************/ /* Return the (most recent) read count value (longword). */ int WsLibReadCount (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputDataCount); } /*****************************************************************************/ /* Return a pointer to the (most recent) read buffer. */ char* WsLibReadData (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->InputDataPtr); } /*****************************************************************************/ /* Return a pointer to the (most recent) read data descriptor. */ struct dsc$descriptor_s* WsLibReadDataDsc (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (&wsptr->InputDataDsc); } /*****************************************************************************/ /* Return a pointer to the total read count value (quadword). */ unsigned long* WsLibReadTotal (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return ((unsigned long*)&wsptr->InputCount); } /*****************************************************************************/ /* Return a pointer to the total messages read value (quadword). */ unsigned long* WsLibReadMsgTotal (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return ((unsigned long*)&wsptr->InputMsgCount); } /*****************************************************************************/ /* Generate a masking key for the supplied IO structure. */ static void MaskingKey (struct WsLibFrmStruct *frmptr) { static unsigned long RandomNumber, RandomFiller; /*********/ /* begin */ /*********/ /* pseudo-random 32 bytes */ if (!(RandomNumber & 0xff)) sys$gettim (&RandomNumber); RandomNumber = RandomNumber * 69069 + 1; frmptr->MaskCount = 0; frmptr->FrameMaskBit = 0x80; /* network byte order */ frmptr->MaskingKey[0] = (RandomNumber & 0xff000000) >> 24; frmptr->MaskingKey[1] = (RandomNumber & 0x00ff0000) >> 16; frmptr->MaskingKey[2] = (RandomNumber & 0x0000ff00) >> 8; frmptr->MaskingKey[3] = RandomNumber & 0x000000ff; } /*****************************************************************************/ /* Write the data pointed to by the supplied string descriptor. */ int WsLibWriteDsc ( struct WsLibStruct *wsptr, struct dsc$descriptor_s *DataDsc, void *AstFunction ) { int status; /*********/ /* begin */ /*********/ if (DataDsc == NULL) status = WsLibWrite (wsptr, NULL, 0, AstFunction); else if (DataDsc->dsc$b_class != DSC$K_CLASS_S && DataDsc->dsc$b_dtype != DSC$K_DTYPE_T) status = LIB$_INVSTRDES; else status = WsLibWrite (wsptr, DataDsc->dsc$a_pointer, DataDsc->dsc$w_length, AstFunction); return (status); } /*****************************************************************************/ /* Queue a write to the client WEBSOCKET_OUTPUT mailbox. If an AST function is supplied then the write is asynchronous, otherwise blocking. If the data pointer is NULL then send a close-connection frame. If the AST function is supplied as (void*)-1 a non-blocking I/O is generated that does not require a target AST function. */ int WsLibWrite ( struct WsLibStruct *wsptr, char *DataPtr, int DataCount, void *AstFunction ) { int cnt, hcnt, status, Utf8Count; unsigned char *cptr, *czptr, *sptr; struct WsLibFrmStruct *frmptr; struct WsLibMsgStruct *msgptr, *nsgptr; /*********/ /* begin */ /*********/ WATCH_WSLIB (wsptr, FI_LI, "WRITE count:!UL", DataCount); if (wsptr->WebSocketClosed) { MsgCallback (wsptr, __LINE__, SS$_SHUT, "can't write; closed"); wsptr->OutputStatus = SS$_SHUT; if (AstFunction && AstFunction != WSLIB_ASYNCH) ((void(*)())AstFunction)(wsptr); return (SS$_SHUT); } msgptr = calloc (1, sizeof(struct WsLibMsgStruct)); if (!msgptr) WsLibExit (wsptr, FI_LI, vaxc$errno); msgptr->WsLibPtr = wsptr; /* null or empty writes send an empty message */ if (!DataPtr) { DataPtr = ""; DataCount = 0; } msgptr->DataPtr = DataPtr; msgptr->DataCount = DataCount; msgptr->AstFunction = AstFunction; if (wsptr->SetAscii) { /* test if any UTF-8 encoding required */ Utf8Count = 0; czptr = (cptr = (unsigned char*)DataPtr) + DataCount; while (cptr < czptr) if (*cptr++ & 0x80) Utf8Count++; if (Utf8Count) { /********************/ /* convert to UTF-8 */ /********************/ WATCH_WSLIB (wsptr, FI_LI, "UTF-8 encode"); msgptr->Utf8Ptr = calloc (1, DataCount+Utf8Count); if (!msgptr->Utf8Ptr) WsLibExit (wsptr, FI_LI, vaxc$errno); czptr = (cptr = (unsigned char*)DataPtr) + DataCount; sptr = (unsigned char*)msgptr->Utf8Ptr; while (cptr < czptr) { if (*cptr & 0x80) { *sptr++ = ((*cptr & 0xc0) >> 6) | 0xc0; *sptr++ = (*cptr++ & 0x3f) | 0x80; } else *sptr++ = *cptr++; } msgptr->DataPtr = msgptr->Utf8Ptr; msgptr->DataCount = (char*)sptr - msgptr->Utf8Ptr; } } if (wsptr->SetBuffer && !msgptr->Utf8Ptr) { /* not UTF-8 and set to internally buffer message */ msgptr->BufferPtr = calloc (1, DataCount); if (!msgptr->BufferPtr) WsLibExit (wsptr, FI_LI, vaxc$errno); memcpy (msgptr->BufferPtr, DataPtr, DataCount); msgptr->DataPtr = msgptr->BufferPtr; } frmptr = &msgptr->FrameData; frmptr->WsLibMsgPtr = msgptr; frmptr->IOsb.iosb$w_status = SS$_NORMAL; msgptr->ThisFrmPtr = frmptr; if (wsptr->MsgWriteQuePtr) { /* append this one to the write queue */ for (nsgptr = wsptr->MsgWriteQuePtr; nsgptr->MsgQuePtr; nsgptr = nsgptr->MsgQuePtr); nsgptr->MsgQuePtr = msgptr; } else { /* only this one in the queue */ wsptr->MsgWriteQuePtr = msgptr; /* either kick-off asynchronous I/O or return after blocking I/O */ wsptr->QueuedOutput++; WriteAst (frmptr); } return (frmptr->IOsb.iosb$w_status); } /*****************************************************************************/ /* Write the message to the WebSocket. Message may be automatically fragmented. Can AST back to this function one or more times with asycnchronous I/O. */ static void WriteAst (struct WsLibFrmStruct *frmptr) { int astatus, cnt, count, hcnt, length, status, DataCount; char *pointer, *DataPtr; struct WsLibStruct *wsptr; struct WsLibMsgStruct *msgptr; /*********/ /* begin */ /*********/ msgptr = frmptr->WsLibMsgPtr; wsptr = msgptr->WsLibPtr; if (wsptr->QueuedOutput) wsptr->QueuedOutput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; while (VMSok (frmptr->IOsb.iosb$w_status)) { if (frmptr->MrsWriteCount) { /* accumulate total message data transfered */ *(int64*)wsptr->OutputCount = *(int64*)wsptr->OutputCount + (int32)frmptr->MrsWriteCount; msgptr->WriteCount += frmptr->MrsWriteCount; frmptr->MrsWriteCount = 0; } WATCH_WSLIB (wsptr, FI_LI, "WRITE outque:!UL payload:!UL/!UL !AZ", wsptr->QueuedOutput, msgptr->WriteCount, msgptr->DataCount, (frmptr->IOsb.iosb$w_bcnt && msgptr->WriteCount == msgptr->DataCount) ? "COMPLETE" : "in-progress"); /* if the header has been written and the required quantity of data */ if (frmptr->IOsb.iosb$w_bcnt && msgptr->WriteCount == msgptr->DataCount) break; if (wsptr->WebSocketClosed) { MsgCallback (wsptr, __LINE__, SS$_SHUT, "can't write; closed"); frmptr->IOsb.iosb$w_status = SS$_SHUT; break; } /*********/ /* frame */ /*********/ /* reset the frame structure (only needed on subsequent writes) */ frmptr = &msgptr->FrameData; if (frmptr->IOsb.iosb$w_status) memset (frmptr, 0, sizeof(struct WsLibFrmStruct)); frmptr->WsLibMsgPtr = msgptr; msgptr->ThisFrmPtr = frmptr; /* if being used as a client then mask the data */ if (wsptr->RoleClient) MaskingKey (frmptr); DataPtr = msgptr->DataPtr + msgptr->WriteCount; if (msgptr->DataCount - msgptr->WriteCount > wsptr->FrameMaxSize) DataCount = wsptr->FrameMaxSize; else DataCount = msgptr->DataCount - msgptr->WriteCount; /* only indicate opcode on first write (in case of fragmentation) */ if (frmptr->IOsb.iosb$w_bcnt) frmptr->FrameOpcode = 0; else if (wsptr->SetAscii || wsptr->SetUtf8) frmptr->FrameOpcode = WSLIB_OPCODE_TEXT; else frmptr->FrameOpcode = WSLIB_OPCODE_BINARY; /* if not the last fragment */ if (msgptr->WriteCount + DataCount < msgptr->DataCount) frmptr->FrameFinBit = 0; else frmptr->FrameFinBit = WSLIB_BIT_FIN; WATCH_WSLIB (wsptr, FI_LI, "WRITE opcode:!2XL(!AZ) fin:!UL mask:!UL data:!UL", frmptr->FrameOpcode, OpCodeName(frmptr->FrameOpcode), frmptr->FrameFinBit ? 1 : 0, frmptr->FrameMaskBit ? 1 : 0, DataCount); hcnt = 0; frmptr->FrameHeader[hcnt++] = frmptr->FrameFinBit | frmptr->FrameOpcode; if (DataCount <= 125) frmptr->FrameHeader[hcnt++] = frmptr->FrameMaskBit + DataCount; else if (DataCount <= MAX16BIT) { frmptr->FrameHeader[hcnt++] = frmptr->FrameMaskBit + 126; /* network byte order */ frmptr->FrameHeader[hcnt++] = (DataCount & 0xff00) >> 8; frmptr->FrameHeader[hcnt++] = DataCount & 0xff; } else { frmptr->FrameHeader[hcnt++] = frmptr->FrameMaskBit + 127; /* network byte order */ frmptr->FrameHeader[hcnt++] = 0; frmptr->FrameHeader[hcnt++] = 0; frmptr->FrameHeader[hcnt++] = 0; frmptr->FrameHeader[hcnt++] = 0; frmptr->FrameHeader[hcnt++] = (DataCount & 0xff000000) >> 24; frmptr->FrameHeader[hcnt++] = (DataCount & 0xff0000) >> 16; frmptr->FrameHeader[hcnt++] = (DataCount & 0xff00) >> 8; frmptr->FrameHeader[hcnt++] = DataCount & 0xff; } if (frmptr->FrameMaskBit) { frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[0]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[1]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[2]; frmptr->FrameHeader[hcnt++] = frmptr->MaskingKey[3]; /* never apply apply the masking key to original data */ if (!(frmptr->MaskedPtr = msgptr->Utf8Ptr) && !(frmptr->MaskedPtr = msgptr->BufferPtr)) { /* allocate a buffer for masked data */ frmptr->MaskedPtr = calloc (1, DataCount); if (!frmptr->MaskedPtr) WsLibExit (wsptr, FI_LI, vaxc$errno); } for (cnt = 0; cnt < DataCount; cnt++) frmptr->MaskedPtr[cnt] = DataPtr[cnt] ^ frmptr->MaskingKey[cnt&0x03]; DataPtr = frmptr->MaskedPtr; } /*********/ /* write */ /*********/ if (DataCount && DataCount <= 125) { /* for efficiency append the data to the header */ memcpy (frmptr->FrameHeader+hcnt, DataPtr, DataCount); hcnt += DataCount; frmptr->MrsWriteCount = DataCount; /* indicate that it's all contained in the header */ DataCount = 0; } frmptr->MrsDataPtr = DataPtr; frmptr->MrsDataCount = DataCount; if (msgptr->AstFunction) { /* asynchronous */ astatus = sys$setast (0); if (DataCount) status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, &frmptr->IOsb, WriteMrsAst, frmptr, frmptr->FrameHeader, hcnt, 0, 0, 0, 0); else status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, &frmptr->IOsb, WriteAst, frmptr, frmptr->FrameHeader, hcnt, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); return; } /* synchronous */ sys$qiow (EfnWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, &frmptr->IOsb, 0, 0, frmptr->FrameHeader, hcnt, 0, 0, 0, 0); if (DataCount) { wsptr->QueuedOutput++; WriteMrsAst (frmptr); } } WATCH_WSLIB (wsptr, FI_LI, "WRITE %X!8XL", frmptr->IOsb.iosb$w_status); /*******/ /* end */ /*******/ if (VMSok (frmptr->IOsb.iosb$w_status)) *(int64*)wsptr->OutputMsgCount = *(int64*)wsptr->OutputMsgCount + 1; else WsLibClose (wsptr, WSLIB_CLOSE_BANG, NULL); if (msgptr->AstFunction && msgptr->AstFunction != WSLIB_ASYNCH) { /* status and count are I/O valid only during the AST function */ status = wsptr->OutputStatus; count = wsptr->OutputDataCount; pointer = wsptr->OutputDataDsc.dsc$a_pointer; length = wsptr->OutputDataDsc.dsc$w_length; wsptr->OutputStatus = frmptr->IOsb.iosb$w_status; wsptr->OutputDataCount = frmptr->IOsb.iosb$w_bcnt; wsptr->OutputDataDsc.dsc$a_pointer = frmptr->DataPtr; wsptr->OutputDataDsc.dsc$w_length = wsptr->OutputDataCount; (*msgptr->AstFunction)(wsptr); wsptr->OutputStatus = status; wsptr->OutputDataCount = count; wsptr->OutputDataDsc.dsc$a_pointer = pointer; wsptr->OutputDataDsc.dsc$w_length = length; } if (msgptr->BufferPtr) free (msgptr->BufferPtr); if (msgptr->Utf8Ptr) free (msgptr->Utf8Ptr); free (msgptr); /* message dequeue */ if (msgptr = wsptr->MsgWriteQuePtr = msgptr->MsgQuePtr) { /* kick-off the next queued write */ frmptr = msgptr->ThisFrmPtr; wsptr->QueuedOutput++; WriteAst (frmptr); } if (wsptr->WatchDogIdleSecs) wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; if (wsptr->WatchDogWakeSecs) wsptr->WatchDogWakeTime = CurrentTime + wsptr->WatchDogWakeSecs; } /*****************************************************************************/ /* Write the frame data in record MRS sized chunks (may only be one). This function is only used for payload greater than 125 bytes. For 125 bytes or less (including empty frames) it's all handled by WriteAst(). */ static void WriteMrsAst (struct WsLibFrmStruct *frmptr) { int astatus, status, DataCount; char *DataPtr; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = frmptr->WsLibMsgPtr->WsLibPtr; if (wsptr->QueuedOutput) wsptr->QueuedOutput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); for (;;) { if (*(ULONGPTR)frmptr->FrameHeader) { /* if the header has just been written these are invalid */ frmptr->MrsWriteCount = frmptr->IOsb.iosb$w_bcnt = 0; *(ULONGPTR)frmptr->FrameHeader = 0; } if (VMSnok (frmptr->IOsb.iosb$w_status)) break; frmptr->MrsWriteCount += frmptr->IOsb.iosb$w_bcnt; WATCH_WSLIB (wsptr, FI_LI, "WRITE outque:!UL mrs:!UL/!UL !AZ", wsptr->QueuedOutput, frmptr->MrsWriteCount, frmptr->MrsDataCount, frmptr->MrsWriteCount == frmptr->MrsDataCount ? "COMPLETE" : "in-progress"); if (frmptr->MrsWriteCount == frmptr->MrsDataCount) break; DataPtr = frmptr->MrsDataPtr + frmptr->MrsWriteCount; if (frmptr->MrsDataCount - frmptr->MrsWriteCount > wsptr->OutputMrs) DataCount = wsptr->OutputMrs; else DataCount = frmptr->MrsDataCount - frmptr->MrsWriteCount; if (frmptr->WsLibMsgPtr->AstFunction) { /* asynchronous data */ astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, &frmptr->IOsb, WriteMrsAst, frmptr, DataPtr, DataCount, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; if (astatus == SS$_WASSET) sys$setast (1); return; } /* synchronous data */ sys$qiow (EfnWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, &frmptr->IOsb, 0, 0, DataPtr, DataCount, 0, 0, 0, 0); } if (frmptr->MaskedPtr) { free (frmptr->MaskedPtr); frmptr->MaskedPtr = NULL; } if (frmptr->WsLibMsgPtr->AstFunction) { wsptr->QueuedOutput++; WriteAst (frmptr); } } /*****************************************************************************/ /* Return the write status value. */ int WsLibWriteStatus (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->OutputStatus); } /*****************************************************************************/ /* Return the write count value (longword). */ int WsLibWriteCount (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->OutputDataCount); } /*****************************************************************************/ /* Return a pointer to the (most recent) write data descriptor. */ struct dsc$descriptor_s* WsLibWriteDataDsc (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (&wsptr->OutputDataDsc); } /*****************************************************************************/ /* Return a pointer to the total write count value (quadword). */ unsigned long* WsLibWriteTotal (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return ((unsigned long*)&wsptr->OutputCount); } /*****************************************************************************/ /* Return a pointer to the total write message value (quadword). */ unsigned long* WsLibWriteMsgTotal (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return ((unsigned long*)&wsptr->OutputMsgCount); } /*****************************************************************************/ /* Set the pointer to the user data associated with the WebSocket structure. */ void WsLibSetUserData ( struct WsLibStruct *wsptr, void *UserDataPtr ) { /*********/ /* begin */ /*********/ wsptr->UserDataPtr = UserDataPtr; } /*****************************************************************************/ /* Return the pointer to the user data. */ void* WsLibGetUserData (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->UserDataPtr); } /*****************************************************************************/ /* Set/reset the callout response AST. Returns the previous callout pointer. */ void* WsLibSetCallout ( struct WsLibStruct *wsptr, void *AstFunction ) { void *PrevCallout; /*********/ /* begin */ /*********/ PrevCallout = wsptr->CalloutAstFunction; wsptr->CalloutAstFunction = AstFunction; return (PrevCallout); } /*****************************************************************************/ /* Set the maxium frame size before the message is fragmented. */ int WsLibSetFrameMax ( struct WsLibStruct *wsptr, int FrameMax ) { int PrevFrameMax; /*********/ /* begin */ /*********/ PrevFrameMax = wsptr->FrameMaxSize; wsptr->FrameMaxSize = FrameMax; return (PrevFrameMax); } /*****************************************************************************/ /* Set wsLIB framing to binary. */ int WsLibSetBinary (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ wsptr->SetBinary = 1; wsptr->SetAscii = wsptr->SetBuffer = wsptr->SetUtf8 = 0; return (1); } /*****************************************************************************/ /* Is wsLIB message content binary? */ int WsLibIsSetBinary (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->SetBinary); } /*****************************************************************************/ /* Set wsLIB framing to Text8, with 8 bit "ASCII" to UTF-8 implicit encoding. */ int WsLibSetAscii (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ wsptr->SetAscii = 1; wsptr->SetBinary = wsptr->SetUtf8 = 0; return (1); } /*****************************************************************************/ /* Is wsLIB message content set to be Text8 (8 bit "ASCII")? */ int WsLibIsSetAscii (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->SetAscii); } /*****************************************************************************/ /* Set wsLIB to buffer the data internally before sending. */ int WsLibSetBuffer (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ wsptr->SetBuffer = 1; return (1); } /*****************************************************************************/ /* Set wsLIB NOT to buffer the data internally before sending. */ int WsLibSetNoBuffer (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ wsptr->SetBuffer = 0; return (0); } /*****************************************************************************/ /* Is wsLIB message set to buffer the conmtent internally before sending? */ int WsLibIsSetBuffer (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->SetBuffer); } /*****************************************************************************/ /* Set wsLIB framing to text (i.e. must already be UTF-8 encoding). */ int WsLibSetUtf8 (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ wsptr->SetUtf8 = 1; wsptr->SetBinary = wsptr->SetAscii = 0; return (1); } /*****************************************************************************/ /* Is wsLIB message content UTF-8? */ int WsLibIsSetUtf8 (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->SetUtf8); } /*****************************************************************************/ /* Set wsLIB framing write data masked. */ int WsLibSetRoleClient (struct WsLibStruct *wsptr) { int PrevRoleClient; /*********/ /* begin */ /*********/ PrevRoleClient = wsptr->RoleClient; wsptr->RoleClient = 1; return (PrevRoleClient); } /*****************************************************************************/ /* Set wsLIB framing write data not masked. */ int WsLibSetRoleServer (struct WsLibStruct *wsptr) { int PrevRoleClient; /*********/ /* begin */ /*********/ PrevRoleClient = wsptr->RoleClient; wsptr->RoleClient = 0; return (PrevRoleClient); } /*****************************************************************************/ /* Is wsLIB message content set to write masked? */ int WsLibIsRoleClient (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->RoleClient); } /*****************************************************************************/ /* Set number of seconds before the application is considered idle and exited. */ void WsLibSetLifeSecs (int LifeSecs) { /*********/ /* begin */ /*********/ if (!(WatchDogLifeSecs = LifeSecs)) WatchDogLifeSecs = DEFAULT_WATCHDOG_IDLE_SECS; } /*****************************************************************************/ /* Set number of seconds before an unresponded-to WebSocket close is considered closed and disconnected. If a WebSocket is not specified then set global value. */ void WsLibSetCloseSecs ( struct WsLibStruct *wsptr, int CloseSecs ) { /*********/ /* begin */ /*********/ if (wsptr) { /* set WebSocket values */ if (!(wsptr->WatchDogCloseSecs = CloseSecs)) wsptr->WatchDogCloseSecs = WatchDogCloseSecs; wsptr->WatchDogCloseTime = CurrentTime + wsptr->WatchDogCloseSecs; } else { /* set global values */ if (!(WatchDogCloseSecs = CloseSecs)) WatchDogCloseSecs = DEFAULT_WATCHDOG_CLOSE_SECS; } } /*****************************************************************************/ /* Set number of seconds before a WebSocket is considered idle and closed. If a WebSocket is not specified then set global value. */ void WsLibSetIdleSecs ( struct WsLibStruct *wsptr, int IdleSecs ) { /*********/ /* begin */ /*********/ if (wsptr) { /* set WebSocket values */ if (!(wsptr->WatchDogIdleSecs = IdleSecs)) wsptr->WatchDogIdleSecs = WatchDogIdleSecs; wsptr->WatchDogIdleTime = CurrentTime + wsptr->WatchDogIdleSecs; } else { /* set global values */ if (!(WatchDogIdleSecs = IdleSecs)) WatchDogIdleSecs = DEFAULT_WATCHDOG_IDLE_SECS; } } /*****************************************************************************/ /* Set number of seconds before a WebSocket is considered ping and closed. If a WebSocket is not specified then set global value. */ void WsLibSetPingSecs ( struct WsLibStruct *wsptr, int PingSecs ) { /*********/ /* begin */ /*********/ if (wsptr) { /* set WebSocket values */ if (!(wsptr->WatchDogPingSecs = PingSecs)) wsptr->WatchDogPingSecs = WatchDogPingSecs; wsptr->WatchDogPingTime = CurrentTime + wsptr->WatchDogPingSecs; } else { /* set global values */ if (!(WatchDogPingSecs = PingSecs)) WatchDogPingSecs = DEFAULT_WATCHDOG_PING_SECS; } } /*****************************************************************************/ /* Set number of seconds a WebSocket read will wait on the client. If a WebSocket is not specified then set global value. */ void WsLibSetReadSecs ( struct WsLibStruct *wsptr, int ReadSecs ) { /*********/ /* begin */ /*********/ if (wsptr) { /* set WebSocket values */ if (!(wsptr->WatchDogReadSecs = ReadSecs)) wsptr->WatchDogReadSecs = WatchDogReadSecs; } else { /* set global values */ if (!(WatchDogReadSecs = ReadSecs)) WatchDogReadSecs = DEFAULT_WATCHDOG_IDLE_SECS; } } /*****************************************************************************/ /* Set/reset the ping (actually pong) callback function. */ void* WsLibSetPongCallback ( struct WsLibStruct *wsptr, void *AstFunction ) { void *PrevCallback; /*********/ /* begin */ /*********/ if (wsptr) { PrevCallback = wsptr->PongCallbackFunction; wsptr->PongCallbackFunction = AstFunction; } else { PrevCallback = PongCallbackFunction; PongCallbackFunction = AstFunction; } return (PrevCallback); } /*****************************************************************************/ /* Set/reset the wake callback function to the specified number of seconds (defaults to the global setting). Returns the previous callback pointer. */ void* WsLibSetWakeCallback ( struct WsLibStruct *wsptr, void *AstFunction, int WakeSecs ) { void *PrevCallback; /*********/ /* begin */ /*********/ if (wsptr) { /* set WebSocket values */ if (!(wsptr->WatchDogWakeSecs = WakeSecs)) wsptr->WatchDogWakeSecs = WatchDogWakeSecs; wsptr->WatchDogWakeTime = CurrentTime + wsptr->WatchDogWakeSecs; PrevCallback = wsptr->WakeCallbackFunction; wsptr->WakeCallbackFunction = AstFunction; } else { /* set global values */ if (!(WatchDogWakeSecs = WakeSecs)) WatchDogWakeSecs = DEFAULT_WATCHDOG_WAKE_SECS; WatchDogWakeTime = CurrentTime + WatchDogWakeSecs; PrevCallback = WakeCallbackFunction; WakeCallbackFunction = AstFunction; } return (PrevCallback); } /*****************************************************************************/ /* Set/reset the error callback function. Returns the previous callback pointer. */ void* WsLibSetMsgCallback ( struct WsLibStruct *wsptr, void *AstFunction ) { void *PrevCallback; /*********/ /* begin */ /*********/ PrevCallback = wsptr->MsgCallbackFunction; wsptr->MsgCallbackFunction = AstFunction; return (PrevCallback); } /*****************************************************************************/ /* Set wsLIB message data. The 'FormatString' must be a $FAO compiliant null-terminated string, with following parametersif required. Activate any set message callback. */ static void MsgCallback ( struct WsLibStruct *wsptr, int LineNumber, int VmsStatus, char *FormatString, ... ) { static $DESCRIPTOR (FormatFaoDsc, ""); int argcnt, cnt, status; unsigned short slen = 0; unsigned long *vecptr; unsigned long FaoVector [32]; char *cptr, *sptr, *zptr; char FormatBuffer [128]; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); /* bit of a sanity check */ if (argcnt-4 > 32) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); cnt = 0; /* some VERY basic sanity checking */ for (cptr = FormatString; *cptr; cptr++) { if (*cptr != '!') continue; cptr++; if (*cptr == '!') continue; cnt++; if (*cptr == '#') cnt++; if (*(USHORTPTR)cptr == '%T') cnt++; if (*(USHORTPTR)cptr == '%D') cnt++; } if (argcnt-4 != cnt) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); zptr = (sptr = FormatBuffer) + sizeof(FormatBuffer)-1; for (cptr = "%X!8XL "; *cptr; *sptr++ = *cptr++); for (cptr = FormatString; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; FormatFaoDsc.dsc$a_pointer = FormatBuffer; FormatFaoDsc.dsc$w_length = sptr - FormatBuffer; vecptr = FaoVector; *vecptr++ = VmsStatus; va_start (argptr, FormatString); for (argcnt -= 4; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); wsptr->MsgDsc.dsc$b_class = DSC$K_CLASS_S; wsptr->MsgDsc.dsc$b_dtype = DSC$K_DTYPE_T; for (;;) { if (wsptr->MsgStringSize) { wsptr->MsgDsc.dsc$a_pointer = wsptr->MsgStringPtr; wsptr->MsgDsc.dsc$w_length = wsptr->MsgStringSize; status = sys$faol (&FormatFaoDsc, &slen, &wsptr->MsgDsc, &FaoVector); if (VMSnok (status)) WsLibExit (NULL, FI_LI, status); if (status != SS$_BUFFEROVF) break; } if (wsptr->MsgStringSize) free (wsptr->MsgStringPtr); wsptr->MsgStringSize += 127; wsptr->MsgStringPtr = calloc (1, wsptr->MsgStringSize+1); if (!wsptr->MsgStringPtr) WsLibExit (NULL, FI_LI, vaxc$errno); } wsptr->MsgStringPtr[wsptr->MsgStringLength=slen] = '\0'; wsptr->MsgDsc.dsc$w_length = slen; wsptr->MsgLineNumber = LineNumber; sys$gettim (&wsptr->MsgBinTime); if (wsptr->MsgCallbackFunction) (*wsptr->MsgCallbackFunction)(wsptr); } /****************************************************************************/ /* Return a pointer to the string descriptor of the latest error string. */ struct dsc$descriptor_s* WsLibMsgDsc (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (&wsptr->MsgDsc); } /****************************************************************************/ /* Return a pointer to the latest error string. */ char* WsLibMsgString (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->MsgStringPtr); } /****************************************************************************/ /* Return the wsLIB source code line at which the last error occured. */ int WsLibMsgLineNumber (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ return (wsptr->MsgLineNumber); } /****************************************************************************/ /* Given a descriptor of UTF-8 convert in-situ to 8 bit ASCII. The output descriptor must be provided even if it points to the same storage as the input descriptor. Return the length of the converted string or -1 to indicated a conversion error. The input string is mangled if an error. */ int WsLibFromUtf8Dsc ( struct dsc$descriptor_s *InDsc, struct dsc$descriptor_s *OutDsc, char SubsChar ) { int len; /*********/ /* begin */ /*********/ if (InDsc->dsc$b_class != DSC$K_CLASS_S && InDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); if (OutDsc->dsc$b_class != DSC$K_CLASS_S && OutDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); len = WsLibFromUtf8 (InDsc->dsc$a_pointer, InDsc->dsc$w_length, SubsChar); if (len >= 0) OutDsc->dsc$w_length = len; return (SS$_NORMAL); } /****************************************************************************/ /* Given a buffer of UTF-8 convert in-situ to 8 bit ASCII. Ignore non- 8 bit ASCII characters. End-of-string is indicated by text-length not a null-character, however the resultant string is nulled. If supplied the 8 bit character 'SubsChar' is substituted for any non 8 bit code in the string. Return the number of converted characters. Return -1 if there is an error. The input string is mangled if an error. */ int WsLibFromUtf8 ( char *UtfPtr, int UtfCount, char SubsChar ) { unsigned char ch; unsigned char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (!UtfPtr) return (-1); if (UtfCount == -1) UtfCount = strlen(UtfPtr); if (UtfCount < 0) return (-1); /* is there a potentially UTF-8 bit pattern here? */ for (zptr = (cptr = (unsigned char*)UtfPtr) + UtfCount; cptr < zptr; cptr++) if ((*cptr & 0xc0) == 0xc0) break; /* return if no UTF-8 conversion necessary (i.e. all 7 bit characters) */ if (cptr >= zptr) return (UtfCount); if (*cptr == 0xff) return (cptr - (unsigned char*)UtfPtr); sptr = cptr; while (cptr < zptr) { if ((*cptr & 0xf8) == 0xf0) { /* four byte sequence */ if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (SubsChar) *sptr++ = SubsChar; } else if ((*cptr & 0xf0) == 0xe0) { /* three byte sequence */ if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (SubsChar) *sptr++ = SubsChar; } else if ((*cptr & 0xe0) == 0xc0) { /* two byte sequence */ if (*cptr & 0x1c) { /* out-of-range character */ if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; if (++cptr >= zptr) goto utf8_nbg; if (SubsChar) *sptr++ = SubsChar; } else { /* 8 bit ASCII 128 to 255 */ ch = (*cptr & 0x03) << 6; if (++cptr >= zptr) goto utf8_nbg; if ((*cptr & 0xc0) != 0x80) goto utf8_nbg; ch |= *cptr & 0x3f; *sptr++ = ch; cptr++; } } else { /* 8 bit ASCII 0 to 127 */ *sptr++ = *cptr++; } } *sptr = '\0'; return (sptr - (unsigned char*)UtfPtr); utf8_nbg: return (-1); } /****************************************************************************/ /* Given a descriptor of 8 bit ASCII convert it to UTF-8. Can be done in-situ. The output descriptor must be provided even if it points to the same storage as the input descriptor. Return the length of the converted string or -1 to indicated a conversion error. */ int WsLibToUtf8Dsc ( struct dsc$descriptor_s *InDsc, struct dsc$descriptor_s *OutDsc ) { int len; /*********/ /* begin */ /*********/ if (InDsc->dsc$b_class != DSC$K_CLASS_S && InDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); if (OutDsc->dsc$b_class != DSC$K_CLASS_S && OutDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); len = WsLibToUtf8 (InDsc->dsc$a_pointer, InDsc->dsc$w_length, OutDsc->dsc$a_pointer, OutDsc->dsc$w_length); if (len == -1) return (SS$_ABORT); return (SS$_NORMAL); } /****************************************************************************/ /* Given a buffer of 8 bit ASCII text convert it to UTF-8. This can be done in-situ with the worst-case the buffer space needs to be two times in size (i.e. every character has the hi bit set requiring a leading UTF-8 byte). End-of-string is indicated by text-length not a null-character, however the resultant string is nulled. Return the length of the converted string. Return -1 if the buffer space will be too small. */ int WsLibToUtf8 ( char *InPtr, int InLength, char *OutPtr, int SizeOfOut ) { int Utf8Count = 0; char *cptr, *czptr, *sptr; /*********/ /* begin */ /*********/ if (!InPtr) return (-1); if (InLength == -1) InLength = strlen(InPtr); for (czptr = (cptr = InPtr) + InLength; cptr < czptr; cptr++) if (*cptr & 0x80) Utf8Count++; if (!Utf8Count) { if (!OutPtr) return (InLength); if (OutPtr == InPtr) return (InLength); /* just copy to output buffer */ if (InLength >= SizeOfOut - 1) return (-1); memcpy (OutPtr, InPtr, InLength); OutPtr[InLength] = '\0'; return (InLength); } if (InLength + Utf8Count >= SizeOfOut - 1) return (-1); cptr = (czptr = InPtr) + InLength - 1; if (!(sptr = OutPtr)) sptr = InPtr; sptr += InLength - 1; sptr += Utf8Count + 1; *sptr-- = '\0'; while (cptr >= czptr) { if (*cptr & 0x80) { *sptr-- = (*cptr & 0x3f) | 0x80; *sptr-- = ((*cptr-- & 0xc0) >> 6) | 0xc0; } else *sptr-- = *cptr--; } return (InLength + Utf8Count); } /****************************************************************************/ /* Called with a frame pointer after reading UTF-8 data from the client. The data is parsed as received (i.e. not necessarily a complete message) to ensure the UTF-8 (received so far) appears legal. Provides "fast fail" on illegal UTF-8. Return true if legal, false if not. Algorithm and essential code ... Copyright (c) 2008-2009 Bjoern Hoehrmann See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */ static int Utf8Legal (struct WsLibFrmStruct *frmptr) { static const unsigned char utf8d[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 }; int cnt, Utf8Count = 0; unsigned int byte, state, type; unsigned char *cptr, *czptr; struct WsLibMsgStruct *msgptr; /*********/ /* begin */ /*********/ msgptr = frmptr->WsLibMsgPtr; state = msgptr->Utf8State; if (!frmptr->IOsb.iosb$w_bcnt) { /* checking code-point at end of message */ return (state == 0); } cptr = (unsigned char*)frmptr->DataPtr + frmptr->DataCount; czptr = cptr + frmptr->IOsb.iosb$w_bcnt; while (cptr < czptr) { /* for efficiency, concurrently apply masking key */ if (frmptr->FrameMaskBit) *cptr ^= frmptr->MaskingKey[frmptr->MaskCount++&0x3]; byte = *cptr++; type = utf8d[byte]; state = utf8d[256+(state*16)+type]; if (!state) Utf8Count++; } msgptr->Utf8State = state; msgptr->Utf8Count += Utf8Count; return (state != 1); } /*****************************************************************************/ /* Return the current wsLIB time in seconds (i.e. C-RTL, Unix time). */ unsigned int WsLibTime () { /*********/ /* begin */ /*********/ return (CurrentTime); } /*****************************************************************************/ /* Called every second to maintain various events in the WebSocket and application life-cycle. */ static void WatchDog () { static unsigned long OneSecondDelta [2] = { -10000000, -1 }; static unsigned long ExitTime; int status, StringLength; char StringBuffer [256]; struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ sys$gettim (&CurrentBinTime); CurrentTime = decc$fix_time (&CurrentBinTime); if (0 && ListHead) WATCH_WSLIB (ListHead, FI_LI, "WATCHDOG !UL", CurrentTime); if (ListHead) ExitTime = 0; else if (!ExitTime) ExitTime = CurrentTime + WatchDogLifeSecs; else if (ExitTime < CurrentTime) exit (SS$_NORMAL); if (WatchDogWakeTime && WatchDogWakeTime < CurrentTime) { /* wake globally if requested */ WatchDogWakeTime = CurrentTime + WatchDogWakeSecs - 1; if (WakeCallbackFunction) sys$dclast (WakeCallbackFunction, 0, 0, 0); } for (wsptr = ListHead; wsptr; wsptr = wsptr->NextPtr) { /* flush any watch log to disk every second */ if (wsptr->WatchLog) fsync (fileno(wsptr->WatchLog)); if (wsptr->WebSocketClosed) { /* if not yet shut then do so after a little patience */ if (!wsptr->WatchDogCloseTime) if (wsptr->WatchDogCloseSecs) wsptr->WatchDogCloseTime = CurrentTime + wsptr->WatchDogCloseSecs; else wsptr->WatchDogCloseTime = CurrentTime + WatchDogCloseSecs; else if (wsptr->WatchDogCloseTime < CurrentTime) sys$dclast (Shut, wsptr, 0, 0); } else if (wsptr->WatchDogReadTime && wsptr->WatchDogReadTime < CurrentTime) { /* advise client to close WebSocket */ WsLibClose (wsptr, WSLIB_CLOSE_POLICY, "read wait exceeded"); } else if (wsptr->WatchDogIdleTime && wsptr->WatchDogIdleTime < CurrentTime) { /* advise client to close WebSocket */ WsLibClose (wsptr, WSLIB_CLOSE_POLICY, "idle connection"); } else if (wsptr->WatchDogPingTime && wsptr->WatchDogPingTime < CurrentTime) { /* periodic ping (heartbeat) to remote end */ StringLength = sprintf (StringBuffer, "%u %u", ++wsptr->WatchDogPingCount, CurrentTime); WsLibPing (wsptr, StringBuffer, StringLength); if (wsptr->WatchDogPingSecs) wsptr->WatchDogPingTime = CurrentTime + wsptr->WatchDogPingSecs - 1; else wsptr->WatchDogPingCount = 0; } else if (wsptr->WatchDogWakeTime && wsptr->WatchDogWakeTime < CurrentTime) { /* wake if requested */ wsptr->WatchDogWakeTime = CurrentTime + wsptr->WatchDogWakeSecs - 1; if (wsptr->WakeCallbackFunction) sys$dclast (wsptr->WakeCallbackFunction, wsptr, 0, 0); } } status = sys$setimr (0, &OneSecondDelta, WatchDog, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); } /*****************************************************************************/ /* Exit from the current image reporting to the server WEBSOCKET_OUTPUT stream (if a channel assigned) or (last-ditch) to the exit code module name and line number. */ void WsLibExit ( struct WsLibStruct *wsptr, char *SourceModuleName, int SourceLineNumber, int status ) { static unsigned long OneSecondDelta [2] = { -10000000, -1 }; static $DESCRIPTOR (FaoDsc, "BYE-BYE [!AZ:!UL] %X!8XL\n\0"); short slen; char MsgBuffer [256]; $DESCRIPTOR (MsgDsc, MsgBuffer); /*********/ /* begin */ /*********/ sys$fao (&FaoDsc, &slen, &MsgDsc, SourceModuleName, SourceLineNumber, status); if (wsptr && wsptr->OutputChannel) sys$qiow (EfnWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, 0, 0, MsgBuffer, slen-1, 0, 0, 0, 0); else { fputs (MsgBuffer, stdout); fflush (stdout); } sys$schdwk (0, 0, OneSecondDelta, 0); sys$hiber (); sys$delprc (0, 0, 0); } /*****************************************************************************/ /* Free C-RTL allocated memory (such as that returned by WsLibReadGrab()) in an application neutral fashion (in case non-C-RTL allocations are used in the future). */ void WsLibFree (char *cptr) { /*********/ /* begin */ /*********/ free (cptr); } /*****************************************************************************/ /* The 'FormatString' must be a $FAO compliant null-terminated string, with following parameters. */ void WsLibCallout ( struct WsLibStruct *wsptr, char *FormatString, ... ) { static $DESCRIPTOR (ErrorFaoDsc, "!!WATCH: $FAO %X!8XL"); int argcnt, astatus, cnt, status; unsigned short slen = 0; unsigned long *vecptr; unsigned long FaoVector [32]; char *aptr, *cptr; char CalloutBuffer [1024]; va_list argptr; $DESCRIPTOR (CalloutBufferDsc, CalloutBuffer); $DESCRIPTOR (FormatDsc, ""); /*********/ /* begin */ /*********/ if (!CgiPlusEscLength && !CgiPlusEotLength) return; if (!wsptr->OutputChannel) return; va_count (argcnt); /* can't call callout while in a callout response delivery */ if (wsptr->CalloutInProgress) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); /* bit of a sanity check */ if (argcnt-2 > 32) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); cnt = 0; for (cptr = FormatString; *cptr; cptr++) if (*cptr == '!' && *(USHORTPTR)cptr != '!!') cnt++; if (argcnt-2 != cnt) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); FormatDsc.dsc$a_pointer = FormatString; FormatDsc.dsc$w_length = strlen(FormatString); vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 2; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); status = sys$faol (&FormatDsc, &slen, &CalloutBufferDsc, &FaoVector); if (VMSnok(status)) status = sys$fao (&ErrorFaoDsc, &slen, &CalloutBufferDsc, status); /* allocate a pointer plus a buffer (freed by OutputFreeAst()) */ aptr = calloc (1, sizeof(struct WsLibStruct*) + slen); if (!aptr) WsLibExit (wsptr, FI_LI, vaxc$errno); *(struct WsLibStruct**)aptr = wsptr; cptr = aptr + sizeof(struct WsLibStruct*); memcpy (cptr, CalloutBuffer, slen); astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputAst, wsptr, CgiPlusEscPtr, CgiPlusEscLength, 0, 0, 0, 0); if (VMSok(status)) { wsptr->QueuedOutput++; status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputFreeAst, aptr, cptr, slen, 0, 0, 0, 0); if (VMSok(status)) { wsptr->QueuedOutput++; status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputAst, wsptr, CgiPlusEotPtr, CgiPlusEotLength, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; } } if (astatus == SS$_WASSET) sys$setast (1); } /*****************************************************************************/ /* Send a script "WATCH:" callout to the server (if [x]script enabled). The 'SourceModuleName' can be NULL. The 'FormatString' must be a $FAO compiliant null-terminated string, with following parameters. A specific channel is assigned for WATCH output so that it can be deassigned as late in request processing as possible. */ void WsLibWatchScript ( struct WsLibStruct *wsptr, char *SourceModuleName, int SourceLineNumber, char *FormatString, ... ) { static $DESCRIPTOR (ErrorFaoDsc, "!!WATCH: $FAO %X!8XL"); static $DESCRIPTOR (TimeFaoDsc, "!%T\0"); static $DESCRIPTOR (Watch1FaoDsc, "!!!!WATCH: [!AZ:!4ZL] !AZ"); static $DESCRIPTOR (Watch2FaoDsc, "!!!!WATCH: !AZ"); static struct WsLibStruct FakeIt; int argcnt, astatus, cnt, status; unsigned short slen = 0; unsigned long *vecptr; unsigned long FaoVector [32]; char *aptr, *cptr; char TimeBuffer [32], WatchBuffer [1024], WatchFao [256]; va_list argptr; $DESCRIPTOR (FaoDsc, WatchFao); $DESCRIPTOR (TimeBufferDsc, TimeBuffer); $DESCRIPTOR (WatchBufferDsc, WatchBuffer); /*********/ /* begin */ /*********/ if (!wsptr) { wsptr = &FakeIt; if (!wsptr->WatchScript) { wsptr->WatchScript = 1; if ((cptr = getenv ("WASD_WSLIB_WATCH_LOG")) != NULL) if ((wsptr->WatchLog = fopen (cptr, "w", "shr=get")) != NULL) WsLibExit (NULL, FI_LI, vaxc$errno); } if (!wsptr->WatchLog) return; } else { if (!wsptr->WatchScript) return; if (!wsptr->OutputChannel) return; /* can't call callout while in a callout response delivery */ if (wsptr->CalloutInProgress) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); } va_count (argcnt); /* bit of a sanity check */ if (argcnt-4 > 32) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); cnt = 0; for (cptr = FormatString; *cptr; cptr++) { if (*cptr != '!') continue; cptr++; if (*cptr == '!') continue; cnt++; if (*cptr == '#') cnt++; if (*(USHORTPTR)cptr == '%T') cnt++; if (*(USHORTPTR)cptr == '%D') cnt++; } if (argcnt-4 != cnt) WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (SourceModuleName) sys$fao (&Watch1FaoDsc, &slen, &FaoDsc, SourceModuleName, SourceLineNumber, FormatString); else sys$fao (&Watch2FaoDsc, &slen, &FaoDsc, FormatString); FaoDsc.dsc$w_length = slen; vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 4; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); status = sys$faol (&FaoDsc, &slen, &WatchBufferDsc, &FaoVector); if (!(status & 1)) status = sys$fao (&ErrorFaoDsc, &slen, &WatchBufferDsc, status); if (!CgiPlusEscLength && !CgiPlusEotLength) { fprintf (stdout, "%*.*s\n", slen, slen, WatchBuffer); return; } if (wsptr->WatchLog) { sys$fao (&TimeFaoDsc, 0, &TimeBufferDsc, 0); fprintf (wsptr->WatchLog, "%s %*.*s\n", TimeBuffer, slen-8, slen-8, WatchBuffer+8); return; } /* allocate a pointer plus a buffer (freed by OutputFreeAst()) */ aptr = calloc (1, sizeof(struct WsLibStruct*) + slen); if (!aptr) WsLibExit (wsptr, FI_LI, vaxc$errno); *(struct WsLibStruct**)aptr = wsptr; cptr = aptr + sizeof(struct WsLibStruct*); memcpy (cptr, WatchBuffer, slen); astatus = sys$setast (0); status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputAst, wsptr, CgiPlusEscPtr, CgiPlusEscLength, 0, 0, 0, 0); if (VMSok(status)) { wsptr->QueuedOutput++; status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputFreeAst, aptr, cptr, slen, 0, 0, 0, 0); if (VMSok(status)) { wsptr->QueuedOutput++; status = sys$qio (EfnNoWait, wsptr->OutputChannel, IO$_WRITELBLK | IO$M_READERCHECK, 0, OutputAst, wsptr, CgiPlusEotPtr, CgiPlusEotLength, 0, 0, 0, 0); if (VMSok(status)) wsptr->QueuedOutput++; } } if (VMSnok(status)) WsLibExit (wsptr, FI_LI, status); if (astatus == SS$_WASSET) sys$setast (1); } /*****************************************************************************/ /* Just decrement the queued output counter. */ void OutputAst (struct WsLibStruct *wsptr) { /*********/ /* begin */ /*********/ if (wsptr->QueuedOutput) wsptr->QueuedOutput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WebSocketShut) Shut (wsptr); } /*****************************************************************************/ /* The parameter is an address to allocated memory. The memory contains a pointer to a WsLib structure and after that opaque data (actually just a write buffer but what the hey). Get the pointer to the owning WsLib structure. Free the allocated memory. Decrement the WsLib structure's queued output counter. */ void OutputFreeAst (char *aptr) { struct WsLibStruct *wsptr; /*********/ /* begin */ /*********/ wsptr = *(struct WsLibStruct**)aptr; free (aptr); if (wsptr->QueuedOutput) wsptr->QueuedOutput--; else WsLibExit (wsptr, FI_LI, SS$_BUGCHECK); if (wsptr->WebSocketShut) Shut (wsptr); } /*****************************************************************************/ /* Just used for WATCH purposes. */ static char* OpCodeName (int OpCode) { /*********/ /* begin */ /*********/ switch (OpCode & 0xf) { case 0 : return ("continue"); case 1 : return ("text"); case 2 : return ("binary"); case 8 : return ("close"); case 9 : return ("ping"); case 10 : return ("pong"); default : return ("unknown"); } } /*****************************************************************************/ /* If a CGIplus environment then output the end-of-request sentinal. */ void WsLibCgiPlusEof () { /*********/ /* begin */ /*********/ if (!CgiPlusEofLength) return; fflush (stdout); fputs (CgiPlusEofPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* Output the callout end sentinal. */ void WsLibCgiPlusEot () { /*********/ /* begin */ /*********/ if (!CgiPlusEotLength) return; fflush (stdout); fputs (CgiPlusEotPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* Output the callout begin sentinal. */ void WsLibCgiPlusEsc () { /*********/ /* begin */ /*********/ if (!CgiPlusEscLength) return; fflush (stdout); fputs (CgiPlusEscPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* Output the callout begin sentinal. */ void WsLibCalloutStart () { /*********/ /* begin */ /*********/ if (!CgiPlusEscLength) return; fflush (stdout); fputs (CgiPlusEscPtr, stdout); fflush (stdout); } /*****************************************************************************/ /* Return true if it's a CGIplus execution environment. */ int WsLibIsCgiPlus () { static int InitIs; /*********/ /* begin */ /*********/ if (!InitIs) { InitIs = 1; if (CgiPlusEofPtr = getenv("CGIPLUSEOF")) CgiPlusEofLength = strlen(CgiPlusEofPtr); if (CgiPlusEscPtr = getenv("CGIPLUSESC")) CgiPlusEscLength = strlen(CgiPlusEscPtr); if (CgiPlusEotPtr = getenv("CGIPLUSEOT")) CgiPlusEotLength = strlen(CgiPlusEotPtr); } return (CgiPlusEofLength); } /*****************************************************************************/ /* Are CGI variables currently available? Returns true or false. */ int WsLibCgiVarAvailable () { /*********/ /* begin */ /*********/ return (CgiVariablesAvailable); } /*****************************************************************************/ /* String descriptor equivalent of WsLibCgiVar() with the same requirements and constraints. Set 'ValueDsc' to the value of the CGI variable (or empty if it doesn't exist). Return the length of the value of -1 if the CGI variable name does not exist. */ int WsLibCgiVarDsc ( struct dsc$descriptor_s *NameDsc, struct dsc$descriptor_s *ValueDsc ) { char *cptr, *czptr, *sptr, *zptr; char VarName [256]; /*********/ /* begin */ /*********/ if (NameDsc->dsc$b_class != DSC$K_CLASS_S && ValueDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); if (ValueDsc->dsc$b_class != DSC$K_CLASS_S && ValueDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES); zptr = (sptr = VarName) + sizeof(VarName)-1; czptr = (cptr = NameDsc->dsc$a_pointer) + NameDsc->dsc$w_length; while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (cptr = WsLibCgiVarNull (VarName)) { for (sptr = cptr; *sptr; sptr++); ValueDsc->dsc$w_length = sptr - cptr; ValueDsc->dsc$a_pointer = cptr; return (SS$_NORMAL); } else return (SS$_ITEMNOTFOUND); } /*****************************************************************************/ /* Return empty string rather than NULL if the CGI variable does not exist. */ char* WsLibCgiVar (char* VarName) { char *cptr; /*********/ /* begin */ /*********/ if (cptr = WsLibCgiVarNull (VarName)) return (cptr); else return (""); } /*****************************************************************************/ /* Return the value of a CGI variable regardless of whether it is used in a standard CGI environment or a WASD CGIplus environment. Automatically switches WASD V7.2 into 'struct' mode for significantly improved performance. Call with 'VarName' empty ("") to synchronize CGIplus requests. This waits for a CGIplus variable stream, checks if it's in 'record' or 'struct' mode, reads the stream appropriately before returning ready to provide variables. DO NOT modify the character string returned by this function. Copy to other storage if this is necessary. The behaviour is indeterminate if the returned values are modified in any way! All CGIplus variables may be returned by making successive calls using a 'VarName' of "*" (often useful when debugging). NULL is returned when variables exhausted. */ char* WsLibCgiVarNull (char *VarName) { #define SOUS sizeof(unsigned short) static FILE *CgiPlusIn; int Length; char WwwVarName [256]; char *bptr, *cptr, *sptr; /*********/ /* begin */ /*********/ /* if not using the AST-driven WsLibOnNextRequest() */ if (!CgiPlusInChannel) { if (!StructBufferSize) { /**************/ /* initialise */ /**************/ if (WsLibIsCgiPlus()) { if ((cptr = getenv ("WASD_WSLIB_STRUCT_BUFFER_SIZE")) != NULL) StructBufferSize = atoi(cptr); if (StructBufferSize < WSLIB_STRUCT_BUFFER_SIZE) StructBufferSize = WSLIB_STRUCT_BUFFER_SIZE; StructBufferPtr = calloc (1, StructBufferSize); if (!StructBufferPtr) WsLibExit (NULL, FI_LI, vaxc$errno); StructBufferLength = 0; } else StructBufferSize = -1; } if (VarName == NULL || !VarName[0]) { /* initialize */ StructBufferLength = WwwPrefix = 0; NextVarNamePtr = StructBufferPtr; if (CgiPlusEofPtr == NULL) WwwPrefix = (getenv ("WWW_SERVER_SOFTWARE") != NULL); if (VarName == NULL) return (NULL); } } if (VarName[0]) { /***************************/ /* return a variable value */ /***************************/ if (*(ULONGPTR)VarName == 'WWW_' && !WwwPrefix) VarName += 4; else if (*(ULONGPTR)VarName != 'WWW_' && WwwPrefix) { strcpy (WwwVarName, "WWW_"); strcpy (WwwVarName+4, VarName); VarName = WwwVarName; } if (CgiPlusEofPtr == NULL) { /* standard CGI environment */ CgiVariablesAvailable = 1; return (getenv (VarName)); } if (!CgiVariablesAvailable) return (NULL); if (VarName[0] == '*') { /* return each CGIplus variable in successive calls */ if (!(Length = *(USHORTPTR)NextVarNamePtr)) { NextVarNamePtr = StructBufferPtr; return (NULL); } sptr = (NextVarNamePtr += SOUS); NextVarNamePtr += Length; return (sptr); } /* return a pointer to this CGIplus variable's value */ for (bptr = StructBufferPtr; Length = *(USHORTPTR)bptr; bptr += Length) { sptr = (bptr += SOUS); for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++) if (toupper(*cptr) != toupper(*sptr)) break; /* if found return a pointer to the value */ if (!*cptr && *sptr == '=') return (sptr+1); } /* not found */ return (NULL); } /*****************************/ /* get the CGIplus variables */ /*****************************/ if (RequestSynchronisation == NULL) RequestSynchronisation = (void*)WsLibCgiVarNull; else if (RequestSynchronisation != (void*)WsLibCgiVarNull) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); /* cannot "sync" in a non-CGIplus environment */ if (CgiPlusEofPtr == NULL) return (NULL); /* the CGIPLUSIN stream can be left open */ if (CgiPlusIn == NULL) if ((CgiPlusIn = fopen (getenv("CGIPLUSIN"), "r")) == NULL) WsLibExit (NULL, FI_LI, vaxc$errno); CgiVariablesAvailable = WwwPrefix = 0; /* get the starting record (the essentially discardable one) */ for (;;) { cptr = fgets (StructBufferPtr, StructBufferSize, CgiPlusIn); if (cptr == NULL) WsLibExit (NULL, FI_LI, vaxc$errno); /* if the starting sentinal is detected then break */ if (*(USHORTPTR)cptr == '!\0' || *(USHORTPTR)cptr == '!\n' || (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))) break; } if (*(USHORTPTR)cptr == '!!') { /********************/ /* CGIplus 'struct' */ /********************/ /* get the size of the binary structure */ StructBufferLength = atoi(cptr+2); if (StructBufferLength <= 0) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (StructBufferLength > StructBufferSize) { while (StructBufferLength > StructBufferSize) StructBufferSize *= 2; free (StructBufferPtr); StructBufferPtr = calloc (1, StructBufferSize); if (!StructBufferPtr) WsLibExit (NULL, FI_LI, vaxc$errno); NextVarNamePtr = StructBufferPtr; } if (!fread (StructBufferPtr, 1, StructBufferLength, CgiPlusIn)) WsLibExit (NULL, FI_LI, vaxc$errno); } else { /*********************/ /* CGIplus 'records' */ /*********************/ /* reconstructs the original 'struct'ure from the records */ sptr = (bptr = StructBufferPtr) + StructBufferSize; while (fgets (bptr+SOUS, sptr-(bptr+SOUS), CgiPlusIn) != NULL) { /* first empty record (line) terminates variables */ if (bptr[SOUS] == '\n') break; /* note the location of the length word */ cptr = bptr; for (bptr += SOUS; *bptr && *bptr != '\n'; bptr++); if (*bptr != '\n') WsLibExit (NULL, FI_LI, SS$_BUGCHECK); *bptr++ = '\0'; if (bptr >= sptr) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); /* update the length word */ *(USHORTPTR)cptr = bptr - (cptr + SOUS); } if (bptr >= sptr) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); /* terminate with a zero-length entry */ *(USHORTPTR)bptr = 0; StructBufferLength = (bptr + SOUS) - StructBufferPtr; } /***********************/ /* variables available */ /***********************/ CgiVariablesAvailable = 1; sptr = StructBufferPtr + SOUS; if (*(ULONGPTR)sptr == 'WWW_') WwwPrefix = 1; if (!CalloutDone) { /* provide the CGI callout to set CGIplus into 'struct' mode */ fflush (stdout); fputs (CgiPlusEscPtr, stdout); fflush (stdout); /* the leading '!' indicates we're not going to read the response */ fputs ("!CGIPLUS: struct", stdout); fflush (stdout); fputs (CgiPlusEotPtr, stdout); fflush (stdout); /* don't need to do this again (the '!!' tells us what mode) */ CalloutDone = 1; } return (NULL); # undef SOUS } /*****************************************************************************/ /* Asynchronous notification of the next request becoming available. This function should be called once during script initialisation specifying the address of the function to be notified of a new request. Until the script issues the CGIplus EOF sentinal the WsLibCgiVar..() family of functions can be used to retrieve the CGI variable values. This asynchronous and the synchronous WsLibCgiVar("") approaches to request synchronisation are mutually exclusive and once one is used, using the other results in a bugcheck. */ void WsLibOnNextRequest (void *AstFunction) { #define SOUS sizeof(unsigned short) static int CGIplusStruct = 0, StartSentinal = 0; static char SentinalBuffer [256]; static $DESCRIPTOR (CgiPlusInDsc, ""); static struct _iosb IOsb; static void *RequestAstFunction; int bcnt, status; char *bptr, *cptr; /*********/ /* begin */ /*********/ if (RequestSynchronisation == NULL) RequestSynchronisation = WsLibOnNextRequest; else if (RequestSynchronisation != WsLibOnNextRequest) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (!CgiPlusInChannel) { /**************/ /* initialise */ /**************/ /* ensure initialisation */ if (!WsLibIsCgiPlus()) { /* hmmm, standard CGI environment - do something sensible */ sys$dclast (AstFunction, 0, 0, 0); return; } if (!AstFunction) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); RequestAstFunction = AstFunction; if ((cptr = getenv("CGIPLUSIN")) == NULL) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); CgiPlusInDsc.dsc$a_pointer = cptr; CgiPlusInDsc.dsc$w_length = strlen(cptr); status = sys$assign (&CgiPlusInDsc, &CgiPlusInChannel, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); if ((cptr = getenv ("WASD_WSLIB_STRUCT_BUFFER_SIZE")) != NULL) StructBufferSize = atoi(cptr); if (StructBufferSize < WSLIB_STRUCT_BUFFER_SIZE) StructBufferSize = WSLIB_STRUCT_BUFFER_SIZE; StructBufferPtr = calloc (1, StructBufferSize+2); if (!StructBufferPtr) WsLibExit (NULL, FI_LI, vaxc$errno); StructBufferLength = 0; status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, SentinalBuffer, sizeof(SentinalBuffer)-1, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); return; } else if (AstFunction) { /* modify AST function */ RequestAstFunction = AstFunction; return; } /***************/ /* read stream */ /***************/ if (!CgiPlusInChannel) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (AstFunction) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (IOsb.iosb$w_status && VMSnok(IOsb.iosb$w_status)) WsLibExit (NULL, FI_LI, IOsb.iosb$w_status); if (!StartSentinal) { cptr = SentinalBuffer; cptr[IOsb.iosb$w_bcnt] = '\0'; /* if the starting sentinal is not detected then ignore */ if (!(*(USHORTPTR)cptr == '!\0' || *(USHORTPTR)cptr == '!\n' || (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2))))) { status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, SentinalBuffer, sizeof(SentinalBuffer)-1, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); return; } StartSentinal = 1; bcnt = CgiVariablesAvailable = StructBufferLength = 0; if (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2))) { /* get the size of the binary structure */ StructBufferLength = atoi(cptr+2); if (StructBufferLength <= 0) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); if (StructBufferLength > StructBufferSize) { while (StructBufferLength > StructBufferSize) StructBufferSize *= 2; free (StructBufferPtr); StructBufferPtr = calloc (1, StructBufferSize+2); if (!StructBufferPtr) WsLibExit (NULL, FI_LI, vaxc$errno); NextVarNamePtr = StructBufferPtr; } CGIplusStruct = 1; /* drop through to the structure processing code */ } else { /* read the first variable record */ status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, StructBufferPtr + SOUS, StructBufferSize - SOUS, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); CGIplusStruct = 0; return; } } else if (CGIplusStruct) { cptr = StructBufferPtr + StructBufferLength; cptr[bcnt = IOsb.iosb$w_bcnt] = '\0'; } else { cptr = StructBufferPtr + StructBufferLength + SOUS; cptr[bcnt = IOsb.iosb$w_bcnt] = '\0'; } if (CGIplusStruct) { /********************/ /* CGIplus 'struct' */ /********************/ if (bcnt < StructBufferLength) { status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, StructBufferPtr, StructBufferLength, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); return; } if (bcnt > StructBufferLength) WsLibExit (NULL, FI_LI, SS$_BUGCHECK); } else { /*********************/ /* CGIplus 'records' */ /*********************/ /* first empty record terminates variables */ if (bcnt) { /* reconstructs the original 'struct'ure from the records */ bptr = StructBufferPtr + StructBufferLength; /* update the length word */ *(USHORTPTR)bptr = bcnt; /* terminate with a zero-length entry */ bptr += bcnt + SOUS; *(USHORTPTR)bptr = 0; StructBufferLength = bptr - StructBufferPtr; status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, StructBufferPtr + StructBufferLength + SOUS, StructBufferSize - StructBufferLength - SOUS, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); return; } } /***********************/ /* variables available */ /***********************/ bptr = StructBufferPtr + SOUS; if (*(ULONGPTR)bptr == 'WWW_') WwwPrefix = 1; if (!CalloutDone) { /* provide the CGI callout to set CGIplus into 'struct' mode */ fflush (stdout); fputs (CgiPlusEscPtr, stdout); fflush (stdout); /* the leading '!' indicates we're not going to read the response */ fputs ("!CGIPLUS: struct", stdout); fflush (stdout); fputs (CgiPlusEotPtr, stdout); fflush (stdout); /* don't need to do this again */ CalloutDone = 1; } CgiVariablesAvailable = 1; sys$dclast (RequestAstFunction, 0, 0, 0); /********************************/ /* queue ready for next request */ /********************************/ StartSentinal = 0; status = sys$qio (EfnNoWait, CgiPlusInChannel, IO$_READLBLK, &IOsb, WsLibOnNextRequest, NULL, SentinalBuffer, sizeof(SentinalBuffer)-1, 0, 0, 0, 0); if (VMSnok(status)) WsLibExit (NULL, FI_LI, status); # undef SOUS } /*****************************************************************************/