Skip to content

Commit 402b411

Browse files
committed
Better compliance with protocol. Restructure code.
1 parent a72a788 commit 402b411

File tree

8 files changed

+1356
-55
lines changed

8 files changed

+1356
-55
lines changed

Assets/WebSocketServer.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using UnityEngine;
2+
// For String
3+
using System;
4+
// For dictionary
5+
using System.Collections.Generic;
6+
// For parsing the client websocket requests
7+
using System.Text;
8+
using System.Text.RegularExpressions;
9+
10+
namespace WebSocketServer {
11+
12+
class RequestHeader {
13+
14+
static Regex head = new Regex("^(GET|POST|PUT|DELETE|OPTIONS) (.+) HTTP/([0-9.]+)", RegexOptions.Compiled);
15+
static Regex body = new Regex("([A-Za-z0-9-]+): ?([^\n^\r]+)", RegexOptions.Compiled);
16+
17+
public string method = "";
18+
public string uri = "";
19+
public string version = "";
20+
public Dictionary<string, string> headers;
21+
22+
public RequestHeader(string data) {
23+
headers = new Dictionary<string, string>();
24+
25+
MatchCollection matches = head.Matches(data);
26+
foreach (Match match in matches) {
27+
method = match.Groups[1].Value.Trim();
28+
uri = match.Groups[2].Value.Trim();
29+
version = match.Groups[3].Value.Trim();
30+
}
31+
32+
matches = body.Matches(data);
33+
foreach (Match match in matches) {
34+
headers.Add(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim());
35+
}
36+
}
37+
}
38+
39+
class WebSocketProtocol {
40+
41+
public static bool CheckConnectionHandshake(RequestHeader request) {
42+
// The method must be GET.
43+
if (!String.Equals(request.method, "GET")) {
44+
Debug.Log("Request does not begin with GET.");
45+
return false;
46+
}
47+
// TODO: Version must be greater than "1.1".
48+
// Must have a Host.
49+
if (!request.headers.ContainsKey("Host")) {
50+
Debug.Log("Request does not have a Host.");
51+
return false;
52+
}
53+
// Must have a Upgrade: websocket
54+
if (!request.headers.ContainsKey("Upgrade") || !String.Equals(request.headers["Upgrade"], "websocket")) {
55+
Debug.Log("Request does not have Upgrade: websocket.");
56+
return false;
57+
}
58+
59+
// Must have a Connection: Upgrade
60+
if (!request.headers.ContainsKey("Connection") || !String.Equals(request.headers["Connection"], "Upgrade")) {
61+
Debug.Log("Request does not have Connection: Upgrade.");
62+
return false;
63+
}
64+
65+
// Must have a Sec-WebSocket-Key
66+
if (!request.headers.ContainsKey("Sec-WebSocket-Key")) {
67+
Debug.Log("Request does not have Sec-WebSocket-Key");
68+
return false;
69+
}
70+
71+
// Must have a Sec-WebSocket-Key
72+
if (!request.headers.ContainsKey("Sec-WebSocket-Key")) {
73+
Debug.Log("Request does not have Sec-WebSocket-Key");
74+
return false;
75+
}
76+
77+
// Must have a Sec-WebSocket-Version: 13
78+
if (!request.headers.ContainsKey("Sec-WebSocket-Version") || !String.Equals(request.headers["Sec-WebSocket-Version"], "13")) {
79+
Debug.Log("Request does not have Sec-WebSocket-Version: 13");
80+
return false;
81+
}
82+
83+
return true;
84+
}
85+
86+
public static Byte[] CreateHandshakeReply(RequestHeader request) {
87+
const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
88+
89+
Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
90+
+ "Connection: Upgrade" + eol
91+
+ "Upgrade: websocket" + eol
92+
+ "Sec-WebSocket-Accept: " + Convert.ToBase64String(
93+
System.Security.Cryptography.SHA1.Create().ComputeHash(
94+
Encoding.UTF8.GetBytes(
95+
request.headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
96+
)
97+
)
98+
) + eol
99+
+ eol);
100+
101+
return response;
102+
}
103+
104+
public static string DecodeMessage(byte[] bytes) {
105+
bool fin = (bytes[0] & 0b10000000) != 0,
106+
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
107+
108+
int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
109+
msglen = bytes[1] - 128, // & 0111 1111
110+
offset = 2;
111+
112+
if (msglen == 126) {
113+
// was ToUInt16(bytes, offset) but the result is incorrect
114+
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
115+
offset = 4;
116+
} else if (msglen == 127) {
117+
Debug.Log("TODO: msglen == 127, needs qword to store msglen");
118+
// i don't really know the byte order, please edit this
119+
// msglen = BitConverter.ToUInt64(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2], bytes[9], bytes[8], bytes[7], bytes[6] }, 0);
120+
// offset = 10;
121+
}
122+
123+
if (msglen == 0)
124+
Debug.Log("msglen == 0");
125+
else if (mask) {
126+
byte[] decoded = new byte[msglen];
127+
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
128+
offset += 4;
129+
130+
for (int i = 0; i < msglen; ++i)
131+
decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);
132+
133+
string text = Encoding.UTF8.GetString(decoded);
134+
return text;
135+
} else {
136+
Debug.Log("mask bit not set");
137+
}
138+
return "";
139+
}
140+
141+
}
142+
143+
}

