├── README.md ├── plugins └── websocket.smx └── scripting ├── include └── websocket.inc └── websocket.sp /README.md: -------------------------------------------------------------------------------- 1 | sm-websocket 2 | ============ 3 | 4 | A WebSocket protocol implementation to create a direct connection between webbrowsers and gameservers. -------------------------------------------------------------------------------- /plugins/websocket.smx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peace-maker/sm-websocket/d17be587b5faae08fa63af7a282b4253641ff561/plugins/websocket.smx -------------------------------------------------------------------------------- /scripting/include/websocket.inc: -------------------------------------------------------------------------------- 1 | #if defined _websocket_included 2 | #endinput 3 | #endif 4 | #define _websocket_included 5 | 6 | enum WebsocketReadyState { 7 | State_Connecting = 0, 8 | State_Open, 9 | State_Closing, 10 | State_Closed // Kinda unused 11 | } 12 | 13 | enum WebsocketHandle { 14 | INVALID_WEBSOCKET_HANDLE = 0 15 | } 16 | 17 | enum WebsocketSendType { 18 | SendType_Text = 0, 19 | SendType_Binary 20 | } 21 | 22 | /** 23 | * called if an unrecoverable error occured on the master socket 24 | * 25 | * @param WebsocketHandle websocket The websocket handle pointing to the calling websocket 26 | * @param cell_t errorType The error type, see defines in socket.inc 27 | * @param cell_t errorNum The errno, see errno.h for details 28 | * @noreturn 29 | */ 30 | typedef WebsocketErrorCB = function void(WebsocketHandle websocket, const int errorType, const int errorNum); 31 | 32 | /** 33 | * called if a master websocket is closed 34 | * 35 | * @param WebsocketHandle websocket The websocket handle pointing to the calling websocket 36 | * @noreturn 37 | */ 38 | typedef WebsocketCloseCB = function void(WebsocketHandle websocket); 39 | 40 | /** 41 | * triggered when a client connected to our websocket 42 | * 43 | * @param Handle websocket The websocket handle pointing to the calling listen-socket 44 | * @param Handle newWebsocket The websocket handle to the newly spawned child socket 45 | * @param String remoteIP The remote IP 46 | * @param cell_t remotePort The remote port 47 | * @param String protocols The subprotocols the client supports seperated by commas. You have to choose one of the list, so "protocols" is only that one protocol. 48 | * @param String getPath The GET path transmitted upon connection by the client. 49 | * @return 50 | */ 51 | typeset WebsocketIncomingCB { 52 | function Action(WebsocketHandle websocket, WebsocketHandle newWebsocket, const char[] remoteIP, int remotePort, char protocols[256]); 53 | function Action(WebsocketHandle websocket, WebsocketHandle newWebsocket, const char[] remoteIP, int remotePort, char protocols[256], char getPath[2000]); 54 | }; 55 | 56 | /** 57 | * triggered if a websocket receives data 58 | * 59 | * @note This is binary safe if you always use dataSize for operations on receiveData[] 60 | * @note packets may be split up into multiple chunks -> multiple calls to the receive callback 61 | * @note if not set otherwise by SocketSetOption(..., ConcatenateCallbacks, ...) receiveData will 62 | * never be longer than 4096 characters including \0 terminator 63 | * 64 | * @param Handle websocket The socket handle pointing to the calling socket 65 | * @param String receiveData The data which arrived, 0-terminated at receiveData[dataSize] 66 | * @param cell_t dataSize The length of the arrived data excluding the 0-termination 67 | * @noreturn 68 | */ 69 | typedef WebsocketReceiveCB = function void(WebsocketHandle websocket, WebsocketSendType iType, const char[] receiveData, const int dataSize); 70 | 71 | /** 72 | * called if a socket has been properly disconnected by the remote side 73 | * 74 | * @param Handle websocket The socket handle pointing to the calling socket 75 | * @noreturn 76 | */ 77 | typedef WebsocketDisconnectCB = function void(WebsocketHandle websocket); 78 | 79 | /** 80 | * called if the readystate of a childsocket changes. 81 | * Only fires for ReadyState_Open and ReadyState_Closing. 82 | * 83 | * @param Handle websocket The socket handle pointing to the calling socket 84 | * @param WebsocketReadyState readystate The new readystate of the childsocket. 85 | * @noreturn 86 | */ 87 | typedef WebsocketReadyStateChangedCB = function void(WebsocketHandle websocket, WebsocketReadyState readystate); 88 | 89 | /** 90 | * Creates a websocket server which listens on the supplied ip:port combination. 91 | * 92 | * @param sHostName The IP to bind to. 93 | * @param iPort The port to listen on 94 | * @param inc The incoming child connection callback 95 | * @param we The error callback 96 | * @return A WebsocketHandle or INVALID_WEBSOCKET_HANDLE on error. 97 | */ 98 | native WebsocketHandle Websocket_Open(const char[] sHostName, int iPort, WebsocketIncomingCB inc, WebsocketErrorCB we, WebsocketCloseCB clo); 99 | 100 | /** 101 | * Hooks child socket's events 102 | * 103 | * @param childwebsocket The child websocket to hook. 104 | * @param recv Data receive callback 105 | * @param disc The disconnect callback 106 | * @param we The error callback 107 | * @return True if child socket was hooked, false otherwise 108 | */ 109 | native bool Websocket_HookChild(WebsocketHandle childwebsocket, WebsocketReceiveCB recv, WebsocketDisconnectCB disc, WebsocketErrorCB we); 110 | 111 | /** 112 | * Hooks child socket's readystate changes 113 | * 114 | * @param childwebsocket The child websocket to hook. 115 | * @param readystate ReadyState change callback 116 | * @return True if child socket was hooked, false otherwise 117 | */ 118 | native bool Websocket_HookReadyStateChange(WebsocketHandle childwebsocket, WebsocketReadyStateChangedCB readystate); 119 | 120 | 121 | /** 122 | * Sends text or binary data through the websocket 123 | * 124 | * @param childwebsocket The child websocket to send to 125 | * @param type The datatype SendType_Text or SendType_Binary 126 | * @param sPayLoad The data to send 127 | * @param dataSize If set, it's used as maxlength. Useful for binary data where \0 might be used before the end of the data. 128 | * @return True if child socket was hooked, false otherwise 129 | */ 130 | native bool Websocket_Send(WebsocketHandle childwebsocket, WebsocketSendType type, const char[] sPayload, const int dataSize =- 1); 131 | 132 | /** 133 | * Gets a child websocket's readyState. 134 | * 135 | * @param childwebsocket The child websocket 136 | * @return The readyState 137 | */ 138 | native WebsocketReadyState Websocket_GetReadyState(WebsocketHandle childwebsocket); 139 | 140 | /** 141 | * Unhooks a child socket's events: If there's no plugin listening anymore, the socket is closed. 142 | * 143 | * @param childwebsocket The child websocket 144 | * @noreturn 145 | */ 146 | native void Websocket_UnhookChild(WebsocketHandle childwebsocket); 147 | 148 | /** 149 | * Closes a listening master socket, created with Websocket_Open. 150 | * Note: The socket will still be open, if there are more plugins using it. 151 | * 152 | * Call this in OnPluginEnd()! 153 | * 154 | * @param websocket The master websocket 155 | * @noreturn 156 | */ 157 | native void Websocket_Close(WebsocketHandle websocket); 158 | 159 | public SharedPlugin __pl_websocket = 160 | { 161 | name = "websocket", 162 | file = "websocket.smx", 163 | #if defined REQUIRE_PLUGIN 164 | required = 1, 165 | #else 166 | required = 0, 167 | #endif 168 | }; 169 | 170 | #if !defined REQUIRE_PLUGIN 171 | public void __pl_websocket_SetNTVOptional() 172 | { 173 | MarkNativeAsOptional("Websocket_Open"); 174 | MarkNativeAsOptional("Websocket_HookReadyStateChange"); 175 | MarkNativeAsOptional("Websocket_HookChild"); 176 | MarkNativeAsOptional("Websocket_Send"); 177 | MarkNativeAsOptional("Websocket_GetReadyState"); 178 | MarkNativeAsOptional("Websocket_UnhookChild"); 179 | MarkNativeAsOptional("Websocket_Close"); 180 | } 181 | #endif 182 | -------------------------------------------------------------------------------- /scripting/websocket.sp: -------------------------------------------------------------------------------- 1 | #pragma semicolon 1 2 | #pragma dynamic 65536 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define PLUGIN_VERSION "1.2" 11 | 12 | #define DEBUG 0 13 | #if DEBUG > 0 14 | new String:g_sLog[PLATFORM_MAX_PATH]; 15 | #endif 16 | 17 | /** 18 | * This implementation follows the draft version 17 of the websocket protocol specifications 19 | * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 20 | */ 21 | 22 | // Used in the g_hMasterSocketPlugins child arrays 23 | enum MasterPluginCallbacks { 24 | Handle:MPC_PluginHandle, 25 | Function:MPC_ErrorCallback, 26 | Function:MPC_IncomingCallback, 27 | Function:MPC_CloseCallback 28 | } 29 | 30 | enum ChildPluginCallbacks { 31 | Handle:CPC_PluginHandle, 32 | Function:CPC_ReceiveCallback, 33 | Function:CPC_DisconnectCallback, 34 | Function:CPC_ErrorCallback, 35 | Function:CPC_ReadystateCallback 36 | } 37 | 38 | #define FRAGMENT_MAX_LENGTH 32768 39 | #define URL_MAX_LENGTH 2000 40 | 41 | // Handshake header parsing 42 | new Handle:g_hRegExKey; 43 | new Handle:g_hRegExPath; 44 | new Handle:g_hRegExProtocol; 45 | 46 | // Array of all master sockets we're listening on 47 | new Handle:g_hMasterSockets; 48 | new Handle:g_hMasterSocketHostPort; 49 | new Handle:g_hMasterSocketIndexes; 50 | // Array of arrays containing plugin handles which use this socket 51 | new Handle:g_hMasterSocketPlugins; 52 | new Handle:g_hMasterErrorForwards; 53 | new Handle:g_hMasterCloseForwards; 54 | new Handle:g_hMasterIncomingForwards; 55 | // Array connecting g_hChildSockets with g_hMasterSockets 56 | new Handle:g_hChildsMasterSockets; 57 | // Array of child sockets spawned by a master socket. Child indexes are mapped to master sockets via g_hMasterChildSockets {child => master,...} 58 | new Handle:g_hChildSockets; 59 | // Array of arrays containing plugin handles which use this child socket 60 | new Handle:g_hChildSocketPlugins; 61 | new Handle:g_hChildSocketIndexes; 62 | new Handle:g_hChildSocketHost; 63 | new Handle:g_hChildSocketPort; 64 | new Handle:g_hChildSocketReadyState; 65 | new Handle:g_hChildSocketFragmentedPayload; 66 | new Handle:g_hChildErrorForwards; 67 | new Handle:g_hChildReceiveForwards; 68 | new Handle:g_hChildDisconnectForwards; 69 | new Handle:g_hChildReadyStateChangeForwards; 70 | //new Handle:g_hChildRandomParameter; 71 | 72 | // This is passed as "handle" to plugins 73 | new g_iLastSocketIndex = 0; 74 | 75 | 76 | enum WebsocketFrameType { 77 | FrameType_Continuation = 0, 78 | FrameType_Text = 1, 79 | FrameType_Binary = 2, 80 | FrameType_Close = 8, 81 | FrameType_Ping = 9, 82 | FrameType_Pong = 10 83 | } 84 | 85 | enum WebsocketFrame 86 | { 87 | FIN, 88 | RSV1, 89 | RSV2, 90 | RSV3, 91 | WebsocketFrameType:OPCODE, 92 | MASK, 93 | PAYLOAD_LEN, 94 | String:MASKINGKEY[5], 95 | CLOSE_REASON 96 | } 97 | 98 | public Plugin:myinfo = 99 | { 100 | name = "Websocket", 101 | author = "Jannik \"Peace-Maker\" Hartung", 102 | description = "Websocket protocol implementation", 103 | version = PLUGIN_VERSION, 104 | url = "http://www.wcfan.de/" 105 | } 106 | 107 | public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) 108 | { 109 | RegPluginLibrary("websocket"); 110 | CreateNative("Websocket_Open", Native_Websocket_Open); 111 | CreateNative("Websocket_HookChild", Native_Websocket_HookChild); 112 | CreateNative("Websocket_HookReadyStateChange", Native_Websocket_HookReadyStateChange); 113 | CreateNative("Websocket_GetReadyState", Native_Websocket_GetReadyState); 114 | CreateNative("Websocket_Send", Native_Websocket_Send); 115 | CreateNative("Websocket_UnhookChild", Native_Websocket_UnhookChild); 116 | CreateNative("Websocket_Close", Native_Websocket_Close); 117 | return APLRes_Success; 118 | } 119 | 120 | public OnPluginStart() 121 | { 122 | new Handle:hVersion = CreateConVar("sm_websocket_version", PLUGIN_VERSION, "", FCVAR_NOTIFY|FCVAR_REPLICATED|FCVAR_DONTRECORD); 123 | if(hVersion != INVALID_HANDLE) 124 | SetConVarString(hVersion, PLUGIN_VERSION); 125 | 126 | // Setup handshake http header parsing regexes 127 | new RegexError:iRegExError, String:sError[64]; 128 | g_hRegExKey = CompileRegex("Sec-WebSocket-Key: (.*)\r\n", 0, sError, sizeof(sError), iRegExError); 129 | if(g_hRegExKey == INVALID_HANDLE) 130 | { 131 | SetFailState("Can't compile Sec-WebSocket-Key regex: %s (%d)", sError, _:iRegExError); 132 | } 133 | g_hRegExPath = CompileRegex("GET (.*)( HTTP/1.\\d)\r\n", 0, sError, sizeof(sError), iRegExError); 134 | if(g_hRegExPath == INVALID_HANDLE) 135 | { 136 | SetFailState("Can't compile GET-Path regex: %s (%d)", sError, _:iRegExError); 137 | } 138 | g_hRegExProtocol = CompileRegex("Sec-WebSocket-Protocol: (.*)\r\n", 0, sError, sizeof(sError), iRegExError); 139 | if(g_hRegExProtocol == INVALID_HANDLE) 140 | { 141 | SetFailState("Can't compile Sec-WebSocket-Protocol regex: %s (%d)", sError, _:iRegExError); 142 | } 143 | 144 | g_hMasterSockets = CreateArray(); 145 | g_hMasterSocketHostPort = CreateArray(ByteCountToCells(128)); 146 | g_hMasterSocketIndexes = CreateArray(); 147 | g_hMasterSocketPlugins = CreateArray(); 148 | g_hChildsMasterSockets = CreateArray(); 149 | g_hChildSockets = CreateArray(); 150 | g_hChildSocketIndexes = CreateArray(); 151 | g_hChildSocketPlugins = CreateArray(); 152 | g_hChildSocketHost = CreateArray(ByteCountToCells(64)); 153 | g_hChildSocketPort = CreateArray(); 154 | g_hChildSocketReadyState = CreateArray(); 155 | g_hChildSocketFragmentedPayload = CreateArray(); 156 | 157 | g_hMasterErrorForwards = CreateArray(); 158 | g_hMasterCloseForwards = CreateArray(); 159 | g_hMasterIncomingForwards = CreateArray(); 160 | g_hChildErrorForwards = CreateArray(); 161 | g_hChildReceiveForwards = CreateArray(); 162 | g_hChildDisconnectForwards = CreateArray(); 163 | g_hChildReadyStateChangeForwards = CreateArray(); 164 | 165 | #if DEBUG > 0 166 | BuildPath(Path_SM, g_sLog, sizeof(g_sLog), "logs/websocket_debug.log"); 167 | #endif 168 | } 169 | 170 | public OnPluginEnd() 171 | { 172 | while(GetArraySize(g_hMasterSockets)) 173 | CloseMasterSocket(0); 174 | } 175 | 176 | public Native_Websocket_Open(Handle:plugin, numParams) 177 | { 178 | new iSize = GetArraySize(g_hMasterSocketPlugins); 179 | new Handle:hMasterSocketPlugins, aPluginInfo[MasterPluginCallbacks]; 180 | 181 | // Currently only one websocket per plugin is supported. 182 | new iPluginCount; 183 | for(new i=0;i= iSize) 298 | { 299 | hMasterSocketPlugins = CreateArray(_:MasterPluginCallbacks); 300 | PushArrayCell(g_hMasterSocketPlugins, hMasterSocketPlugins); 301 | } 302 | else 303 | hMasterSocketPlugins = GetArrayCell(g_hMasterSocketPlugins, iIndex); 304 | 305 | aPluginInfo[MPC_PluginHandle] = plugin; 306 | aPluginInfo[MPC_ErrorCallback] = fErrorCallback; 307 | aPluginInfo[MPC_IncomingCallback] = fIncomingCallback; 308 | aPluginInfo[MPC_CloseCallback] = fCloseCallback; 309 | PushArrayArray(hMasterSocketPlugins, aPluginInfo[0], _:MasterPluginCallbacks); 310 | 311 | return _:iPseudoHandle; 312 | } 313 | 314 | public Native_Websocket_Send(Handle:plugin, numParams) 315 | { 316 | new WebsocketHandle:iPseudoChildHandle = WebsocketHandle:GetNativeCell(1); 317 | new iChildIndex; 318 | if(iPseudoChildHandle == INVALID_WEBSOCKET_HANDLE 319 | || (iChildIndex = FindValueInArray(g_hChildSocketIndexes, _:iPseudoChildHandle)) == -1) 320 | { 321 | ThrowNativeError(SP_ERROR_NATIVE, "Invalid child websocket handle."); 322 | return false; 323 | } 324 | 325 | new vFrame[WebsocketFrame]; 326 | vFrame[OPCODE] = WebsocketSendType:GetNativeCell(2)==SendType_Text?FrameType_Text:FrameType_Binary; 327 | 328 | vFrame[PAYLOAD_LEN] = GetNativeCell(4); 329 | if(vFrame[PAYLOAD_LEN] == -1) 330 | GetNativeStringLength(3, vFrame[PAYLOAD_LEN]); 331 | 332 | new String:sPayLoad[vFrame[PAYLOAD_LEN]+1]; 333 | 334 | GetNativeString(3, sPayLoad, vFrame[PAYLOAD_LEN]+1); 335 | 336 | vFrame[FIN] = 1; 337 | vFrame[CLOSE_REASON] = -1; 338 | SendWebsocketFrame(iChildIndex, sPayLoad, vFrame); 339 | return true; 340 | } 341 | 342 | public Native_Websocket_HookChild(Handle:plugin, numParams) 343 | { 344 | new WebsocketHandle:iPseudoChildHandle = WebsocketHandle:GetNativeCell(1); 345 | new iChildIndex; 346 | if(iPseudoChildHandle == INVALID_WEBSOCKET_HANDLE 347 | || (iChildIndex = FindValueInArray(g_hChildSocketIndexes, _:iPseudoChildHandle)) == -1) 348 | { 349 | ThrowNativeError(SP_ERROR_NATIVE, "Invalid child websocket handle."); 350 | return false; 351 | } 352 | 353 | new Handle:hReceiveForward = Handle:GetArrayCell(g_hChildReceiveForwards, iChildIndex); 354 | new Handle:hErrorForward = Handle:GetArrayCell(g_hChildErrorForwards, iChildIndex); 355 | new Handle:hDisconnectForward = Handle:GetArrayCell(g_hChildDisconnectForwards, iChildIndex); 356 | 357 | // Did this plugin already hook the child socket? Replace callbacks! 358 | new Handle:hChildSocketPlugin = Handle:GetArrayCell(g_hChildSocketPlugins, iChildIndex); 359 | new iPluginCount = GetArraySize(hChildSocketPlugin); 360 | new aPluginInfo[ChildPluginCallbacks]; 361 | new iPluginInfoIndex = -1; 362 | for(new p=0;p 0) 567 | { 568 | // There's been an error. 569 | if(bError) 570 | { 571 | // Inform plugins there's been an error 572 | Call_StartForward(hErrorForward); 573 | Call_PushCell(WebsocketHandle:iPseudoHandle); 574 | Call_PushCell(errorType); 575 | Call_PushCell(errorNum); 576 | Call_PushCell(0); // Dummy value as the master socket doesn't use the any:data paramter. 577 | Call_Finish(); 578 | } 579 | else 580 | { 581 | Call_StartForward(hCloseForward); 582 | Call_PushCell(WebsocketHandle:iPseudoHandle); 583 | Call_Finish(); 584 | } 585 | 586 | // Loop through all plugins using this socket 587 | new Handle:hPlugins = GetArrayCell(g_hMasterSocketPlugins, iIndex); 588 | new iPluginCount = GetArraySize(hPlugins); 589 | new aPluginInfo[MasterPluginCallbacks]; 590 | // This master socket is gone, remove forward 591 | for(new p=0;p 0) 759 | { 760 | if(!GetRegexSubString(g_hRegExProtocol, 1, sProtocol, sizeof(sProtocol))) 761 | { 762 | Format(sProtocol, sizeof(sProtocol), ""); 763 | // It's not required to specify a subprotocol! 764 | /*LogError("Failed to extract sub protocols."); 765 | CloseChildSocket(iIndex); 766 | return;*/ 767 | } 768 | } 769 | 770 | decl String:sPath[URL_MAX_LENGTH]; 771 | 772 | // Get the path 773 | iSubStrings = MatchRegex(g_hRegExPath, receiveData, iRegexError); 774 | if(iSubStrings <= 0 || !GetRegexSubString(g_hRegExPath, 1, sPath, sizeof(sPath))) 775 | sPath = ""; 776 | 777 | // Inform plugins, there's an incoming request 778 | new iMasterIndex = GetArrayCell(g_hChildsMasterSockets, iIndex); 779 | 780 | new Handle:hIncomingForward = GetArrayCell(g_hMasterIncomingForwards, iMasterIndex); 781 | Call_StartForward(hIncomingForward); 782 | Call_PushCell(WebsocketHandle:GetArrayCell(g_hMasterSocketIndexes, iMasterIndex)); 783 | Call_PushCell(WebsocketHandle:GetArrayCell(g_hChildSocketIndexes, iIndex)); 784 | decl String:remoteIP[65]; 785 | GetArrayString(g_hChildSocketHost, iIndex, remoteIP, sizeof(remoteIP)); 786 | Call_PushString(remoteIP); 787 | Call_PushCell(GetArrayCell(g_hChildSocketPort, iIndex)); 788 | // TODO SM_PARAM_STRING_UTF8 might be wrong here? SM_PARAM_STRING_COPY? 789 | new String:sProtocolReturn[256]; 790 | strcopy(sProtocolReturn, sizeof(sProtocolReturn), sProtocol); 791 | Call_PushStringEx(sProtocolReturn, sizeof(sProtocolReturn), SM_PARAM_STRING_UTF8, SM_PARAM_COPYBACK); 792 | Call_PushString(sPath); 793 | 794 | new Action:iResult; 795 | Call_Finish(iResult); 796 | 797 | // TODO be more friendly in refusing connections by sending a proper http header. 798 | // Someone doesn't like this connection.. 799 | if(iResult >= Plugin_Handled) 800 | { 801 | Debug(1, "IncomingForward is >= Plugin_Handled. Closing child socket."); 802 | // Uhm.. Just because of that one? plugin denying the connection, we need to revert any Websocket_HookChild call.. 803 | CloseChildSocket(iIndex); 804 | 805 | return; 806 | } 807 | 808 | // Check if any plugin called Websocket_HookChild and close the socket, if not. 809 | new Handle:hChildSocketPlugins = GetArrayCell(g_hChildSocketPlugins, iIndex); 810 | if(GetArraySize(hChildSocketPlugins) == 0) 811 | { 812 | Debug(1, "No plugin hooked the new child socket. Closing child socket."); 813 | CloseChildSocket(iIndex); 814 | 815 | return; 816 | } 817 | 818 | // Make sure the server offered this protocol the plugin chose. 819 | // Should probably error out here? 820 | if(StrContains(sProtocol, sProtocolReturn) == -1) 821 | { 822 | Debug(1, "Plugin chose non-existant subprotocol. Offered: \"%s\" - Chosen: \"%s\"", sProtocol, sProtocolReturn); 823 | Format(sProtocolReturn, sizeof(sProtocolReturn), ""); 824 | } 825 | else if(strlen(sProtocol) > 0) 826 | Format(sProtocolReturn, sizeof(sProtocolReturn), "\r\nSec-Websocket-Protocol: %s", sProtocol); 827 | 828 | // Prepare HTTP request 829 | decl String:sHTTPRequest[512]; 830 | Format(sHTTPRequest, sizeof(sHTTPRequest), "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s%s\r\n\r\n", sResponseKey, sProtocolReturn); 831 | SocketSend(socket, sHTTPRequest); 832 | 833 | Debug(2, "Responding: %s", sHTTPRequest); 834 | 835 | SetArrayCell(g_hChildSocketReadyState, iIndex, State_Open); 836 | 837 | // Inform the other plugins of the change. 838 | new Handle:hReadyStateChangeForward = GetArrayCell(g_hChildReadyStateChangeForwards, iIndex); 839 | 840 | Call_StartForward(hReadyStateChangeForward); 841 | Call_PushCell(WebsocketHandle:GetArrayCell(g_hChildSocketIndexes, iIndex)); 842 | Call_PushCell(GetArrayCell(g_hChildSocketReadyState, iIndex)); 843 | Call_Finish(); 844 | } 845 | // We're open to receive info! Parse the input. 846 | case State_Open: 847 | { 848 | new vFrame[WebsocketFrame], String:sPayLoad[dataSize-1]; 849 | ParseFrame(vFrame, receiveData, dataSize, sPayLoad); 850 | if(!PreprocessFrame(iIndex, vFrame, sPayLoad)) 851 | { 852 | // Call forward 853 | //SendWebsocketFrame(iIndex, sPayLoad, vFrame); 854 | new Handle:hReceiveForward = Handle:GetArrayCell(g_hChildReceiveForwards, iIndex); 855 | Call_StartForward(hReceiveForward); 856 | Call_PushCell(arg); 857 | 858 | // This is a fragmented message. 859 | if(vFrame[OPCODE] == FrameType_Continuation) 860 | { 861 | new Handle:hFragmentedPayload = Handle:GetArrayCell(g_hChildSocketFragmentedPayload, iIndex); 862 | new iPayloadLength = GetArrayCell(hFragmentedPayload, 0); 863 | 864 | new String:sConcatPayload[iPayloadLength]; 865 | new String:sPayloadPart[FRAGMENT_MAX_LENGTH]; 866 | new iSize = GetArraySize(hFragmentedPayload); 867 | // Concat all the payload parts 868 | // TODO: Make this binary safe? GetArrayArray vs. GetArrayString? 869 | for(new i=2;i 126) 998 | { 999 | new String:sLoongLength[49]; 1000 | for(new i=2;i<8;i++) 1001 | Format(sLoongLength, sizeof(sLoongLength), "%s%08b", sLoongLength, receiveData[i]); 1002 | 1003 | vFrame[PAYLOAD_LEN] = bindec(sLoongLength); 1004 | iOffset += 6; 1005 | } 1006 | else if(vFrame[PAYLOAD_LEN] > 125) 1007 | { 1008 | new String:sLongLength[17]; 1009 | for(new i=2;i<4;i++) 1010 | Format(sLongLength, sizeof(sLongLength), "%s%08b", sLongLength, receiveData[i]); 1011 | 1012 | vFrame[PAYLOAD_LEN] = bindec(sLongLength); 1013 | iOffset += 2; 1014 | } 1015 | if(vFrame[MASK]) 1016 | { 1017 | for(new i=iOffset,j=0;j<4;i++,j++) 1018 | vFrame[MASKINGKEY][j] = receiveData[i]; 1019 | vFrame[MASKINGKEY][4] = '\0'; 1020 | iOffset += 4; 1021 | } 1022 | 1023 | new iPayLoad[vFrame[PAYLOAD_LEN]]; 1024 | for(new i=iOffset,j=0;j= FrameType_Close) 1077 | { 1078 | LogError("Received fragmented control frame. %d", vFrame[OPCODE]); 1079 | CloseConnection(iIndex, 1002, "Received fragmented control frame."); 1080 | return true; 1081 | } 1082 | 1083 | new Handle:hFragmentedPayload = Handle:GetArrayCell(g_hChildSocketFragmentedPayload, iIndex); 1084 | new iPayloadLength = GetArrayCell(hFragmentedPayload, 0); 1085 | 1086 | // This is the first frame of a serie of fragmented ones. 1087 | if(iPayloadLength == 0) 1088 | { 1089 | if(vFrame[OPCODE] == FrameType_Continuation) 1090 | { 1091 | LogError("Received first fragmented frame with opcode 0. The first fragment MUST have a different opcode set."); 1092 | CloseConnection(iIndex, 1002, "Received first fragmented frame with opcode 0. The first fragment MUST have a different opcode set."); 1093 | return true; 1094 | } 1095 | 1096 | // Remember which type of message this fragmented one is. 1097 | SetArrayCell(hFragmentedPayload, 1, vFrame[OPCODE]); 1098 | } 1099 | else 1100 | { 1101 | if(vFrame[OPCODE] != FrameType_Continuation) 1102 | { 1103 | LogError("Received second or later frame of fragmented message with opcode %d. opcode must be 0.", vFrame[OPCODE]); 1104 | CloseConnection(iIndex, 1002, "Received second or later frame of fragmented message with opcode other than 0. opcode must be 0."); 1105 | return true; 1106 | } 1107 | } 1108 | 1109 | // Keep track of the overall payload length of the fragmented message. 1110 | // This is used to create the buffer of the right size when passing it to the listening plugin. 1111 | iPayloadLength += vFrame[PAYLOAD_LEN]; 1112 | SetArrayCell(hFragmentedPayload, 0, iPayloadLength); 1113 | 1114 | // This doesn't fit inside one array cell? Split it up. 1115 | if(vFrame[PAYLOAD_LEN] > FRAGMENT_MAX_LENGTH) 1116 | { 1117 | for(new i=0;i FRAGMENT_MAX_LENGTH) 1162 | { 1163 | for(new i=0;i 65535) 1246 | length += 10; 1247 | else if(length > 125) 1248 | length += 4; 1249 | else 1250 | length += 2; 1251 | if(vFrame[CLOSE_REASON] != -1) 1252 | length += 2; 1253 | Debug(1, "Sending: \"%s\"", sFrame); 1254 | Debug(2, "FIN: %d", vFrame[FIN]); 1255 | Debug(2, "RSV1: %d", vFrame[RSV1]); 1256 | Debug(2, "RSV2: %d", vFrame[RSV2]); 1257 | Debug(2, "RSV3: %d", vFrame[RSV3]); 1258 | Debug(2, "OPCODE: %d", _:vFrame[OPCODE]); 1259 | Debug(2, "MASK: %d", vFrame[MASK]); 1260 | Debug(2, "PAYLOAD_LEN: %d", vFrame[PAYLOAD_LEN]); 1261 | Debug(2, "PAYLOAD: %s", sPayLoad); 1262 | Debug(2, "CLOSE_REASON: %d", vFrame[CLOSE_REASON]); 1263 | Debug(2, "Frame: %s", sFrame); 1264 | SocketSend(GetArrayCell(g_hChildSockets, iIndex), sFrame, length); 1265 | return true; 1266 | } 1267 | 1268 | return false; 1269 | } 1270 | 1271 | bool:PackFrame(String:sPayLoad[], String:sFrame[], vFrame[WebsocketFrame]) 1272 | { 1273 | new length = vFrame[PAYLOAD_LEN]; 1274 | 1275 | // We don't split frames (yet), so FIN is always 1. 1276 | switch(vFrame[OPCODE]) 1277 | { 1278 | case FrameType_Text: 1279 | { 1280 | sFrame[0] = 129; // (1<<0)|(1<<7) - Text-Frame (10000001): 1281 | } 1282 | case FrameType_Close: 1283 | { 1284 | sFrame[0] = 136; // (1<<3)|(1<<7) - Close-Frame (10001000): 1285 | length += 2; // Remember the 2byte close reason 1286 | } 1287 | case FrameType_Ping: 1288 | { 1289 | sFrame[0] = 137; // (1<<0)|(1<<3)|(1<<7) - Ping-Frame (10001001): 1290 | } 1291 | case FrameType_Pong: 1292 | { 1293 | sFrame[0] = 138; // (1<<1)|(1<<3)|(1<<7) - Pong-Frame (10001010): 1294 | } 1295 | default: 1296 | { 1297 | LogError("Trying to send frame with unknown opcode = %d", _:vFrame[OPCODE]); 1298 | return false; 1299 | } 1300 | } 1301 | 1302 | new iOffset; 1303 | 1304 | // Add the length "byte" (7bit). We're only sending unmasked messages 1305 | if(length > 65535) 1306 | { 1307 | sFrame[1] = 127; 1308 | decl String:sLengthBin[65], String:sByte[9]; 1309 | Format(sLengthBin, 65, "%064b", length); 1310 | for(new i=0,j=2;j<=10;i++) 1311 | { 1312 | if(i && !(i%8)) 1313 | { 1314 | sFrame[j] = bindec(sByte); 1315 | Format(sByte, 9, ""); 1316 | j++; 1317 | } 1318 | Format(sByte, 9, "%s%s", sByte, sLengthBin[i]); 1319 | } 1320 | 1321 | // the most significant bit MUST be 0 1322 | if(sFrame[2] > 127) 1323 | { 1324 | LogError("Can't send frame. Too much data."); 1325 | return false; 1326 | } 1327 | iOffset = 9; 1328 | } 1329 | else if(length > 125) 1330 | { 1331 | sFrame[1] = 126; 1332 | if(length < 256) 1333 | { 1334 | sFrame[2] = 0; 1335 | sFrame[3] = length; 1336 | } 1337 | else 1338 | { 1339 | new String:sLengthBin[17], String:sByte[9]; 1340 | Format(sLengthBin, 17, "%016b", length); 1341 | for(new i=0,j=2;i<=16;i++) 1342 | { 1343 | if(i && !(i%8)) 1344 | { 1345 | sFrame[j] = bindec(sByte); 1346 | Format(sByte, 9, ""); 1347 | j++; 1348 | } 1349 | Format(sByte, 9, "%s%s", sByte, sLengthBin[i]); 1350 | } 1351 | } 1352 | iOffset = 4; 1353 | } 1354 | else 1355 | { 1356 | sFrame[1] = length; 1357 | iOffset = 2; 1358 | } 1359 | 1360 | // Make sure we didn't set the MASK bit by accident.. 1361 | sFrame[1] &= ~(1<<7); 1362 | vFrame[MASK] = 0; 1363 | 1364 | // We got a closing reason. Add it right in front of the payload. 1365 | if(vFrame[OPCODE] == FrameType_Close && vFrame[CLOSE_REASON] != -1) 1366 | { 1367 | new String:sCloseReasonBin[17], String:sByte[9]; 1368 | Format(sCloseReasonBin, 17, "%016b", vFrame[CLOSE_REASON]); 1369 | for(new i=0,j=iOffset;i<=16;i++) 1370 | { 1371 | if(i && !(i%8)) 1372 | { 1373 | sFrame[j] = bindec(sByte); 1374 | Format(sByte, 9, ""); 1375 | j++; 1376 | } 1377 | Format(sByte, 9, "%s%s", sByte, sCloseReasonBin[i]); 1378 | } 1379 | iOffset += 2; 1380 | } 1381 | 1382 | // Add the payload at the end. 1383 | strcopy(sFrame[iOffset], length+iOffset, sPayLoad); 1384 | 1385 | return true; 1386 | } 1387 | 1388 | // Close the connection by initiating the connection close handshake with the CLOSE opcode 1389 | CloseConnection(iIndex, iCloseReason, String:sPayLoad[]) 1390 | { 1391 | new vFrame[WebsocketFrame]; 1392 | vFrame[OPCODE] = FrameType_Close; 1393 | vFrame[CLOSE_REASON] = iCloseReason; 1394 | vFrame[PAYLOAD_LEN] = strlen(sPayLoad); 1395 | SendWebsocketFrame(iIndex, sPayLoad, vFrame); 1396 | SetArrayCell(g_hChildSocketReadyState, iIndex, State_Closing); 1397 | 1398 | new Handle:hReadyStateChangeForward = GetArrayCell(g_hChildReadyStateChangeForwards, iIndex); 1399 | 1400 | // Inform the other plugins of the change. 1401 | Call_StartForward(hReadyStateChangeForward); 1402 | Call_PushCell(WebsocketHandle:GetArrayCell(g_hChildSocketIndexes, iIndex)); 1403 | Call_PushCell(GetArrayCell(g_hChildSocketReadyState, iIndex)); 1404 | Call_Finish(); 1405 | } 1406 | 1407 | stock Debug(iDebugLevel, String:fmt[], any:...) 1408 | { 1409 | #if DEBUG > 0 1410 | if(iDebugLevel > DEBUG) 1411 | return; 1412 | decl String:sBuffer[512]; 1413 | VFormat(sBuffer, sizeof(sBuffer), fmt, 3); 1414 | LogToFile(g_sLog, sBuffer); 1415 | #endif 1416 | } 1417 | 1418 | stock GetIndexOfMasterSocket(Handle:socket) 1419 | { 1420 | new iSize = GetArraySize(g_hMasterSockets); 1421 | for(new i=0;i