├── .gitignore ├── .vscode └── settings.json ├── Example_Chat_Server.pb ├── Example_Chat_Server_Polling.pb ├── Fuzzing ├── Fuzzy_Server.pb ├── README.md └── config │ ├── fuzzing-pb.json │ └── massconnect.json ├── HTML ├── Chat_Client.html ├── audio │ └── Click.mp3 ├── css │ └── chat.css └── js │ ├── WebSocket.js │ └── jquery-2.1.3.min.js ├── Includes ├── AllocationDumper.pbi └── WebSocket_Server.pbi ├── LICENSE ├── README.md └── StressTest ├── ReAllocateTest.pb ├── StressTest.pb ├── Tests ├── Test 1.md ├── Test 2.md ├── Test 3.md ├── Test 4.md └── Test 5.md ├── connection.go ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | Thumbs.db 3 | __debug_bin 4 | 5 | /Fuzzing/reports 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Dadido", 4 | "Testsuite", 5 | "Threadsafe", 6 | "Vogel", 7 | "mutex", 8 | "mutexless", 9 | "struct", 10 | "unfragmented" 11 | ] 12 | } -------------------------------------------------------------------------------- /Example_Chat_Server.pb: -------------------------------------------------------------------------------- 1 | Structure Chat_Message 2 | Type.s 3 | Author.s 4 | Message.s 5 | Timestamp.q 6 | EndStructure 7 | 8 | Structure Chat_Username_Change 9 | Type.s 10 | Username.s 11 | EndStructure 12 | 13 | Structure Chat_Userlist 14 | Type.s 15 | 16 | List Username.s() 17 | EndStructure 18 | 19 | Structure Client 20 | *WebSocket_Client 21 | 22 | Username.s 23 | EndStructure 24 | 25 | Global NewList Client.Client() 26 | 27 | Global Mutex.i = CreateMutex() ; Mutex to prevent simultaneous access to the Client() list 28 | 29 | XIncludeFile "Includes/WebSocket_Server.pbi" 30 | 31 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) 32 | Protected Chat_Message.Chat_Message 33 | Protected Chat_Username_Change.Chat_Username_Change 34 | Protected Chat_Userlist.Chat_Userlist 35 | Protected JSON_ID.i 36 | Protected JSON2_ID.i 37 | Protected JSON_String.s 38 | 39 | LockMutex(Mutex) 40 | 41 | Select Event 42 | Case WebSocket_Server::#Event_Connect 43 | PrintN(" #### Client connected: " + *Client) 44 | AddElement(Client()) 45 | Client()\WebSocket_Client = *Client 46 | 47 | JSON2_ID = CreateJSON(#PB_Any) 48 | If JSON2_ID 49 | 50 | Chat_Userlist\Type = "Userlist" 51 | ForEach Client() 52 | AddElement(Chat_Userlist\UserName()) 53 | Chat_Userlist\UserName() = Client()\Username 54 | Next 55 | 56 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 57 | 58 | WebSocket_Server::Frame_Text_Send(*Server, *Client, ComposeJSON(JSON2_ID)) 59 | 60 | FreeJSON(JSON2_ID) 61 | EndIf 62 | 63 | Case WebSocket_Server::#Event_Disconnect 64 | PrintN(" #### Client disconnected: " + *Client) 65 | ForEach Client() 66 | If Client()\WebSocket_Client = *Client 67 | DeleteElement(Client()) 68 | Break 69 | EndIf 70 | Next 71 | 72 | JSON2_ID = CreateJSON(#PB_Any) 73 | If JSON2_ID 74 | 75 | Chat_Userlist\Type = "Userlist" 76 | ForEach Client() 77 | AddElement(Chat_Userlist\UserName()) 78 | Chat_Userlist\UserName() = Client()\Username 79 | Next 80 | 81 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 82 | 83 | JSON_String = ComposeJSON(JSON2_ID) 84 | ForEach Client() 85 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 86 | Next 87 | 88 | FreeJSON(JSON2_ID) 89 | EndIf 90 | 91 | Case WebSocket_Server::#Event_Frame 92 | Select *Event_Frame\Opcode 93 | Case WebSocket_Server::#Opcode_Ping 94 | PrintN(" #### Ping from *Client " + *Client) 95 | Case WebSocket_Server::#Opcode_Text 96 | JSON_ID = ParseJSON(#PB_Any, PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength)) 97 | If JSON_ID 98 | 99 | Select GetJSONString(GetJSONMember(JSONValue(JSON_ID), "Type")) 100 | Case "Message" 101 | ExtractJSONStructure(JSONValue(JSON_ID), Chat_Message, Chat_Message) 102 | PrintN(Chat_Message\Author + ": " + Chat_Message\Message) 103 | 104 | Debug PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength) 105 | 106 | JSON2_ID = CreateJSON(#PB_Any) 107 | If JSON2_ID 108 | 109 | ForEach Client() 110 | If Client()\WebSocket_Client = *Client 111 | Chat_Message\Author = Client()\Username 112 | ;Chat_Message\Timestamp = Date() 113 | Break 114 | EndIf 115 | Next 116 | 117 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Message, Chat_Message) 118 | 119 | JSON_String = ComposeJSON(JSON2_ID) 120 | ;Debug JSON_String 121 | ForEach Client() 122 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 123 | Next 124 | 125 | FreeJSON(JSON2_ID) 126 | EndIf 127 | 128 | Case "Username_Change" 129 | ExtractJSONStructure(JSONValue(JSON_ID), Chat_Username_Change, Chat_Username_Change) 130 | ForEach Client() 131 | If Client()\WebSocket_Client = *Client 132 | Client()\Username = Chat_Username_Change\Username 133 | Break 134 | EndIf 135 | Next 136 | 137 | JSON2_ID = CreateJSON(#PB_Any) 138 | If JSON2_ID 139 | 140 | Chat_Userlist\Type = "Userlist" 141 | ForEach Client() 142 | AddElement(Chat_Userlist\UserName()) 143 | Chat_Userlist\UserName() = Client()\Username 144 | Next 145 | 146 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 147 | 148 | JSON_String = ComposeJSON(JSON2_ID) 149 | ForEach Client() 150 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 151 | Next 152 | 153 | FreeJSON(JSON2_ID) 154 | EndIf 155 | 156 | EndSelect 157 | 158 | FreeJSON(JSON_ID) 159 | EndIf 160 | EndSelect 161 | 162 | EndSelect 163 | 164 | UnlockMutex(Mutex) 165 | EndProcedure 166 | 167 | OpenConsole() 168 | 169 | *Server = WebSocket_Server::Create(8090, @WebSocket_Event()) 170 | 171 | Repeat 172 | Text.s = Input() 173 | 174 | LockMutex(Mutex) 175 | ForEach Client() 176 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, Text) 177 | Next 178 | UnlockMutex(Mutex) 179 | ForEver 180 | 181 | ; IDE Options = PureBasic 6.00 LTS (Windows - x64) 182 | ; CursorPosition = 45 183 | ; FirstLine = 21 184 | ; Folding = - 185 | ; EnableThread 186 | ; EnableXP 187 | ; EnablePurifier = 1,1,1,1 188 | ; EnableUnicode -------------------------------------------------------------------------------- /Example_Chat_Server_Polling.pb: -------------------------------------------------------------------------------- 1 | Structure Chat_Message 2 | Type.s 3 | Author.s 4 | Message.s 5 | Timestamp.q 6 | EndStructure 7 | 8 | Structure Chat_Username_Change 9 | Type.s 10 | Username.s 11 | EndStructure 12 | 13 | Structure Chat_Userlist 14 | Type.s 15 | 16 | List Username.s() 17 | EndStructure 18 | 19 | Structure Client 20 | *WebSocket_Client 21 | 22 | Username.s 23 | EndStructure 24 | 25 | Global NewList Client.Client() 26 | 27 | XIncludeFile "Includes/WebSocket_Server.pbi" 28 | 29 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) 30 | Protected Chat_Message.Chat_Message 31 | Protected Chat_Username_Change.Chat_Username_Change 32 | Protected Chat_Userlist.Chat_Userlist 33 | Protected JSON_ID.i 34 | Protected JSON2_ID.i 35 | Protected JSON_String.s 36 | 37 | Select Event 38 | Case WebSocket_Server::#Event_Connect 39 | PrintN(" #### Client connected: " + *Client) 40 | AddElement(Client()) 41 | Client()\WebSocket_Client = *Client 42 | 43 | JSON2_ID = CreateJSON(#PB_Any) 44 | If JSON2_ID 45 | 46 | Chat_Userlist\Type = "Userlist" 47 | ForEach Client() 48 | AddElement(Chat_Userlist\UserName()) 49 | Chat_Userlist\UserName() = Client()\Username 50 | Next 51 | 52 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 53 | 54 | WebSocket_Server::Frame_Text_Send(*Server, *Client, ComposeJSON(JSON2_ID)) 55 | 56 | FreeJSON(JSON2_ID) 57 | EndIf 58 | 59 | Case WebSocket_Server::#Event_Disconnect 60 | PrintN(" #### Client disconnected: " + *Client) 61 | ForEach Client() 62 | If Client()\WebSocket_Client = *Client 63 | DeleteElement(Client()) 64 | Break 65 | EndIf 66 | Next 67 | 68 | JSON2_ID = CreateJSON(#PB_Any) 69 | If JSON2_ID 70 | 71 | Chat_Userlist\Type = "Userlist" 72 | ForEach Client() 73 | AddElement(Chat_Userlist\UserName()) 74 | Chat_Userlist\UserName() = Client()\Username 75 | Next 76 | 77 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 78 | 79 | JSON_String = ComposeJSON(JSON2_ID) 80 | ForEach Client() 81 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 82 | Next 83 | 84 | FreeJSON(JSON2_ID) 85 | EndIf 86 | 87 | Case WebSocket_Server::#Event_Frame 88 | Select *Event_Frame\Opcode 89 | Case WebSocket_Server::#Opcode_Ping 90 | PrintN(" #### Ping from *Client " + *Client) 91 | Case WebSocket_Server::#Opcode_Text 92 | JSON_ID = ParseJSON(#PB_Any, PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength)) 93 | If JSON_ID 94 | 95 | Select GetJSONString(GetJSONMember(JSONValue(JSON_ID), "Type")) 96 | Case "Message" 97 | ExtractJSONStructure(JSONValue(JSON_ID), Chat_Message, Chat_Message) 98 | PrintN(Chat_Message\Author + ": " + Chat_Message\Message) 99 | 100 | Debug PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength) 101 | 102 | JSON2_ID = CreateJSON(#PB_Any) 103 | If JSON2_ID 104 | 105 | ForEach Client() 106 | If Client()\WebSocket_Client = *Client 107 | Chat_Message\Author = Client()\Username 108 | ;Chat_Message\Timestamp = Date() 109 | Break 110 | EndIf 111 | Next 112 | 113 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Message, Chat_Message) 114 | 115 | JSON_String = ComposeJSON(JSON2_ID) 116 | ;Debug JSON_String 117 | ForEach Client() 118 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 119 | Next 120 | 121 | FreeJSON(JSON2_ID) 122 | EndIf 123 | 124 | Case "Username_Change" 125 | ExtractJSONStructure(JSONValue(JSON_ID), Chat_Username_Change, Chat_Username_Change) 126 | ForEach Client() 127 | If Client()\WebSocket_Client = *Client 128 | Client()\Username = Chat_Username_Change\Username 129 | Break 130 | EndIf 131 | Next 132 | 133 | JSON2_ID = CreateJSON(#PB_Any) 134 | If JSON2_ID 135 | 136 | Chat_Userlist\Type = "Userlist" 137 | ForEach Client() 138 | AddElement(Chat_Userlist\UserName()) 139 | Chat_Userlist\UserName() = Client()\Username 140 | Next 141 | 142 | InsertJSONStructure(JSONValue(JSON2_ID), Chat_Userlist, Chat_Userlist) 143 | 144 | JSON_String = ComposeJSON(JSON2_ID) 145 | ForEach Client() 146 | WebSocket_Server::Frame_Text_Send(*Server, Client()\WebSocket_Client, JSON_String) 147 | Next 148 | 149 | FreeJSON(JSON2_ID) 150 | EndIf 151 | 152 | EndSelect 153 | 154 | FreeJSON(JSON_ID) 155 | EndIf 156 | EndSelect 157 | 158 | EndSelect 159 | EndProcedure 160 | 161 | OpenConsole() 162 | 163 | *Server = WebSocket_Server::Create(8090) 164 | 165 | Repeat 166 | While WebSocket_Server::Event_Callback(*Server, @WebSocket_Event()) 167 | Wend 168 | 169 | Delay(10) 170 | ForEver 171 | 172 | ; IDE Options = PureBasic 6.00 LTS (Windows - x64) 173 | ; CursorPosition = 160 174 | ; FirstLine = 115 175 | ; Folding = - 176 | ; EnableThread 177 | ; EnableXP -------------------------------------------------------------------------------- /Fuzzing/Fuzzy_Server.pb: -------------------------------------------------------------------------------- 1 | XIncludeFile "../Includes/WebSocket_Server.pbi" 2 | 3 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) 4 | Protected Message.s 5 | 6 | Select Event 7 | Case WebSocket_Server::#Event_Connect 8 | PrintN("Client connected: " + *Client) 9 | 10 | Case WebSocket_Server::#Event_Disconnect 11 | PrintN("Client disconnected: " + *Client) 12 | 13 | Case WebSocket_Server::#Event_Frame 14 | Select *Event_Frame\Opcode 15 | Case WebSocket_Server::#Opcode_Ping 16 | PrintN(" Ping from: " + *Client) 17 | ; #### Pong is sent by the server automatically 18 | 19 | Case WebSocket_Server::#Opcode_Pong 20 | PrintN(" Pong from: " + *Client) 21 | 22 | Case WebSocket_Server::#Opcode_Connection_Close 23 | PrintN(" Close request from: " + *Client) 24 | 25 | Case WebSocket_Server::#Opcode_Text 26 | Message = PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength) 27 | If Len(Message) >= 60 28 | PrintN(" Echo message from: " + *Client + " (" + Left(Message, 60-3) + "...)") 29 | Else 30 | PrintN(" Echo message from: " + *Client + " (" + Message + ")") 31 | EndIf 32 | If CountString(Message, "�") 33 | ; #### Invalid string, disconnect client. This will cause another test case to fail. Best would be to have something that checks UTF-8 string for validity. 34 | WebSocket_Server::Client_Disconnect(*Server, *Client, WebSocket_Server::#CloseStatusCode_1007) 35 | Else 36 | WebSocket_Server::Frame_Text_Send(*Server, *Client, Message) 37 | EndIf 38 | 39 | Case WebSocket_Server::#Opcode_Binary 40 | PrintN(" Echo binary from: " + *Client + " (" + *Event_Frame\Payload_Size + " bytes)") 41 | WebSocket_Server::Frame_Send(*Server, *Client, #True, 0, WebSocket_Server::#Opcode_Binary, *Event_Frame\Payload, *Event_Frame\Payload_Size) 42 | 43 | EndSelect 44 | 45 | EndSelect 46 | EndProcedure 47 | 48 | *Server = WebSocket_Server::Create(8090, @WebSocket_Event(), 32000000) 49 | 50 | OpenConsole() 51 | 52 | Repeat 53 | Delay(10) 54 | ForEver 55 | 56 | ; IDE Options = PureBasic 5.72 (Windows - x64) 57 | ; CursorPosition = 25 58 | ; Folding = - 59 | ; EnableThread 60 | ; EnableXP 61 | ; DisableDebugger 62 | ; EnablePurifier = 1,1,1,1 63 | ; EnableUnicode -------------------------------------------------------------------------------- /Fuzzing/README.md: -------------------------------------------------------------------------------- 1 | # Using `Autobahn|Testsuite` for automated websocket server testing 2 | 3 | ## How to do a `fuzzing` test 4 | 5 | 1. Get docker 6 | 2. Download docker image for websocket fuzzing and load testing: `docker pull crossbario/autobahn-testsuite` 7 | 3. Open cmd/shell/terminal inside of `.../WebSocket_Server/Fuzzying/` 8 | 4. Start the `Fuzzy_Server.pb` server 9 | 5. Run `docker run -it --rm -v "${PWD}/config:/config" -v "${PWD}/reports:/reports" --name fuzzing --entrypoint=/bin/bash crossbario/autobahn-testsuite` 10 | 6. Enter `/usr/local/bin/wstest --mode fuzzingclient --spec /config/fuzzing-pb.json` 11 | 12 | ## How to do a `massconnect` test 13 | 14 | 1. Get docker 15 | 2. Download docker image for websocket fuzzing and load testing: `docker pull crossbario/autobahn-testsuite` 16 | 3. Open cmd/shell/terminal inside of `.../WebSocket_Server/Fuzzying/` 17 | 4. Start the `Fuzzy_Server.pb` server 18 | 5. Run `docker run -it --rm -v "${PWD}/config:/config" -v "${PWD}/reports:/reports" --name fuzzing --entrypoint=/bin/bash crossbario/autobahn-testsuite` 19 | 6. Enter `/usr/local/bin/wstest --mode massconnect --spec /config/massconnect.json` 20 | -------------------------------------------------------------------------------- /Fuzzing/config/fuzzing-pb.json: -------------------------------------------------------------------------------- 1 | { 2 | "outdir": "./reports/servers", 3 | "servers": [ 4 | { 5 | "url": "ws://host.docker.internal:8090" 6 | } 7 | ], 8 | "cases": ["*"], 9 | "exclude-cases": ["12.*", "13.*"], 10 | "exclude-agent-cases": {} 11 | } -------------------------------------------------------------------------------- /Fuzzing/config/massconnect.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "name": "PureBasic WebSocket server", 5 | "uri": "ws://host.docker.internal:8090" 6 | } 7 | ], 8 | "options": { 9 | "connections": 10000, 10 | "batchsize": 100, 11 | "batchdelay": 10, 12 | "retrydelay": 10 13 | } 14 | } -------------------------------------------------------------------------------- /HTML/Chat_Client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebSocket Chat Client 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | Userlist 16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | No connection 28 |
29 |
30 |
31 |
32 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /HTML/audio/Click.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dadido3/WebSocket_Server/aa7643e19023039f16349d92acccc1d9138a7006/HTML/audio/Click.mp3 -------------------------------------------------------------------------------- /HTML/css/chat.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } 8 | 9 | * { 10 | font-size: 10pt; 11 | font-family: sans-serif; 12 | } 13 | 14 | #Chat_Container { 15 | position: absolute; 16 | height: 100%; 17 | width: 100%; 18 | overflow: hidden; 19 | } 20 | 21 | #Chat_Bubbles { 22 | position: absolute; 23 | overflow-y: scroll; 24 | display: flex; 25 | flex-direction: column; 26 | 27 | left: 0; 28 | right: 0; 29 | top: 0; 30 | bottom: 6em; 31 | padding: 0 0.5em; 32 | 33 | } 34 | 35 | #Chat_Bubbles .Message_Container { 36 | display: flex; 37 | margin: 0 0 0.5em 0; 38 | flex-shrink: 0; 39 | } 40 | 41 | #Chat_Bubbles .Message_Container .Author { 42 | max-width: 8em; 43 | flex-shrink: 0; 44 | padding: 0.5em; 45 | } 46 | 47 | #Chat_Bubbles .Message_Container .Message { 48 | display: flex; 49 | justify-content: space-between; 50 | width: 100%; 51 | background-color: #d2e5ff; 52 | border-radius: 0.5em; 53 | padding: 0.5em; 54 | margin: 0; 55 | word-wrap:break-word; 56 | white-space: pre-line; 57 | } 58 | 59 | #Chat_Bubbles .Message_Container .Timestamp { 60 | flex-shrink: 0; 61 | } 62 | 63 | .Own_Message .Author { 64 | /*order: 1;*/ 65 | } 66 | .Own_Message .Message { 67 | background-color: #cbffc4 !important; 68 | } 69 | .Info .Message { 70 | background-color: #ffffa1 !important; 71 | } 72 | .Warning .Message { 73 | background-color: #ff8a8a !important; 74 | } 75 | .Error .Message { 76 | background-color: #f00 !important; 77 | color: #fff; 78 | } 79 | 80 | #Chat_Userlist_Container { 81 | position: absolute; 82 | color: #fff; 83 | background-color: rgba(0, 0, 0, 0.85); 84 | text-align: center; 85 | 86 | top:0; left:0; right:0; bottom:0; 87 | 88 | padding: 0.5em; 89 | font-size: 30pt; 90 | font-family: sans-serif; 91 | } 92 | 93 | #Chat_Userlist { 94 | text-align: left; 95 | font-size: 15pt; 96 | font-family: sans-serif; 97 | } 98 | 99 | #Chat_Userlist li a{ 100 | font-size: 15pt; 101 | font-family: sans-serif; 102 | } 103 | 104 | #Chat_Disconnected { 105 | position: absolute; 106 | color: #fff; 107 | background-color: rgba(255, 0, 0, 0.75); 108 | text-align: center; 109 | 110 | top:0; left:0; right:0; bottom:0; 111 | 112 | font-size: 30pt; 113 | font-family: sans-serif; 114 | } 115 | 116 | #Chat_Input { 117 | position: absolute; 118 | bottom: 0; 119 | right: 0; 120 | left: 0; 121 | height: 6em; 122 | display: flex; 123 | justify-content: space-around; 124 | 125 | background-color: #e0ffdc; 126 | } 127 | 128 | #Chat_Input textarea { 129 | width: 100%; 130 | height: 100%; 131 | 132 | background-color: #cbffc4; 133 | 134 | resize: none; 135 | 136 | border: 0; 137 | margin: 0; 138 | padding: 0; 139 | } -------------------------------------------------------------------------------- /HTML/js/WebSocket.js: -------------------------------------------------------------------------------- 1 | var wsUri = ["ws://localhost:8090", "ws://192.168.1.100:8090", "ws://D3nexus.de:8090"]; 2 | var websocket; 3 | var wsUri_Counter = 0; 4 | 5 | function Chat_WebSocket_Connect() { 6 | $("#Chat_Disconnected div").text('Trying to connect to "' + wsUri[wsUri_Counter] + '"') 7 | 8 | websocket = new WebSocket(wsUri[wsUri_Counter]); 9 | websocket.onopen = function (evt) { onOpen(evt); }; 10 | websocket.onclose = function (evt) { onClose(evt); }; 11 | websocket.onmessage = function (evt) { onMessage(evt); }; 12 | websocket.onerror = function (evt) { onError(evt); }; 13 | 14 | wsUri_Counter += 1; 15 | if (wsUri_Counter >= wsUri.length) { 16 | wsUri_Counter = 0; 17 | } 18 | } 19 | 20 | function onOpen(evt) { 21 | chat_bubble_add("Info", "Info", "Connection established!", Date.now()); 22 | 23 | $("#Chat_Disconnected").fadeOut(); 24 | 25 | var obj = new Object(); 26 | obj.Type = "Username_Change"; 27 | obj.Username = document.getElementById('Chat_Author').value; 28 | var jsonString= JSON.stringify(obj); 29 | websocket.send(jsonString); 30 | } 31 | 32 | function onClose(evt) { 33 | //chat_bubble_add("Warning", "Warning", "Connection lost! Trying again!", Date.now()); 34 | 35 | Chat_WebSocket_Connect(); 36 | 37 | $("#Chat_Disconnected").fadeIn(); 38 | } 39 | 40 | function onMessage(evt) { 41 | var arr_from_json = JSON.parse( evt.data ); 42 | 43 | switch(arr_from_json.Type){ 44 | case "Message": 45 | case "Info": 46 | case "Error": 47 | if (arr_from_json.Author === document.getElementById('Chat_Author').value) 48 | arr_from_json.Type = "Own_Message"; 49 | chat_bubble_add(arr_from_json.Type, arr_from_json.Author, arr_from_json.Message, arr_from_json.Timestamp * 1000); 50 | break; 51 | 52 | case "Userlist": 53 | var cList = $('#Chat_Userlist'); 54 | cList.empty(); 55 | $.each(arr_from_json.Username, function(i) 56 | { 57 | var li = $('
  • ') 58 | .attr('role', 'menuitem') 59 | .appendTo(cList); 60 | var aaa = $('') 61 | .text(arr_from_json.Username[i]) 62 | .appendTo(li); 63 | }); 64 | 65 | break; 66 | } 67 | } 68 | 69 | function onError(evt) { 70 | //chat_bubble_add("Error", "Error", evt.data, Date.now()); 71 | } 72 | 73 | function chat_bubble_add(Type, Author, Message, Timestamp) { 74 | var chat_bubbles = document.getElementById('Chat_Bubbles'); 75 | var message_container = document.createElement("div"); 76 | var message_author = document.createElement("div"); 77 | var message = document.createElement("div"); 78 | var message_text = document.createElement("div"); 79 | var message_timestamp = document.createElement("div"); 80 | 81 | switch(Type) { 82 | case "Message": 83 | message_container.className = "Message_Container"; 84 | $("#Audio_Click")[0].play(); 85 | break; 86 | case "Own_Message": 87 | message_container.className = "Message_Container Own_Message"; 88 | break; 89 | case "Info": 90 | message_container.className = "Message_Container Info"; 91 | break; 92 | case "Warning": 93 | message_container.className = "Message_Container Warning"; 94 | break; 95 | case "Error": 96 | message_container.className = "Message_Container Error"; 97 | break; 98 | } 99 | 100 | var date = new Date(Timestamp); 101 | var hours = date.getHours(); 102 | var minutes = date.getMinutes(); 103 | 104 | message_author.className = "Author"; 105 | message.className = "Message"; 106 | message_timestamp.className = "Timestamp"; 107 | 108 | $(message_author).text(Author); 109 | $(message).text(Message); 110 | $(message_timestamp).text(hours.padLeft() + ":" + minutes.padLeft()); 111 | 112 | message_container.appendChild(message_author); 113 | message_container.appendChild(message); 114 | message.appendChild(message_text); 115 | message.appendChild(message_timestamp); 116 | chat_bubbles.appendChild(message_container); 117 | 118 | $("#Chat_Bubbles").animate({ 119 | scrollTop: $('#Chat_Bubbles')[0].scrollHeight - $('#Chat_Bubbles')[0].clientHeight 120 | }, 200); 121 | //$('#Chat_Bubbles').scrollTop($('#Chat_Bubbles').height()) 122 | } 123 | 124 | function doSendInput() { 125 | 126 | if (($("#Chat_Message_Text").val() !== "") && ($("#Chat_Author").val() !== "")) { 127 | var obj = new Object(); 128 | obj.Type = "Message"; 129 | obj.Author = document.getElementById('Chat_Author').value; 130 | obj.Message = document.getElementById('Chat_Message_Text').value; 131 | obj.Timestamp = Math.floor(Date.now() / 1000); 132 | var jsonString= JSON.stringify(obj); 133 | 134 | websocket.send(jsonString); 135 | 136 | $("#Chat_Message_Text").val(""); 137 | } 138 | } 139 | 140 | function Chat_Init() { 141 | 142 | $("#Chat_Message_Text").keydown(function(e){ 143 | if (e.keyCode == 13 && !e.shiftKey) 144 | { 145 | e.preventDefault(); 146 | doSendInput(); 147 | } 148 | }); 149 | 150 | $("#Chat_Author").val("GUEST_" + (Math.random() * 10000).toFixed()); 151 | 152 | Chat_WebSocket_Connect(); 153 | } 154 | 155 | function toggleUserlist() { 156 | $("#Chat_Userlist_Container").fadeToggle(); 157 | } 158 | 159 | function changeUsername() { 160 | var obj = new Object(); 161 | obj.Type = "Username_Change"; 162 | obj.Username = document.getElementById('Chat_Author').value; 163 | var jsonString= JSON.stringify(obj); 164 | websocket.send(jsonString); 165 | } 166 | 167 | $(document).ready(function() { 168 | $("#Chat_Userlist_Container").hide(); 169 | }) 170 | 171 | window.addEventListener("load", Chat_Init, false); 172 | 173 | Number.prototype.padLeft = function(base,chr){ 174 | var len = (String(base || 10).length - String(this).length)+1; 175 | return len > 0? new Array(len).join(chr || '0')+this : this; 176 | } -------------------------------------------------------------------------------- /Includes/AllocationDumper.pbi: -------------------------------------------------------------------------------- 1 | ; #### Silly include to dump memory and structure allocations by Dadido3 2 | ; #### No support for flags yet 3 | 4 | Global MemoryAllocationMutex = CreateMutex() 5 | Structure MemoryAllocation 6 | Line.i 7 | Size.i 8 | EndStructure 9 | Global NewMap MemoryAllocations.MemoryAllocation() 10 | 11 | Procedure __AllocateStructure(*mem, line.i) 12 | If *mem 13 | LockMutex(MemoryAllocationMutex) 14 | MemoryAllocations(Str(*mem))\Line = line 15 | UnlockMutex(MemoryAllocationMutex) 16 | Else 17 | Debug "Failed to allocate structure at line " + line 18 | EndIf 19 | ProcedureReturn *mem 20 | EndProcedure 21 | Macro _AllocateStructure(struct) ; #### It's not possible to override AllocateStructure, please replace all AllocateStructure with _AllocateStructure in your code. 22 | __AllocateStructure(AllocateStructure(struct), #PB_Compiler_Line) 23 | EndMacro 24 | 25 | Procedure _FreeStructure(*mem, line.i) 26 | If Not *mem 27 | Debug "Trying to free null pointer structure at line " + line 28 | EndIf 29 | LockMutex(MemoryAllocationMutex) 30 | If FindMapElement(MemoryAllocations(), Str(*mem)) 31 | DeleteMapElement(MemoryAllocations()) 32 | Else 33 | Debug "Freed an unknown structure address at line " + line 34 | EndIf 35 | UnlockMutex(MemoryAllocationMutex) 36 | FreeStructure(*mem) 37 | EndProcedure 38 | Macro FreeStructure(mem) 39 | _FreeStructure(mem, #PB_Compiler_Line) 40 | EndMacro 41 | 42 | Procedure _AllocateMemory(size.i, line.i) 43 | Protected *mem = AllocateMemory(size) 44 | If *mem 45 | LockMutex(MemoryAllocationMutex) 46 | MemoryAllocations(Str(*mem))\Line = line 47 | MemoryAllocations()\Size = size 48 | UnlockMutex(MemoryAllocationMutex) 49 | Else 50 | Debug "Failed to allocate mem at line " + line 51 | EndIf 52 | ProcedureReturn *mem 53 | EndProcedure 54 | Macro AllocateMemory(size) 55 | _AllocateMemory(size, #PB_Compiler_Line) 56 | EndMacro 57 | 58 | Procedure _ReAllocateMemory(*mem, size.i, line.i) 59 | If Not *mem 60 | Debug "Trying to reallocate null pointer memory at line " + line 61 | EndIf 62 | LockMutex(MemoryAllocationMutex) 63 | ;If FindMapElement(MemoryAllocations(), Str(*mem)) 64 | ; MemoryAllocations()\State = 1 65 | ;EndIf 66 | Protected *newMem = ReAllocateMemory(*mem, size) 67 | ;If FindMapElement(MemoryAllocations(), Str(*mem)) 68 | ; MemoryAllocations()\State = 2 69 | ;EndIf 70 | If *newMem 71 | If FindMapElement(MemoryAllocations(), Str(*mem)) 72 | DeleteMapElement(MemoryAllocations()) 73 | Else 74 | Debug "Reallocated an unknown memory address at line " + line 75 | EndIf 76 | MemoryAllocations(Str(*newMem))\Line = line 77 | MemoryAllocations()\Size = size 78 | Else 79 | Debug "Couldn't reallocate memory at line " + line 80 | EndIf 81 | UnlockMutex(MemoryAllocationMutex) 82 | ProcedureReturn *newMem 83 | EndProcedure 84 | Macro ReAllocateMemory(mem, size) 85 | _ReAllocateMemory(mem, size, #PB_Compiler_Line) 86 | EndMacro 87 | 88 | Procedure _FreeMemory(*mem, line.i) 89 | If Not *mem 90 | Debug "Trying to free null pointer memory at line " + line 91 | EndIf 92 | LockMutex(MemoryAllocationMutex) 93 | ;If FindMapElement(MemoryAllocations(), Str(*mem)) 94 | ; MemoryAllocations()\State = 1 95 | ;EndIf 96 | ;If FindMapElement(MemoryAllocations(), Str(*mem)) 97 | ; MemoryAllocations()\State = 2 98 | ;EndIf 99 | If FindMapElement(MemoryAllocations(), Str(*mem)) 100 | DeleteMapElement(MemoryAllocations()) 101 | Else 102 | Debug "Freed an unknown memory address at line " + line 103 | EndIf 104 | UnlockMutex(MemoryAllocationMutex) 105 | FreeMemory(*mem) 106 | EndProcedure 107 | Macro FreeMemory(mem) 108 | _FreeMemory(mem, #PB_Compiler_Line) 109 | EndMacro 110 | 111 | Procedure AllocationDumper_Output() 112 | LockMutex(MemoryAllocationMutex) 113 | Debug "---- Allocations: " + MapSize(MemoryAllocations()) + " ----" 114 | ForEach MemoryAllocations() 115 | Debug " - Line: " + MemoryAllocations()\Line + " Size: " + MemoryAllocations()\Size 116 | Next 117 | UnlockMutex(MemoryAllocationMutex) 118 | EndProcedure 119 | 120 | Procedure AllocationDumper_Thread(*Dummy) 121 | Repeat 122 | AllocationDumper_Output() 123 | 124 | Delay(10000) 125 | ForEver 126 | EndProcedure 127 | 128 | CreateThread(@AllocationDumper_Thread(), #Null) 129 | ; IDE Options = PureBasic 5.72 (Windows - x64) 130 | ; CursorPosition = 104 131 | ; FirstLine = 75 132 | ; Folding = --- 133 | ; EnableXP -------------------------------------------------------------------------------- /Includes/WebSocket_Server.pbi: -------------------------------------------------------------------------------- 1 | ; ##################################################### License / Copyright ######################################### 2 | ; 3 | ; The MIT License (MIT) 4 | ; 5 | ; Copyright (c) 2015-2025 David Vogel 6 | ; 7 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 8 | ; of this software and associated documentation files (the "Software"), to deal 9 | ; in the Software without restriction, including without limitation the rights 10 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ; copies of the Software, and to permit persons to whom the Software is 12 | ; furnished to do so, subject to the following conditions: 13 | ; 14 | ; The above copyright notice and this permission notice shall be included in all 15 | ; copies or substantial portions of the Software. 16 | ; 17 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | ; SOFTWARE. 24 | ; 25 | ; #################################################### Documentation ################################################ 26 | ; 27 | ; #### WebSocket_Server #### 28 | ; 29 | ; Enable Threadsafe! 30 | ; 31 | ; - Works in unicode and ascii mode 32 | ; - Works under x86 and x64 33 | ; 34 | ; Working OSes: 35 | ; - Windows 36 | ; - Tested: 7 x64 37 | ; - Tested: 10 x64 38 | ; - Linux 39 | ; - Tested: Ubuntu 17.10 x64 40 | ; - MacOS 41 | ; - Not tested yet 42 | ; 43 | ; 44 | ; 45 | ; Version history: 46 | ; - V0.990 (05.02.2015) 47 | ; - Everything done (Hopefully) 48 | ; 49 | ; - V0.991 (09.02.2015) 50 | ; - Changed endian conversion from bit-shifting to direct memory access to make the include working with x86. 51 | ; 52 | ; - V0.992 (09.02.2015) 53 | ; - Added #Frame_Payload_Max to prevent clients to make the server allocate a lot of memory. 54 | ; - Some other small bugfixes. 55 | ; 56 | ; - V0.993 (24.10.2015) 57 | ; - Made it compatible with PB 5.40 LTS (Mainly SHA-1) 58 | ; - Bugfix with pokes 59 | ; 60 | ; - V0.994 (20.05.2017) 61 | ; - Made it compatible with PB 5.60 62 | ; 63 | ; - V0.995 (28.02.2018) 64 | ; - Fixed possible deadlock 65 | ; - Fixed unnecessary disconnect event 66 | ; 67 | ; - V0.996 (01.03.2018) 68 | ; - Fixed error on too quick disconnect 69 | ; 70 | ; - V0.997 (02.03.2018) 71 | ; - Use extra thread for callbacks 72 | ; - Optimized the network event handling 73 | ; - Fixed some leftovers when freeing the server 74 | ; 75 | ; - V0.998 (15.11.2019) 76 | ; - Fix missing or wrong rx frames on slower connections 77 | ; 78 | ; - V0.999 (07.05.2020) 79 | ; - Fix rare crash after calling CloseNetworkConnection from another thread. 80 | ; 81 | ; - V1.000 (09.10.2020) 82 | ; - Fix issue with some HTTP header fields and values not being treated case-insensitive. 83 | ; 84 | ; - V1.003 (09.02.2021) 85 | ; - Fix typos and misspelled words 86 | ; - Remove Event_Disconnect field from client 87 | ; - Don't break loop in Thread_Receive_Frame() after every frame 88 | ; - Remove some commented code 89 | ; - Add mutexless variant of Frame_Send() for internal use 90 | ; - Fix a race condition that may happen when using Client_Disconnect() 91 | ; - Fix a memory leak in the HTTP header receiver 92 | ; - Get rid of pushing and popping RX_Frame 93 | ; - Set Event_Disconnect_Manually flag inside of Frame_Send() and Frame_Send_Mutexless() 94 | ; - Remove the Done field from Frames 95 | ; - Add *New_RX_FRAME to client 96 | ; - Simplify how frames are received 97 | ; - Check result of all AllocateMemory and AllocateStructure calls 98 | ; - Null any freed memory or structure pointer 99 | ; - Prevent client to receive HTTP header if there is a forced disconnect 100 | ; - Limit HTTP header allocation size 101 | ; - Add internal function Client_Free() that frees everything that is allocated by a client 102 | ; 103 | ; - V1.004 (11.02.2021) 104 | ; - Use Autobahn|Testsuite for fuzzing 105 | ; - Fix most test cases 106 | ; - Fix how server closes the connection on client request 107 | ; - Fix data frame length encoding for transmitted frames 108 | ; - Limit the payload size of control frames (defined by websocket standard) 109 | ; - Free semaphore right before the server thread ends, not when the event thread ends 110 | ; - Move built in frame actions (ping and disconnect request handling) into Event_Callback so the actions stay in sync with everything else 111 | ; - Send signal to event thread every time a frame has been sent 112 | ; - Use local pointer to frame data in Event_Callback 113 | ; - Get rid of unnecessary second FirstElement() 114 | ; - Check if control frames are fragmented 115 | ; - Don't execute frame actions on malformed frames 116 | ; - Add a fragmented payload limit 117 | ; - Add a FrameData field that contains the raw frame Data 118 | ; - Add HandleFragmentation parameter to Create 119 | ; - Add Fragments List to client, that stores a fragment frame series 120 | ; - Add logic to combine fragmented frames 121 | ; - Allow fragmented messages to have a payload of 0 length 122 | ; - Add close status code enumeration 123 | ; - Add status code and reason to client disconnect 124 | ; - Add Client_Disconnect_Mutexless 125 | ; - Use default disconnect reason of 0, which means no reason at all 126 | ; - Remove all other (unsent) TX_Frame elements before sending a disconnect control frame 127 | ; - Add reason to Client_Disconnect 128 | ; - Close connection with correct status code in case of error 129 | ; 130 | ; - V1.005 (28.02.2021) 131 | ; - Use suggested min. size for Base64EncoderBuffer output buffer 132 | ; - Add connect (handshake) and disconnect timeouts 133 | ; - Read http header in bigger chunks, assume that clients don't send any data after #CRLF$ #CRLF$ 134 | ; - Implement client queue instead of iterating over all clients every event 135 | ; - On forced connection close, dump incoming network data into dummy buffer 136 | ; - Enqueue client on every possible action that needs to trigger a Event_Callback call 137 | ; - Throttle network thread when client queue is too large, this gives Event_Callback more processing time 138 | ; - Use allocation dumper to find memory leaks and other memory problems 139 | ; - Fix possible memory leak in Client_Disconnect_Mutexless() 140 | ; 141 | ; - V1.006 (05.12.2022) 142 | ; - Add Get_HTTP_Header function and make HTTP_Header structure public 143 | ; 144 | ; - V1.007 (04.04.2025) 145 | ; - Make InitNetwork() conditional. 146 | ; - Remove event thread, and move the logic into the main thread. There is no advantage in having two threads, because they both use the same mutex. This also prevents internal lock contention. 147 | ; - Add delay of 0s to help with external lock contention, this is useful when a user calls server API while the server is busy with sending data. 148 | ; - Increased throughput and lower latency due to two changes mentioned above. 149 | ; - Remove use of ClientQueueEnqueue(*Object, *Client) outside of the server's mutex. This was only called in case AllocateMemory failed, so basically never. 150 | ; 151 | ; - V1.008 (04.04.2025) 152 | ; - Forgot to increase #Version 153 | ; - Restore fast path of ClientQueueRemove 154 | 155 | ; ##################################################### Check Compiler options ###################################### 156 | 157 | CompilerIf Not #PB_Compiler_Thread 158 | CompilerError "Thread-Safe is not activated!" 159 | CompilerEndIf 160 | 161 | ; ##################################################### Module ###################################################### 162 | 163 | DeclareModule WebSocket_Server 164 | 165 | ; ##################################################### Public Constants ############################################ 166 | 167 | #Version = 1008 168 | 169 | Enumeration 170 | #Event_None 171 | #Event_Connect 172 | #Event_Disconnect 173 | #Event_Frame 174 | EndEnumeration 175 | 176 | Enumeration 177 | #Opcode_Continuation 178 | #Opcode_Text 179 | #Opcode_Binary 180 | 181 | #Opcode_Connection_Close = 8 182 | #Opcode_Ping 183 | #Opcode_Pong 184 | EndEnumeration 185 | 186 | Enumeration 187 | #CloseStatusCode_Normal = 1000 ; indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled. 188 | #CloseStatusCode_GoingAway ; indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page. 189 | #CloseStatusCode_ProtocolError ; indicates that an endpoint is terminating the connection due to a protocol error. 190 | #CloseStatusCode_UnhandledDataType ; indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message). 191 | #CloseStatusCode_1004 ; Reserved. The specific meaning might be defined in the future. 192 | #CloseStatusCode_NoStatusCode ; is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that no status code was actually present. 193 | #CloseStatusCode_AbnormalClose ; is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame. 194 | #CloseStatusCode_1007 ; indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [RFC3629] data within a text message). 195 | #CloseStatusCode_PolicyViolation ; indicates that an endpoint is terminating the connection because it has received a message that violates its policy. This is a generic status code that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the policy. 196 | #CloseStatusCode_SizeLimit ; indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process. 197 | #CloseStatusCode_1010 198 | #CloseStatusCode_1011 199 | #CloseStatusCode_1015 200 | EndEnumeration 201 | 202 | #RSV1 = %00000100 203 | #RSV2 = %00000010 204 | #RSV3 = %00000001 205 | 206 | #Frame_Payload_Max = 10000000 ; Default max. size of an incoming frame's payload. If the payload exceeds this value, the client will be disconnected. 207 | #Frame_Fragmented_Payload_Max = 100000000 ; Default max. size of the total payload of a series of frame fragments. If the payload exceeds this value, the client will be disconnected. If the user/application needs more, it has To handle fragmentation on its own. 208 | #Frame_Control_Payload_Max = 125 ; Max. allowed amount of bytes in the payload of control frames. This is defined by the websocket standard. 209 | 210 | #ClientDisconnectTimeout = 5000 ; Maximum duration in ms a client waits to send all queued outgoing frames on connection closure. 211 | #ClientConnectTimeout = 45000 ; Maximum duration in ms a client is allowed to take for connection and handshake related activities. 212 | 213 | ; ##################################################### Public Structures ########################################### 214 | 215 | Structure Event_Frame 216 | Fin.a ; #True if this is the final frame of a series of frames. 217 | RSV.a ; Extension bits: RSV1, RSV2, RSV3. 218 | Opcode.a ; Opcode. 219 | 220 | *Payload 221 | Payload_Size.i 222 | 223 | *FrameData ; Raw frame data. don't use this, you should use the *Payload instead. 224 | EndStructure 225 | 226 | Structure HTTP_Header 227 | *Data 228 | RX_Pos.i 229 | 230 | Request.s ; The HTTP request that was originally sent by the client. 231 | Map Field.s() ; The HTTP header key value pairs originally sent by the client. 232 | EndStructure 233 | 234 | ; ##################################################### Public Variables ############################################ 235 | 236 | ; ##################################################### Public Prototypes ########################################### 237 | 238 | Prototype Event_Callback(*Object, *Client, Event.i, *Custom_Structure=#Null) 239 | 240 | ; ##################################################### Public Procedures (Declares) ################################ 241 | 242 | Declare.i Create(Port, *Event_Thread_Callback.Event_Callback=#Null, Frame_Payload_Max.q=#Frame_Payload_Max, HandleFragmentation=#True) ; Creates a new WebSocket server. *Event_Thread_Callback is the callback which will be called out of the server thread. 243 | Declare Free(*Object) ; Closes the WebSocket server. 244 | 245 | Declare Frame_Text_Send(*Object, *Client, Text.s) ; Sends a text-frame. 246 | Declare Frame_Send(*Object, *Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q) ; Sends a frame. FIN, RSV and Opcode can be freely defined. Normally you should use #Opcode_Binary. 247 | 248 | Declare Event_Callback(*Object, *Callback.Event_Callback) ; Checks for events, and calls the *Callback function if there are any. 249 | 250 | Declare.i Get_HTTP_Header(*Client) ; Returns a pointer to the HTTP_Header structure that contains the parsed HTTP request of the given client. 251 | 252 | Declare Client_Disconnect(*Object, *Client, statusCode.u=0, reason.s="") ; Disconnects the specified *Client. 253 | 254 | EndDeclareModule 255 | 256 | ; ##################################################### Module (Private Part) ####################################### 257 | 258 | Module WebSocket_Server 259 | 260 | EnableExplicit 261 | 262 | ; #### Only use this for debugging purposes. 263 | ;XIncludeFile "AllocationDumper.pbi" 264 | 265 | CompilerIf #PB_Compiler_Version < 600 266 | InitNetwork() 267 | CompilerEndIf 268 | UseSHA1Fingerprint() 269 | 270 | ; ##################################################### Constants ################################################### 271 | 272 | #Frame_Data_Size_Min = 2048 273 | 274 | #HTTP_Header_Data_Read_Step = 1024 275 | #HTTP_Header_Data_Size_Step = 2048 276 | #HTTP_Header_Data_Size_Max = 8192 277 | 278 | #GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 279 | 280 | Enumeration 281 | #Mode_Handshake 282 | #Mode_Frames 283 | EndEnumeration 284 | 285 | ; ##################################################### Structures ################################################## 286 | 287 | Structure Eight_Bytes 288 | Byte.a[8] 289 | EndStructure 290 | 291 | Structure Frame_Header_Length 292 | Dummy.a 293 | Length.a 294 | Extended.a[8] 295 | EndStructure 296 | 297 | Structure Frame_Header 298 | StructureUnion 299 | Byte.a[14] ; Size of the header is 14B max. 300 | Length.Frame_Header_Length 301 | EndStructureUnion 302 | EndStructure 303 | 304 | Structure Frame 305 | *Data.Frame_Header 306 | 307 | RxTx_Pos.i ; Current position while receiving or sending the frame. 308 | RxTx_Size.i ; Size of the frame (Header + Payload). 309 | 310 | Payload_Pos.i 311 | Payload_Size.q ; Quad, because a frame can be 2^64B large. 312 | EndStructure 313 | 314 | Structure Client 315 | ID.i ; Client ID. Is set to #Null when the TCP connection closes. The user can still read all incoming frames, though. 316 | 317 | HTTP_Header.HTTP_Header 318 | 319 | *New_RX_FRAME.Frame ; A frame that is currently being received. 320 | 321 | List RX_Frame.Frame() ; List of fully received incoming frames (They need to be passed to the user of this library). 322 | List TX_Frame.Frame() ; List of outgoing frames. First one is currently being sent. 323 | 324 | List Fragments.Event_Frame() ; List of (parsed) fragment frames. A series of fragments will be stored here temporarily. 325 | Fragments_Size.q ; Total size sum of all fragments. 326 | 327 | Mode.i 328 | 329 | Event_Connect.i ; #True --> Generate connect callback. 330 | Event_Disconnect_Manually.i ; #True --> Generate disconnect callback and delete client as soon as all data is sent and read by the application. (This gets set by the application or websocket protocol, there is possibly still a TCP connection) 331 | DisconnectTimeout.q ; When Event_Disconnect_Manually is #True: Point in time when the server forcefully disconnects the client, no matter if all packets have been sent or not. 332 | ConnectTimeout.q ; Point in time when a client will be disconnected. Reset after the handshake was successful. 333 | 334 | Enqueued.i ; #True --> This client is already inside the ClientQueue of the server. 335 | 336 | External_Reference.i ; #True --> An external reference was given to the application (via event). If the connection closes, there must be a closing event. 337 | EndStructure 338 | 339 | Structure Object 340 | Server_ID.i 341 | 342 | Network_Thread_ID.i ; Thread handling in and outgoing data. 343 | 344 | Event_Thread_ID.i ; Thread handling event callbacks and client deletions. 345 | 346 | List Client.Client() 347 | List *ClientQueue.Client() ; A queue of clients that need to be processed in Event_Callback(). 348 | 349 | *Event_Thread_Callback.Event_Callback 350 | 351 | Frame_Payload_Max.q ; Max-Size of an incoming frame's payload. If the frame exceeds this value, the client will be disconnected. 352 | HandleFragmentation.i ; Let the library handle frame fragmentation. If set to true, the user/application will only receive coalesced frames. If set to false, the user/application has to handle fragmentation (By checking the Fin flag and #Opcode_Continuation) 353 | 354 | Mutex.i 355 | 356 | Free_Event.i ; Free the event thread and its semaphore. 357 | Free.i ; Free the main networking thread and all the resources. 358 | EndStructure 359 | 360 | ; ##################################################### Variables ################################################### 361 | 362 | Global DummyMemorySize = 1024 363 | Global *DummyMemory = AllocateMemory(DummyMemorySize) 364 | 365 | ; ##################################################### Declares #################################################### 366 | 367 | Declare Frame_Send_Mutexless(*Object.Object, *Client.Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q) 368 | Declare Client_Disconnect_Mutexless(*Object.Object, *Client.Client, statusCode.u=0, reason.s="") 369 | 370 | ; ##################################################### Procedures ################################################## 371 | 372 | Procedure Client_Select(*Object.Object, ID.i) 373 | If Not ID 374 | ProcedureReturn #False 375 | EndIf 376 | 377 | ForEach *Object\Client() 378 | If *Object\Client()\ID = ID 379 | ProcedureReturn #True 380 | EndIf 381 | Next 382 | 383 | ProcedureReturn #False 384 | EndProcedure 385 | 386 | Procedure ClientQueueEnqueue(*Object.Object, *Client.Client) 387 | If *Client\Enqueued 388 | ProcedureReturn #True 389 | EndIf 390 | 391 | LastElement(*Object\ClientQueue()) 392 | If AddElement(*Object\ClientQueue()) 393 | *Client\Enqueued = #True 394 | *Object\ClientQueue() = *Client 395 | ProcedureReturn #True 396 | EndIf 397 | 398 | ProcedureReturn #False 399 | EndProcedure 400 | 401 | Procedure ClientQueueDequeue(*Object.Object) 402 | Protected *Client.Client 403 | 404 | If FirstElement(*Object\ClientQueue()) 405 | *Client = *Object\ClientQueue() 406 | DeleteElement(*Object\ClientQueue()) 407 | *Client\Enqueued = #False 408 | ProcedureReturn *Client 409 | EndIf 410 | 411 | ProcedureReturn #Null 412 | EndProcedure 413 | 414 | Procedure ClientQueueRemove(*Object.Object, *Client.Client) 415 | If Not *Client\Enqueued 416 | ProcedureReturn #True 417 | EndIf 418 | 419 | ForEach *Object\ClientQueue() 420 | If *Object\ClientQueue() = *Client 421 | DeleteElement(*Object\ClientQueue()) 422 | *Client\Enqueued = #False 423 | ProcedureReturn #True 424 | EndIf 425 | Next 426 | 427 | ProcedureReturn #False 428 | EndProcedure 429 | 430 | Procedure Client_Free(*Client.Client) 431 | ; #### Free all RX_Frames() 432 | While FirstElement(*Client\RX_Frame()) 433 | If *Client\RX_Frame()\Data 434 | FreeMemory(*Client\RX_Frame()\Data) : *Client\RX_Frame()\Data = #Null 435 | EndIf 436 | DeleteElement(*Client\RX_Frame()) 437 | Wend 438 | 439 | ; #### Free all TX_Frames() 440 | While FirstElement(*Client\TX_Frame()) 441 | If *Client\TX_Frame()\Data 442 | FreeMemory(*Client\TX_Frame()\Data) : *Client\TX_Frame()\Data = #Null 443 | EndIf 444 | DeleteElement(*Client\TX_Frame()) 445 | Wend 446 | 447 | ; #### Free all Fragments() 448 | While FirstElement(*Client\Fragments()) 449 | If *Client\Fragments()\FrameData 450 | FreeMemory(*Client\Fragments()\FrameData) : *Client\Fragments()\FrameData = #Null 451 | EndIf 452 | DeleteElement(*Client\Fragments()) 453 | Wend 454 | 455 | ; #### Free HTTP header data, if still present 456 | If *Client\HTTP_Header\Data 457 | FreeMemory(*Client\HTTP_Header\Data) : *Client\HTTP_Header\Data = #Null 458 | EndIf 459 | 460 | ; #### Free temporary RX frame 461 | If *Client\New_RX_FRAME 462 | If *Client\New_RX_FRAME\Data 463 | FreeMemory(*Client\New_RX_FRAME\Data) : *Client\New_RX_FRAME\Data = #Null 464 | EndIf 465 | FreeStructure(*Client\New_RX_FRAME) : *Client\New_RX_FRAME = #Null 466 | EndIf 467 | EndProcedure 468 | 469 | Procedure.s Generate_Key(Client_Key.s) 470 | Protected *Temp_Data_2, *Temp_Data_3 471 | Protected Temp_String.s 472 | Protected Temp_SHA1.s 473 | Protected i 474 | Protected Result.s 475 | 476 | Temp_String.s = Client_Key + #GUID 477 | 478 | ; #### Generate the SHA1 479 | *Temp_Data_2 = AllocateMemory(20) 480 | If Not *Temp_Data_2 481 | ProcedureReturn "" 482 | EndIf 483 | Temp_SHA1.s = StringFingerprint(Temp_String, #PB_Cipher_SHA1, 0, #PB_Ascii) 484 | ;Debug Temp_SHA1 485 | For i = 0 To 19 486 | PokeA(*Temp_Data_2+i, Val("$"+Mid(Temp_SHA1, 1+i*2, 2))) 487 | Next 488 | 489 | ; #### Encode the SHA1 as Base64 490 | *Temp_Data_3 = AllocateMemory(64) ; Expected max. size of Base64 encoded string is 27 bytes. But Base64EncoderBuffer has a min. output buffer size of 64 bytes. 491 | If Not *Temp_Data_3 492 | FreeMemory(*Temp_Data_2) 493 | ProcedureReturn "" 494 | EndIf 495 | CompilerIf #PB_Compiler_Version < 560 496 | Base64Encoder(*Temp_Data_2, 20, *Temp_Data_3, 64) 497 | CompilerElse 498 | Base64EncoderBuffer(*Temp_Data_2, 20, *Temp_Data_3, 64) 499 | CompilerEndIf 500 | 501 | Result = PeekS(*Temp_Data_3, -1, #PB_Ascii) 502 | 503 | FreeMemory(*Temp_Data_2) 504 | FreeMemory(*Temp_Data_3) 505 | 506 | ProcedureReturn Result 507 | EndProcedure 508 | 509 | Procedure Thread_Receive_Handshake(*Object.Object, *Client.Client) 510 | Protected Result.i 511 | Protected *Temp_Data 512 | Protected Temp_Text.s 513 | Protected Temp_Line.s 514 | Protected Temp_Key.s 515 | Protected Response.s 516 | Protected i 517 | 518 | If *Client\Event_Disconnect_Manually 519 | ; #### Read data into dummy memory to dump it. Otherwise this will be called over and over again, as there is "new" data. 520 | While ReceiveNetworkData(*Client\ID, *DummyMemory, DummyMemorySize) > 0 521 | Wend 522 | ProcedureReturn #False 523 | EndIf 524 | 525 | Repeat 526 | 527 | ; #### Limit memory usage. 528 | If *Client\HTTP_Header\RX_Pos > #HTTP_Header_Data_Size_Max 529 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 530 | ProcedureReturn #False 531 | EndIf 532 | 533 | ; #### Manage memory 534 | If Not *Client\HTTP_Header\Data 535 | *Client\HTTP_Header\Data = AllocateMemory(#HTTP_Header_Data_Size_Step) ; This will be purged when the header got fully parsed, when the client is deleted or when the server is released. 536 | If Not *Client\HTTP_Header\Data 537 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 538 | ProcedureReturn #False 539 | EndIf 540 | EndIf 541 | If MemorySize(*Client\HTTP_Header\Data) < *Client\HTTP_Header\RX_Pos + #HTTP_Header_Data_Read_Step 542 | *Temp_Data = ReAllocateMemory(*Client\HTTP_Header\Data, ((*Client\HTTP_Header\RX_Pos + #HTTP_Header_Data_Read_Step) / #HTTP_Header_Data_Size_Step + 1) * #HTTP_Header_Data_Size_Step) 543 | If *Temp_Data 544 | *Client\HTTP_Header\Data = *Temp_Data 545 | Else 546 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 547 | ProcedureReturn #False 548 | EndIf 549 | EndIf 550 | 551 | ; #### Receive a chunk of data. 552 | Result = ReceiveNetworkData(*Client\ID, *Client\HTTP_Header\Data + *Client\HTTP_Header\RX_Pos, #HTTP_Header_Data_Read_Step) 553 | If Result > 0 554 | *Client\HTTP_Header\RX_Pos + Result 555 | ElseIf Result = 0 556 | Break 557 | Else 558 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 559 | ProcedureReturn #False 560 | EndIf 561 | 562 | ; #### Check if the header ends 563 | If *Client\HTTP_Header\RX_Pos >= 4 564 | If PeekL(*Client\HTTP_Header\Data + *Client\HTTP_Header\RX_Pos - 4) = 168626701 ; ### CR LF CR LF 565 | 566 | Temp_Text = PeekS(*Client\HTTP_Header\Data, *Client\HTTP_Header\RX_Pos-2, #PB_Ascii) 567 | FreeMemory(*Client\HTTP_Header\Data) : *Client\HTTP_Header\Data = #Null 568 | 569 | *Client\HTTP_Header\Request = StringField(Temp_Text, 1, #CRLF$) 570 | 571 | For i = 2 To CountString(Temp_Text, #CRLF$) 572 | Temp_Line = StringField(Temp_Text, i, #CRLF$) 573 | *Client\HTTP_Header\Field(LCase(StringField(Temp_Line, 1, ":"))) = Trim(StringField(Temp_Line, 2, ":")) 574 | Next 575 | 576 | ; #### Check if the request is correct 577 | ;TODO: Check if this mess works with most clients/browsers! 578 | If StringField(*Client\HTTP_Header\Request, 1, " ") = "GET" 579 | If LCase(*Client\HTTP_Header\Field("upgrade")) = "websocket" 580 | If FindString(LCase(*Client\HTTP_Header\Field("connection")), "upgrade") 581 | If Val(*Client\HTTP_Header\Field("sec-websocket-version")) = 13 And FindMapElement(*Client\HTTP_Header\Field(), "sec-websocket-key") 582 | *Client\Mode = #Mode_Frames 583 | *Client\Event_Connect = #True : ClientQueueEnqueue(*Object, *Client) 584 | Response = "HTTP/1.1 101 Switching Protocols" + #CRLF$ + 585 | "Upgrade: websocket" + #CRLF$ + 586 | "Connection: Upgrade" + #CRLF$ + 587 | "Sec-WebSocket-Accept: " + Generate_Key(*Client\HTTP_Header\Field("sec-websocket-key")) + #CRLF$ + 588 | #CRLF$ 589 | Else 590 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 591 | Response = "HTTP/1.1 400 Bad Request" + #CRLF$ + 592 | "Content-Type: text/html" + #CRLF$ + 593 | "Content-Length: 63" + #CRLF$ + 594 | #CRLF$ + 595 | "

    400 Bad Request

    " 596 | EndIf 597 | Else 598 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 599 | Response = "HTTP/1.1 400 WebSocket Upgrade Failure" + #CRLF$ + 600 | "Content-Type: text/html" + #CRLF$ + 601 | "Content-Length: 77" + #CRLF$ + 602 | #CRLF$ + 603 | "

    400 WebSocket Upgrade Failure

    " 604 | EndIf 605 | Else 606 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 607 | Response = "HTTP/1.1 404 Not Found" + #CRLF$ + 608 | "Content-Type: text/html" + #CRLF$ + 609 | "Content-Length: 61" + #CRLF$ + 610 | #CRLF$ + 611 | "

    404 Not Found

    " 612 | EndIf 613 | Else 614 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 615 | Response = "HTTP/1.1 405 Method Not Allowed" + #CRLF$ + 616 | "Content-Type: text/html" + #CRLF$ + 617 | "Content-Length: 70" + #CRLF$ + 618 | #CRLF$ + 619 | "

    405 Method Not Allowed

    " 620 | EndIf 621 | 622 | ; #### Misuse a frame for the HTTP response 623 | LastElement(*Client\TX_Frame()) 624 | If AddElement(*Client\TX_Frame()) 625 | 626 | *Client\TX_Frame()\RxTx_Size = StringByteLength(Response, #PB_Ascii) 627 | *Client\TX_Frame()\Data = AllocateMemory(*Client\TX_Frame()\RxTx_Size) 628 | If Not *Client\TX_Frame()\Data 629 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 630 | DeleteElement(*Client\TX_Frame()) 631 | ProcedureReturn #False 632 | EndIf 633 | 634 | PokeS(*Client\TX_Frame()\Data, Response, -1, #PB_Ascii | #PB_String_NoZero) 635 | 636 | EndIf 637 | 638 | ProcedureReturn #True 639 | EndIf 640 | EndIf 641 | 642 | ForEver 643 | 644 | ProcedureReturn #True 645 | EndProcedure 646 | 647 | Procedure Thread_Receive_Frame(*Object.Object, *Client.Client) 648 | Protected Receive_Size.i 649 | Protected Result.i 650 | Protected *Temp_Data 651 | Protected Mask.l, *Pointer_Mask.Long 652 | Protected *Eight_Bytes.Eight_Bytes 653 | Protected *TempFrame.Frame 654 | Protected i 655 | 656 | If *Client\Event_Disconnect_Manually 657 | ; #### Read data into dummy memory to dump it. Otherwise this will be called over and over again, as there is "new" data. 658 | While ReceiveNetworkData(*Client\ID, *DummyMemory, DummyMemorySize) > 0 659 | Wend 660 | ProcedureReturn #False 661 | EndIf 662 | 663 | Repeat 664 | 665 | ; #### Create new temporary frame if there is none yet. 666 | If Not *Client\New_RX_FRAME 667 | *Client\New_RX_FRAME = AllocateStructure(Frame) ; This will be purged when the frame is fully received, when the client is deleted or when the server is freed. 668 | If Not *Client\New_RX_FRAME 669 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 670 | ProcedureReturn #False 671 | EndIf 672 | *Client\New_RX_FRAME\RxTx_Size = 2 673 | EndIf 674 | 675 | *TempFrame = *Client\New_RX_FRAME 676 | 677 | ; #### Check if the frame exceeds the max. frame-size. 678 | If *TempFrame\Payload_Size > *Object\Frame_Payload_Max 679 | Client_Disconnect_Mutexless(*Object, *Client, #CloseStatusCode_SizeLimit) 680 | ProcedureReturn #False 681 | EndIf 682 | 683 | ; #### Check if a control frame exceeds the max. payload size. 684 | If *TempFrame\Payload_Size > #Frame_Control_Payload_Max 685 | ; #### Control frames are identified by opcodes where the most significant bit of the opcode is 1. 686 | If *TempFrame\RxTx_Pos >= 1 And *TempFrame\Data\Byte[0] & %00001000 = %1000 687 | Client_Disconnect_Mutexless(*Object, *Client, #CloseStatusCode_ProtocolError) 688 | ProcedureReturn #False 689 | EndIf 690 | EndIf 691 | 692 | ; #### Manage memory 693 | If Not *TempFrame\Data 694 | *TempFrame\Data = AllocateMemory(#Frame_Data_Size_Min) ; This will be purged when the client is deleted or when the server is freed, otherwise it will be reused in RX_Frame. 695 | If Not *TempFrame\Data 696 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 697 | ProcedureReturn #False 698 | EndIf 699 | EndIf 700 | If MemorySize(*TempFrame\Data) < *TempFrame\RxTx_Size + 3 ; #### Add 3 bytes so that the (de)masking doesn't write outside of the buffer 701 | *Temp_Data = ReAllocateMemory(*TempFrame\Data, *TempFrame\RxTx_Size + 3) 702 | If *Temp_Data 703 | *TempFrame\Data = *Temp_Data 704 | Else 705 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 706 | ProcedureReturn #False 707 | EndIf 708 | EndIf 709 | 710 | ; #### Calculate how many bytes need to be received 711 | Receive_Size = *TempFrame\RxTx_Size - *TempFrame\RxTx_Pos 712 | 713 | ; #### Receive... 714 | Result = ReceiveNetworkData(*Client\ID, *TempFrame\Data + *TempFrame\RxTx_Pos, Receive_Size) 715 | If Result > 0 716 | *TempFrame\RxTx_Pos + Result 717 | Else 718 | ProcedureReturn #False 719 | EndIf 720 | 721 | ; #### Recalculate the size of the current frame (Only if all data is received) 722 | If *TempFrame\RxTx_Pos >= *TempFrame\RxTx_Size 723 | 724 | ; #### Size of the first 2 byte in the header 725 | *TempFrame\RxTx_Size = 2 726 | 727 | ; #### Determine the length of the payload 728 | Select *TempFrame\Data\Length\Length & %01111111 729 | Case 0 To 125 730 | *TempFrame\Payload_Size = *TempFrame\Data\Length\Length & %01111111 731 | 732 | Case 126 733 | *TempFrame\RxTx_Size + 2 734 | If *TempFrame\RxTx_Pos = *TempFrame\RxTx_Size 735 | *Eight_Bytes = @*TempFrame\Payload_Size 736 | *Eight_Bytes\Byte[1] = *TempFrame\Data\Length\Extended[0] 737 | *Eight_Bytes\Byte[0] = *TempFrame\Data\Length\Extended[1] 738 | EndIf 739 | 740 | Case 127 741 | *TempFrame\RxTx_Size + 8 742 | If *TempFrame\RxTx_Pos = *TempFrame\RxTx_Size 743 | *Eight_Bytes = @*TempFrame\Payload_Size 744 | *Eight_Bytes\Byte[7] = *TempFrame\Data\Length\Extended[0] 745 | *Eight_Bytes\Byte[6] = *TempFrame\Data\Length\Extended[1] 746 | *Eight_Bytes\Byte[5] = *TempFrame\Data\Length\Extended[2] 747 | *Eight_Bytes\Byte[4] = *TempFrame\Data\Length\Extended[3] 748 | *Eight_Bytes\Byte[3] = *TempFrame\Data\Length\Extended[4] 749 | *Eight_Bytes\Byte[2] = *TempFrame\Data\Length\Extended[5] 750 | *Eight_Bytes\Byte[1] = *TempFrame\Data\Length\Extended[6] 751 | *Eight_Bytes\Byte[0] = *TempFrame\Data\Length\Extended[7] 752 | EndIf 753 | 754 | EndSelect 755 | 756 | If *TempFrame\RxTx_Pos >= *TempFrame\RxTx_Size 757 | 758 | ; #### Add the payload length to the size of the frame data 759 | *TempFrame\RxTx_Size + *TempFrame\Payload_Size 760 | 761 | ; #### Check if there is a mask 762 | If *TempFrame\Data\Byte[1] & %10000000 763 | *TempFrame\RxTx_Size + 4 764 | EndIf 765 | 766 | *TempFrame\Payload_Pos = *TempFrame\RxTx_Size - *TempFrame\Payload_Size 767 | 768 | EndIf 769 | 770 | EndIf 771 | 772 | ; #### Check if the frame is received completely. 773 | If *TempFrame\RxTx_Pos >= *TempFrame\RxTx_Size 774 | 775 | ; #### (De)masking 776 | If *TempFrame\Data\Byte[1] & %10000000 777 | ; #### Get mask 778 | Mask = PeekL(*TempFrame\Data + *TempFrame\Payload_Pos - 4) 779 | 780 | ; #### XOr mask 781 | *Pointer_Mask = *TempFrame\Data + *TempFrame\Payload_Pos 782 | For i = 0 To *TempFrame\Payload_Size-1 Step 4 783 | *Pointer_Mask\l = *Pointer_Mask\l ! Mask 784 | *Pointer_Mask + 4 785 | Next 786 | 787 | EndIf 788 | 789 | ; #### Move this frame into the RX_Frame list. 790 | LastElement(*Client\RX_Frame()) 791 | If AddElement(*Client\RX_Frame()) 792 | *Client\RX_Frame()\Data = *TempFrame\Data 793 | *Client\RX_Frame()\Payload_Pos = *TempFrame\Payload_Pos 794 | *Client\RX_Frame()\Payload_Size = *TempFrame\Payload_Size 795 | *Client\RX_Frame()\RxTx_Pos = *TempFrame\RxTx_Pos 796 | *Client\RX_Frame()\RxTx_Size = *TempFrame\RxTx_Size 797 | EndIf 798 | 799 | ClientQueueEnqueue(*Object, *Client) 800 | 801 | ; #### Remove temporary frame, but don't free the memory, as it is used in the RX_Frame list now. 802 | FreeStructure(*Client\New_RX_FRAME) : *Client\New_RX_FRAME = #Null 803 | 804 | EndIf 805 | 806 | ForEver 807 | 808 | ProcedureReturn #True 809 | EndProcedure 810 | 811 | Procedure Thread_Transmit(*Object.Object, *Client.Client) 812 | Protected Transmit_Size.i 813 | Protected Result.i 814 | 815 | While FirstElement(*Client\TX_Frame()) 816 | 817 | Transmit_Size = *Client\TX_Frame()\RxTx_Size - *Client\TX_Frame()\RxTx_Pos 818 | 819 | If Transmit_Size > 0 820 | ; #### Some data needs to be sent 821 | Result = SendNetworkData(*Client\ID, *Client\TX_Frame()\Data + *Client\TX_Frame()\RxTx_Pos, Transmit_Size) 822 | If Result > 0 823 | *Client\TX_Frame()\RxTx_Pos + Result 824 | Else 825 | ProcedureReturn #False 826 | EndIf 827 | EndIf 828 | 829 | Transmit_Size = *Client\TX_Frame()\RxTx_Size - *Client\TX_Frame()\RxTx_Pos 830 | 831 | If Transmit_Size <= 0 832 | ; #### Frame can be deleted 833 | FreeMemory(*Client\TX_Frame()\Data) : *Client\TX_Frame()\Data = #Null 834 | DeleteElement(*Client\TX_Frame()) 835 | 836 | ; #### The event thread may have to handle stuff, send a signal. 837 | If ListSize(*Client\TX_Frame()) = 0 838 | ClientQueueEnqueue(*Object, *Client) 839 | EndIf 840 | EndIf 841 | 842 | Wend 843 | 844 | ProcedureReturn #True 845 | EndProcedure 846 | 847 | Procedure Thread(*Object.Object) 848 | Protected Busy, Counter, ms 849 | Protected *Client.Client 850 | 851 | Repeat 852 | ; #### Network Events 853 | Counter = 0 854 | 855 | LockMutex(*Object\Mutex) 856 | Repeat 857 | Select NetworkServerEvent(*Object\Server_ID) 858 | Case #PB_NetworkEvent_None 859 | Break 860 | 861 | Case #PB_NetworkEvent_Connect 862 | LastElement(*Object\Client()) 863 | If AddElement(*Object\Client()) 864 | *Object\Client()\ConnectTimeout = ElapsedMilliseconds() + #ClientConnectTimeout 865 | *Object\Client()\ID = EventClient() 866 | EndIf 867 | Counter + 1 868 | 869 | Case #PB_NetworkEvent_Disconnect 870 | If Client_Select(*Object, EventClient()) 871 | *Object\Client()\ID = #Null : ClientQueueEnqueue(*Object, *Object\Client()) ; #### The application can still read all incoming frames. The client will be deleted after all incoming frames have been read. 872 | EndIf 873 | Counter + 1 874 | 875 | Case #PB_NetworkEvent_Data 876 | If Client_Select(*Object, EventClient()) 877 | Select *Object\Client()\Mode 878 | Case #Mode_Handshake : Thread_Receive_Handshake(*Object, *Object\Client()) 879 | Case #Mode_Frames : Thread_Receive_Frame(*Object, *Object\Client()) 880 | EndSelect 881 | EndIf 882 | Counter + 1 883 | 884 | EndSelect 885 | Until Counter > 10 886 | UnlockMutex(*Object\Mutex) 887 | 888 | ; #### Busy when there was at least one network event. 889 | Busy = Bool(Counter > 0) 890 | 891 | ; #### Handle the enqueued clients and issue callbacks. 892 | While Event_Callback(*Object, *Object\Event_Thread_Callback) 893 | Wend 894 | 895 | LockMutex(*Object\Mutex) 896 | ;Debug "Queue: " + ListSize(*Object\ClientQueue()) + " Clients: " + ListSize(*Object\Client()) 897 | ms = ElapsedMilliseconds() 898 | ForEach *Object\Client() 899 | *Client = *Object\Client() 900 | 901 | ; #### Send Data. 902 | If *Client\ID 903 | Busy | Bool(Thread_Transmit(*Object, *Client) = #False) 904 | EndIf 905 | 906 | ; #### Handle timeouts: Check if a client timed out before the handshake was successful. 907 | If *Client\ConnectTimeout And *Client\ConnectTimeout <= ms 908 | *Client\ConnectTimeout = 0 909 | *Client\Event_Disconnect_Manually = #True 910 | ClientQueueEnqueue(*Object, *Client) 911 | EndIf 912 | 913 | ; #### Handle timeouts: Disconnect timeout, so the client has some time to receive its disconnect message. 914 | If *Client\DisconnectTimeout And *Client\DisconnectTimeout <= ms 915 | ClientQueueEnqueue(*Object, *Client) 916 | EndIf 917 | Next 918 | UnlockMutex(*Object\Mutex) 919 | 920 | ; #### Delay only if there is nothing to do 921 | If Not Busy 922 | Delay(1) 923 | Else 924 | Delay(0) ; This is better than no delay, otherwise other threads don't have a chance to unlock the mutex. 925 | EndIf 926 | 927 | Until *Object\Free 928 | 929 | CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 930 | 931 | ForEach *Object\Client() 932 | Client_Free(*Object\Client()) 933 | Next 934 | 935 | FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 936 | FreeStructure(*Object) 937 | EndProcedure 938 | 939 | Procedure Frame_Send_Mutexless(*Object.Object, *Client.Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q) 940 | Protected *Pointer.Ascii 941 | Protected *Eight_Bytes.Eight_Bytes 942 | 943 | If Not *Object 944 | ProcedureReturn #False 945 | EndIf 946 | 947 | If Not *Client 948 | ProcedureReturn #False 949 | EndIf 950 | 951 | If Not *Client\ID Or *Client\Event_Disconnect_Manually 952 | ProcedureReturn #False 953 | EndIf 954 | 955 | If Payload_Size < 0 956 | ProcedureReturn #False 957 | EndIf 958 | 959 | If Not *Payload 960 | Payload_Size = 0 961 | EndIf 962 | 963 | ; #### Special case: Connection close request (or answer). 964 | If Opcode = #Opcode_Connection_Close 965 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 966 | 967 | ; #### Remove all TX_Frame elements (Except the one that is being sent right now). 968 | While LastElement(*Client\TX_Frame()) And ListIndex(*Client\TX_Frame()) > 0 969 | If *Client\TX_Frame()\Data 970 | FreeMemory(*Client\TX_Frame()\Data) : *Client\TX_Frame()\Data = #Null 971 | EndIf 972 | DeleteElement(*Client\TX_Frame()) 973 | Wend 974 | EndIf 975 | 976 | LastElement(*Client\TX_Frame()) 977 | If AddElement(*Client\TX_Frame()) 978 | 979 | *Client\TX_Frame()\Data = AllocateMemory(10 + Payload_Size) 980 | If Not *Client\TX_Frame()\Data 981 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 982 | ProcedureReturn #False 983 | EndIf 984 | 985 | ; #### FIN, RSV and Opcode 986 | *Pointer = *Client\TX_Frame()\Data 987 | *Pointer\a = (FIN & 1) << 7 | (RSV & %111) << 4 | (Opcode & %1111) : *Pointer + 1 988 | *Client\TX_Frame()\RxTx_Size + 1 989 | 990 | ; #### Payload_Size and extended stuff 991 | Select Payload_Size 992 | Case 0 To 125 993 | *Pointer\a = Payload_Size : *Pointer + 1 994 | *Client\TX_Frame()\RxTx_Size + 1 995 | Case 126 To 65535 996 | *Eight_Bytes = @Payload_Size 997 | *Pointer\a = 126 : *Pointer + 1 998 | *Pointer\a = *Eight_Bytes\Byte[1] : *Pointer + 1 999 | *Pointer\a = *Eight_Bytes\Byte[0] : *Pointer + 1 1000 | *Client\TX_Frame()\RxTx_Size + 3 1001 | Default 1002 | *Eight_Bytes = @Payload_Size 1003 | *Pointer\a = 127 : *Pointer + 1 1004 | *Pointer\a = *Eight_Bytes\Byte[7] : *Pointer + 1 1005 | *Pointer\a = *Eight_Bytes\Byte[6] : *Pointer + 1 1006 | *Pointer\a = *Eight_Bytes\Byte[5] : *Pointer + 1 1007 | *Pointer\a = *Eight_Bytes\Byte[4] : *Pointer + 1 1008 | *Pointer\a = *Eight_Bytes\Byte[3] : *Pointer + 1 1009 | *Pointer\a = *Eight_Bytes\Byte[2] : *Pointer + 1 1010 | *Pointer\a = *Eight_Bytes\Byte[1] : *Pointer + 1 1011 | *Pointer\a = *Eight_Bytes\Byte[0] : *Pointer + 1 1012 | *Client\TX_Frame()\RxTx_Size + 9 1013 | EndSelect 1014 | 1015 | If *Payload 1016 | CopyMemory(*Payload, *Pointer, Payload_Size) 1017 | ;*Pointer + Payload_Size 1018 | *Client\TX_Frame()\RxTx_Size + Payload_Size 1019 | EndIf 1020 | 1021 | ProcedureReturn #True 1022 | EndIf 1023 | 1024 | ProcedureReturn #False 1025 | EndProcedure 1026 | 1027 | Procedure Frame_Send(*Object.Object, *Client.Client, FIN.a, RSV.a, Opcode.a, *Payload, Payload_Size.q) 1028 | Protected Result 1029 | 1030 | If Not *Object 1031 | ProcedureReturn #False 1032 | EndIf 1033 | 1034 | LockMutex(*Object\Mutex) 1035 | Result = Frame_Send_Mutexless(*Object, *Client, FIN, RSV, Opcode, *Payload, Payload_Size) 1036 | UnlockMutex(*Object\Mutex) 1037 | 1038 | ProcedureReturn Result 1039 | EndProcedure 1040 | 1041 | Procedure Frame_Text_Send(*Object.Object, *Client.Client, Text.s) 1042 | Protected *Temp, Temp_Size.i 1043 | Protected Result 1044 | 1045 | Temp_Size = StringByteLength(Text, #PB_UTF8) 1046 | If Temp_Size = 0 1047 | ProcedureReturn Frame_Send(*Object, *Client, #True, 0, #Opcode_Text, #Null, 0) 1048 | EndIf 1049 | If Temp_Size < 0 1050 | ProcedureReturn #False 1051 | EndIf 1052 | *Temp = AllocateMemory(Temp_Size) 1053 | If Not *Temp 1054 | ProcedureReturn #False 1055 | EndIf 1056 | 1057 | PokeS(*Temp, Text, -1, #PB_UTF8 | #PB_String_NoZero) 1058 | 1059 | Result = Frame_Send(*Object, *Client, #True, 0, #Opcode_Text, *Temp, Temp_Size) 1060 | 1061 | FreeMemory(*Temp) 1062 | 1063 | ProcedureReturn Result 1064 | EndProcedure 1065 | 1066 | Procedure Event_Callback(*Object.Object, *Callback.Event_Callback) 1067 | Protected Event_Frame.Event_Frame 1068 | Protected *Client.Client 1069 | Protected *Frame_Data.Frame_Header 1070 | Protected MalformedFrame.i 1071 | Protected TempOffset.i 1072 | 1073 | If Not *Object 1074 | ProcedureReturn #False 1075 | EndIf 1076 | 1077 | If Not *Callback 1078 | ProcedureReturn #False 1079 | EndIf 1080 | 1081 | If *Object\Free 1082 | ProcedureReturn #False 1083 | EndIf 1084 | 1085 | LockMutex(*Object\Mutex) 1086 | 1087 | *Client = ClientQueueDequeue(*Object) 1088 | If Not *Client 1089 | UnlockMutex(*Object\Mutex) 1090 | ProcedureReturn #False 1091 | EndIf 1092 | 1093 | Repeat 1094 | 1095 | ; #### Event: Client connected and handshake was successful. 1096 | If *Client\Event_Connect 1097 | *Client\Event_Connect = #False 1098 | *Client\ConnectTimeout = 0 1099 | *Client\External_Reference = #True 1100 | UnlockMutex(*Object\Mutex) 1101 | *Callback(*Object, *Client, #Event_Connect) 1102 | LockMutex(*Object\Mutex) 1103 | Continue 1104 | EndIf 1105 | 1106 | ; #### Event: Client disconnected (TCP connection got terminated) (Only return this event if there are no incoming frames left to be read by the application) 1107 | If *Client\ID = #Null And ListSize(*Client\RX_Frame()) = 0 1108 | If *Client\External_Reference 1109 | UnlockMutex(*Object\Mutex) 1110 | *Callback(*Object, *Client, #Event_Disconnect) 1111 | LockMutex(*Object\Mutex) 1112 | EndIf 1113 | ; #### Delete the client and all its data. 1114 | ClientQueueRemove(*Object, *Client) 1115 | Client_Free(*Client) 1116 | ChangeCurrentElement(*Object\Client(), *Client) 1117 | DeleteElement(*Object\Client()) 1118 | Break 1119 | EndIf 1120 | 1121 | ; #### Disconnect timeout. The client will be enqueued for this in Thread(). 1122 | If *Client\Event_Disconnect_Manually And Not *Client\DisconnectTimeout 1123 | *Client\DisconnectTimeout = ElapsedMilliseconds() + #ClientDisconnectTimeout 1124 | EndIf 1125 | 1126 | ; #### Event: Close connection (By the user of the library, by any error that forces a disconnect or by an incoming disconnect request of the client via ws control frame) (Only close the connection if there are no frames left) 1127 | If *Client\Event_Disconnect_Manually And (ListSize(*Client\TX_Frame()) = 0 Or *Client\DisconnectTimeout <= ElapsedMilliseconds()) And ListSize(*Client\RX_Frame()) = 0 1128 | ; #### Forward event to application, but only if there was a connect event for this client before 1129 | If *Client\External_Reference 1130 | UnlockMutex(*Object\Mutex) 1131 | *Callback(*Object, *Client, #Event_Disconnect) 1132 | LockMutex(*Object\Mutex) 1133 | EndIf 1134 | If *Client\ID 1135 | CloseNetworkConnection(*Client\ID) : *Client\ID = #Null 1136 | EndIf 1137 | ; #### Delete the client and all its data. 1138 | ClientQueueRemove(*Object, *Client) 1139 | Client_Free(*Client) 1140 | ChangeCurrentElement(*Object\Client(), *Client) 1141 | DeleteElement(*Object\Client()) 1142 | Break 1143 | EndIf 1144 | 1145 | ; #### Event: Frame available 1146 | If FirstElement(*Client\RX_Frame()) 1147 | *Frame_Data = *Client\RX_Frame()\Data : *Client\RX_Frame()\Data = #Null 1148 | 1149 | Event_Frame\Fin = *Frame_Data\Byte[0] >> 7 & %00000001 1150 | Event_Frame\RSV = *Frame_Data\Byte[0] >> 4 & %00000111 1151 | Event_Frame\Opcode = *Frame_Data\Byte[0] & %00001111 1152 | Event_Frame\Payload = *Frame_Data + *Client\RX_Frame()\Payload_Pos 1153 | Event_Frame\Payload_Size = *Client\RX_Frame()\Payload_Size 1154 | Event_Frame\FrameData = *Frame_Data : *Frame_Data = #Null 1155 | 1156 | ; #### Remove RX_Frame. Its data is either freed below, after it has been read by the user/application, or it is freed in the fragmentation handling code, or when the user is deleted, or when the server is freed. 1157 | DeleteElement(*Client\RX_Frame()) 1158 | 1159 | ; #### Enqueue again. Either because there are still frames to be read by the user, or because there are no frames anymore and the client can disconnect. 1160 | ;ClientQueueEnqueue(*Object, *Client) 1161 | 1162 | ; #### Check if any extension bit is set. This lib doesn't support any extensions. 1163 | If Event_Frame\RSV <> 0 1164 | MalformedFrame = #True 1165 | EndIf 1166 | 1167 | ; #### Check if a control frame is being fragmented. 1168 | If Bool(Event_Frame\Opcode & %1000) And Event_Frame\Fin = #False 1169 | MalformedFrame = #True 1170 | EndIf 1171 | 1172 | ; #### Do default actions for specific opcodes. 1173 | If Not MalformedFrame 1174 | Select Event_Frame\Opcode 1175 | Case #Opcode_Continuation ; continuation frame 1176 | Case #Opcode_Text ; text frame 1177 | ; TODO: Check if payload is a valid UTF-8 string and contains valid code points (There may be a corner case when frame fragments are split between code points) 1178 | Case #Opcode_Binary ; binary frame 1179 | Case #Opcode_Connection_Close ; connection close 1180 | Protected statusCode.u, reason.s 1181 | If Event_Frame\Payload_Size >= 2 1182 | statusCode = PeekU(Event_Frame\Payload) 1183 | statusCode = ((statusCode & $FF00) >> 8) | ((statusCode & $FF) << 8) 1184 | reason = PeekS(Event_Frame\Payload + 2, Event_Frame\Payload_Size - 2, #PB_UTF8 | #PB_ByteLength) 1185 | EndIf 1186 | ; TODO: Check if status code is valid 1187 | ; TODO: Check if reason is a valid UTF-8 string and contains valid code points 1188 | Client_Disconnect_Mutexless(*Object, *Client, statusCode, reason) 1189 | Case #Opcode_Ping ; ping 1190 | Frame_Send_Mutexless(*Object, *Client, #True, 0, #Opcode_Pong, Event_Frame\Payload, Event_Frame\Payload_Size) 1191 | Case #Opcode_Pong ; pong 1192 | Default ; undefined 1193 | MalformedFrame = #True 1194 | EndSelect 1195 | EndIf 1196 | 1197 | ; #### Coalesce frame fragments. This will prevent the application/user from receiving fragmented frames. 1198 | ; #### Messy code, i wish there was something like go's defer and some other things. 1199 | If Not MalformedFrame And *Object\HandleFragmentation 1200 | If Not Event_Frame\Fin 1201 | 1202 | If Event_Frame\Opcode = #Opcode_Continuation 1203 | ; #### This frame is in the middle of a fragment series. 1204 | If Not LastElement(*Client\Fragments()) Or Not AddElement(*Client\Fragments()) 1205 | MalformedFrame = #True 1206 | Else 1207 | *Client\Fragments() = Event_Frame : Event_Frame\FrameData = #Null : Event_Frame\Payload = #Null 1208 | *Client\Fragments_Size + Event_Frame\Payload_Size 1209 | 1210 | If *Client\Fragments_Size > #Frame_Fragmented_Payload_Max 1211 | MalformedFrame = #True 1212 | Else 1213 | Continue ; Don't forward the frame to the user/application. 1214 | EndIf 1215 | EndIf 1216 | Else 1217 | ; #### This frame is the beginning of a fragment series. 1218 | If ListSize(*Client\Fragments()) > 0 1219 | ; #### Another fragment series is already started. Interleaving with other fragments is not allowed. 1220 | MalformedFrame = #True 1221 | Else 1222 | LastElement(*Client\Fragments()) 1223 | If Not AddElement(*Client\Fragments()) 1224 | MalformedFrame = #True 1225 | Else 1226 | *Client\Fragments() = Event_Frame : Event_Frame\FrameData = #Null : Event_Frame\Payload = #Null 1227 | *Client\Fragments_Size + Event_Frame\Payload_Size 1228 | 1229 | If *Client\Fragments_Size > #Frame_Fragmented_Payload_Max 1230 | MalformedFrame = #True 1231 | Else 1232 | Continue ; Don't forward the frame to the user/application. 1233 | EndIf 1234 | EndIf 1235 | EndIf 1236 | EndIf 1237 | Else 1238 | If Event_Frame\Opcode = #Opcode_Continuation 1239 | ; #### This frame is the end of a fragment series. 1240 | LastElement(*Client\Fragments()) 1241 | If Not AddElement(*Client\Fragments()) 1242 | MalformedFrame = #True 1243 | Else 1244 | *Client\Fragments() = Event_Frame : Event_Frame\FrameData = #Null : Event_Frame\Payload = #Null 1245 | *Client\Fragments_Size + Event_Frame\Payload_Size 1246 | 1247 | If *Client\Fragments_Size > #Frame_Fragmented_Payload_Max 1248 | MalformedFrame = #True 1249 | Else 1250 | 1251 | ; #### Combine fragments, overwrite Event_Frame to simulate one large incoming frame. 1252 | If FirstElement(*Client\Fragments()) 1253 | If *Client\Fragments()\Opcode <> #Opcode_Binary And *Client\Fragments()\Opcode <> #Opcode_Text 1254 | MalformedFrame = #True 1255 | Else 1256 | Event_Frame\Fin = #True 1257 | Event_Frame\RSV = 0 1258 | Event_Frame\Opcode = *Client\Fragments()\Opcode 1259 | Event_Frame\FrameData = AllocateMemory(*Client\Fragments_Size+1) 1260 | Event_Frame\Payload = Event_Frame\FrameData 1261 | Event_Frame\Payload_Size = *Client\Fragments_Size 1262 | If Not Event_Frame\FrameData 1263 | MalformedFrame = #True 1264 | Else 1265 | While FirstElement(*Client\Fragments()) 1266 | CopyMemory(*Client\Fragments()\Payload, Event_Frame\Payload + TempOffset, *Client\Fragments()\Payload_Size) : TempOffset + *Client\Fragments()\Payload_Size 1267 | FreeMemory(*Client\Fragments()\FrameData) : *Client\Fragments()\FrameData = #Null 1268 | DeleteElement(*Client\Fragments()) 1269 | Wend 1270 | EndIf 1271 | EndIf 1272 | EndIf 1273 | 1274 | EndIf 1275 | EndIf 1276 | Else 1277 | ; #### This frame is a normal unfragmented frame. 1278 | If Not Bool(Event_Frame\Opcode & %1000) And ListSize(*Client\Fragments()) > 0 1279 | ; #### This frame is not a control frame, but there is a started series of fragmented frames. 1280 | MalformedFrame = #True 1281 | EndIf 1282 | EndIf 1283 | EndIf 1284 | EndIf 1285 | 1286 | If MalformedFrame 1287 | ; #### Close connection as the frame is malformed in some way. 1288 | Client_Disconnect_Mutexless(*Object, *Client, #CloseStatusCode_ProtocolError) 1289 | Else 1290 | ; #### Forward event to application/user. 1291 | UnlockMutex(*Object\Mutex) 1292 | *Callback(*Object, *Client, #Event_Frame, Event_Frame) 1293 | LockMutex(*Object\Mutex) 1294 | EndIf 1295 | 1296 | If Event_Frame\FrameData 1297 | FreeMemory(Event_Frame\FrameData) : Event_Frame\FrameData = #Null 1298 | EndIf 1299 | 1300 | Continue 1301 | EndIf 1302 | 1303 | Break 1304 | ForEver 1305 | 1306 | UnlockMutex(*Object\Mutex) 1307 | ProcedureReturn #True 1308 | EndProcedure 1309 | 1310 | Procedure.i Get_HTTP_Header(*Client.Client) 1311 | If Not *Client 1312 | ProcedureReturn #Null 1313 | EndIf 1314 | 1315 | ProcedureReturn *Client\HTTP_Header 1316 | EndProcedure 1317 | 1318 | Procedure Client_Disconnect_Mutexless(*Object.Object, *Client.Client, statusCode.u=0, reason.s="") 1319 | If Not *Object 1320 | ProcedureReturn #False 1321 | EndIf 1322 | 1323 | If Not *Client 1324 | ProcedureReturn #False 1325 | EndIf 1326 | 1327 | If statusCode 1328 | Protected tempSize = 2 + StringByteLength(reason, #PB_UTF8) 1329 | Protected *tempMemory = AllocateMemory(tempSize) 1330 | If Not *tempMemory 1331 | *Client\Event_Disconnect_Manually = #True : ClientQueueEnqueue(*Object, *Client) 1332 | ProcedureReturn #False 1333 | EndIf 1334 | PokeU(*tempMemory, ((statusCode & $FF00) >> 8) | ((statusCode & $FF) << 8)) 1335 | If StringByteLength(reason, #PB_UTF8) > 0 1336 | PokeS(*tempMemory + 2, reason, -1, #PB_UTF8 | #PB_String_NoZero) 1337 | EndIf 1338 | Frame_Send_Mutexless(*Object, *Client, 1, 0, #Opcode_Connection_Close, *tempMemory, tempSize) ; This will also set the \Event_Disconnect_Manually flag 1339 | FreeMemory(*tempMemory) 1340 | Else 1341 | Frame_Send_Mutexless(*Object, *Client, 1, 0, #Opcode_Connection_Close, #Null, 0) ; This will also set the \Event_Disconnect_Manually flag 1342 | EndIf 1343 | 1344 | ProcedureReturn #True 1345 | EndProcedure 1346 | 1347 | Procedure Client_Disconnect(*Object.Object, *Client.Client, statusCode.u=0, reason.s="") 1348 | Protected Result 1349 | 1350 | If Not *Object 1351 | ProcedureReturn #False 1352 | EndIf 1353 | 1354 | LockMutex(*Object\Mutex) 1355 | Result = Client_Disconnect_Mutexless(*Object, *Client, statusCode, reason) 1356 | UnlockMutex(*Object\Mutex) 1357 | 1358 | ProcedureReturn Result 1359 | EndProcedure 1360 | 1361 | Procedure Create(Port, *Event_Thread_Callback.Event_Callback=#Null, Frame_Payload_Max.q=#Frame_Payload_Max, HandleFragmentation=#True) 1362 | Protected *Object.Object 1363 | 1364 | *Object = AllocateStructure(Object) 1365 | If Not *Object 1366 | ProcedureReturn #Null 1367 | EndIf 1368 | 1369 | *Object\Frame_Payload_Max = Frame_Payload_Max 1370 | *Object\HandleFragmentation = HandleFragmentation 1371 | *Object\Event_Thread_Callback = *Event_Thread_Callback 1372 | 1373 | *Object\Mutex = CreateMutex() 1374 | If Not *Object\Mutex 1375 | FreeStructure(*Object) 1376 | ProcedureReturn #Null 1377 | EndIf 1378 | 1379 | *Object\Server_ID = CreateNetworkServer(#PB_Any, Port, #PB_Network_TCP) 1380 | If Not *Object\Server_ID 1381 | FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 1382 | FreeStructure(*Object) 1383 | ProcedureReturn #Null 1384 | EndIf 1385 | 1386 | *Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 1387 | If Not *Object\Network_Thread_ID 1388 | FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 1389 | CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 1390 | FreeStructure(*Object) 1391 | ProcedureReturn #Null 1392 | EndIf 1393 | 1394 | ProcedureReturn *Object 1395 | EndProcedure 1396 | 1397 | Procedure Free(*Object.Object) 1398 | If Not *Object 1399 | ProcedureReturn #False 1400 | EndIf 1401 | 1402 | ; #### Fetch thread ID here, because the *Object is invalid some time after *Object\Free is set true 1403 | Protected Network_Thread_ID.i = *Object\Network_Thread_ID 1404 | 1405 | *Object\Free = #True 1406 | If Network_Thread_ID 1407 | WaitThread(Network_Thread_ID) 1408 | EndIf 1409 | 1410 | ProcedureReturn #True 1411 | EndProcedure 1412 | 1413 | EndModule 1414 | ; IDE Options = PureBasic 6.20 (Windows - x64) 1415 | ; CursorPosition = 866 1416 | ; FirstLine = 851 1417 | ; Folding = ---- 1418 | ; EnableThread 1419 | ; EnableXP 1420 | ; EnablePurifier = 1,1,1,1 1421 | ; EnableUnicode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 David Vogel 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSocket Server 2 | 3 | A conforming server implementation of the WebSocket standard as a module for PureBasic. 4 | 5 | **Features:** 6 | 7 | - Supports unfragmented and fragmented binary and text frames. 8 | - Callback based for easy use and low latency. 9 | - The module combines fragment frames automatically, this is on by default and can be turned off in case the application needs to handle fragmentation itself. 10 | - Built in handling of control frames (ping, disconnect). 11 | - Passes nearly all of [Autobahn|Testsuite](https://github.com/crossbario/autobahn-testsuite) test cases (289 / 303). 12 | 13 | **Not supported:** 14 | 15 | - Any sort of compression. This will not hurt compatibility, as compression is optional. 16 | - Automatic splitting of frames into fragments, but this can be done by the application if needed. 17 | - Any WebSocket extensions. 18 | - TLS. Best is to use a webserver (like [Caddy](https://github.com/caddyserver/caddy)) and setup a reverse proxy to the WebSocket server. If you need to supply static files (html, js), this is the preferred way anyways. 19 | 20 | ## Usage (Easy: Polling for events) 21 | 22 | The easier (but slower) method of using this WebSocket server is to poll for events. 23 | Even though the server uses threads internally, you don't have to worry about race conditions, deadlocks and so on. 24 | All you need to do is to call `WebSocket_Server::Event_Callback(*Server, *Callback)` from your own main loop, this will call your event handling function that you have passed as the `*callback` parameter in the same context as your main loop. 25 | 26 | Open a WebSocket-Server: 27 | 28 | ``` PureBasic 29 | *Server = WebSocket_Server::Create(8090) 30 | ``` 31 | 32 | Receive events as callback: 33 | 34 | ``` PureBasic 35 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) ; no need to worry about mutexes as this call is coming from your main loop 36 | Select Event 37 | Case WebSocket_Server::#Event_Connect 38 | PrintN(" #### Client connected: " + *Client) 39 | 40 | Case WebSocket_Server::#Event_Disconnect 41 | PrintN(" #### Client disconnected: " + *Client) 42 | ; !!!! From the moment you receive this event *Client must not be used anymore !!!! 43 | 44 | Case WebSocket_Server::#Event_Frame 45 | PrintN(" #### Frame received from " + *Client) 46 | 47 | ; #### OpCode is the type of frame you receive. 48 | ; #### It's either Text, Binary-Data, Ping-Frames or other stuff. 49 | ; #### You only need to care about text and binary frames. 50 | Select *Event_Frame\Opcode 51 | Case WebSocket_Server::#Opcode_Ping 52 | PrintN(" Client sent a ping frame") 53 | Case WebSocket_Server::#Opcode_Text 54 | PrintN(" Text received: " + PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength)) 55 | Case WebSocket_Server::#Opcode_Binary 56 | PrintN(" Binary data received") 57 | ; *Event_Frame\Payload contains the data, *Event_Frame\Payload_Size is the size of the data in bytes. 58 | ; !!!! Don't use the Payload after you return from this callback. If you need to do so, make a copy of the memory in here. !!!! 59 | EndSelect 60 | 61 | EndSelect 62 | EndProcedure 63 | ``` 64 | 65 | Your main loop: 66 | 67 | ``` PureBasic 68 | Repeat 69 | ; Other stuff 70 | While WebSocket_Server::Event_Callback(*Server, @WebSocket_Event()) 71 | Wend 72 | ; Other stuff 73 | ForEver 74 | ``` 75 | 76 | Send a text-frame: 77 | 78 | ``` PureBasic 79 | WebSocket_Server::Frame_Text_Send(*Server, *Client, "Hello Client!") 80 | ``` 81 | 82 | Send a binary-frame: 83 | 84 | ``` PureBasic 85 | WebSocket_Server::Frame_Send(*Server, *Client, #True, 0, WebSocket_Server::#Opcode_Binary, *Data, Data_Size) 86 | ``` 87 | 88 | Close and free a WebSocket-Server: 89 | 90 | ``` PureBasic 91 | Free(*Server) 92 | ``` 93 | 94 | ## Usage (Advanced: Threaded callback) 95 | 96 | This is similar to the method above, but instead of calling `WebSocket_Server::Event_Callback()` you set your event handler callback when you create your WebSocket server. 97 | Your event handler will be called as soon as any event occurs. 98 | But as the callback is called from a thread, you have to make sure everything in your event handler is thread-safe with the rest of your program. 99 | This mode has better performance, less latency and uses less resources. 100 | But it may cause problems like deadlocks or race conditions in your code if you use it wrong. 101 | 102 | To make it clear, you only have to make sure that all **your** resources/lists/variables/... that you access from the event handler function are thread-safe with the rest of your program. 103 | But you can still send websocket message from any thread or the event handler itself without using mutexes, as the functions of this module are thread-safe. 104 | 105 | Open a WebSocket-Server: 106 | 107 | ``` PureBasic 108 | *Server = WebSocket_Server::Create(8090, @WebSocket_Event()) 109 | ``` 110 | 111 | Receive events as callback: 112 | 113 | ``` PureBasic 114 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) 115 | Select Event 116 | Case WebSocket_Server::#Event_Connect 117 | PrintN(" #### Client connected: " + *Client) 118 | 119 | Case WebSocket_Server::#Event_Disconnect 120 | PrintN(" #### Client disconnected: " + *Client) 121 | ; !!!! From the moment you receive this event *Client must not be used anymore !!!! 122 | 123 | Case WebSocket_Server::#Event_Frame 124 | PrintN(" #### Frame received from " + *Client) 125 | 126 | ; #### OpCode is the type of frame you receive. 127 | ; #### It's either Text, Binary-Data, Ping-Frames or other stuff. 128 | ; #### You only need to care about text and binary frames. 129 | Select *Event_Frame\Opcode 130 | Case WebSocket_Server::#Opcode_Ping 131 | PrintN(" Client sent a ping frame") 132 | Case WebSocket_Server::#Opcode_Text 133 | PrintN(" Text received: " + PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength)) 134 | Case WebSocket_Server::#Opcode_Binary 135 | PrintN(" Binary data received") 136 | ; *Event_Frame\Payload contains the data, *Event_Frame\Payload_Size is the size of the data in bytes 137 | ; !!!! Don't use the Payload after you return from this callback. If you need to do so, make a copy of the memory in here. !!!! 138 | EndSelect 139 | 140 | EndSelect 141 | EndProcedure 142 | ``` 143 | 144 | Send a text-frame: 145 | 146 | ``` PureBasic 147 | WebSocket_Server::Frame_Text_Send(*Server, *Client, "Hello Client!") 148 | ``` 149 | 150 | Send a binary-frame: 151 | 152 | ``` PureBasic 153 | WebSocket_Server::Frame_Send(*Server, *Client, #True, 0, WebSocket_Server::#Opcode_Binary, *Data, Data_Size) 154 | ``` 155 | 156 | Close and free a WebSocket-Server: 157 | 158 | ``` PureBasic 159 | Free(*Server) 160 | ``` 161 | 162 | ## Example 163 | 164 | Here is an example chat application made with WebSockets. 165 | 166 | [Dadido3/WebSocket_Server/master/HTML/Chat_Client.html](http://rawgit.com/Dadido3/WebSocket_Server/master/HTML/Chat_Client.html) 167 | 168 | This example needs to connect to a WebSocket server. 169 | If it doesn't connect to my server you can run your own local server. 170 | For this you just have to compile and run `Example_Chat_Server.pb`. 171 | -------------------------------------------------------------------------------- /StressTest/ReAllocateTest.pb: -------------------------------------------------------------------------------- 1 | ; Just a test to see how well the purifier works with ReAllocateMemory 2 | 3 | EnableExplicit 4 | 5 | ;Procedure _ReAllocateMemory(*mem, newSize.i) 6 | ; Protected *newMem = AllocateMemory(newSize) 7 | ; If Not *newMem 8 | ; ProcedureReturn *mem 9 | ; EndIf 10 | ; 11 | ; Protected oldSize.i = MemorySize(*mem) 12 | ; If oldSize < newSize 13 | ; CopyMemory(*mem, *newMem, oldSize) 14 | ; Else 15 | ; CopyMemory(*mem, *newMem, newSize) 16 | ; EndIf 17 | ; 18 | ; FreeMemory(*mem) 19 | ; ProcedureReturn *newMem 20 | ;EndProcedure 21 | ;Macro ReAllocateMemory(mem, newSize) 22 | ; _ReAllocateMemory(mem, newSize) 23 | ;EndMacro 24 | 25 | ;Global allocationMutex = CreateMutex() 26 | 27 | Procedure TestThread(*Dummy) 28 | Protected *testMem = AllocateMemory(1000) 29 | Protected *newMem 30 | Protected newSize.i 31 | 32 | Repeat 33 | newSize = Random(4096, 1000) 34 | ;LockMutex(allocationMutex) 35 | *newMem = ReAllocateMemory(*testMem, newSize) 36 | ;UnlockMutex(allocationMutex) 37 | If Not *newMem 38 | FreeMemory(*testMem) 39 | Debug "ReAllocateMemory failed" 40 | Break 41 | EndIf 42 | *testMem = *newMem 43 | 44 | Debug *testMem 45 | 46 | ForEver 47 | EndProcedure 48 | 49 | Define i 50 | For i = 1 To 10 51 | CreateThread(@TestThread(), #Null) 52 | Next 53 | 54 | OpenConsole() 55 | Input() 56 | 57 | ; IDE Options = PureBasic 5.72 (Windows - x64) 58 | ; CursorPosition = 13 59 | ; Folding = - 60 | ; EnableThread 61 | ; EnablePurifier = 1,1,64,64 -------------------------------------------------------------------------------- /StressTest/StressTest.pb: -------------------------------------------------------------------------------- 1 | XIncludeFile "../Includes/WebSocket_Server.pbi" 2 | 3 | Procedure WebSocket_Event(*Server, *Client, Event, *Event_Frame.WebSocket_Server::Event_Frame) 4 | Protected Message.s 5 | 6 | Select Event 7 | Case WebSocket_Server::#Event_Connect 8 | ;PrintN("Client connected: " + *Client) 9 | 10 | Case WebSocket_Server::#Event_Disconnect 11 | ;PrintN("Client disconnected: " + *Client) 12 | 13 | Case WebSocket_Server::#Event_Frame 14 | Select *Event_Frame\Opcode 15 | Case WebSocket_Server::#Opcode_Ping 16 | ;PrintN(" Ping from: " + *Client) 17 | ; #### Pong is sent by the server automatically 18 | 19 | Case WebSocket_Server::#Opcode_Pong 20 | ;PrintN(" Pong from: " + *Client) 21 | 22 | Case WebSocket_Server::#Opcode_Connection_Close 23 | ;PrintN(" Close request from: " + *Client) 24 | 25 | Case WebSocket_Server::#Opcode_Text 26 | Message = PeekS(*Event_Frame\Payload, *Event_Frame\Payload_Size, #PB_UTF8|#PB_ByteLength) 27 | ;If Len(Message) >= 60 28 | ; PrintN(" Echo message from: " + *Client + " (" + Left(Message, 60-3) + "...)") 29 | ;Else 30 | ; PrintN(" Echo message from: " + *Client + " (" + Message + ")") 31 | ;EndIf 32 | ;If CountString(Message, "�") 33 | ; #### Invalid string, disconnect client. This will cause another test case to fail. Best would be to have something that checks UTF-8 string for validity. 34 | ; WebSocket_Server::Client_Disconnect(*Server, *Client, WebSocket_Server::#CloseStatusCode_1007) 35 | ;Else 36 | WebSocket_Server::Frame_Text_Send(*Server, *Client, Message) 37 | ;EndIf 38 | 39 | Case WebSocket_Server::#Opcode_Binary 40 | ;PrintN(" Echo binary from: " + *Client + " (" + *Event_Frame\Payload_Size + " bytes)") 41 | WebSocket_Server::Frame_Send(*Server, *Client, #True, 0, WebSocket_Server::#Opcode_Binary, *Event_Frame\Payload, *Event_Frame\Payload_Size) 42 | 43 | EndSelect 44 | 45 | EndSelect 46 | EndProcedure 47 | 48 | OpenConsole() 49 | 50 | *Server = WebSocket_Server::Create(8090, @WebSocket_Event()) 51 | 52 | Repeat 53 | Delay(10) 54 | ForEver 55 | 56 | ; IDE Options = PureBasic 6.20 (Windows - x64) 57 | ; CursorPosition = 7 58 | ; Folding = - 59 | ; EnableThread 60 | ; EnableXP 61 | ; Executable = stress-test-server.exe 62 | ; EnablePurifier = 32,32,4096,2 63 | ; EnableUnicode -------------------------------------------------------------------------------- /StressTest/Tests/Test 1.md: -------------------------------------------------------------------------------- 1 | # Test 1 2 | 3 | `Thread(*Client)` is called from the main thread: 4 | 5 | ``` PureBasic 6 | ;*Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 7 | ;If Not *Object\Network_Thread_ID 8 | ; FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 9 | ; If *Object\ClientQueueSemaphore : FreeSemaphore(*Object\ClientQueueSemaphore) : *Object\ClientQueueSemaphore = #Null : EndIf 10 | ; CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 11 | ; FreeStructure(*Object) 12 | ; ProcedureReturn #Null 13 | ;EndIf 14 | 15 | If *Event_Thread_Callback 16 | *Object\Event_Thread_ID = CreateThread(@Thread_Events(), *Object) 17 | If Not *Object\Event_Thread_ID 18 | *Object\Free = #True 19 | ProcedureReturn #Null 20 | EndIf 21 | EndIf 22 | 23 | Thread(*Object) 24 | ``` 25 | 26 | About half an hour after bombarding the server with 250 clients reconnecting and sending packets non stop, the purifier stopped the server. 27 | 28 | ``` text 29 | [23:54:23] Executable-Typ: Windows - x64 (64bit, Unicode, Thread, Purifier) 30 | [23:54:23] Executable gestartet. 31 | [00:26:52] [ERROR] WebSocket_Server.pbi (Zeile: 938) 32 | [00:26:52] [ERROR] Overflow in a dynamically allocated memory block. 33 | ``` 34 | 35 | Line of error (Without any meaning, as it is from a different thread): 36 | 37 | ``` PureBasic 38 | ; #### Wait for client queue entries. 39 | ClientQueueWait(*Object) 40 | ``` 41 | 42 | ## Allocations 43 | 44 | Allocation dump at the moment of the crash. 45 | 46 | - Allocation #1 from line 687, Size 2048: RX frame memory 47 | 48 | ``` PureBasic 49 | *TempFrame\Data = AllocateMemory(#Frame_Data_Size_Min) ; This will be purged when the client is deleted or when the server is freed, otherwise it will be reused in RX_Frame. 50 | ``` 51 | 52 | Raw data, plus 8 bytes before and 8 bytes after: 53 | 54 | ``` text 55 | 000000000559888C 4C 03 00 88 0D F0 AD 0B 82 FE 10 00 00 00 00 00 L..ˆ.ð­.‚þ...... 56 | 000000000559889C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 57 | 00000000055988AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 58 | 00000000055988BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 59 | 00000000055988CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 60 | 00000000055988DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 61 | 00000000055988EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 62 | 00000000055988FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 63 | 000000000559890C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 64 | 000000000559891C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 65 | 000000000559892C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 66 | 000000000559893C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 67 | 000000000559894C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 68 | 000000000559895C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 69 | 000000000559896C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 70 | 000000000559897C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 71 | 000000000559898C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 72 | 000000000559899C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 73 | 00000000055989AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 74 | 00000000055989BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 75 | 00000000055989CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 76 | 00000000055989DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 77 | 00000000055989EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 78 | 00000000055989FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 79 | 0000000005598A0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 80 | 0000000005598A1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 81 | 0000000005598A2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 82 | 0000000005598A3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 83 | 0000000005598A4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 84 | 0000000005598A5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 85 | 0000000005598A6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 86 | 0000000005598A7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 87 | 0000000005598A8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 88 | 0000000005598A9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 89 | 0000000005598AAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 90 | 0000000005598ABC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 91 | 0000000005598ACC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 92 | 0000000005598ADC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 93 | 0000000005598AEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 94 | 0000000005598AFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 95 | 0000000005598B0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 96 | 0000000005598B1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 97 | 0000000005598B2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 98 | 0000000005598B3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 99 | 0000000005598B4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 100 | 0000000005598B5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 101 | 0000000005598B6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 102 | 0000000005598B7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 103 | 0000000005598B8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 104 | 0000000005598B9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 105 | 0000000005598BAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 106 | 0000000005598BBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 107 | 0000000005598BCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 108 | 0000000005598BDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 109 | 0000000005598BEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 110 | 0000000005598BFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 111 | 0000000005598C0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 112 | 0000000005598C1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 113 | 0000000005598C2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 114 | 0000000005598C3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 115 | 0000000005598C4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 116 | 0000000005598C5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 117 | 0000000005598C6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 118 | 0000000005598C7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 119 | 0000000005598C8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 120 | 0000000005598C9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 121 | 0000000005598CAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 122 | 0000000005598CBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 123 | 0000000005598CCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 124 | 0000000005598CDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 125 | 0000000005598CEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 126 | 0000000005598CFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 127 | 0000000005598D0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 128 | 0000000005598D1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 129 | 0000000005598D2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 130 | 0000000005598D3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 131 | 0000000005598D4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 132 | 0000000005598D5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 133 | 0000000005598D6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 134 | 0000000005598D7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 135 | 0000000005598D8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 136 | 0000000005598D9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 137 | 0000000005598DAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 138 | 0000000005598DBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 139 | 0000000005598DCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 140 | 0000000005598DDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 141 | 0000000005598DEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 142 | 0000000005598DFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 143 | 0000000005598E0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 144 | 0000000005598E1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 145 | 0000000005598E2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 146 | 0000000005598E3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 147 | 0000000005598E4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 148 | 0000000005598E5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 149 | 0000000005598E6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 150 | 0000000005598E7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 151 | 0000000005598E8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 152 | 0000000005598E9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 153 | 0000000005598EAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 154 | 0000000005598EBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 155 | 0000000005598ECC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 156 | 0000000005598EDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 157 | 0000000005598EEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 158 | 0000000005598EFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 159 | 0000000005598F0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 160 | 0000000005598F1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 161 | 0000000005598F2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 162 | 0000000005598F3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 163 | 0000000005598F4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 164 | 0000000005598F5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 165 | 0000000005598F6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 166 | 0000000005598F7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 167 | 0000000005598F8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 168 | 0000000005598F9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 169 | 0000000005598FAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 170 | 0000000005598FBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 171 | 0000000005598FCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 172 | 0000000005598FDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 173 | 0000000005598FEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 174 | 0000000005598FFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 175 | 000000000559900C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 176 | 000000000559901C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 177 | 000000000559902C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 178 | 000000000559903C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 179 | 000000000559904C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 180 | 000000000559905C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 181 | 000000000559906C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 182 | 000000000559907C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 183 | 000000000559908C 00 00 00 00 00 00 00 00 00 00 00 00 DD 92 92 52 ............ݒ’R 184 | ``` 185 | 186 | - Allocation #2 from line 990, Size 14: TX frame memory 187 | 188 | ``` PureBasic 189 | *Client\TX_Frame()\Data = AllocateMemory(10 + Payload_Size) 190 | ``` 191 | 192 | Raw data, plus 8 bytes before and 8 bytes after: 193 | 194 | ``` text 195 | 00000000055C18DC 62 1F 00 8A 0D F0 AD 0B 81 04 54 65 73 74 00 00 b..Š.ð­..Test.. 196 | 00000000055C18EC 00 00 00 00 00 00 0D F0 AD 0B 00 00 5B CA .......ð­...[Ê 197 | ``` 198 | 199 | - Allocation #3 from line 660, Size unknown: Frame structure 200 | 201 | ``` PureBasic 202 | *Client\New_RX_FRAME = _AllocateStructure(Frame) ; This will be purged when the frame is fully received, when the client is deleted or when the server is freed. 203 | ``` 204 | 205 | Raw data, plus 8 bytes before: 206 | 207 | ``` text 208 | 000000000557DCC4 00 00 00 00 00 00 00 00 94 88 59 05 00 00 00 00 ........”ˆY..... 209 | 000000000557DCD4 04 00 00 00 00 00 00 00 08 10 00 00 00 00 00 00 ................ 210 | 000000000557DCE4 08 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 211 | 000000000557DCF4 0D F0 AD 0B 1B 76 D6 50 00 12 00 80 0D F0 AD 0B .ð­..vÖP...€.ð­. 212 | 000000000557DD04 88 02 03 E8 ˆ..è 213 | ``` 214 | 215 | - Allocation #4 from line 687, Size 2048: RX frame memory 216 | 217 | ``` PureBasic 218 | *TempFrame\Data = AllocateMemory(#Frame_Data_Size_Min) ; This will be purged when the client is deleted or when the server is freed, otherwise it will be reused in RX_Frame. 219 | ``` 220 | 221 | Raw data, plus 8 bytes before and 8 bytes after: 222 | 223 | ``` text 224 | 000000000559D11C F1 0C 00 88 0D F0 AD 0B 82 81 CE 7E 82 AE 7B 7E ñ..ˆ.ð­.‚Î~‚®{~ 225 | 000000000559D12C 82 AE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ‚®.............. 226 | 000000000559D13C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 227 | 000000000559D14C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 228 | 000000000559D15C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 229 | 000000000559D16C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 230 | 000000000559D17C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 231 | 000000000559D18C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 232 | 000000000559D19C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 233 | 000000000559D1AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 234 | 000000000559D1BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 235 | 000000000559D1CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 236 | 000000000559D1DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 237 | 000000000559D1EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 238 | 000000000559D1FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 239 | 000000000559D20C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 240 | 000000000559D21C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 241 | 000000000559D22C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 242 | 000000000559D23C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 243 | 000000000559D24C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 244 | 000000000559D25C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 245 | 000000000559D26C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 246 | 000000000559D27C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 247 | 000000000559D28C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 248 | 000000000559D29C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 249 | 000000000559D2AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 250 | 000000000559D2BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 251 | 000000000559D2CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 252 | 000000000559D2DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 253 | 000000000559D2EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 254 | 000000000559D2FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 255 | 000000000559D30C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 256 | 000000000559D31C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 257 | 000000000559D32C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 258 | 000000000559D33C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 259 | 000000000559D34C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 260 | 000000000559D35C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 261 | 000000000559D36C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 262 | 000000000559D37C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 263 | 000000000559D38C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 264 | 000000000559D39C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 265 | 000000000559D3AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 266 | 000000000559D3BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 267 | 000000000559D3CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 268 | 000000000559D3DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 269 | 000000000559D3EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 270 | 000000000559D3FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 271 | 000000000559D40C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 272 | 000000000559D41C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 273 | 000000000559D42C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 274 | 000000000559D43C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 275 | 000000000559D44C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 276 | 000000000559D45C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 277 | 000000000559D46C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 278 | 000000000559D47C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 279 | 000000000559D48C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 280 | 000000000559D49C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 281 | 000000000559D4AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 282 | 000000000559D4BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 283 | 000000000559D4CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 284 | 000000000559D4DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 285 | 000000000559D4EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 286 | 000000000559D4FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 287 | 000000000559D50C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 288 | 000000000559D51C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 289 | 000000000559D52C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 290 | 000000000559D53C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 291 | 000000000559D54C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 292 | 000000000559D55C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 293 | 000000000559D56C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 294 | 000000000559D57C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 295 | 000000000559D58C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 296 | 000000000559D59C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 297 | 000000000559D5AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 298 | 000000000559D5BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 299 | 000000000559D5CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 300 | 000000000559D5DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 301 | 000000000559D5EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 302 | 000000000559D5FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 303 | 000000000559D60C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 304 | 000000000559D61C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 305 | 000000000559D62C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 306 | 000000000559D63C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 307 | 000000000559D64C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 308 | 000000000559D65C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 309 | 000000000559D66C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 310 | 000000000559D67C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 311 | 000000000559D68C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 312 | 000000000559D69C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 313 | 000000000559D6AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 314 | 000000000559D6BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 315 | 000000000559D6CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 316 | 000000000559D6DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 317 | 000000000559D6EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 318 | 000000000559D6FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 319 | 000000000559D70C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 320 | 000000000559D71C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 321 | 000000000559D72C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 322 | 000000000559D73C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 323 | 000000000559D74C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 324 | 000000000559D75C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 325 | 000000000559D76C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 326 | 000000000559D77C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 327 | 000000000559D78C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 328 | 000000000559D79C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 329 | 000000000559D7AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 330 | 000000000559D7BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 331 | 000000000559D7CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 332 | 000000000559D7DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 333 | 000000000559D7EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 334 | 000000000559D7FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 335 | 000000000559D80C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 336 | 000000000559D81C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 337 | 000000000559D82C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 338 | 000000000559D83C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 339 | 000000000559D84C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 340 | 000000000559D85C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 341 | 000000000559D86C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 342 | 000000000559D87C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 343 | 000000000559D88C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 344 | 000000000559D89C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 345 | 000000000559D8AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 346 | 000000000559D8BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 347 | 000000000559D8CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 348 | 000000000559D8DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 349 | 000000000559D8EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 350 | 000000000559D8FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 351 | 000000000559D90C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 352 | 000000000559D91C 00 00 00 00 00 00 00 00 0D F0 AD 0B 46 96 0B 56 .........ð­.F–.V 353 | ``` 354 | 355 | - Allocation #5 from line 687, Size 2048: RX frame memory 356 | 357 | ``` PureBasic 358 | *TempFrame\Data = AllocateMemory(#Frame_Data_Size_Min) ; This will be purged when the client is deleted or when the server is freed, otherwise it will be reused in RX_Frame. 359 | ``` 360 | 361 | Raw data, plus 8 bytes before and 8 bytes after: 362 | 363 | ``` text 364 | 000000000559C0FC 1A 0A 00 88 0D F0 AD 0B 81 FE 02 4F 4D 0B 76 B3 ...ˆ.ð­.þ.OM.v³ 365 | 000000000559C10C 4C 6F 72 65 6D 20 69 70 73 75 6D 20 64 6F 6C 6F Lorem ipsum dolo 366 | 000000000559C11C 72 20 73 69 74 20 61 6D 65 74 2C 20 63 6F 6E 73 r sit amet, cons 367 | 000000000559C12C 65 74 65 74 75 72 20 73 61 64 69 70 73 63 69 6E etetur sadipscin 368 | 000000000559C13C 67 20 65 6C 69 74 72 2C 20 73 65 64 20 64 69 61 g elitr, sed dia 369 | 000000000559C14C 6D 20 6E 6F 6E 75 6D 79 20 65 69 72 6D 6F 64 20 m nonumy eirmod 370 | 000000000559C15C 74 65 6D 70 6F 72 20 69 6E 76 69 64 75 6E 74 20 tempor invidunt 371 | 000000000559C16C 75 74 20 6C 61 62 6F 72 65 20 65 74 20 64 6F 6C ut labore et dol 372 | 000000000559C17C 6F 72 65 20 6D 61 67 6E 61 20 61 6C 69 71 75 79 ore magna aliquy 373 | 000000000559C18C 61 6D 20 65 72 61 74 2C 20 73 65 64 20 64 69 61 am erat, sed dia 374 | 000000000559C19C 6D 20 76 6F 6C 75 70 74 75 61 2E 20 41 74 20 76 m voluptua. At v 375 | 000000000559C1AC 65 72 6F 20 65 6F 73 20 65 74 20 61 63 63 75 73 ero eos et accus 376 | 000000000559C1BC 61 6D 20 65 74 20 6A 75 73 74 6F 20 64 75 6F 20 am et justo duo 377 | 000000000559C1CC 64 6F 6C 6F 72 65 73 20 65 74 20 65 61 20 72 65 dolores et ea re 378 | 000000000559C1DC 62 75 6D 2E 20 53 74 65 74 20 63 6C 69 74 61 20 bum. Stet clita 379 | 000000000559C1EC 6B 61 73 64 20 67 75 62 65 72 67 72 65 6E 2C 20 kasd gubergren, 380 | 000000000559C1FC 6E 6F 20 73 65 61 20 74 61 6B 69 6D 61 74 61 20 no sea takimata 381 | 000000000559C20C 73 61 6E 63 74 75 73 20 65 73 74 20 4C 6F 72 65 sanctus est Lore 382 | 000000000559C21C 6D 20 69 70 73 75 6D 20 64 6F 6C 6F 72 20 73 69 m ipsum dolor si 383 | 000000000559C22C 74 20 61 6D 65 74 2E 20 4C 6F 72 65 6D 20 69 70 t amet. Lorem ip 384 | 000000000559C23C 73 75 6D 20 64 6F 6C 6F 72 20 73 69 74 20 61 6D sum dolor sit am 385 | 000000000559C24C 65 74 2C 20 63 6F 6E 73 65 74 65 74 75 72 20 73 et, consetetur s 386 | 000000000559C25C 61 64 69 70 73 63 69 6E 67 20 65 6C 69 74 72 2C adipscing elitr, 387 | 000000000559C26C 20 73 65 64 20 64 69 61 6D 20 6E 6F 6E 75 6D 79 sed diam nonumy 388 | 000000000559C27C 20 65 69 72 6D 6F 64 20 74 65 6D 70 6F 72 20 69 eirmod tempor i 389 | 000000000559C28C 6E 76 69 64 75 6E 74 20 75 74 20 6C 61 62 6F 72 nvidunt ut labor 390 | 000000000559C29C 65 20 65 74 20 64 6F 6C 6F 72 65 20 6D 61 67 6E e et dolore magn 391 | 000000000559C2AC 61 20 61 6C 69 71 75 79 61 6D 20 65 72 61 74 2C a aliquyam erat, 392 | 000000000559C2BC 20 73 65 64 20 64 69 61 6D 20 76 6F 6C 75 70 74 sed diam volupt 393 | 000000000559C2CC 75 61 2E 20 41 74 20 76 65 72 6F 20 65 6F 73 20 ua. At vero eos 394 | 000000000559C2DC 65 74 20 61 63 63 75 73 61 6D 20 65 74 20 6A 75 et accusam et ju 395 | 000000000559C2EC 73 74 6F 20 64 75 6F 20 64 6F 6C 6F 72 65 73 20 sto duo dolores 396 | 000000000559C2FC 65 74 20 65 61 20 72 65 62 75 6D 2E 20 53 74 65 et ea rebum. Ste 397 | 000000000559C30C 74 20 63 6C 69 74 61 20 6B 61 73 64 20 67 75 62 t clita kasd gub 398 | 000000000559C31C 65 72 67 72 65 6E 2C 20 6E 6F 20 73 65 61 20 74 ergren, no sea t 399 | 000000000559C32C 61 6B 69 6D 61 74 61 20 73 61 6E 63 74 75 73 20 akimata sanctus 400 | 000000000559C33C 65 73 74 20 4C 6F 72 65 6D 20 69 70 73 75 6D 20 est Lorem ipsum 401 | 000000000559C34C 64 6F 6C 6F 72 20 73 69 74 20 61 6D 65 74 2E B3 dolor sit amet.³ 402 | 000000000559C35C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 403 | 000000000559C36C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 404 | 000000000559C37C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 405 | 000000000559C38C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 406 | 000000000559C39C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 407 | 000000000559C3AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 408 | 000000000559C3BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 409 | 000000000559C3CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 410 | 000000000559C3DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 411 | 000000000559C3EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 412 | 000000000559C3FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 413 | 000000000559C40C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 414 | 000000000559C41C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 415 | 000000000559C42C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 416 | 000000000559C43C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 417 | 000000000559C44C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 418 | 000000000559C45C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 419 | 000000000559C46C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 420 | 000000000559C47C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 421 | 000000000559C48C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 422 | 000000000559C49C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 423 | 000000000559C4AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 424 | 000000000559C4BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 425 | 000000000559C4CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 426 | 000000000559C4DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 427 | 000000000559C4EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 428 | 000000000559C4FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 429 | 000000000559C50C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 430 | 000000000559C51C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 431 | 000000000559C52C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 432 | 000000000559C53C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 433 | 000000000559C54C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 434 | 000000000559C55C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 435 | 000000000559C56C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 436 | 000000000559C57C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 437 | 000000000559C58C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 438 | 000000000559C59C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 439 | 000000000559C5AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 440 | 000000000559C5BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 441 | 000000000559C5CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 442 | 000000000559C5DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 443 | 000000000559C5EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 444 | 000000000559C5FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 445 | 000000000559C60C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 446 | 000000000559C61C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 447 | 000000000559C62C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 448 | 000000000559C63C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 449 | 000000000559C64C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 450 | 000000000559C65C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 451 | 000000000559C66C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 452 | 000000000559C67C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 453 | 000000000559C68C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 454 | 000000000559C69C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 455 | 000000000559C6AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 456 | 000000000559C6BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 457 | 000000000559C6CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 458 | 000000000559C6DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 459 | 000000000559C6EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 460 | 000000000559C6FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 461 | 000000000559C70C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 462 | 000000000559C71C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 463 | 000000000559C72C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 464 | 000000000559C73C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 465 | 000000000559C74C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 466 | 000000000559C75C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 467 | 000000000559C76C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 468 | 000000000559C77C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 469 | 000000000559C78C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 470 | 000000000559C79C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 471 | 000000000559C7AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 472 | 000000000559C7BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 473 | 000000000559C7CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 474 | 000000000559C7DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 475 | 000000000559C7EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 476 | 000000000559C7FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 477 | 000000000559C80C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 478 | 000000000559C81C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 479 | 000000000559C82C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 480 | 000000000559C83C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 481 | 000000000559C84C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 482 | 000000000559C85C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 483 | 000000000559C86C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 484 | 000000000559C87C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 485 | 000000000559C88C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 486 | 000000000559C89C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 487 | 000000000559C8AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 488 | 000000000559C8BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 489 | 000000000559C8CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 490 | 000000000559C8DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 491 | 000000000559C8EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 492 | 000000000559C8FC 00 00 00 00 00 00 00 00 0D F0 AD 0B 44 97 15 55 .........ð­.D—.U 493 | ``` 494 | 495 | - Allocation #6 from line 1370, Size unknown: Server object structure 496 | 497 | ``` PureBasic 498 | *Object = _AllocateStructure(Object) 499 | ``` 500 | 501 | Raw data, plus 8 bytes before: 502 | 503 | ``` text 504 | 0000000000AE0C74 48 56 05 40 01 00 00 00 30 C0 39 02 00 00 00 00 HV.@....0À9..... 505 | 0000000000AE0C84 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 506 | 0000000000AE0C94 F0 1E 44 02 00 00 00 00 00 E2 BA 04 00 00 00 00 ð.D......âº..... 507 | 0000000000AE0CA4 E0 BE 39 02 00 00 00 00 50 4C CA 04 00 00 00 00 à¾9.....PLÊ..... 508 | 0000000000AE0CB4 E0 1B 44 02 00 00 00 00 E5 D1 00 40 01 00 00 00 à.D.....åÑ.@.... 509 | 0000000000AE0CC4 80 96 98 00 00 00 00 00 01 00 00 00 00 00 00 00 €–˜............. 510 | 0000000000AE0CD4 F0 BF 39 02 00 00 00 00 00 00 00 00 00 00 00 00 ð¿9............. 511 | 0000000000AE0CE4 00 00 00 00 00 00 00 00 0D F0 AD 0B 00 00 00 00 .........ð­..... 512 | ``` 513 | 514 | - Allocation #7 from line 344, Size 1024: Dummy memory 515 | 516 | ``` PureBasic 517 | Global *DummyMemory = AllocateMemory(DummyMemorySize) 518 | ``` 519 | 520 | Raw data, plus 8 bytes before and 8 bytes after: 521 | 522 | ``` text 523 | 0000000000AE085C 8A C6 00 08 0D F0 AD 0B 00 00 00 00 00 00 00 00 ŠÆ...ð­......... 524 | 0000000000AE086C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 525 | 0000000000AE087C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 526 | 0000000000AE088C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 527 | 0000000000AE089C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 528 | 0000000000AE08AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 529 | 0000000000AE08BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 530 | 0000000000AE08CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 531 | 0000000000AE08DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 532 | 0000000000AE08EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 533 | 0000000000AE08FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 534 | 0000000000AE090C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 535 | 0000000000AE091C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 536 | 0000000000AE092C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 537 | 0000000000AE093C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 538 | 0000000000AE094C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 539 | 0000000000AE095C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 540 | 0000000000AE096C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 541 | 0000000000AE097C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 542 | 0000000000AE098C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 543 | 0000000000AE099C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 544 | 0000000000AE09AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 545 | 0000000000AE09BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 546 | 0000000000AE09CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 547 | 0000000000AE09DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 548 | 0000000000AE09EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 549 | 0000000000AE09FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 550 | 0000000000AE0A0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 551 | 0000000000AE0A1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 552 | 0000000000AE0A2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 553 | 0000000000AE0A3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 554 | 0000000000AE0A4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 555 | 0000000000AE0A5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 556 | 0000000000AE0A6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 557 | 0000000000AE0A7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 558 | 0000000000AE0A8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 559 | 0000000000AE0A9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 560 | 0000000000AE0AAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 561 | 0000000000AE0ABC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 562 | 0000000000AE0ACC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 563 | 0000000000AE0ADC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 564 | 0000000000AE0AEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 565 | 0000000000AE0AFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 566 | 0000000000AE0B0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 567 | 0000000000AE0B1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 568 | 0000000000AE0B2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 569 | 0000000000AE0B3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 570 | 0000000000AE0B4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 571 | 0000000000AE0B5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 572 | 0000000000AE0B6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 573 | 0000000000AE0B7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 574 | 0000000000AE0B8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 575 | 0000000000AE0B9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 576 | 0000000000AE0BAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 577 | 0000000000AE0BBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 578 | 0000000000AE0BCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 579 | 0000000000AE0BDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 580 | 0000000000AE0BEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 581 | 0000000000AE0BFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 582 | 0000000000AE0C0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 583 | 0000000000AE0C1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 584 | 0000000000AE0C2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 585 | 0000000000AE0C3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 586 | 0000000000AE0C4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 587 | 0000000000AE0C5C 00 00 00 00 00 00 00 00 0D F0 AD 0B 7D CE 39 97 .........ð­.}Î9— 588 | ``` 589 | 590 | ## Analysis 591 | 592 | Allocation #1 seems to be corrupted, as its purifier salt section at the end is not intact anymore. 593 | It got overwritten with zeros. 594 | 595 | The RX packet in allocation #1 decodes the following way: 596 | 597 | ``` text 598 | 82 FE 10 00 --> 10000010 11111110 00010000 00000000 599 | ``` 600 | 601 | - FIN: 1 602 | - RSV1: 0 603 | - RSV2: 0 604 | - RSV3: 0 605 | - OpCode: 2 (Binary frame) 606 | - MASK: 1 607 | - Payload len: 126 608 | - Extended payload len: 4096 609 | 610 | The incoming frame would be filled with random data, this is not the case as the server just has received and decoded the header. 611 | Therefore the frame is in the middle of being received. 612 | 613 | Looking at allocation #3 we can see the current state of the incoming frame: 614 | 615 | ``` text 616 | 000000000557DCC4 94 88 59 05 00 00 00 00 ”ˆY..... 617 | 000000000557DCD4 04 00 00 00 00 00 00 00 08 10 00 00 00 00 00 00 ................ 618 | 000000000557DCE4 08 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................ 619 | ``` 620 | 621 | This decodes to: 622 | 623 | - `94 88 59 05 00 00 00 00`: The memory address where the frame in allocation #1 starts 624 | - `04 00 00 00 00 00 00 00`: RX position of 4 bytes 625 | - `08 10 00 00 00 00 00 00`: RX size of 4104 bytes (4096 + 8 header bytes (including mask)) 626 | - `08 00 00 00 00 00 00 00`: Payload offset of 8 bytes 627 | - `00 10 00 00 00 00 00 00`: Payload size of 4096 bytes 628 | 629 | This means that the server: 630 | 631 | 1. Received 2 bytes 632 | 2. Decoded the payload length to find that it needs to read the extended length 633 | 3. Received 2 more bytes 634 | 4. Decoded the payload length with 4096 635 | 636 | The next step for the server would be to reallocate the frame memory. 637 | But the purifier has stopped the server right before or in the process of reallocation. 638 | 639 | ## Conclusion 640 | 641 | Possible explanations: 642 | 643 | - The purifier did give a false positive while the server was reallocating the memory. (-> PureBasic bug) This also means that the original crash is caused by something else, and this is an additional error. (-> Unlikely) 644 | - ReallocateMemory is bugged. (-> PureBasic bug) 645 | - The memory got overwritten from somewhere else in the include. (-> Most likely) 646 | - ...? 647 | -------------------------------------------------------------------------------- /StressTest/Tests/Test 2.md: -------------------------------------------------------------------------------- 1 | # Test 2 2 | 3 | `Thread(*Client)` is called from the main thread, and `Event_Callback()` is called from inside the `Thread()` function: 4 | 5 | ``` PureBasic 6 | ;*Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 7 | ;If Not *Object\Network_Thread_ID 8 | ; FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 9 | ; If *Object\ClientQueueSemaphore : FreeSemaphore(*Object\ClientQueueSemaphore) : *Object\ClientQueueSemaphore = #Null : EndIf 10 | ; CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 11 | ; FreeStructure(*Object) 12 | ; ProcedureReturn #Null 13 | ;EndIf 14 | 15 | ;If *Event_Thread_Callback 16 | ; *Object\Event_Thread_ID = CreateThread(@Thread_Events(), *Object) 17 | ; If Not *Object\Event_Thread_ID 18 | ; *Object\Free = #True 19 | ; ProcedureReturn #Null 20 | ; EndIf 21 | ;EndIf 22 | 23 | Thread(*Object) 24 | ``` 25 | 26 | ``` PureBasic 27 | ; #### Busy when there was at least one network event 28 | Busy = Bool(Counter > 0) 29 | 30 | While Event_Callback(*Object, *Object\Event_Thread_Callback) 31 | Wend 32 | 33 | LockMutex(*Object\Mutex) 34 | ;Debug "Queue: " + ListSize(*Object\ClientQueue()) + " Clients: " + ListSize(*Object\Client()) 35 | ``` 36 | 37 | No crash occurred after bombarding the server with 250 clients reconnecting and sending packets non stop for ~9 hours. 38 | -------------------------------------------------------------------------------- /StressTest/Tests/Test 3.md: -------------------------------------------------------------------------------- 1 | # Test 3 2 | 3 | `Thread(*Client)` is called from the main thread: 4 | 5 | ``` PureBasic 6 | ;*Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 7 | ;If Not *Object\Network_Thread_ID 8 | ; FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 9 | ; If *Object\ClientQueueSemaphore : FreeSemaphore(*Object\ClientQueueSemaphore) : *Object\ClientQueueSemaphore = #Null : EndIf 10 | ; CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 11 | ; FreeStructure(*Object) 12 | ; ProcedureReturn #Null 13 | ;EndIf 14 | 15 | If *Event_Thread_Callback 16 | *Object\Event_Thread_ID = CreateThread(@Thread_Events(), *Object) 17 | If Not *Object\Event_Thread_ID 18 | *Object\Free = #True 19 | ProcedureReturn #Null 20 | EndIf 21 | EndIf 22 | 23 | Thread(*Object) 24 | ``` 25 | 26 | Additionally, all calls to `FreeMemory()` and `FreeStructure()` are protected by a mutex. 27 | 28 | No crash occurred after bombarding the server with 250 clients reconnecting and sending packets non stop for ~1.5 hours. 29 | -------------------------------------------------------------------------------- /StressTest/Tests/Test 4.md: -------------------------------------------------------------------------------- 1 | # Test 4 2 | 3 | `Thread(*Client)` is called from the main thread: 4 | 5 | ``` PureBasic 6 | ;*Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 7 | ;If Not *Object\Network_Thread_ID 8 | ; FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 9 | ; If *Object\ClientQueueSemaphore : FreeSemaphore(*Object\ClientQueueSemaphore) : *Object\ClientQueueSemaphore = #Null : EndIf 10 | ; CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 11 | ; FreeStructure(*Object) 12 | ; ProcedureReturn #Null 13 | ;EndIf 14 | 15 | If *Event_Thread_Callback 16 | *Object\Event_Thread_ID = CreateThread(@Thread_Events(), *Object) 17 | If Not *Object\Event_Thread_ID 18 | *Object\Free = #True 19 | ProcedureReturn #Null 20 | EndIf 21 | EndIf 22 | 23 | Thread(*Object) 24 | ``` 25 | 26 | This is basically the same setup as test 1, except that the allocation dumper stores a bit more metadata. 27 | 28 | No crash occurred after bombarding the server with 250 clients reconnecting and sending packets non stop for ~2 hours. 29 | -------------------------------------------------------------------------------- /StressTest/Tests/Test 5.md: -------------------------------------------------------------------------------- 1 | # Test 5 2 | 3 | `Thread(*Client)` is called from the main thread: 4 | 5 | ``` PureBasic 6 | ;*Object\Network_Thread_ID = CreateThread(@Thread(), *Object) 7 | ;If Not *Object\Network_Thread_ID 8 | ; FreeMutex(*Object\Mutex) : *Object\Mutex = #Null 9 | ; If *Object\ClientQueueSemaphore : FreeSemaphore(*Object\ClientQueueSemaphore) : *Object\ClientQueueSemaphore = #Null : EndIf 10 | ; CloseNetworkServer(*Object\Server_ID) : *Object\Server_ID = #Null 11 | ; FreeStructure(*Object) 12 | ; ProcedureReturn #Null 13 | ;EndIf 14 | 15 | If *Event_Thread_Callback 16 | *Object\Event_Thread_ID = CreateThread(@Thread_Events(), *Object) 17 | If Not *Object\Event_Thread_ID 18 | *Object\Free = #True 19 | ProcedureReturn #Null 20 | EndIf 21 | EndIf 22 | 23 | Thread(*Object) 24 | ``` 25 | 26 | This is exactly the same setup as test 1. 27 | 28 | About half an hour after bombarding each of 4 servers with 250 clients reconnecting and sending packets non stop, the purifier stopped 2 servers for the same reason (~10 minutes apart): 29 | 30 | ``` text 31 | [16:51:30] Executable-Typ: Windows - x64 (64bit, Unicode, Thread, Purifier) 32 | [16:51:30] Executable gestartet. 33 | [17:26:09] [ERROR] WebSocket_Server.pbi (Zeile: 943) 34 | [17:26:09] [ERROR] Overflow in a dynamically allocated memory block. 35 | ``` 36 | 37 | Line of error (Without any meaning, as it is from a different thread): 38 | 39 | ``` PureBasic 40 | ; #### Wait for client queue entries. 41 | ClientQueueWait(*Object) 42 | ``` 43 | 44 | ## Allocations 45 | 46 | Allocation dump at the moment of the crash. 47 | 48 | - Allocation #1 from line 689, Size 2048: RX frame memory 49 | 50 | ``` PureBasic 51 | *TempFrame\Data = AllocateMemory(#Frame_Data_Size_Min) ; This will be purged when the client is deleted or when the server is freed, otherwise it will be reused in RX_Frame. 52 | ``` 53 | 54 | Raw data, plus 8 bytes before and 8 bytes after: 55 | 56 | ``` text 57 | 00000000051374DC 00 46 00 88 0D F0 AD 0B 82 81 2F D0 F5 90 7B D0 .F.ˆ.ð­.‚/Ðõ{Ð 58 | 00000000051374EC F5 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 õ.............. 59 | 00000000051374FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 60 | 000000000513750C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 61 | 000000000513751C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 62 | 000000000513752C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 63 | 000000000513753C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 64 | 000000000513754C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 65 | 000000000513755C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 66 | 000000000513756C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 67 | 000000000513757C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 68 | 000000000513758C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 69 | 000000000513759C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 70 | 00000000051375AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 71 | 00000000051375BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 72 | 00000000051375CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 73 | 00000000051375DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 74 | 00000000051375EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 75 | 00000000051375FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 76 | 000000000513760C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 77 | 000000000513761C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 78 | 000000000513762C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 79 | 000000000513763C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 80 | 000000000513764C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 81 | 000000000513765C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 82 | 000000000513766C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 83 | 000000000513767C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 84 | 000000000513768C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 85 | 000000000513769C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 86 | 00000000051376AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 87 | 00000000051376BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 88 | 00000000051376CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 89 | 00000000051376DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 90 | 00000000051376EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 91 | 00000000051376FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 92 | 000000000513770C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 93 | 000000000513771C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 94 | 000000000513772C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 95 | 000000000513773C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 96 | 000000000513774C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 97 | 000000000513775C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 98 | 000000000513776C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 99 | 000000000513777C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 100 | 000000000513778C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 101 | 000000000513779C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 102 | 00000000051377AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 103 | 00000000051377BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 104 | 00000000051377CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 105 | 00000000051377DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 106 | 00000000051377EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 107 | 00000000051377FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 108 | 000000000513780C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 109 | 000000000513781C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 110 | 000000000513782C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 111 | 000000000513783C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 112 | 000000000513784C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 113 | 000000000513785C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 114 | 000000000513786C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 115 | 000000000513787C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 116 | 000000000513788C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 117 | 000000000513789C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 118 | 00000000051378AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 119 | 00000000051378BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 120 | 00000000051378CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 121 | 00000000051378DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 122 | 00000000051378EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 123 | 00000000051378FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 124 | 000000000513790C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 125 | 000000000513791C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 126 | 000000000513792C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 127 | 000000000513793C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 128 | 000000000513794C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 129 | 000000000513795C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 130 | 000000000513796C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 131 | 000000000513797C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 132 | 000000000513798C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 133 | 000000000513799C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 134 | 00000000051379AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 135 | 00000000051379BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 136 | 00000000051379CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 137 | 00000000051379DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 138 | 00000000051379EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 139 | 00000000051379FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 140 | 0000000005137A0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 141 | 0000000005137A1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 142 | 0000000005137A2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 143 | 0000000005137A3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 144 | 0000000005137A4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 145 | 0000000005137A5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 146 | 0000000005137A6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 147 | 0000000005137A7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 148 | 0000000005137A8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 149 | 0000000005137A9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 150 | 0000000005137AAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 151 | 0000000005137ABC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 152 | 0000000005137ACC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 153 | 0000000005137ADC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 154 | 0000000005137AEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 155 | 0000000005137AFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 156 | 0000000005137B0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 157 | 0000000005137B1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 158 | 0000000005137B2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 159 | 0000000005137B3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 160 | 0000000005137B4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 161 | 0000000005137B5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 162 | 0000000005137B6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 163 | 0000000005137B7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 164 | 0000000005137B8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 165 | 0000000005137B9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 166 | 0000000005137BAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 167 | 0000000005137BBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 168 | 0000000005137BCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 169 | 0000000005137BDC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 170 | 0000000005137BEC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 171 | 0000000005137BFC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 172 | 0000000005137C0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 173 | 0000000005137C1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 174 | 0000000005137C2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 175 | 0000000005137C3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 176 | 0000000005137C4C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 177 | 0000000005137C5C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 178 | 0000000005137C6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 179 | 0000000005137C7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 180 | 0000000005137C8C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 181 | 0000000005137C9C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 182 | 0000000005137CAC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 183 | 0000000005137CBC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 184 | 0000000005137CCC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 185 | 0000000005137CDC 00 00 00 00 00 00 00 00 0D F0 AD 0B 19 39 19 57 .........ð­..9.W 186 | ``` 187 | 188 | - Allocation #2 from line 995, Size 601: TX frame memory 189 | 190 | ``` PureBasic 191 | *Client\TX_Frame()\Data = AllocateMemory(10 + Payload_Size) 192 | ``` 193 | 194 | Raw data, plus 8 bytes before and 8 bytes after: 195 | 196 | ``` text 197 | 000000000240B24C 6E 06 00 8F 0D F0 AD 0B 81 7E 02 4F 4C 6F 72 65 n...ð­.~.OLore 198 | 000000000240B25C 6D 20 69 70 73 75 6D 20 64 6F 6C 6F 72 20 73 69 m ipsum dolor si 199 | 000000000240B26C 74 20 61 6D 65 74 2C 20 63 6F 6E 73 65 74 65 74 t amet, consetet 200 | 000000000240B27C 75 72 20 73 61 64 69 70 73 63 69 6E 67 20 65 6C ur sadipscing el 201 | 000000000240B28C 69 74 72 2C 20 73 65 64 20 64 69 61 6D 20 6E 6F itr, sed diam no 202 | 000000000240B29C 6E 75 6D 79 20 65 69 72 6D 6F 64 20 74 65 6D 70 numy eirmod temp 203 | 000000000240B2AC 6F 72 20 69 6E 76 69 64 75 6E 74 20 75 74 20 6C or invidunt ut l 204 | 000000000240B2BC 61 62 6F 72 65 20 65 74 20 64 6F 6C 6F 72 65 20 abore et dolore 205 | 000000000240B2CC 6D 61 67 6E 61 20 61 6C 69 71 75 79 61 6D 20 65 magna aliquyam e 206 | 000000000240B2DC 72 61 74 2C 20 73 65 64 20 64 69 61 6D 20 76 6F rat, sed diam vo 207 | 000000000240B2EC 6C 75 70 74 75 61 2E 20 41 74 20 76 65 72 6F 20 luptua. At vero 208 | 000000000240B2FC 65 6F 73 20 65 74 20 61 63 63 75 73 61 6D 20 65 eos et accusam e 209 | 000000000240B30C 74 20 6A 75 73 74 6F 20 64 75 6F 20 64 6F 6C 6F t justo duo dolo 210 | 000000000240B31C 72 65 73 20 65 74 20 65 61 20 72 65 62 75 6D 2E res et ea rebum. 211 | 000000000240B32C 20 53 74 65 74 20 63 6C 69 74 61 20 6B 61 73 64 Stet clita kasd 212 | 000000000240B33C 20 67 75 62 65 72 67 72 65 6E 2C 20 6E 6F 20 73 gubergren, no s 213 | 000000000240B34C 65 61 20 74 61 6B 69 6D 61 74 61 20 73 61 6E 63 ea takimata sanc 214 | 000000000240B35C 74 75 73 20 65 73 74 20 4C 6F 72 65 6D 20 69 70 tus est Lorem ip 215 | 000000000240B36C 73 75 6D 20 64 6F 6C 6F 72 20 73 69 74 20 61 6D sum dolor sit am 216 | 000000000240B37C 65 74 2E 20 4C 6F 72 65 6D 20 69 70 73 75 6D 20 et. Lorem ipsum 217 | 000000000240B38C 64 6F 6C 6F 72 20 73 69 74 20 61 6D 65 74 2C 20 dolor sit amet, 218 | 000000000240B39C 63 6F 6E 73 65 74 65 74 75 72 20 73 61 64 69 70 consetetur sadip 219 | 000000000240B3AC 73 63 69 6E 67 20 65 6C 69 74 72 2C 20 73 65 64 scing elitr, sed 220 | 000000000240B3BC 20 64 69 61 6D 20 6E 6F 6E 75 6D 79 20 65 69 72 diam nonumy eir 221 | 000000000240B3CC 6D 6F 64 20 74 65 6D 70 6F 72 20 69 6E 76 69 64 mod tempor invid 222 | 000000000240B3DC 75 6E 74 20 75 74 20 6C 61 62 6F 72 65 20 65 74 unt ut labore et 223 | 000000000240B3EC 20 64 6F 6C 6F 72 65 20 6D 61 67 6E 61 20 61 6C dolore magna al 224 | 000000000240B3FC 69 71 75 79 61 6D 20 65 72 61 74 2C 20 73 65 64 iquyam erat, sed 225 | 000000000240B40C 20 64 69 61 6D 20 76 6F 6C 75 70 74 75 61 2E 20 diam voluptua. 226 | 000000000240B41C 41 74 20 76 65 72 6F 20 65 6F 73 20 65 74 20 61 At vero eos et a 227 | 000000000240B42C 63 63 75 73 61 6D 20 65 74 20 6A 75 73 74 6F 20 ccusam et justo 228 | 000000000240B43C 64 75 6F 20 64 6F 6C 6F 72 65 73 20 65 74 20 65 duo dolores et e 229 | 000000000240B44C 61 20 72 65 62 75 6D 2E 20 53 74 65 74 20 63 6C a rebum. Stet cl 230 | 000000000240B45C 69 74 61 20 6B 61 73 64 20 67 75 62 65 72 67 72 ita kasd gubergr 231 | 000000000240B46C 65 6E 2C 20 6E 6F 20 73 65 61 20 74 61 6B 69 6D en, no sea takim 232 | 000000000240B47C 61 74 61 20 73 61 6E 63 74 75 73 20 65 73 74 20 ata sanctus est 233 | 000000000240B48C 4C 6F 72 65 6D 20 69 70 73 75 6D 20 64 6F 6C 6F Lorem ipsum dolo 234 | 000000000240B49C 72 20 73 69 74 20 61 6D 65 74 2E 00 00 00 00 00 r sit amet...... 235 | 000000000240B4AC 00 0D F0 AD 0B 00 00 00 00 ..ð­..... 236 | ``` 237 | 238 | - Allocation #3 from line 662, Size unknown: RX frame structure 239 | 240 | ``` PureBasic 241 | *Client\New_RX_FRAME = _AllocateStructure(Frame) ; This will be purged when the frame is fully received, when the client is deleted or when the server is freed. 242 | ``` 243 | 244 | Raw data, plus 8 bytes before: 245 | 246 | ``` text 247 | 000000000240D864 00 00 00 00 00 00 00 00 04 04 13 05 00 00 00 00 ................ 248 | 000000000240D874 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 249 | 000000000240D884 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 250 | 000000000240D894 0D F0 AD 0B 5E 03 E8 74 78 11 00 80 0D F0 AD 0B .ð­.^.ètx..€.ð­. 251 | ``` 252 | 253 | There are more allocations, but i'll not list them. 254 | 255 | ## Analysis 256 | 257 | From looking over the allocations (which are not fully provided here), the purifier stopped both servers in the exact same way (while receiving the 4096 byte random data packet) as it did in test 1. 258 | 259 | Further testing shows that the purifier gives definitely false positives. 260 | The following code induces the error nearly instantly: 261 | 262 | ``` PureBasic 263 | ; Just a test to see how well the purifier works with ReAllocateMemory 264 | 265 | EnableExplicit 266 | 267 | ;Global allocationMutex = CreateMutex() 268 | 269 | Procedure TestThread(*Dummy) 270 | Protected *testMem = AllocateMemory(1000) 271 | Protected *newMem 272 | Protected newSize.i 273 | 274 | Repeat 275 | newSize = Random(4096, 1000) 276 | ;LockMutex(allocationMutex) 277 | *newMem = ReAllocateMemory(*testMem, newSize) 278 | ;UnlockMutex(allocationMutex) 279 | If Not *newMem 280 | FreeMemory(*testMem) 281 | Debug "ReAllocateMemory failed" 282 | Break 283 | EndIf 284 | *testMem = *newMem 285 | 286 | Debug *testMem 287 | 288 | ForEver 289 | EndProcedure 290 | 291 | Define i 292 | For i = 1 To 10 293 | CreateThread(@TestThread(), #Null) 294 | Next 295 | 296 | OpenConsole() 297 | Input() 298 | ``` 299 | -------------------------------------------------------------------------------- /StressTest/connection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | // TestPacket represents a single packet that will be sent to the server and is expected to be looped back. 13 | type TestPacket struct { 14 | Type int // The packet/message types are defined in RFC 6455 15 | Payload []byte 16 | } 17 | 18 | // TestOptions contains all needed test parameters and values for a single connection test. 19 | type TestOptions struct { 20 | URL url.URL 21 | Packets []TestPacket 22 | } 23 | 24 | // TestResult contains all measurable values from a single connection test. 25 | type TestResult struct { 26 | TotalDuration time.Duration // Total duration of DoConnectionTest. 27 | ConnectLatency time.Duration // Duration to establish the websocket connection. 28 | FirstRoundtripLatency time.Duration // Roundtrip time of the first packet. This includes the duration it takes to send the message. 29 | FullRoundtripLatency time.Duration // Roundtrip time of all packets. This includes the duration it takes to send the messages. 30 | DisconnectLatency time.Duration // Duration for connection closure. 31 | } 32 | 33 | // DoConnectionTest connects to a given web-socket server. 34 | // It will send and receive (a) message(s), and check the received message for correctness. 35 | // This assumes that the server loops back any received message. 36 | func DoConnectionTest(opt TestOptions) (TestResult, error) { 37 | startTime := time.Now() 38 | res := TestResult{} 39 | 40 | // Open connection. 41 | c, _, err := websocket.DefaultDialer.Dial(opt.URL.String(), nil) 42 | if err != nil { 43 | return res, err 44 | } 45 | defer c.Close() 46 | 47 | res.ConnectLatency = time.Now().Sub(startTime) 48 | 49 | // Receive data and/or handle errors or disconnects. 50 | done := make(chan error, 1) 51 | received := make(chan struct{}) 52 | go func() { 53 | defer close(done) 54 | index := 0 55 | 56 | for { 57 | mType, message, err := c.ReadMessage() 58 | if err != nil { 59 | // Ignore error if the connection closed due to normal closure. 60 | if closeErr, ok := err.(*websocket.CloseError); ok && closeErr.Code == websocket.CloseNormalClosure { 61 | return 62 | } 63 | 64 | done <- err 65 | return 66 | } 67 | 68 | // Check if the amount is not over the expected packet amount. 69 | if index >= len(opt.Packets) { 70 | done <- fmt.Errorf("Received more packets that expected") 71 | return 72 | } 73 | 74 | // Get next expected packet. 75 | expectedPacket := opt.Packets[index] 76 | index++ 77 | 78 | // Check if the packet type is correct. 79 | if expectedPacket.Type != mType { 80 | done <- fmt.Errorf("Received unexpected packet type") 81 | return 82 | } 83 | 84 | // Check if the payload is correct. 85 | if bytes.Compare(expectedPacket.Payload, message) != 0 { 86 | done <- fmt.Errorf("Received unexpected packet payload") 87 | return 88 | } 89 | 90 | // Measure roundtrip time of the first packet. 91 | if index == 1 { 92 | res.FirstRoundtripLatency = time.Now().Sub(startTime) - res.ConnectLatency 93 | } 94 | 95 | // Signal that everything expected has been received. But don't stop this listener yet. 96 | if index == len(opt.Packets) { 97 | close(received) 98 | } 99 | 100 | } 101 | }() 102 | 103 | // Send payload. 104 | for _, packet := range opt.Packets { 105 | err = c.WriteMessage(packet.Type, packet.Payload) 106 | if err != nil { 107 | return res, err 108 | } 109 | } 110 | 111 | // Wait either until all the data was received correctly, an error was encountered, or until a timeout is reached. 112 | select { 113 | case err := <-done: 114 | return res, err 115 | case <-received: 116 | res.FullRoundtripLatency = time.Now().Sub(startTime) - res.ConnectLatency 117 | case <-time.After(5 * time.Second): 118 | return res, fmt.Errorf("Receive timeout. Not all packets were received in time") 119 | } 120 | 121 | // Cleanly close connection. 122 | err = c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 123 | if err != nil { 124 | return res, err 125 | } 126 | 127 | // Wait either until the connection is closed by the server (as reaction to the close message), or until a timeout is reached. 128 | select { 129 | case err := <-done: 130 | res.DisconnectLatency = time.Now().Sub(startTime) - res.FullRoundtripLatency 131 | res.TotalDuration = res.ConnectLatency + res.FullRoundtripLatency + res.DisconnectLatency 132 | return res, err 133 | case <-time.After(5 * time.Second): 134 | return res, fmt.Errorf("Closure timeout") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /StressTest/go.mod: -------------------------------------------------------------------------------- 1 | module stress-test 2 | 3 | go 1.16 4 | 5 | require github.com/gorilla/websocket v1.4.2 6 | -------------------------------------------------------------------------------- /StressTest/go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 2 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | -------------------------------------------------------------------------------- /StressTest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "flag" 6 | "log" 7 | "net/url" 8 | "sync" 9 | "time" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | var addr = flag.String("addr", "localhost:8090", "http service address") 15 | var path = flag.String("path", "/", "http path") 16 | 17 | func main() { 18 | flag.Parse() 19 | 20 | // Setup test options and test data that is sent over and received from every server connection. 21 | 22 | randomData := make([]byte, 4096) 23 | rand.Read(randomData) 24 | 25 | testOptions := TestOptions{ 26 | URL: url.URL{Scheme: "ws", Host: *addr, Path: *path}, 27 | Packets: []TestPacket{ 28 | {Type: websocket.TextMessage, Payload: []byte("Test")}, 29 | {Type: websocket.TextMessage, Payload: []byte("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")}, 30 | {Type: websocket.BinaryMessage, Payload: []byte{123}}, 31 | {Type: websocket.BinaryMessage, Payload: randomData}, 32 | {Type: websocket.TextMessage, Payload: []byte("1")}, 33 | {Type: websocket.TextMessage, Payload: []byte("2")}, 34 | {Type: websocket.TextMessage, Payload: []byte("3")}, 35 | {Type: websocket.TextMessage, Payload: []byte("4")}, 36 | {Type: websocket.TextMessage, Payload: []byte("5")}, 37 | {Type: websocket.TextMessage, Payload: []byte("12")}, 38 | {Type: websocket.BinaryMessage, Payload: []byte("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")}, 39 | }, 40 | } 41 | 42 | wg := &sync.WaitGroup{} 43 | for i := 0; i < 200; i++ { 44 | wg.Add(1) 45 | go func() { 46 | defer wg.Done() 47 | 48 | for true { 49 | 50 | res, err := DoConnectionTest(testOptions) 51 | if err != nil { 52 | log.Printf("Error: %v", err) 53 | time.Sleep(3000 * time.Millisecond) 54 | } else { 55 | log.Printf("Full roundtrip latency: %v", res.FullRoundtripLatency) 56 | time.Sleep(100 * time.Millisecond) 57 | } 58 | 59 | } 60 | }() 61 | } 62 | wg.Wait() 63 | } 64 | --------------------------------------------------------------------------------