Assets/WebSocketServer/WebSocketProtocol.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/WebSocketServer.cs renamed to Assets/WebSocketServer/WebSocketServer.cs

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// Networking libs
33
using System.Net;
44
using System.Net.Sockets;
5-
// For parsing the client websocket requests
6-
using System.Text;
7-
using System.Text.RegularExpressions;
85
// For creating a thread
96
using System.Threading;
107
// For List & ConcurrentQueue
@@ -14,6 +11,10 @@
1411
using UnityEngine;
1512
using UnityEngine.Events;
1613

14+
// For parsing the client websocket requests
15+
using System.Text;
16+
using System.Text.RegularExpressions;
17+
1718
namespace WebSocketServer {
1819
[System.Serializable]
1920
public class StringEvent : UnityEvent<string> {}
@@ -93,27 +94,15 @@ private void EstablishConnection (WebSocketConnection connection) {
9394
// Wait for enough bytes to be available
9495
while (!connection.stream.DataAvailable);
9596
while(connection.client.Available < 3);
96-
// Translate bytes of request to string
97+
// Translate bytes of request to a RequestHeader object
9798
Byte[] bytes = new Byte[connection.client.Available];
9899
connection.stream.Read(bytes, 0, bytes.Length);
99-
String data = Encoding.UTF8.GetString(bytes);
100-
101-
// Check if the input has a "GET" header. If so, initiate the connection.
102-
if (Regex.IsMatch(data, "^GET")) {
103-
const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
104-
105-
Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
106-
+ "Connection: Upgrade" + eol
107-
+ "Upgrade: websocket" + eol
108-
+ "Sec-WebSocket-Accept: " + Convert.ToBase64String(
109-
System.Security.Cryptography.SHA1.Create().ComputeHash(
110-
Encoding.UTF8.GetBytes(
111-
new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
112-
)
113-
)
114-
) + eol
115-
+ eol);
100+
RequestHeader request = new RequestHeader(Encoding.UTF8.GetString(bytes));
116101

102+
// Check if the request complies with WebSocket protocol.
103+
if (WebSocketProtocol.CheckConnectionHandshake(request)) {
104+
// If so, initiate the connection by sending a reply according to protocol.
105+
Byte[] response = WebSocketProtocol.CreateHandshakeReply(request);
117106
connection.stream.Write(response, 0, response.Length);
118107
Debug.Log("WebSocket client connected.");
119108
}
@@ -133,40 +122,7 @@ private string ReceiveMessage(TcpClient client, NetworkStream stream) {
133122
Byte[] bytes = new Byte[client.Available];
134123
stream.Read(bytes, 0, bytes.Length);
135124

136-
bool fin = (bytes[0] & 0b10000000) != 0,
137-
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
138-
139-
int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
140-
msglen = bytes[1] - 128, // & 0111 1111
141-
offset = 2;
142-
143-
if (msglen == 126) {
144-
// was ToUInt16(bytes, offset) but the result is incorrect
145-
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
146-
offset = 4;
147-
} else if (msglen == 127) {
148-
Debug.Log("TODO: msglen == 127, needs qword to store msglen");
149-
// i don't really know the byte order, please edit this
150-
// msglen = BitConverter.ToUInt64(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2], bytes[9], bytes[8], bytes[7], bytes[6] }, 0);
151-
// offset = 10;
152-
}
153-
154-
if (msglen == 0)
155-
Debug.Log("msglen == 0");
156-
else if (mask) {
157-
byte[] decoded = new byte[msglen];
158-
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
159-
offset += 4;
160-
161-
for (int i = 0; i < msglen; ++i)
162-
decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);
163-
164-
string text = Encoding.UTF8.GetString(decoded);
165-
return text;
166-
} else {
167-
Debug.Log("mask bit not set");
168-
}
169-
return "";
125+
return WebSocketProtocol.DecodeMessage(bytes);
170126
}
171127

172128
// private void SendMessage() {
File renamed without changes.

Logs/ApiUpdaterCheck.txt

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,113 @@ C# parse time : 263ms
138138
candidates check time : 46ms
139139
console write time : 1ms
140140

141+
[api-updater (non-obsolete-error-filter)] 6/14/2021 5:38:32 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
142+
[api-updater (non-obsolete-error-filter)]
143+
----------------------------------
144+
jit/startup time : 121.532ms
145+
moved types parse time: 53ms
146+
candidates parse time : 1ms
147+
C# parse time : 234ms
148+
candidates check time : 27ms
149+
console write time : 1ms
150+
151+
[api-updater (non-obsolete-error-filter)] 6/14/2021 5:45:24 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
152+
[api-updater (non-obsolete-error-filter)]
153+
----------------------------------
154+
jit/startup time : 108.804ms
155+
moved types parse time: 55ms
156+
candidates parse time : 1ms
157+
C# parse time : 264ms
158+
candidates check time : 34ms
159+
console write time : 0ms
160+
161+
[api-updater (non-obsolete-error-filter)] 6/14/2021 5:45:42 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
162+
[api-updater (non-obsolete-error-filter)]
163+
----------------------------------
164+
jit/startup time : 51.548ms
165+
moved types parse time: 59ms
166+
candidates parse time : 1ms
167+
C# parse time : 251ms
168+
candidates check time : 37ms
169+
console write time : 0ms
170+
171+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:05:21 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
172+
[api-updater (non-obsolete-error-filter)]
173+
----------------------------------
174+
jit/startup time : 63.113ms
175+
moved types parse time: 47ms
176+
candidates parse time : 1ms
177+
C# parse time : 227ms
178+
candidates check time : 31ms
179+
console write time : 0ms
180+
181+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:05:27 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
182+
[api-updater (non-obsolete-error-filter)]
183+
----------------------------------
184+
jit/startup time : 47.497ms
185+
moved types parse time: 46ms
186+
candidates parse time : 1ms
187+
C# parse time : 211ms
188+
candidates check time : 31ms
189+
console write time : 0ms
190+
191+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:05:50 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
192+
[api-updater (non-obsolete-error-filter)]
193+
----------------------------------
194+
jit/startup time : 48.64ms
195+
moved types parse time: 48ms
196+
candidates parse time : 1ms
197+
C# parse time : 247ms
198+
candidates check time : 35ms
199+
console write time : 0ms
200+
201+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:15:44 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
202+
[api-updater (non-obsolete-error-filter)]
203+
----------------------------------
204+
jit/startup time : 103.824ms
205+
moved types parse time: 48ms
206+
candidates parse time : 1ms
207+
C# parse time : 253ms
208+
candidates check time : 64ms
209+
console write time : 0ms
210+
211+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:18:40 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
212+
[api-updater (non-obsolete-error-filter)]
213+
----------------------------------
214+
jit/startup time : 47.007ms
215+
moved types parse time: 45ms
216+
candidates parse time : 1ms
217+
C# parse time : 221ms
218+
candidates check time : 56ms
219+
console write time : 0ms
220+
221+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:21:49 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
222+
[api-updater (non-obsolete-error-filter)]
223+
----------------------------------
224+
jit/startup time : 123.176ms
225+
moved types parse time: 50ms
226+
candidates parse time : 1ms
227+
C# parse time : 239ms
228+
candidates check time : 62ms
229+
console write time : 0ms
230+
231+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:22:35 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
232+
[api-updater (non-obsolete-error-filter)]
233+
----------------------------------
234+
jit/startup time : 49.268ms
235+
moved types parse time: 51ms
236+
candidates parse time : 1ms
237+
C# parse time : 242ms
238+
candidates check time : 71ms
239+
console write time : 0ms
240+
241+
[api-updater (non-obsolete-error-filter)] 6/14/2021 6:33:13 PM : Starting /Applications/Unity/2020.3.5f1c1/Unity.app/Contents/Tools/ScriptUpdater/APIUpdater.NonObsoleteApiUpdaterDetector.exe
242+
[api-updater (non-obsolete-error-filter)]
243+
----------------------------------
244+
jit/startup time : 118.538ms
245+
moved types parse time: 54ms
246+
candidates parse time : 1ms
247+
C# parse time : 257ms
248+
candidates check time : 34ms
249+
console write time : 1ms
250+

0 commit comments

Comments
 (0)