├── .gitignore ├── LICENSE ├── README.md ├── docker-compose.yml ├── lua └── autorun │ └── server │ ├── sha1.lua │ ├── websocket.lua │ └── ws_tests.lua └── test_config └── fuzzingserver.json /.gitignore: -------------------------------------------------------------------------------- 1 | test_reports -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 David "HunterNL" van der Does 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gmod Websockets 2 | GLua module to allow communication via websockets 3 | 4 | Requires [gm_bromsock](https://github.com/Bromvlieg/gm_bromsock) 5 | 6 | Uses a SHA1 implementation by [Enrique García](https://github.com/kikito) [Repo link](https://github.com/kikito/sha1.lua) 7 | 8 | Tested with [Autobahn test suite](http://autobahn.ws/testsuite/) 9 | 10 | #### Limitations 11 | * No UTF8 validity checks 12 | * Cannot send/receive payloads larger than 4MB 13 | 14 | 15 | #### Documentation 16 | Likely to change if I pick this up again, but as of this commit: 17 | 18 | `WS.Client(url,port)` takes a url and port to server, returns a websocket client object 19 | 20 | `WS:Connect()` Connect to server 21 | 22 | `WS:Send(data)` Send given data to server, data can either be a string or an array of bytes (numbers) 23 | 24 | `WS:Close()` Close the connection 25 | 26 | `WS:IsActive()` Returns `true` if connection is active, `false` otherwise 27 | 28 | `WS:on("open", func)` Takes a function to run when a websocket connection has been established 29 | 30 | `WS:on("message", func)` Takes a function to run when a message from the server has been received, receives single argument with server message as string 31 | 32 | `WS:on("close", func)` Takes a function to run when the connection is closed 33 | 34 | :tiger2: 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | autobahn: 4 | image: crossbario/autobahn-testsuite 5 | ports: 6 | - "9333:9333" 7 | volumes: 8 | - ./test_config:/config 9 | - ./test_reports:/reports 10 | 11 | -------------------------------------------------------------------------------- /lua/autorun/server/sha1.lua: -------------------------------------------------------------------------------- 1 | sha1 = { 2 | _VERSION = "sha.lua 0.5.0", 3 | _URL = "https://github.com/kikito/sha.lua", 4 | _DESCRIPTION = [[ 5 | SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1) 6 | Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1) 7 | And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt) 8 | ]], 9 | _LICENSE = [[ 10 | MIT LICENSE 11 | 12 | Copyright (c) 2013 Enrique García Cota + Eike Decker + Jeffrey Friedl 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the 16 | "Software"), to deal in the Software without restriction, including 17 | without limitation the rights to use, copy, modify, merge, publish, 18 | distribute, sublicense, and/or sell copies of the Software, and to 19 | permit persons to whom the Software is furnished to do so, subject to 20 | the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included 23 | in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 26 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 28 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 29 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 30 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | ]] 33 | } 34 | 35 | ----------------------------------------------------------------------------------- 36 | 37 | -- loading this file (takes a while but grants a boost of factor 13) 38 | local PRELOAD_CACHE = true 39 | 40 | local BLOCK_SIZE = 64 -- 512 bits 41 | --local BLOCK_SIZE = 20 -- 160 bits 42 | 43 | -- local storing of global functions (minor speedup) 44 | local floor,modf = math.floor,math.modf 45 | local char,format,rep = string.char,string.format,string.rep 46 | 47 | -- merge 4 bytes to an 32 bit word 48 | local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end 49 | -- split a 32 bit word into four 8 bit numbers 50 | local function w32_to_bytes(i) 51 | return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100 52 | end 53 | 54 | -- shift the bits of a 32 bit word. Don't use negative values for "bits" 55 | local function w32_rot(bits,a) 56 | local b2 = 2^(32-bits) 57 | local a,b = modf(a/b2) 58 | return a+b*b2*(2^(bits)) 59 | end 60 | 61 | -- caching function for functions that accept 2 arguments, both of values between 62 | -- 0 and 255. The function to be cached is passed, all values are calculated 63 | -- during loading and a function is returned that returns the cached values (only) 64 | local function cache2arg(fn) 65 | if not PRELOAD_CACHE then return fn end 66 | local lut = {} 67 | for i=0,0xffff do 68 | local a,b = floor(i/0x100),i%0x100 69 | lut[i] = fn(a,b) 70 | end 71 | return function(a,b) 72 | return lut[a*0x100+b] 73 | end 74 | end 75 | 76 | -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans 77 | local function byte_to_bits(b) 78 | local b = function(n) 79 | local b = floor(b/n) 80 | return b%2==1 81 | end 82 | return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) 83 | end 84 | 85 | -- builds an 8bit number from 8 booleans 86 | local function bits_to_byte(a,b,c,d,e,f,g,h) 87 | local function n(b,x) return b and x or 0 end 88 | return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128) 89 | end 90 | 91 | -- bitwise "and" function for 2 8bit number 92 | local band = cache2arg (function(a,b) 93 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 94 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 95 | return bits_to_byte( 96 | A and a, B and b, C and c, D and d, 97 | E and e, F and f, G and g, H and h) 98 | end) 99 | 100 | -- bitwise "or" function for 2 8bit numbers 101 | local bor = cache2arg(function(a,b) 102 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 103 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 104 | return bits_to_byte( 105 | A or a, B or b, C or c, D or d, 106 | E or e, F or f, G or g, H or h) 107 | end) 108 | 109 | -- bitwise "xor" function for 2 8bit numbers 110 | local bxor = cache2arg(function(a,b) 111 | local A,B,C,D,E,F,G,H = byte_to_bits(b) 112 | local a,b,c,d,e,f,g,h = byte_to_bits(a) 113 | return bits_to_byte( 114 | A ~= a, B ~= b, C ~= c, D ~= d, 115 | E ~= e, F ~= f, G ~= g, H ~= h) 116 | end) 117 | 118 | -- bitwise complement for one 8bit number 119 | local function bnot(x) 120 | return 255-(x % 256) 121 | end 122 | 123 | -- creates a function to combine to 32bit numbers using an 8bit combination function 124 | local function w32_comb(fn) 125 | return function(a,b) 126 | local aa,ab,ac,ad = w32_to_bytes(a) 127 | local ba,bb,bc,bd = w32_to_bytes(b) 128 | return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd)) 129 | end 130 | end 131 | 132 | -- create functions for and, xor and or, all for 2 32bit numbers 133 | local w32_and = w32_comb(band) 134 | local w32_xor = w32_comb(bxor) 135 | local w32_or = w32_comb(bor) 136 | 137 | -- xor function that may receive a variable number of arguments 138 | local function w32_xor_n(a,...) 139 | local aa,ab,ac,ad = w32_to_bytes(a) 140 | for i=1,select('#',...) do 141 | local ba,bb,bc,bd = w32_to_bytes(select(i,...)) 142 | aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd) 143 | end 144 | return bytes_to_w32(aa,ab,ac,ad) 145 | end 146 | 147 | -- combining 3 32bit numbers through binary "or" operation 148 | local function w32_or3(a,b,c) 149 | local aa,ab,ac,ad = w32_to_bytes(a) 150 | local ba,bb,bc,bd = w32_to_bytes(b) 151 | local ca,cb,cc,cd = w32_to_bytes(c) 152 | return bytes_to_w32( 153 | bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd)) 154 | ) 155 | end 156 | 157 | -- binary complement for 32bit numbers 158 | local function w32_not(a) 159 | return 4294967295-(a % 4294967296) 160 | end 161 | 162 | -- adding 2 32bit numbers, cutting off the remainder on 33th bit 163 | local function w32_add(a,b) return (a+b) % 4294967296 end 164 | 165 | -- adding n 32bit numbers, cutting off the remainder (again) 166 | local function w32_add_n(a,...) 167 | for i=1,select('#',...) do 168 | a = (a+select(i,...)) % 4294967296 169 | end 170 | return a 171 | end 172 | -- converting the number to a hexadecimal string 173 | local function w32_to_hexstring(w) return format("%08x",w) end 174 | 175 | local function hex_to_binary(hex) 176 | return hex:gsub('..', function(hexval) 177 | return string.char(tonumber(hexval, 16)) 178 | end) 179 | end 180 | 181 | -- building the lookuptables ahead of time (instead of littering the source code 182 | -- with precalculated values) 183 | local xor_with_0x5c = {} 184 | local xor_with_0x36 = {} 185 | for i=0,0xff do 186 | xor_with_0x5c[char(i)] = char(bxor(i,0x5c)) 187 | xor_with_0x36[char(i)] = char(bxor(i,0x36)) 188 | end 189 | 190 | ----------------------------------------------------------------------------- 191 | 192 | -- calculating the SHA1 for some text 193 | function sha1.sha1(msg) 194 | local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 195 | local msg_len_in_bits = #msg * 8 196 | 197 | local first_append = char(0x80) -- append a '1' bit plus seven '0' bits 198 | 199 | local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length 200 | local current_mod = non_zero_message_bytes % 64 201 | local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" 202 | 203 | -- now to append the length as a 64-bit number. 204 | local B1, R1 = modf(msg_len_in_bits / 0x01000000) 205 | local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) 206 | local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) 207 | local B4 = 0x00000100 * R3 208 | 209 | local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits 210 | .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits 211 | 212 | msg = msg .. first_append .. second_append .. L64 213 | 214 | assert(#msg % 64 == 0) 215 | 216 | local chunks = #msg / 64 217 | 218 | local W = { } 219 | local start, A, B, C, D, E, f, K, TEMP 220 | local chunk = 0 221 | 222 | while chunk < chunks do 223 | -- 224 | -- break chunk up into W[0] through W[15] 225 | -- 226 | start,chunk = chunk * 64 + 1,chunk + 1 227 | 228 | for t = 0, 15 do 229 | W[t] = bytes_to_w32(msg:byte(start, start + 3)) 230 | start = start + 4 231 | end 232 | 233 | -- 234 | -- build W[16] through W[79] 235 | -- 236 | for t = 16, 79 do 237 | -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). 238 | W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16])) 239 | end 240 | 241 | A,B,C,D,E = H0,H1,H2,H3,H4 242 | 243 | for t = 0, 79 do 244 | if t <= 19 then 245 | -- (B AND C) OR ((NOT B) AND D) 246 | f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) 247 | K = 0x5A827999 248 | elseif t <= 39 then 249 | -- B XOR C XOR D 250 | f = w32_xor_n(B, C, D) 251 | K = 0x6ED9EBA1 252 | elseif t <= 59 then 253 | -- (B AND C) OR (B AND D) OR (C AND D 254 | f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) 255 | K = 0x8F1BBCDC 256 | else 257 | -- B XOR C XOR D 258 | f = w32_xor_n(B, C, D) 259 | K = 0xCA62C1D6 260 | end 261 | 262 | -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 263 | A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K), 264 | A, w32_rot(30, B), C, D 265 | end 266 | -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 267 | H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E) 268 | end 269 | local f = w32_to_hexstring 270 | return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) 271 | end 272 | 273 | 274 | function sha1.binary(msg) 275 | return hex_to_binary(sha1.sha1(msg)) 276 | end 277 | 278 | function sha1.hmac(key, text) 279 | assert(type(key) == 'string', "key passed to sha1.hmac should be a string") 280 | assert(type(text) == 'string', "text passed to sha1.hmac should be a string") 281 | 282 | if #key > BLOCK_SIZE then 283 | key = sha1.binary(key) 284 | end 285 | 286 | local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key) 287 | local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key) 288 | 289 | return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text)) 290 | end 291 | 292 | function sha1.hmac_binary(key, text) 293 | return hex_to_binary(sha1.hmac(key, text)) 294 | end 295 | 296 | setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end }) 297 | 298 | return sha1 299 | -------------------------------------------------------------------------------- /lua/autorun/server/websocket.lua: -------------------------------------------------------------------------------- 1 | --See RFC 6455 https://tools.ietf.org/html/rfc6455 for how websockets are supposed to work 2 | --Also I'm just calling the first 2 bytes the header 3 | if CLIENT then return end 4 | 5 | print("Websockets loaded") 6 | 7 | require( "bromsock" ); 8 | include("sha1.lua") 9 | 10 | if 11 | not WS 12 | then 13 | WS = {} 14 | WS.__index = WS 15 | 16 | WS.Client = {} 17 | WS.Client.__index = WS.Client 18 | setmetatable(WS.Client,{ 19 | __call = function(self,...) 20 | return WS.Client.Create(...) 21 | end, 22 | __index = WS --Inherits WS 23 | }) 24 | 25 | WS.Server = {} 26 | WS.Server.__index = WS.Server 27 | setmetatable(WS.Server,{ 28 | __call = function(self,...) 29 | return WS.Server.Create(...) 30 | end, 31 | __index = WS --Inherits WS 32 | }) 33 | 34 | WS.Connection = {} 35 | WS.Connection.__index = WS.Connection 36 | setmetatable(WS.Connection,{ 37 | __call = function(self,...) 38 | return WS.Connection.Create(...) 39 | end 40 | --Does NOT inherit 41 | }) 42 | 43 | WS.verbose = false --Debugging 44 | WS.close_timeout = 5 -- Time to wait for a server close reply before just closing the socket 45 | end; 46 | 47 | WS.GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 48 | 49 | WS.OPCODES = {} 50 | WS.OPCODES.OPCODE_CONTINUE = 0x0 51 | WS.OPCODES.OPCODE_TEXT_FRAME = 0x1 52 | WS.OPCODES.OPCODE_BINARY_FRAME = 0x2 53 | WS.OPCODES.OPCODE_CNX_CLOSE = 0x8 54 | WS.OPCODES.OPCODE_PING = 0x9 55 | WS.OPCODES.OPCODE_PONG = 0xA 56 | 57 | --For debugging 58 | local function toBitsMSB(num,bits) 59 | bits = bits or select(2,math.frexp(num)) 60 | local t={} -- will contain the bits 61 | for b=bits,1,-1 do 62 | t[b]=math.fmod(num,2) 63 | num=(num-t[b])/2 64 | end 65 | return table.concat(t) 66 | end 67 | 68 | 69 | --Constructor 70 | function WS.Client.Create(url,port) 71 | local self = setmetatable({},WS.Client) 72 | if(WS.verbose) then 73 | print("Made new websocket client ") 74 | end 75 | 76 | self.bsock = BromSock() 77 | 78 | self.port = port 79 | 80 | self.url_info = WS.parseUrl(url) 81 | self.url_info.port = self.port 82 | self.url_info.httphost = self.url_info.host .. ":" .. self.port 83 | 84 | self.websocket = WS.Connection(self.bsock,false,self.url_info) 85 | 86 | self.eventListeners = {} 87 | 88 | self.websocket:SetOnCloseCallback(function() 89 | self:fireEvent("close") 90 | end) 91 | 92 | self.websocket:SetOnMessageCallback(function(msg) 93 | self:fireEvent("message",msg) 94 | end) 95 | 96 | self.websocket:SetOnOpenCallback(function() 97 | self:fireEvent("open") 98 | end) 99 | 100 | self.bsock:SetCallbackConnect(function(...) 101 | self:OnConnected(...) 102 | end) 103 | 104 | 105 | 106 | return self 107 | end 108 | 109 | function WS.Client:Connect() 110 | self.bsock:Connect(self.url_info.host,self.port) 111 | end 112 | 113 | function WS.Client:OnConnected(sock,success,ip,port) 114 | if(not success) then 115 | print(success) 116 | error("Socked failed to connect") 117 | end 118 | 119 | if self.url_info.protocol == "wss://" then 120 | self.bsock:StartSSLClient() 121 | end 122 | 123 | self.websocket:Connect() 124 | end 125 | 126 | 127 | function WS.Client:Send(data) 128 | self.websocket:Send(data) 129 | end 130 | 131 | function WS.Client:Close() 132 | if(self.websocket.state != "CLOSED") then 133 | self.websocket:Close() 134 | end 135 | end 136 | 137 | function WS.Client:IsActive() 138 | 139 | local wsstate = self.websocket.state 140 | print("isactive state "..wsstate) 141 | return (wsstate != "CLOSED") 142 | end 143 | 144 | function WS.Connection.Create(bsock,isServer,destination_info) --Takes fully enstablished bromsock and bool for being server 145 | --Being server affect handshake order, masking requirements anup close order 146 | local self = setmetatable({},WS.Connection) 147 | 148 | self.isServer = isServer 149 | self.isClient = not self.isServer 150 | 151 | self.echo = false --Debugging/testing aid, set true to echo all text and binary frames back 152 | 153 | self.bsock = bsock 154 | 155 | if(self.isClient) then 156 | table.Merge(self,destination_info) 157 | end 158 | 159 | 160 | self.state = "CLOSED" 161 | self.frame = {} 162 | 163 | self.split_payload = nil 164 | self.sentCloseFrame = false 165 | self.receivedCloseFrame = false 166 | 167 | --self.receiving_fragmented_payload = false 168 | self.receivedHTTPHandshake = false 169 | 170 | self.bsock:SetCallbackReceive(function(...) 171 | self:OnReceive(...) 172 | end) 173 | 174 | self.bsock:SetCallbackSend(function(...) 175 | self:OnSend(...) 176 | end) 177 | 178 | self.bsock:SetCallbackDisconnect(function(...) 179 | self:OnDisconnect(...) 180 | end) 181 | 182 | return self 183 | end 184 | 185 | function WS.Connection:Connect() 186 | if(not self.state=="CLOSED") then 187 | error("Websocket must be closed to connect") 188 | end 189 | self.state = "CONNECTING" 190 | if(self.isClient) then 191 | self:SendHTTPHandShake() --Client initiates handshake 192 | end 193 | 194 | self:ReceiveHTTPHandShake() --Server AND clientlisten to handshake or response 195 | end 196 | 197 | function WS.Connection:ReceiveFrame() 198 | if(WS.verbose) then 199 | print("Preparing to receive next frame") 200 | end 201 | self.frame = {} 202 | 203 | self.frame.receiveState = "HEADER" 204 | self.bsock:Receive(2) --Receive the header (first 2 bytes) 205 | end 206 | 207 | function WS.Connection:Close(code) 208 | if(self.state=="CLOSED") then 209 | error("Connection already closed, cannot close, current state "..(self.state or "")) 210 | return 211 | end 212 | 213 | if(self.state=="OPEN") then 214 | self.state="CLOSING" 215 | end 216 | 217 | if(not self.sentCloseFrame) then 218 | self:SendCloseFrame(code or 1000) 219 | end 220 | 221 | if(not self.receivedCloseFrame) then 222 | self:ReceiveFrame() 223 | end 224 | 225 | end 226 | 227 | function WS.Connection:Disconnect() 228 | if(self.state=="CLOSED") then return end; 229 | if(WS.verbose) then 230 | print("Closing connection") 231 | end 232 | 233 | self.bsock:Disconnect() 234 | end 235 | 236 | function WS.Connection:OnFrameComplete(frame) 237 | local opcode = frame.opcode 238 | local payload = frame.payload 239 | 240 | if(WS.verbose) then 241 | print("Received opcode: "..WS.findOpcode(opcode)) 242 | print("Received payload: ".. (frame.payload or "")) 243 | end 244 | 245 | if(opcode > 15) then --Check if reversed bits are set 246 | self:ProtocolError(1002,"Reserved bits must be 0") 247 | return 248 | end 249 | 250 | if(opcode == WS.OPCODES.OPCODE_CNX_CLOSE) then 251 | self:onCloseMessage(frame) --Handle close messages in seperate function 252 | return 253 | end 254 | 255 | if(self.state!="OPEN") then --If we're not properly connected and we get a message discard it and get a new one 256 | print("Unwanted message while not OPEN, current state is "..self.state) 257 | print("Discaring message with opcode "..WS.findOpcode(frame.opcode)) 258 | self:ReceiveFrame() 259 | return 260 | end 261 | 262 | if (opcode == WS.OPCODES.OPCODE_PING) then 263 | self:onPing(frame) --Reply to pings 264 | return 265 | end 266 | 267 | --Main frame handler 268 | if (opcode == WS.OPCODES.OPCODE_TEXT_FRAME or opcode == WS.OPCODES.OPCODE_BINARY_FRAME) then --We accept binary frames, but don't really support them :V 269 | 270 | 271 | if(self.split_payload!=nil) then 272 | self:ProtocolError(1002,"Continuation frames must have continue opcode") 273 | return 274 | end 275 | 276 | if(frame.fin) then 277 | self:OnPayloadComplete(payload,opcode) 278 | else --If final frame in message, end, else save payload for next frame 279 | self.split_payload=(payload or "") 280 | self.split_payload_type = opcode 281 | 282 | if(WS.verbose) then 283 | print("Receiving split message") 284 | end 285 | 286 | end 287 | 288 | self:ReceiveFrame() 289 | return 290 | end 291 | 292 | if(opcode == WS.OPCODES.OPCODE_CONTINUE) then 293 | 294 | if(self.split_payload==nil) then 295 | self:ProtocolError(1002,"Received continue opcode, yet nothing to continue") 296 | return 297 | end 298 | 299 | self.split_payload = (self.split_payload or "")..(payload or "") --very safely concatinate payloads 300 | if(frame.fin) then 301 | self:OnPayloadComplete(self.split_payload,split_payload_type) 302 | end 303 | 304 | self:ReceiveFrame() 305 | return 306 | end 307 | 308 | if (opcode == WS.OPCODES.OPCODE_PONG) then 309 | print("Got unwanted pong") --We shouldn't be getting pongs? 310 | --self:ProtocolError(1002,"Unwanted pong") 311 | self:ReceiveFrame() 312 | return 313 | end 314 | 315 | self:ProtocolError(1002,"Invalid opcode "..(opcode or "NONE")) --Instantly fail the connection for unknown opcodes 316 | end 317 | 318 | function WS.Connection:OnSend() 319 | if(self.sentCloseFrame and self.receivedCloseFrame) then 320 | self:Disconnect() 321 | end 322 | end 323 | 324 | function WS.Connection:OnDisconnect() 325 | if(self.state=="CLOSED") then return end 326 | self.state='CLOSED' 327 | if(WS.verbose) then print("Disconnected, calling onclose") end 328 | if(isfunction(self.onClose)) then 329 | self.onClose() 330 | end 331 | end 332 | 333 | function WS.Connection:OnPayloadComplete(payload,opcode) 334 | if(WS.verbose) then 335 | print("Payload complete!") 336 | end 337 | 338 | if(self.echo) then 339 | self:Send(payload,opcode) 340 | end 341 | 342 | --If application registered callback, call it 343 | if(isfunction(self.onMessage)) then 344 | self.onMessage(payload) 345 | end 346 | 347 | --And reset 348 | self.split_payload = nil 349 | self.split_payload_type = nil 350 | end 351 | 352 | function WS.Connection:OnReceive(sock,packet) 353 | if(WS.verbose) then 354 | print("\n\nRECEIVING, ".. packet:InSize() .." bytes in buffer") 355 | end 356 | 357 | if (not self.receivedHTTPHandshake) then --If we haven't gotten the handshake yet, handle it 358 | self:handleHTTPHandshake(packet) 359 | else --Else asume its a regular frame 360 | local frame = self.frame 361 | local bsock = self.bsock 362 | local receiveState = frame.receiveState 363 | 364 | if(receiveState=="HEADER") then --If we haven't gotten the header, asume this is the header 365 | if(WS.verbose) then print("Reading header") end 366 | table.Merge(frame,self:readHeader(packet)) --Read header to receive fin,opcode,payload length and payload leght size 367 | 368 | if(frame.payload_length==0) then 369 | if(WS.verbose) then 370 | print("No payload"); 371 | end 372 | self:OnFrameComplete(frame) --No payload, end the message 373 | 374 | elseif(frame.payload_length_size>=2) then --Payload is oversized, receive the size bytes 375 | if(WS.verbose) then 376 | print("Large payload") 377 | end 378 | frame.receiveState = "PAYLOAD_LENGTH" 379 | bsock:Receive(frame.payload_length_size) 380 | 381 | else 382 | if(WS.verbose) then 383 | print("Normal payload") 384 | end 385 | bsock:Receive(frame.payload_length) --Payload is small, just receive it 386 | frame.receiveState = "PAYLOAD" 387 | end 388 | 389 | return --No need to run the rest of the function 390 | end 391 | 392 | if(receiveState=="PAYLOAD_LENGTH") then --Receive the extra size bytes 393 | if(frame.payload_length_size==2) then 394 | frame.payload_length = WS.readNumber(packet,2) 395 | elseif(frame.payload_length_size==8) then 396 | frame.payload_length = WS.readNumber(packet,8) 397 | else 398 | WS.Error("Unknown payload length size") 399 | end 400 | --print("Extended payload length:"..frame.payload_length) 401 | bsock:Receive(frame.payload_length) 402 | frame.receiveState="PAYLOAD" 403 | return 404 | end 405 | 406 | if(receiveState=="PAYLOAD") then --Actually receive a payload 407 | frame.payload = packet:ReadStringAll() 408 | self:OnFrameComplete(frame) 409 | end 410 | end 411 | end 412 | 413 | --[[ 414 | function WS.Client:SetOnOpenCallback(func) 415 | if(isfunction(func)) then 416 | self.onOpen = func 417 | else 418 | error("Argument error, passed non-function to SetOnOpenCallback") 419 | end 420 | end 421 | 422 | function WS.Client:SetOnMessageCallback(func) 423 | if(isfunction(func)) then 424 | self.onMessage = func 425 | else 426 | error("Argument error, passed non-function to SetOnMessageCallback") 427 | end 428 | end 429 | 430 | function WS.Client:SetOnCloseCallback(func) 431 | if(isfunction(func)) then 432 | self.onClose = func 433 | else 434 | error("Argument error, passed non-function to SetOnCloseCallback") 435 | end 436 | end]] 437 | 438 | 439 | 440 | function WS.Connection:SetOnOpenCallback(func) 441 | if(isfunction(func)) then 442 | self.onOpen = func 443 | else 444 | error("Argument error, passed non-function to SetOnOpenCallback") 445 | end 446 | end 447 | 448 | function WS.Connection:SetOnMessageCallback(func) 449 | if(isfunction(func)) then 450 | self.onMessage = func 451 | else 452 | error("Argument error, passed non-function to SetOnMessageCallback") 453 | end 454 | end 455 | 456 | function WS.Connection:SetOnCloseCallback(func) 457 | if(isfunction(func)) then 458 | self.onClose = func 459 | else 460 | error("Argument error, passed non-function to SetOnCloseCallback") 461 | end 462 | end 463 | 464 | 465 | function WS.Server.Create() 466 | local self = setmetatable({},WS.Server) 467 | 468 | self.eventListeners = {} 469 | 470 | self.bsock = BromSock() 471 | 472 | self.bsock:SetCallbackAccept(function(...) 473 | self:acceptCallback(...) 474 | end) 475 | 476 | return self 477 | end 478 | 479 | function WS.Server:Listen(port) 480 | self.port = port 481 | 482 | local succes = self.bsock:Listen(self.port); 483 | if succes then 484 | self.bsock:Accept() --initial accept to get the first client 485 | if(WS.verbose) then 486 | print("Listening on port "..self.port) 487 | end 488 | else 489 | WS.Error("Couldn't listen to port "..self.port) 490 | end 491 | end 492 | 493 | function WS.Server:acceptCallback(server,client) 494 | print("New client") 495 | self.client = client 496 | 497 | self.websocket = WS.Connection(self.client,true) 498 | self.websocket.echo = true 499 | self.websocket:ReceiveHTTPHandShake() 500 | 501 | --If we could handle multiple clients, we'd have to call Accept() again to allow another client to connect 502 | end 503 | 504 | function WS.Server:IsActive() 505 | return self.websocket:IsActive() 506 | end 507 | 508 | 509 | 510 | --High level utility function for just sending and/or retrieving a single frame 511 | function WS.Get(url,port,callback,data) 512 | local socket = WS.Client(url,port) 513 | 514 | local function onOpen() 515 | socket:Send(data or nil) 516 | end 517 | 518 | local function onReady(data) 519 | callback(data) 520 | socket:Close() 521 | end 522 | 523 | 524 | socket:on("open",onOpen) 525 | socket:on("message",onReady) 526 | 527 | -- socket:SetCallbackReceive(onReady) 528 | -- socket:SetCallbackClose(onClose) 529 | socket:Connect() 530 | end 531 | 532 | 533 | --[[ 534 | function WS:SetCallbackConnected(func) 535 | self.callbackConnected = func 536 | end 537 | 538 | function WS:SetCallbackReceive(func) 539 | self.callbackReceive = func 540 | end 541 | 542 | function WS:SetCallbackClose(func) 543 | self.callbackClose = func 544 | end]] 545 | 546 | function WS:acceptCallback(server,client) 547 | 548 | --Can only handle one client atm 549 | if(not self.client) then 550 | self.client = client 551 | server:Accept() 552 | 553 | if(WS.verbose) then 554 | print("Accepted connecting with client") 555 | end 556 | else 557 | --Sorry client :( 558 | 559 | if(WS.verbose) then 560 | print("Declined client connecting, can only handle one client right now") 561 | end 562 | client:Disconnect() 563 | end 564 | end 565 | 566 | function WS.readNumber(packet,n) --read n bytes of data from packet 567 | local res = 0 568 | local i 569 | for i= 1,n do 570 | res = bit.lshift(res,8) + packet:ReadByte() 571 | end 572 | return res 573 | end 574 | 575 | function WS.WriteNumber(packet,data,n) --writes n bytes of data to packet 576 | local i 577 | local byte 578 | for i=1,n do 579 | packet:WriteByte(bit.ror(data,(n-i)*8)) 580 | end 581 | end 582 | 583 | function WS.writeDataSize(packet,mask,data_size) --Also writes mask, since its in the same byte as size 584 | local payload_size_basic 585 | local payload_size_extended 586 | 587 | local mask = 0x80 588 | local max_size = 2^64 589 | 590 | if(data_size<126) then 591 | payload_size_basic = data_size --Write just the payload lengt byte 592 | elseif (data_size >= 126 && data_size < 65536) then 593 | payload_size_basic=126 --Set payload size to 126 and set the next 2 bytes as length 594 | payload_size_extended=data_size 595 | elseif(data_size>=65536&&data_size=(2^32)) then 611 | WR.WriteNumber(packet,payload_size_extended,8) 612 | else 613 | WS.WriteNumber(packet,0,4) --TODO Figure out lua int size properly and make this work 614 | WS.WriteNumber(packet,payload_size_extended,4) 615 | end 616 | end 617 | 618 | 619 | end 620 | 621 | --Creates a new data frame ready to send 622 | --TODO merge with createcloseframe 623 | function WS.Connection:createDataFrame(data,opcode) 624 | local packet = BromPacket() 625 | local data_size 626 | opcode = opcode or WS.OPCODES.OPCODE_TEXT_FRAME 627 | 628 | 629 | 630 | packet:WriteByte(0x80+opcode) --fin/reserved/opcode 631 | 632 | if(data) then 633 | data_size = #data 634 | else 635 | data_size = 0 636 | end 637 | 638 | if(WS.verbose) then print("Creating frame with size "..data_size.." and opcode "..WS.findOpcode(opcode)) end 639 | 640 | WS.writeDataSize(packet,true,data_size) 641 | 642 | local mask = WS.randomByteArray(4) --Client to server traffic needs to be xor encoded 643 | WS.writeMask(packet,mask) 644 | 645 | if(data) then 646 | WS.writeDataEncoded(packet,data,mask) 647 | end 648 | 649 | 650 | return packet 651 | end 652 | -- 653 | --Callback from socket when initial connection is succesfulll or aborted 654 | --[[ 655 | function WS:connectCallback(socket,connected,ip,port) 656 | if not connected then 657 | --For connection errors, timeout, ect 658 | print("Could not connect to "..self.host..":"..self.port) 659 | return false 660 | end 661 | if(WS.verbose) then 662 | print("Connected!") 663 | end 664 | 665 | self:SendHTTPHandShake() --Send the HTTP handshake so we can start speaking websocket 666 | self.bsock:ReceiveUntil("\r\n\r\n") --And await the server's handshake 667 | end]] 668 | 669 | function WS.Connection:ReceiveHTTPHandShake() 670 | self.bsock:ReceiveUntil("\r\n\r\n") --HTTP requests ends with double return+newline 671 | end 672 | 673 | 674 | --Socket callback after we sent a message 675 | function WS.Connection:sentCallback(socket,length) 676 | if(self.state=="CLOSING" && self.sentCloseFrame && self.receivedCloseFrame) then 677 | self:Disconnect() 678 | end 679 | if(WS.verbose) then 680 | print("Sent "..length.." bytes") 681 | end 682 | end 683 | 684 | --Ran when connection is definitly closed 685 | function WS:OnClose() 686 | if(self.state=="CLOSED") then return end 687 | self.state="CLOSED" 688 | 689 | 690 | if(WS.verbose) then 691 | print("Websocket connection closed") 692 | end 693 | 694 | --If callback is set, call the callback 695 | if(isfunction(self.callbackClose)) then 696 | self.callbackClose() 697 | end 698 | end 699 | 700 | --Socket callback when disconnected 701 | function WS:disconnectCallback(socket) 702 | if(WS.verbose) then 703 | print("BROMSOCK CLOSED") 704 | end 705 | 706 | self:OnClose() 707 | end 708 | 709 | --Read 2 bytes from given packet, this should be the header 710 | function WS.Connection:readHeader(packet) 711 | local byte_1 = packet:ReadByte(1) 712 | local byte_2 = packet:ReadByte(1) 713 | local fin,opcode,mask,payload_length,payload_length_size 714 | --read FIN(1),reserved bits(3) and opcode (4) 715 | --print("FIN/RES/OPCODE: "..toBitsMSB(message,8)) 716 | 717 | 718 | if byte_1 > 127 then --If first bit is set 719 | fin = true -- packet is final fragment 720 | opcode = byte_1-128 --unset first bit 721 | else 722 | fin = false 723 | opcode = byte_1 724 | end 725 | --Invalid opcode checks are done in OnMessageEnd 726 | 727 | if byte_2>127 then --If mask set 728 | mask_enabled = true 729 | payload_length = byte_2-128 --unset first bit 730 | else 731 | mask_enabled = false 732 | payload_length = byte_2 733 | end 734 | 735 | if(payload_length==126) then --Set appropriate ammount of payload size bytes 736 | payload_length_size = 2 --Extra 2 bytes as read as length 737 | elseif(payload_length==127) then 738 | payload_length_size = 8 --Extra 8 bytes as read as length 739 | else 740 | payload_length_size = 0 741 | end 742 | 743 | return { 744 | fin = fin, 745 | opcode = opcode, 746 | mask = mask, 747 | payload_length = payload_length, 748 | payload_length_size = payload_length_size 749 | } 750 | --print("MASK: "..(mask_enabled and "True" or "False")) 751 | --print("PAYLOAD LENGTH "..self.current_message.payload_length) 752 | end 753 | 754 | function WS.calculateSecKey(key) 755 | return util.Base64Encode(sha1.binary(key..WS.GUID)) 756 | end 757 | 758 | 759 | --Sends the HTTP handshake 760 | function WS.Connection:SendHTTPHandShake() 761 | if(WS.verbose) then 762 | print("Sending HTTP handshake") 763 | end 764 | local packet = BromPacket() 765 | 766 | if(self.isClient) then 767 | packet:WriteLine("GET "..self.path.." HTTP/1.1" ) 768 | packet:WriteLine("Host: ".. self.httphost ) 769 | packet:WriteLine("Sec-WebSocket-Key: "..util.Base64Encode(WS.randomString(16))) 770 | packet:WriteLine("Sec-WebSocket-Version: 13") 771 | end 772 | 773 | if(self.isServer) then 774 | packet:WriteLine("HTTP/1.1 101 Switching Protocols") 775 | packet:WriteLine("Sec-Websocket-Accept: "..WS.calculateSecKey(self.seckey)) 776 | end 777 | packet:WriteLine("Connection: Upgrade") 778 | packet:WriteLine("Upgrade: websocket") 779 | packet:WriteLine("") --Empty line to finish HTTP request 780 | 781 | self.bsock:Send(packet,true) --true means don't prepend payload size 782 | end 783 | 784 | --Handle http handshake, asumes packet is http handshake 785 | function WS.Connection:handleHTTPHandshake(packet) 786 | local httphandshake = packet:ReadStringAll() 787 | self.seckey = WS.verifyhandshake(httphandshake,self.isServer or false) 788 | 789 | if(self.seckey == nil or self.seckey == false) then 790 | print("INvalid handsake, disconnecting") 791 | self:Disconnect() 792 | return false --If there's no key, the handshake was invalid 793 | end 794 | 795 | if(WS.verbose) then print("Received valid HTTP handshake") end 796 | 797 | self.receivedHTTPHandshake = true 798 | 799 | if(self.isServer) then 800 | self:SendHTTPHandShake() 801 | end 802 | 803 | 804 | 805 | self.state = "OPEN" 806 | 807 | --If callback is set, call it 808 | if(isfunction(self.onOpen)) then 809 | self.onOpen() 810 | end 811 | 812 | --Prepare to receive websocket frames 813 | self:ReceiveFrame() 814 | end 815 | 816 | 817 | function WS.Connection:IsActive() 818 | return (self.state != "CLOSED") 819 | end 820 | 821 | --Handler for close frames 822 | function WS.Connection:onCloseMessage(frame) --Handle frame with close opdoe 823 | local payload = frame.payload 824 | 825 | self.receivedCloseFrame = true 826 | 827 | if(payload) then 828 | 829 | if(frame.payload_length>=126) then 830 | self:ProtocolError(1002,"Payload to large in close frame") 831 | return 832 | end 833 | 834 | if(frame.payload_length==1) then 835 | self:ProtocolError(1002,"Payload size is 1 in close frame") 836 | return 837 | end 838 | 839 | code = (bit.lshift(string.byte(payload[1]),8)+string.byte(payload[2])) 840 | if(WS.verbose) then 841 | print("Received close payload: ".. code .. " - ".. payload) 842 | end 843 | 844 | if(!WS.isValidCloseReason(code)) then 845 | self:ProtocolError(1002,"Invalid close code received: "..(code or "NONE")) 846 | return 847 | end 848 | end 849 | 850 | if(self.state=="OPEN") then 851 | self.state="CLOSING" 852 | end 853 | 854 | 855 | if(self.sentCloseFrame) then 856 | self:Disconnect() -- We sent and received close frames, drop the connection 857 | else 858 | self:Close() 859 | end 860 | end 861 | 862 | function WS.Connection:ProtocolError(code,message) 863 | WS.Error(message) 864 | self:Close(code) 865 | end 866 | 867 | --Ping message handler 868 | function WS.Connection:onPing(frame) 869 | 870 | if(frame.payload_length>=126) then 871 | self:ProtocolError(1002,"Ping payload too large ("..frame.payload_length..")") 872 | return 873 | end 874 | 875 | if(!frame.fin) then 876 | self:ProtocolError(1002,"Ping cannot be fragmented") 877 | return 878 | end 879 | 880 | self:Send(frame.payload,WS.OPCODES.OPCODE_PONG) --Send pong with identical payload 881 | self:ReceiveFrame() 882 | end 883 | 884 | --Application level send function, takes data and opcode for payload type 885 | function WS.Connection:Send(data,opcode) 886 | if(self.state=="OPEN") then 887 | local packet = self:createDataFrame(data,opcode) 888 | self.bsock:Send(packet,true) 889 | else 890 | print("Cannot send message in current state "..self.state.."\nUse the onOpen callback") 891 | end 892 | end 893 | --[[ 894 | function WS:Disconnect() 895 | 896 | local socketstate = self.bsock:GetState() 897 | if(socketstate==2 or socketstate==7) then 898 | self.bsock:Close() 899 | end 900 | 901 | self:OnClose() 902 | end 903 | ]] 904 | 905 | --Application/internal level close function, takes error code (see RFC) and if we should close quickly (don't inform server) 906 | function WS:Close(code) 907 | code = code or 1000 908 | if(self.state=="OPEN") then 909 | self.state="CLOSING" 910 | self:SendCloseFrame(code) 911 | self:prepareToReceive() 912 | 913 | if(WS.verbose) then print("CLOSING CONNECTION, state is now "..self.state) end 914 | 915 | timer.Simple(WS.close_timeout,function() 916 | self:Disconnect() 917 | end) 918 | end 919 | end 920 | 921 | --Used to raise an error and fail the connection 922 | function WS:ProtocolError(code,reason) 923 | print("Websocket protocol error: "..reason) 924 | if(self.state=="OPEN" or self.state=="CONNECTING") then 925 | self:Close(code,true) 926 | end 927 | end 928 | 929 | --Sends a close frame to the server 930 | function WS.Connection:SendCloseFrame(code) 931 | --local packet = self:createCloseFrame(code) 932 | local codeAsTable = {bit.rshift(code,8),code} 933 | local packet = self:createDataFrame(codeAsTable,WS.OPCODES.OPCODE_CNX_CLOSE) 934 | if(packet!=nil) then 935 | self.bsock:Send(packet,true) 936 | end --If nil, packet called ProtocolError and din't return anything 937 | self.sentCloseFrame = true 938 | if(self.state=="OPEN") then 939 | self.state = "CLOSING" 940 | end 941 | end 942 | 943 | 944 | --Helper to create Sec-WebSocket-Key 945 | function WS.randomString(len) 946 | local s = "" 947 | local i 948 | for i=1,len do 949 | s = s .. string.char(math.random(97, 122)) 950 | end 951 | return s 952 | end 953 | 954 | --Helper to create mask 955 | function WS.randomByteArray(len) 956 | local tbl = {} 957 | local i 958 | for i=1,len do 959 | tbl[i]=math.random(255) 960 | end 961 | return tbl 962 | end 963 | 964 | --Helper to write the mask 965 | function WS.writeMask(packet,mask) 966 | local i 967 | for i=1,4 do 968 | packet:WriteByte(mask[i]) 969 | end 970 | end 971 | 972 | --Helper to write data encoded with given mask 973 | function WS.writeDataEncoded(packet,data,mask) 974 | local i 975 | 976 | for i = 1,#data do 977 | local byte = data[i] 978 | if(type(byte)=="string") then 979 | byte = string.byte(byte) 980 | end 981 | 982 | packet:WriteByte(bit.bxor(byte,mask[((i-1)%4)+1])) 983 | end 984 | end 985 | 986 | --Helper to check for valid close reason 987 | function WS.isValidCloseReason(reason) 988 | --Optimize for common use first 989 | if(reason>=1000 and reason <= 1003) then return true end 990 | 991 | if (reason==1007) then return true end 992 | 993 | if(reason>=1004 && reason <=1006) then return false end 994 | 995 | if(reason<1000) then return false end 996 | 997 | if(reason>=1012 && reason < 3000) then 998 | return false 999 | end 1000 | 1001 | if(reason>=3000 and reason < 5000) then 1002 | return true 1003 | end 1004 | 1005 | print("Unverified close reason "..(reason or "NONE")) 1006 | return true -- 1007 | end 1008 | 1009 | 1010 | 1011 | --Verify if the HTTP handshake is valid 1012 | function WS.verifyhandshake(message,isServer) 1013 | --TODO: More checks, check the checks 1014 | if(WS.verbose) then 1015 | print("Veryifing handshake") 1016 | end 1017 | local msg = string.Explode("\r\n",message) 1018 | 1019 | --PrintTable(msg) 1020 | local headers = {} 1021 | 1022 | local first_line = msg[1] --Not really a header, but oh well, also case-sensitive 1023 | 1024 | local function fixString(str) 1025 | return string.Trim(string.lower(str)) 1026 | end 1027 | 1028 | for i=2,#msg do 1029 | local line = msg[i] 1030 | local findpos = string.find(line,":") 1031 | if(findpos and findpos > 0) then 1032 | local key = fixString(string.Left(line,findpos-1)) --This is case insensetive, lets lowercase it all 1033 | local value = string.Trim(string.sub(line,findpos+1)) 1034 | --print("|",key,value,"|") 1035 | headers[key]=value 1036 | end 1037 | end 1038 | --PrintTable(headers) 1039 | 1040 | --print(first_line) 1041 | --HTTP version 1042 | 1043 | 1044 | if(isServer) then 1045 | local http_version = string.Explode(" ",first_line)[3] 1046 | if(http_version!="HTTP/1.1") then 1047 | WS.Error("Invalid HTTP version, remote party uses "..http_version) 1048 | return false 1049 | end 1050 | 1051 | if(headers["sec-websocket-version"]!="13") then 1052 | WS.Error("Client uses unsupported websocket version") 1053 | return false 1054 | end 1055 | else 1056 | if(not string.StartWith(first_line,"HTTP/1.1 101")) then 1057 | WS.Error("Server not sending proper response code") 1058 | return false 1059 | end 1060 | end 1061 | 1062 | --Connection 1063 | if(string.find(string.lower(headers.connection),"upgrade")==nil) then --Doesn't need to be exact match, just contain the "upgrade" token 1064 | print(headers.connection) 1065 | WS.Error("Invalid \"connection\" header") 1066 | return false 1067 | end 1068 | 1069 | --Upgrade 1070 | if(string.find(string.lower(headers.upgrade),"websocket")==nil) then 1071 | WS.Error("Invalid \"upgrade\" header") 1072 | return false 1073 | end 1074 | 1075 | 1076 | local user_agent = headers["user-agent"] 1077 | 1078 | --TODO Sec-WebSocket-Key 1079 | 1080 | --Lill hack to pass the seckey back to the connection object 1081 | return headers["sec-websocket-key"] or true 1082 | end 1083 | 1084 | --For debugging, find the key for the opcode value 1085 | function WS.findOpcode(message) 1086 | for k,v in pairs(WS.OPCODES) do 1087 | if(message==v) then return k end 1088 | end 1089 | return "Invalid opcode: "..(message or "") 1090 | end 1091 | 1092 | --Throws a nice error into the console 1093 | function WS.Error(msg) 1094 | if(type(msg) == "table") then 1095 | PrintTable(msg) 1096 | end 1097 | ErrorNoHalt("\nWEBSOCKET ERROR\n"..msg.."\n\n") 1098 | end 1099 | 1100 | --Helper to get usefull bits of data out of the URL 1101 | function WS.parseUrl(url) 1102 | if (url==nil) then 1103 | error("No argument given to WS.ParseURL") 1104 | end 1105 | local ret = {} 1106 | ret.path = "/" 1107 | 1108 | local protocolIndex = string.find(url,"://") 1109 | if (protocolIndex && protocolIndex > -1) then 1110 | ret.protocol = string.sub(url,0,protocolIndex+2) 1111 | url = string.Right(url,#url-protocolIndex-2) 1112 | end 1113 | 1114 | local pathindex = string.find(url,"/") 1115 | if (pathindex && pathindex > -1) then 1116 | ret.host = string.sub(url,1,pathindex-1) 1117 | ret.path = string.sub(url,pathindex) 1118 | else 1119 | ret.host = url 1120 | end 1121 | 1122 | return ret; 1123 | end 1124 | 1125 | --Event system 1126 | function WS:on(eventName,func) 1127 | if(not self.eventListeners[eventName]) then 1128 | self.eventListeners[eventName] = {} 1129 | end 1130 | 1131 | table.insert(self.eventListeners[eventName],func) 1132 | end 1133 | 1134 | function WS:fireEvent(eventName,...) 1135 | local events = self.eventListeners[eventName] 1136 | if(not events) then return end; --If no events, return 1137 | 1138 | --Call every callback with given arguments 1139 | for k,v in pairs(events) do 1140 | v(...) 1141 | end 1142 | end 1143 | -------------------------------------------------------------------------------- /lua/autorun/server/ws_tests.lua: -------------------------------------------------------------------------------- 1 | 2 | local AB_URL = "localhost" -- Autobahn ip and port 3 | local AB_PORT = 9333 4 | 5 | local function runCase(caseId, onClose) 6 | local gsocket = WS.Client(AB_URL.."/runCase?case="..caseId.."&agent=gmod_13",AB_PORT); 7 | 8 | gsocket.websocket.echo = true; 9 | gsocket:on("close", onClose); 10 | gsocket:Connect(); 11 | end 12 | 13 | local function requestCaseCount(onCountReceived) 14 | WS.Get(AB_URL.."/getCaseCount",AB_PORT,function(returnData) 15 | onCountReceived(tonumber(returnData)) 16 | end) 17 | end 18 | 19 | 20 | local function runTests(currentCase,finalCase, onFinish) 21 | print("Running test " .. currentCase .. "/" .. finalCase); 22 | 23 | if(currentCase > finalCase) then 24 | if onFinish then onFinish() end 25 | return 26 | end 27 | 28 | runCase(currentCase, function() 29 | runTests(currentCase+1, finalCase, onFinish) 30 | end) 31 | end 32 | 33 | concommand.Add("ws_case",function(ply,cmd,args) 34 | local caseId = tonumber(args[1]) 35 | runCase(caseId) 36 | end) 37 | 38 | local function updateReports() 39 | print("Updating report") 40 | local gsocket = WS.Client(AB_URL.."/updateReports?agent=gmod_13",AB_PORT) 41 | gsocket:Connect() 42 | end 43 | 44 | concommand.Add("ws_test",function() 45 | requestCaseCount(function(caseCount) 46 | runTests(0,caseCount, updateReports) 47 | end) 48 | end) 49 | 50 | concommand.Add("ws_updatereports",function(ply,cmd,args) 51 | updateReports() 52 | end) 53 | 54 | concommand.Add("ws_casecount",function() 55 | WS.Get(AB_URL.."/getCaseCount",AB_PORT,print) 56 | end) 57 | 58 | -- concommand.Add("ws_listen",function() 59 | -- if(gsocket and gsocket:IsActive()) then 60 | -- print("listen later") 61 | -- gsocket:SetOnCloseCallback(function() 62 | -- gsocket = WS.Server() 63 | -- gsocket:Listen(4176) 64 | -- end) 65 | -- gsocket:Disconnect() 66 | -- else 67 | -- print("listen now") 68 | -- gsocket = WS.Server() 69 | -- gsocket:Listen(4176) 70 | -- end 71 | -- end) 72 | -------------------------------------------------------------------------------- /test_config/fuzzingserver.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "ws://127.0.0.1:9333", 3 | "outdir": "./reports/clients", 4 | "cases": ["*"], 5 | "exclude-cases": [ 6 | "9.*", 7 | "12.*", 8 | "13.*", 9 | "6.*" 10 | ], 11 | "exclude-agent-cases": {} 12 | } --------------------------------------------------------------------------------