├── .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 |
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 |
--------------------------------------------------------------------------------