├── .gitignore
├── CONTRIBUTING.md
├── CSSockets.Tests
├── CSSockets.Tests.csproj
└── Program.cs
├── CSSockets.sln
├── CSSockets
├── Binary
│ ├── Binary.cs
│ └── BinaryConversion.cs
├── CSSockets.csproj
├── Http
│ ├── Definition
│ │ ├── Connection.cs
│ │ ├── Encoding.cs
│ │ ├── Head.cs
│ │ ├── Headers.cs
│ │ ├── Messages.cs
│ │ ├── URL.cs
│ │ └── Version.cs
│ └── Reference
│ │ ├── Body.cs
│ │ ├── ClientConnection.cs
│ │ ├── Listener.cs
│ │ ├── Messages.cs
│ │ ├── RequestHead.cs
│ │ ├── ResponseHead.cs
│ │ └── ServerConnection.cs
├── Streams
│ ├── Base.cs
│ ├── Compressors.cs
│ ├── Primitive.cs
│ └── Wrappers.cs
├── Tcp
│ ├── Connection.cs
│ ├── IOControl.cs
│ ├── Listener.cs
│ ├── SocketWrapper.cs
│ └── Types.cs
└── WebSockets
│ ├── Definition
│ ├── Connection.cs
│ ├── ConnectionFactory.cs
│ ├── ConnectionMode.cs
│ ├── Headers.cs
│ ├── Listener.cs
│ ├── Negotiator.cs
│ ├── Protocol.cs
│ └── Secret.cs
│ └── Primitive
│ ├── Connection.cs
│ ├── ConnectionFactory.cs
│ └── Listener.cs
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
262 |
263 | # NuGet command line
264 | CSSockets/nuget.exe
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | I can finish this project myself, however contribution in the form of unit tests and bug reports would be extremely helpful.
2 |
--------------------------------------------------------------------------------
/CSSockets.Tests/CSSockets.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 | CSSockets.Tests
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/CSSockets.Tests/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Linq;
4 | using CSSockets.Tcp;
5 | using System.Threading;
6 | using CSSockets.Streams;
7 | using System.Diagnostics;
8 | using CSSockets.Http.Reference;
9 | using CSSockets.Http.Definition;
10 | using System.Collections.Generic;
11 | using static System.Text.Encoding;
12 | using TcpListener = CSSockets.Tcp.Listener;
13 | using HttpListener = CSSockets.Http.Reference.Listener;
14 | using WebSocket = CSSockets.WebSockets.Primitive.Connection;
15 | using WebSocketListener = CSSockets.WebSockets.Primitive.Listener;
16 | using WebSocketFactory = CSSockets.WebSockets.Primitive.ConnectionFactory;
17 |
18 | namespace CSSockets.Tests
19 | {
20 | class Program
21 | {
22 | static void Main(string[] args)
23 | {
24 | WebSocketClientTest(args);
25 | }
26 |
27 | public static void WebSocketClientTest(string[] args)
28 | {
29 | IPEndPoint serverEP = new IPEndPoint(IPAddress.Any, 420);
30 | IPEndPoint clientEP = new IPEndPoint(IPAddress.Loopback, 420);
31 | WebSocketListener listener = new WebSocketListener(serverEP);
32 | WebSocket server = null;
33 | WebSocket client = null;
34 | listener.ClientVerifier =
35 | (address, subprotocols, head) =>
36 | subprotocols.Contains("test");
37 | listener.SubprotocolChooser = (address, subprotocols, head) => "test";
38 | listener.OnConnection += (connection) =>
39 | {
40 | server = connection;
41 | Console.WriteLine("listener connection");
42 | Console.WriteLine(server.Subprotocol);
43 | server.OnBinary += (data) => Console.WriteLine("SERVER BINARY {0}", data.LongLength);
44 | server.OnString += (data) => Console.WriteLine("SERVER STRING {0}", data.Length);
45 | server.OnClose += (code, reason) => Console.WriteLine("SERVER CLOSED {0} '{1}'", code, reason);
46 | server.SendBinary(new byte[1]);
47 | server.SendBinary(new byte[10]);
48 | server.SendBinary(new byte[100]);
49 | server.SendString("Hijklmn");
50 | server.SendClose(1000, "OK");
51 | };
52 | listener.Start();
53 | Console.WriteLine("listener open");
54 | Connection clientTcp = new Connection();
55 | clientTcp.OnOpen += () =>
56 | {
57 | client = WebSocketFactory.Default.Generate(clientTcp, "/");
58 | client.OnOpen += () =>
59 | {
60 | client.OnBinary += (data) => Console.WriteLine("CLIENT BINARY {0}", data.LongLength);
61 | client.OnString += (data) => Console.WriteLine("CLIENT STRING {0}", data.Length);
62 | client.OnClose += (code, reason) => Console.WriteLine("CLIENT CLOSED {0} '{1}'", code, reason);
63 | /*client.SendBinary(new byte[1]);
64 | client.SendBinary(new byte[10]);
65 | client.SendBinary(new byte[100]);
66 | client.SendString("Abcdefg");*/
67 | client.SendClose(1000, "OK");
68 | };
69 | };
70 | clientTcp.Connect(clientEP);
71 | Console.ReadKey();
72 | Console.WriteLine("client states: {0} {1} {2} {3}", client.Opening, client.Open, client.Closing, client.Closed);
73 | Console.ReadKey();
74 | listener.Stop();
75 | Console.ReadKey();
76 | }
77 |
78 | public static void WebSocketServerTest(string[] args)
79 | {
80 | IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 420);
81 | WebSocketListener server = new WebSocketListener(endPoint);
82 | WebSocket client = null;
83 | server.OnConnection += (connection) =>
84 | {
85 | client = connection;
86 | Console.WriteLine("connection");
87 | connection.OnBinary += (data) => Console.WriteLine("BINARY {0}", data.LongLength);
88 | connection.OnString += (data) => Console.WriteLine("STRING {0}", data.Length);
89 | connection.OnClose += (code, reason) => Console.WriteLine("CLOSED {0} '{1}'", code, reason);
90 | connection.SendBinary(new byte[1]);
91 | connection.SendBinary(new byte[10]);
92 | connection.SendBinary(new byte[100]);
93 | };
94 | Console.WriteLine("open");
95 | server.Start();
96 | Console.ReadKey();
97 | client.SendClose(1000, "OK");
98 | server.Stop();
99 | Console.ReadKey();
100 | }
101 |
102 | public static void HttpClientConnectionTest(string[] args)
103 | {
104 | IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.Loopback, 420);
105 | IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 420);
106 | HttpListener listener = new HttpListener(serverEndPoint);
107 | ServerConnection server = null;
108 | listener.OnRequest = (sreq, res) =>
109 | {
110 | Console.WriteLine("Server request");
111 | server = sreq.Connection;
112 | if (sreq.Path != "/echo")
113 | {
114 | res["Content-Length"] = "0";
115 | res.End(404, "Not Found"); return;
116 | }
117 | switch (sreq.Method)
118 | {
119 | case "GET":
120 | Console.WriteLine("Server GET request");
121 | res["Content-Type"] = "text/plain";
122 | res["Transfer-Encoding"] = "chunked";
123 | res.SendHead(200, "OK");
124 | res.Write(UTF8.GetBytes("GET on /echo\r\nMake a POST request to echo its body"));
125 | res.End();
126 | Console.WriteLine("Server response finish");
127 | break;
128 | case "POST":
129 | Console.WriteLine("Server POST request");
130 | sreq.OnFinish += () =>
131 | {
132 | Console.WriteLine("Server POST request finish");
133 | if (sreq.BufferedReadable == 0) { res.End(400, "Bad Request"); return; }
134 | res["Content-Type"] = "text/plain";
135 | res["Transfer-Encoding"] = "chunked";
136 | res.SendHead(200, "OK");
137 | res.Write(UTF8.GetBytes("/ path on POST!\r\n"));
138 | res.Write(UTF8.GetBytes("Body is as follows:\r\n\r\n"));
139 | byte[] data = sreq.Read();
140 | Console.WriteLine(UTF8.GetString(data).Replace("\r", "\\r").Replace("\n", "\\n"));
141 | res.Write(data);
142 | res.End();
143 | Console.WriteLine("Server response finish");
144 | };
145 | break;
146 | default: res.End(400, "Bad Request"); break;
147 | }
148 | };
149 | listener.Start();
150 | ClientConnection client = null;
151 | client = new ClientConnection(clientEndPoint, () =>
152 | {
153 | OutgoingRequest creq = client.Enqueue("HTTP/1.1", "POST", "/echo");
154 | creq.OnResponse += (res) =>
155 | {
156 | Console.WriteLine("Client response {0} {1}", res.StatusCode, res.StatusDescription);
157 | res.OnFinish += () =>
158 | {
159 | Console.WriteLine("Client response finish\r\n{0}", UTF8.GetString(res.Read()));
160 | client.End();
161 | client.Base.End();
162 | };
163 | };
164 | creq["Transfer-Encoding"] = "chunked";
165 | creq.SendHead();
166 | creq.Write(UTF8.GetBytes("I am a body"));
167 | creq.End();
168 | Console.WriteLine("Client request finish");
169 | });
170 | Console.ReadKey();
171 | listener.Stop();
172 | Console.ReadKey();
173 | //client.Terminate();
174 | }
175 |
176 | public static void HttpServerConnectionTest(string[] args)
177 | {
178 | HttpListener server = new HttpListener(new IPEndPoint(IPAddress.Any, 420));
179 | server.OnConnection += (connection) => Console.WriteLine("connection");
180 | server.OnRequest = (req, res) =>
181 | {
182 | Console.WriteLine("request");
183 | if (req.Path == "/favicon.ico") res.End(404, "Not Found");
184 | else
185 | {
186 | Stopwatch sw = Stopwatch.StartNew();
187 | res.OnFinish += () => Console.WriteLine("{0}ms for onfinish fire", sw.Elapsed.TotalMilliseconds.ToString("F2"));
188 | res["Transfer-Encoding"] = "chunked";
189 | res["Content-Type"] = "text/plain";
190 | res["Connection"] = "close";
191 | res.SendHead(200, "OK");
192 | for (int i = 0; i < 1000000; i++) res.Write(ASCII.GetBytes(i.ToString() + " "));
193 | res.End();
194 | Console.WriteLine("{0}ms for req end", sw.Elapsed.TotalMilliseconds.ToString("F2"));
195 | sw.Stop();
196 | }
197 | };
198 | server.Start();
199 | Console.WriteLine("started");
200 | Console.ReadKey();
201 | server.Stop();
202 | Console.WriteLine("stopped");
203 | Console.ReadKey();
204 | }
205 |
206 | public static void BodyParseSerializeTest(string[] args)
207 | {
208 | BodyParser parser = new BodyParser();
209 | BodySerializer serial = new BodySerializer();
210 |
211 | serial.Pipe(parser);
212 | serial.OnFail += () => Console.WriteLine("serializer failed");
213 | parser.OnData += (data) => Console.WriteLine(UTF8.GetString(data));
214 | parser.OnFinish += () => Console.WriteLine("parser finished");
215 | parser.Excess.Pipe(VoidWritable.Default);
216 |
217 | BodyType bodyType = new BodyType(null, TransferEncoding.Chunked, TransferCompression.Deflate);
218 | if (!parser.TrySetFor(bodyType)) Console.WriteLine("parser failed to set");
219 | if (!serial.TrySetFor(bodyType)) Console.WriteLine("serializer failed to set");
220 |
221 | serial.Write(UTF8.GetBytes("I am a body\r\nxd\r\n"));
222 | serial.Write(UTF8.GetBytes("I am a body\r\nasfjaskfd\r\nasdfa"));
223 | serial.Finish();
224 |
225 | Console.ReadKey();
226 | }
227 |
228 | public static void HeadParseSerializeTest(string[] args)
229 | {
230 | RequestHeadParser rParser = new RequestHeadParser();
231 | ResponseHeadParser RParser = new ResponseHeadParser();
232 | RequestHeadSerializer rSerial = new RequestHeadSerializer();
233 | ResponseHeadSerializer RSerial = new ResponseHeadSerializer();
234 |
235 | RequestHead rHead = new RequestHead("HTTP/1.1", "GET", "/");
236 | rHead.Headers["test1"] = "123";
237 | rHead.Headers["test2"] = "456";
238 |
239 | ResponseHead RHead = new ResponseHead("HTTP/1.1", 200, "OK");
240 | RHead.Headers["test1"] = "123";
241 | RHead.Headers["test2"] = "456";
242 |
243 | rSerial.Pipe(rParser);
244 | RSerial.Pipe(RParser);
245 |
246 | rParser.OnCollect += (head) => Console.WriteLine("request parser:\n{0}", head.Stringify());
247 | RParser.OnCollect += (head) => Console.WriteLine("response parser:\n{0}", head.Stringify());
248 | rSerial.OnFail += () => Console.WriteLine("request serializer failed");
249 | RSerial.OnFail += () => Console.WriteLine("response serializer failed");
250 |
251 | rSerial.Write(rHead);
252 | RSerial.Write(RHead);
253 |
254 | Console.ReadKey();
255 | }
256 |
257 | public static void CompressorTest(string[] args)
258 | {
259 | MemoryDuplex memory = new MemoryDuplex();
260 | GzipCompressor c = new GzipCompressor();
261 | c.Pipe(memory);
262 | GzipDecompressor d = new GzipDecompressor();
263 | memory.Pipe(d);
264 | d.OnData += (data) => Console.WriteLine("data {0}", data.Stringify());
265 | c.Write(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
266 | c.Finish();
267 | d.Finish();
268 | Console.ReadKey();
269 | }
270 |
271 | struct TcpSocketScalabilityMetrics
272 | {
273 | public int attempts,
274 | clientCreated,
275 | clientActive,
276 | clientSuccessful,
277 | clientError,
278 | serverActive,
279 | serverTimeout,
280 | serverSuccessful,
281 | serverError,
282 | datasSent,
283 | datasReceieved;
284 |
285 | public override string ToString()
286 | {
287 | return string.Format(
288 | "client: {0:0000}c/{1:0000}a/{2:0000}s/{3:0000}e/{4:0000}T" +
289 | " server: {5:0000}a/{6:0000}t/{7:0000}s/{8:0000}e/{9:0000}T" +
290 | " data: {10:0000}s/{11:0000}r" +
291 | " io: {12:0000}c/{13:0000}l/{14:0000}s/{15:00}t",
292 | clientCreated, clientActive, clientSuccessful, clientError, clientSuccessful + clientError,
293 | serverActive, serverTimeout, serverSuccessful, serverError, serverSuccessful + serverError + serverTimeout,
294 | datasSent, datasReceieved,
295 | IOControl.ConnectionCount, IOControl.ListenerCount, IOControl.SocketCount, IOControl.ThreadCount
296 | );
297 | }
298 | }
299 |
300 | public static void TcpSocketScalabilityTest(string[] args)
301 | {
302 | EndPoint clientEndPoint = new IPEndPoint(IPAddress.Loopback, 420);
303 | EndPoint serverendPoint = new IPEndPoint(IPAddress.Any, 420);
304 | byte[] sending = new byte[] { 1, 2, 3, 4, 5 };
305 |
306 | TcpSocketScalabilityMetrics metrics = new TcpSocketScalabilityMetrics
307 | {
308 | attempts = 10000
309 | };
310 |
311 | TcpListener listener = new TcpListener
312 | {
313 | Backlog = metrics.attempts,
314 | BindEndPoint = serverendPoint
315 | };
316 | listener.OnConnection += (server) =>
317 | {
318 | Interlocked.Increment(ref metrics.serverActive);
319 | server.TimeoutAfter = new TimeSpan(0, 1, 0);
320 | server.OnData += (data) => Interlocked.Increment(ref metrics.datasReceieved);
321 | server.OnError += (e) =>
322 | {
323 | Interlocked.Increment(ref metrics.serverSuccessful);
324 | Interlocked.Increment(ref metrics.serverError);
325 | };
326 | server.OnClose += () =>
327 | {
328 | Interlocked.Increment(ref metrics.serverSuccessful);
329 | Interlocked.Decrement(ref metrics.serverActive);
330 | };
331 | server.OnTimeout += () =>
332 | {
333 | Interlocked.Increment(ref metrics.serverTimeout);
334 | server.Terminate();
335 | };
336 | };
337 | listener.Start();
338 |
339 | Thread.Sleep(1000);
340 |
341 | DateTime start = DateTime.UtcNow;
342 | List clients = new List();
343 |
344 | Console.WriteLine("CLIENT: created / active / success / error / total SERVER: active / timeout / success / error / total DATA: sent / recvd IO: connections / listeners / sockets / threads");
345 | for (int i = 0; i < metrics.attempts; i++)
346 | {
347 | Interlocked.Increment(ref metrics.clientCreated);
348 | Tcp.Connection client = new Tcp.Connection();
349 | client.OnDrain += () => Interlocked.Increment(ref metrics.datasSent);
350 | client.OnClose += () =>
351 | {
352 | Interlocked.Increment(ref metrics.clientSuccessful);
353 | Interlocked.Decrement(ref metrics.clientActive);
354 | };
355 | client.OnError += (e) =>
356 | {
357 | if (client.State != TcpSocketState.Open)
358 | Interlocked.Increment(ref metrics.clientActive);
359 | Interlocked.Decrement(ref metrics.clientSuccessful);
360 | Interlocked.Increment(ref metrics.clientError);
361 | };
362 | client.OnOpen += () =>
363 | {
364 | Interlocked.Increment(ref metrics.clientActive);
365 | client.Write(sending);
366 | client.End();
367 | };
368 | clients.Add(client);
369 | if (metrics.clientCreated % 100 == 0)
370 | Console.WriteLine("[{0}] {1}", (DateTime.UtcNow - start).TotalSeconds.ToString("F6").PadLeft(12), metrics.ToString());
371 | }
372 |
373 | Console.WriteLine("[{0}] {1} clients created", (DateTime.UtcNow - start).TotalSeconds.ToString("F6").PadLeft(12), metrics.attempts);
374 |
375 | for (int i = 0; i < metrics.attempts; i++)
376 | clients[i].Connect(clientEndPoint);
377 |
378 | Console.WriteLine("[{0}] {1} clients connecting", (DateTime.UtcNow - start).TotalSeconds.ToString("F6").PadLeft(12), metrics.attempts);
379 |
380 | Thread.Sleep(100);
381 | while (true)
382 | {
383 | Console.WriteLine("[{0}] {1}", (DateTime.UtcNow - start).TotalSeconds.ToString("F6").PadLeft(12), metrics.ToString());
384 | if (IOControl.ConnectionCount == 0) break;
385 | Thread.Sleep(200);
386 | }
387 | listener.Stop();
388 | Console.WriteLine("[{0}] done", (DateTime.UtcNow - start).TotalSeconds.ToString("F6").PadLeft(12));
389 | Console.ReadKey();
390 | }
391 | }
392 |
393 | public static class Extensions
394 | {
395 | private const string CR = "\\r\r";
396 | private const string LF = "\\n\n";
397 | private const string CRLF = "\\r\\n\r\n";
398 | private const char COLON = ':';
399 | private const string WS = " ";
400 |
401 | public static string Stringify(this byte[] array)
402 | {
403 | if (array.LongLength == 0) return "";
404 | string s = "";
405 | foreach (byte item in array) s += item.ToString("X2");
406 | return s;
407 | }
408 |
409 | public static string Stringify(this RequestHead source)
410 | {
411 | string stringified = source.Method + WS + source.URL + WS + source.Version + CRLF;
412 | for (int i = 0; i < source.Headers.Length; i++)
413 | stringified += source.Headers[i].Key.ToLower() + COLON + WS + source.Headers[i].Value + CRLF;
414 | stringified += CRLF;
415 | return stringified;
416 | }
417 |
418 | public static string Stringify(this ResponseHead source)
419 | {
420 | string stringified = source.Version + WS + source.StatusCode + WS + source.StatusDescription + CRLF;
421 | for (int i = 0; i < source.Headers.Length; i++)
422 | stringified += source.Headers[i].Key.ToLower() + COLON + WS + source.Headers[i].Value + CRLF;
423 | stringified += CRLF;
424 | return stringified;
425 | }
426 | }
427 | }
428 |
--------------------------------------------------------------------------------
/CSSockets.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSSockets", "CSSockets\CSSockets.csproj", "{F4541532-7252-4924-BDC4-64BEDE117381}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSSockets.Tests", "CSSockets.Tests\CSSockets.Tests.csproj", "{573A6E5A-BC66-41A4-BC3A-F7113742FE94}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {F4541532-7252-4924-BDC4-64BEDE117381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {F4541532-7252-4924-BDC4-64BEDE117381}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {F4541532-7252-4924-BDC4-64BEDE117381}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {F4541532-7252-4924-BDC4-64BEDE117381}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {573A6E5A-BC66-41A4-BC3A-F7113742FE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {573A6E5A-BC66-41A4-BC3A-F7113742FE94}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {573A6E5A-BC66-41A4-BC3A-F7113742FE94}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {573A6E5A-BC66-41A4-BC3A-F7113742FE94}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {4AA31DB5-986E-4CCC-A1D3-499D5F629D5F}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/CSSockets/Binary/Binary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using CSSockets.Streams;
5 |
6 | namespace CSSockets.Binary
7 | {
8 | public abstract class Reader
9 | {
10 | protected abstract byte[] NonBlockingRead(ulong length);
11 | protected abstract byte[] NonBlockingUnsafeRead(ulong length);
12 |
13 | public byte ReadUInt8() => NonBlockingRead(1)[0];
14 | public sbyte ReadInt8() => (sbyte)NonBlockingRead(1)[0];
15 |
16 | public ushort ReadUInt16BE() => (ushort)BinaryConversion.GetBE(NonBlockingRead(2));
17 | public ushort ReadUInt16LE() => (ushort)BinaryConversion.GetLE(NonBlockingRead(2));
18 | public short ReadInt16BE() => (short)BinaryConversion.GetBE(NonBlockingRead(2));
19 | public short ReadInt16LE() => (short)BinaryConversion.GetLE(NonBlockingRead(2));
20 |
21 | public uint ReadUInt32BE() => (uint)BinaryConversion.GetBE(NonBlockingRead(4));
22 | public uint ReadUInt32LE() => (uint)BinaryConversion.GetLE(NonBlockingRead(4));
23 | public int ReadInt32BE() => (int)BinaryConversion.GetBE(NonBlockingRead(4));
24 | public int ReadInt32LE() => (int)BinaryConversion.GetLE(NonBlockingRead(4));
25 |
26 | public ulong ReadUInt64BE() => BinaryConversion.GetBE(NonBlockingRead(8));
27 | public ulong ReadUInt64LE() => BinaryConversion.GetLE(NonBlockingRead(8));
28 | public long ReadInt64BE() => (long)BinaryConversion.GetBE(NonBlockingRead(8));
29 | public long ReadInt64LE() => (long)BinaryConversion.GetLE(NonBlockingRead(8));
30 |
31 | public ulong ReadUIntBE(byte size) => BinaryConversion.GetBE(NonBlockingRead(size / 8u));
32 | public ulong ReadUIntLE(byte size) => BinaryConversion.GetLE(NonBlockingRead(size / 8u));
33 | public long ReadIntBE(byte size) => BinaryConversion.ToSignedBE(BinaryConversion.GetBE(NonBlockingRead(size / 8u)), size);
34 | public long ReadIntLE(byte size) => BinaryConversion.ToSignedLE(BinaryConversion.GetLE(NonBlockingRead(size / 8u)), size);
35 |
36 | public float ReadFloat32BE() => new BinaryConversion.IntFloat32(ReadInt32BE()).Float;
37 | public float ReadFloat32LE() => new BinaryConversion.IntFloat32(ReadInt32LE()).Float;
38 | public double ReadFloat64BE() => new BinaryConversion.IntFloat64(ReadInt64BE()).Float;
39 | public double ReadFloat64LE() => new BinaryConversion.IntFloat64(ReadInt64LE()).Float;
40 |
41 | public string ReadString(Encoding encoding, ulong length, byte size) => encoding.GetString(NonBlockingRead(length * (size / 8u)));
42 |
43 | public string ReadStringUnicodeBEZT() => ReadStringZT(Encoding.BigEndianUnicode, 16);
44 | public string ReadStringUnicodeLEZT() => ReadStringZT(Encoding.Unicode, 16);
45 | public string ReadStringUTF8ZT() => ReadStringZT(Encoding.UTF8, 8);
46 | public string ReadStringZT(Encoding encoding, byte size)
47 | {
48 | PrimitiveBuffer buffer = new PrimitiveBuffer();
49 | ulong block = size / 8u;
50 | while (true)
51 | {
52 | byte[] read = NonBlockingUnsafeRead(block);
53 | if (read == null || read.All((v) => v == 0)) break;
54 | buffer.Write(read);
55 | }
56 | return encoding.GetString(buffer.Read(buffer.Length));
57 | }
58 | }
59 | public sealed class StreamReader : Reader
60 | {
61 | public IReadable Stream { get; }
62 |
63 | public StreamReader(IReadable readable) => Stream = readable;
64 |
65 | protected sealed override byte[] NonBlockingUnsafeRead(ulong length)
66 | {
67 | byte[] data = new byte[length];
68 | ulong read = Stream.Read(data);
69 | if (read != length) return null;
70 | return data;
71 | }
72 | protected sealed override byte[] NonBlockingRead(ulong length)
73 | {
74 | byte[] data = new byte[length];
75 | ulong read = Stream.Read(data);
76 | if (read != length) throw new IndexOutOfRangeException("Reached end of stream");
77 | return data;
78 | }
79 | }
80 | public sealed class MemoryReader : Reader
81 | {
82 | public byte[] Data { get; }
83 | private readonly object Sync = new object();
84 | private ulong size;
85 | private ulong offset = 0;
86 |
87 | public MemoryReader(byte[] data, ulong offset = 0)
88 | {
89 | Data = data;
90 | size = (ulong)data.LongLength;
91 | this.offset = offset;
92 | }
93 |
94 | protected sealed override byte[] NonBlockingUnsafeRead(ulong length)
95 | {
96 | lock (Sync)
97 | {
98 | if (offset + length > size) return null;
99 | return PrimitiveBuffer.Slice(Data, offset - length, offset += length);
100 | }
101 | }
102 | protected sealed override byte[] NonBlockingRead(ulong length)
103 | {
104 | lock (Sync)
105 | {
106 | if (offset + length > size) throw new IndexOutOfRangeException("Reached end of memory");
107 | return PrimitiveBuffer.Slice(Data, offset - length, offset += length);
108 | }
109 | }
110 | }
111 |
112 | public class StreamWriter
113 | {
114 | public IWritable Stream { get; }
115 |
116 | public StreamWriter(IWritable writable) => Stream = writable;
117 |
118 | public bool WriteUInt8(byte value) => Stream.Write(new byte[] { value });
119 | public bool WriteInt8(sbyte value) => Stream.Write(new byte[] { (byte)value });
120 |
121 | public bool WriteUInt16BE(ushort value) => Stream.Write(BinaryConversion.SerializeBE(value, 2));
122 | public bool WriteUInt16LE(ushort value) => Stream.Write(BinaryConversion.SerializeLE(value, 2));
123 | public bool WriteInt16BE(short value) => Stream.Write(BinaryConversion.SerializeBE((ushort)value, 2));
124 | public bool WriteInt16LE(short value) => Stream.Write(BinaryConversion.SerializeLE((ushort)value, 2));
125 |
126 | public bool WriteUInt32BE(uint value) => Stream.Write(BinaryConversion.SerializeBE(value, 4));
127 | public bool WriteUInt32LE(uint value) => Stream.Write(BinaryConversion.SerializeLE(value, 4));
128 | public bool WriteInt32BE(int value) => Stream.Write(BinaryConversion.SerializeBE((uint)value, 4));
129 | public bool WriteInt32LE(int value) => Stream.Write(BinaryConversion.SerializeLE((uint)value, 4));
130 |
131 | public bool WriteUInt64BE(ulong value) => Stream.Write(BinaryConversion.SerializeBE(value, 8));
132 | public bool WriteUInt64LE(ulong value) => Stream.Write(BinaryConversion.SerializeLE(value, 8));
133 | public bool WriteInt64BE(long value) => Stream.Write(BinaryConversion.SerializeBE((ulong)value, 8));
134 | public bool WriteInt64LE(long value) => Stream.Write(BinaryConversion.SerializeLE((ulong)value, 8));
135 |
136 | public bool WriteUIntBE(ulong value, byte size) => Stream.Write(BinaryConversion.SerializeBE(value, size / 8));
137 | public bool WriteUIntLE(ulong value, byte size) => Stream.Write(BinaryConversion.SerializeLE(value, size / 8));
138 | public bool WriteIntBE(long value, byte size) => Stream.Write(BinaryConversion.SerializeBE((ulong)value, size / 8));
139 | public bool WriteIntLE(long value, byte size) => Stream.Write(BinaryConversion.SerializeLE((ulong)value, size / 8));
140 |
141 | public bool WriteFloat32BE(float value) => Stream.Write(BinaryConversion.SerializeBE((ulong)new BinaryConversion.IntFloat32(value).Integer, 4));
142 | public bool WriteFloat32LE(float value) => Stream.Write(BinaryConversion.SerializeLE((ulong)new BinaryConversion.IntFloat32(value).Integer, 4));
143 | public bool WriteFloat64BE(double value) => Stream.Write(BinaryConversion.SerializeBE((ulong)new BinaryConversion.IntFloat64(value).Integer, 8));
144 | public bool WriteFloat64LE(double value) => Stream.Write(BinaryConversion.SerializeLE((ulong)new BinaryConversion.IntFloat64(value).Integer, 8));
145 |
146 | public bool WriteStringUTF8(string value) => WriteString(value, Encoding.UTF8);
147 | public bool WriteStringUnicodeBE(string value) => WriteString(value, Encoding.BigEndianUnicode);
148 | public bool WriteStringUnicodeLE(string value) => WriteString(value, Encoding.Unicode);
149 | public bool WriteString(string value, Encoding encoding) => Stream.Write(encoding.GetBytes(value));
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/CSSockets/Binary/BinaryConversion.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace CSSockets.Binary
5 | {
6 | public static class BinaryConversion
7 | {
8 | public static ulong GetBE(byte[] data)
9 | {
10 | ulong value = 0;
11 | for (int i = 0; i < data.Length; i++) value = unchecked(value << 8 | (data[i] & 0xFFu));
12 | return value;
13 | }
14 | public static ulong GetLE(byte[] data)
15 | {
16 | ulong value = 0;
17 | for (int i = 0; i < data.Length; i++) value = unchecked(value << 8 | (data[data.Length - 1 - i] & 0xFFu));
18 | return value;
19 | }
20 | public static bool IsNegativeBE(ulong value, int size) => ((value >> (size - 8)) & 128) == 128;
21 | public static bool IsNegativeLE(ulong value, int size) => (value & 128) == 128;
22 | public static long ToSignedBE(ulong value, int size) => IsNegativeBE(value, size) ? -(long)Math.Pow(2, size) + (long)value : (long)value;
23 | public static long ToSignedLE(ulong value, int size) => IsNegativeLE(value, size) ? -(long)Math.Pow(2, size) + (long)value : (long)value;
24 | public static byte[] SerializeBE(ulong value, int size)
25 | {
26 | byte[] data = new byte[size];
27 | for (int i = 0; i < size; i++)
28 | {
29 | data[size - 1 - i] = (byte)(value & 0xFF);
30 | value >>= 8;
31 | }
32 | return data;
33 | }
34 | public static byte[] SerializeLE(ulong value, int size)
35 | {
36 | byte[] data = new byte[size];
37 | for (int i = 0; i < size; i++)
38 | {
39 | data[i] = (byte)(value & 0xFF);
40 | value >>= 8;
41 | }
42 | return data;
43 | }
44 |
45 | [StructLayout(LayoutKind.Explicit)]
46 | public struct IntFloat32
47 | {
48 | [FieldOffset(0)]
49 | int i;
50 | [FieldOffset(0)]
51 | float f;
52 |
53 | public int Integer => i;
54 | public float Float => f;
55 |
56 | public IntFloat32(int i) : this()
57 | {
58 | f = default(float);
59 | this.i = i;
60 | }
61 | public IntFloat32(float f) : this()
62 | {
63 | i = default(int);
64 | this.f = f;
65 | }
66 | }
67 | [StructLayout(LayoutKind.Explicit)]
68 | public struct IntFloat64
69 | {
70 | [FieldOffset(0)]
71 | long i;
72 | [FieldOffset(0)]
73 | double f;
74 |
75 | public long Integer => i;
76 | public double Float => f;
77 |
78 | public IntFloat64(long i) : this()
79 | {
80 | f = default(double);
81 | this.i = i;
82 | }
83 | public IntFloat64(double f) : this()
84 | {
85 | i = default(long);
86 | this.f = f;
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/CSSockets/CSSockets.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 | CSSockets
6 |
7 |
8 |
9 | true
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Connection.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Tcp;
2 | using CSSockets.Streams;
3 | using CSSockets.Http.Reference;
4 |
5 | namespace CSSockets.Http.Definition
6 | {
7 | public abstract class Connection : IEndable
8 | where TReq : Head, new() where TRes : Head, new()
9 | {
10 | protected readonly object Sync = new object();
11 |
12 | protected HeadParser IncomingHead;
13 | protected HeadSerializer OutgoingHead;
14 | protected BodyParser IncomingBody;
15 | protected BodySerializer OutgoingBody;
16 |
17 | public virtual event ControlHandler OnEnd;
18 |
19 | public Connection Base { get; }
20 | public bool Frozen { get; protected set; } = false;
21 | public bool Ended { get; protected set; } = false;
22 |
23 | public Connection(Connection connection)
24 | {
25 | Base = connection;
26 | Base.OnClose += OnBaseClose;
27 | }
28 |
29 | protected virtual void OnBaseClose() => End();
30 |
31 | public abstract IncomingMessage Incoming { get; }
32 | public abstract OutgoingMessage Outgoing { get; }
33 | public abstract bool StartOutgoing(TRes head);
34 | public abstract bool FinishOutgoing();
35 |
36 | public abstract byte[] Freeze();
37 | public virtual bool End()
38 | {
39 | lock (Sync)
40 | {
41 | if (Ended) return false;
42 | if (!Frozen) Freeze();
43 | OnEnd?.Invoke();
44 | return Ended = true;
45 | }
46 | }
47 | public virtual bool Terminate()
48 | {
49 | lock (Sync) return End() && Base.Terminate() && (Ended = true);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Encoding.cs:
--------------------------------------------------------------------------------
1 | namespace CSSockets.Http.Definition
2 | {
3 | public enum TransferEncoding : byte
4 | {
5 | None,
6 | Binary,
7 | Chunked
8 | }
9 | public enum TransferCompression : byte
10 | {
11 | None,
12 | Deflate,
13 | Gzip,
14 | Compress
15 | }
16 | public struct BodyType
17 | {
18 | public ulong? Length { get; }
19 | public TransferEncoding Encoding { get; }
20 | public TransferCompression Compression { get; }
21 |
22 | public BodyType(ulong? length, TransferEncoding encoding, TransferCompression compression) : this()
23 | {
24 | Length = length;
25 | Encoding = encoding;
26 | Compression = compression;
27 | }
28 |
29 | public static BodyType? TryDetectFor(Head head, bool defaultNoBody)
30 | {
31 | // Reference: RFC 7320's 3.3.3
32 | TransferEncoding encoding = TransferEncoding.Binary;
33 | TransferCompression compression = TransferCompression.None;
34 | ulong? contentLen = null;
35 | if (head.Headers["Content-Length"] != null)
36 | {
37 | if (!ulong.TryParse(head.Headers["Content-Length"], out ulong len))
38 | return null;
39 | contentLen = len;
40 | }
41 | if (head.Headers["Transfer-Encoding"] == null)
42 | {
43 | if (contentLen == null && defaultNoBody)
44 | // 3.3.3.6
45 | return new BodyType(null, encoding = TransferEncoding.None, compression);
46 | return new BodyType(contentLen, encoding, compression);
47 | }
48 | string[] split = head.Headers["Transfer-Encoding"].Split(',');
49 | for (int i = 0; i < split.Length; i++)
50 | {
51 | switch (split[i].Trim())
52 | {
53 | case "chunked":
54 | if (contentLen != null) return null; // 3.3.3.3
55 | encoding = TransferEncoding.Chunked;
56 | break;
57 | case "gzip":
58 | if (compression != TransferCompression.None) return null;
59 | compression = TransferCompression.Gzip;
60 | break;
61 | case "deflate":
62 | if (compression != TransferCompression.None) return null;
63 | compression = TransferCompression.Deflate;
64 | break;
65 | case "compress":
66 | return null; // not implemented
67 | default: return null; // unknown encoding
68 | }
69 | }
70 | return new BodyType(contentLen, encoding, compression);
71 | }
72 |
73 | public override string ToString() => string.Format("{0} transfer {1} compression ({2} content length)", Encoding, Compression, Length ?? 0);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Head.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Streams;
2 |
3 | namespace CSSockets.Http.Definition
4 | {
5 | public abstract class Head
6 | {
7 | public Version Version { get; set; }
8 | public HeaderCollection Headers { get; }
9 |
10 | public Head() => Headers = new HeaderCollection();
11 | public Head(Version version) : this() => Version = version;
12 | public Head(Version version, HeaderCollection headers)
13 | {
14 | Version = version;
15 | Headers = headers;
16 | }
17 | }
18 |
19 | public abstract class HeadParser : Translator where T : Head
20 | {
21 | protected const char CR = '\r';
22 | protected const char LF = '\n';
23 | protected const string CRLF = "\r\n";
24 | protected const char COLON = ':';
25 | protected const char WS = ' ';
26 | }
27 | public abstract class HeadSerializer : Transform where T : Head
28 | {
29 | protected const char CR = '\r';
30 | protected const char LF = '\n';
31 | protected const string CRLF = "\r\n";
32 | protected const char COLON = ':';
33 | protected const char WS = ' ';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Headers.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.Specialized;
3 |
4 | namespace CSSockets.Http.Definition
5 | {
6 | public struct Header
7 | {
8 | public string Key { get; }
9 | public string Value { get; }
10 | public Header(string key, string value) : this() { Key = key; Value = value; }
11 | }
12 | public sealed class HeaderCollection
13 | {
14 | private readonly StringDictionary Collection = new StringDictionary();
15 | private readonly StringCollection Keys = new StringCollection();
16 | public int Length => Keys.Count;
17 |
18 | public HeaderCollection() { }
19 | public HeaderCollection(IDictionary headers)
20 | {
21 | foreach (KeyValuePair header in headers) Add(header.Key, header.Value);
22 | }
23 | public HeaderCollection(IEnumerable headers)
24 | {
25 | foreach (Header header in headers) Add(header.Key, header.Value);
26 | }
27 | public HeaderCollection(params Header[] headers)
28 | {
29 | foreach (Header header in headers) Add(header.Key, header.Value);
30 | }
31 |
32 | public string this[string key]
33 | {
34 | get => Collection[key.ToLower()];
35 | set
36 | {
37 | if (key == null) Remove(key);
38 | else if (!Keys.Contains(key)) Insert(key, value);
39 | else if (value != string.Empty) Collection[key] = value;
40 | else Remove(key);
41 | }
42 | }
43 | public Header this[int index] => new Header(Keys[index], Collection[Keys[index]]);
44 |
45 | public bool Exists(string key) => Keys.Contains(key.ToLower());
46 | public bool Add(Header header)
47 | {
48 | if (Keys.Contains(header.Key.ToLower())) return false;
49 | return Insert(header.Key.ToLower(), header.Value);
50 | }
51 | public bool Add(string key, string value)
52 | {
53 | if (Keys.Contains(key = key.ToLower())) return false;
54 | return Insert(key, value);
55 | }
56 | private bool Insert(string key, string value)
57 | {
58 | Keys.Add(key);
59 | Collection[key] = value;
60 | return true;
61 | }
62 | public bool Remove(string key)
63 | {
64 | if (!Keys.Contains(key)) return false;
65 | Keys.Remove(key);
66 | Collection.Remove(key);
67 | return true;
68 | }
69 | public bool Clear()
70 | {
71 | Collection.Clear();
72 | Keys.Clear();
73 | return true;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Messages.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Streams;
2 |
3 | namespace CSSockets.Http.Definition
4 | {
5 | public abstract class IncomingMessage : MemoryDuplex, IFinishable, IEndable
6 | where TReq : Head, new() where TRes : Head, new()
7 | {
8 | public TReq Head { get; }
9 | public Connection Connection { get; }
10 |
11 | // Head is supposed to be static, don't lock on Sync
12 | public Version Version => Head.Version;
13 | public string this[string key] => Head.Headers[key];
14 |
15 | public virtual event ControlHandler OnFinish;
16 |
17 | public bool Ended { get; private set; } = false;
18 | public bool Finished { get; private set; } = false;
19 |
20 | public IncomingMessage(Connection connection, TReq head)
21 | {
22 | Connection = connection;
23 | Head = head;
24 | }
25 |
26 | public virtual bool Finish()
27 | {
28 | lock (Sync)
29 | {
30 | OnFinish?.Invoke();
31 | return Finished = true;
32 | }
33 | }
34 | public virtual bool End()
35 | {
36 | lock (Sync)
37 | {
38 | return Ended = true && Connection.Terminate();
39 | }
40 | }
41 | }
42 |
43 | public abstract class OutgoingMessage : MemoryDuplex, IFinishable, IEndable
44 | where TReq : Head, new() where TRes : Head, new()
45 | {
46 | public TRes Head { get; set; } = new TRes();
47 | public Connection Connection { get; }
48 |
49 | public Version Version
50 | {
51 | get { lock (Sync) return Head.Version; }
52 | set { lock (Sync) { if (SentHead) return; Head.Version = value; } }
53 | }
54 | public string this[string key]
55 | {
56 | get { lock (Sync) return Head.Headers[key]; }
57 | set { lock (Sync) { if (SentHead) return; Head.Headers[key] = value; } }
58 | }
59 |
60 | public virtual event ControlHandler OnFinish;
61 |
62 | public bool Ended { get; private set; } = false;
63 | public bool SentHead { get; private set; } = false;
64 | public bool Finished { get; private set; } = false;
65 |
66 | public OutgoingMessage(Connection connection, Version version)
67 | {
68 | Connection = connection;
69 | Head.Version = version;
70 | }
71 |
72 | public virtual bool Finish()
73 | {
74 | lock (Sync)
75 | {
76 | OnFinish?.Invoke();
77 | return Finished = true;
78 | }
79 | }
80 | public virtual bool SendHead()
81 | {
82 | lock (Sync)
83 | {
84 | if (SentHead) return false;
85 | return Connection.StartOutgoing(Head) && (SentHead = true);
86 | }
87 | }
88 | public virtual bool End()
89 | {
90 | lock (Sync)
91 | {
92 | return (!SentHead ? SendHead() : true) && Connection.FinishOutgoing() && (Ended = true);
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/URL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace CSSockets.Http.Definition
5 | {
6 | public struct Query
7 | {
8 | public string Key { get;set; }
9 | public string Value { get; set; }
10 | public Query(string key, string value)
11 | {
12 | Key = key;
13 | Value = value;
14 | }
15 | public override string ToString()
16 | => Key + "=" + Value;
17 | }
18 | public sealed class Queries
19 | {
20 | private readonly Dictionary Tokens = new Dictionary();
21 | private readonly List Keys = new List();
22 | public int Length => Keys.Count;
23 |
24 | public string this[string key]
25 | {
26 | get => Get(key)?.Value;
27 | set => Set(key, value);
28 | }
29 | public Query? this[int index] => Get(Keys[index]);
30 |
31 | public Query? Get(string key)
32 | {
33 | if (!Tokens.TryGetValue(key, out Query item))
34 | return null;
35 | return item;
36 | }
37 | public bool Set(string key, string value)
38 | {
39 | if (!Tokens.ContainsKey(key)) Keys.Add(key);
40 | Tokens[key] = new Query(key, value);
41 | return true;
42 | }
43 | public bool Remove(string key)
44 | {
45 | if (!Tokens.ContainsKey(key)) return false;
46 | Tokens.Remove(key);
47 | Keys.Remove(key);
48 | return true;
49 | }
50 | public bool Clear()
51 | {
52 | Tokens.Clear();
53 | Keys.Clear();
54 | return true;
55 | }
56 |
57 | public static bool TryParse(string s, out Queries result)
58 | {
59 | Queries temp = new Queries();
60 | result = null;
61 | if (s.Length == 0)
62 | {
63 | // empty
64 | result = temp;
65 | return true;
66 | }
67 | if (!s.StartsWith("?")) return false;
68 | string[] split = s.Substring(1).Split("&");
69 | for (int i = 0; i < split.Length; i++)
70 | {
71 | string queryToken = split[i];
72 | string[] queryTokenSplit = queryToken.Split("=");
73 | if (queryTokenSplit.Length != 2) return false;
74 | temp.Set(queryTokenSplit[0], queryTokenSplit[1]);
75 | }
76 | result = temp;
77 | return true;
78 | }
79 | public static Queries Parse(string s)
80 | {
81 | if (!TryParse(s, out Queries result))
82 | throw new ArgumentException("Invalid query string");
83 | return result;
84 | }
85 |
86 | public override string ToString()
87 | {
88 | string s = null;
89 | for (int i = 0; i < Keys.Count; i++)
90 | {
91 | s = s ?? "?";
92 | s += Keys[i] + "=" + Tokens[Keys[i]].Value + "&";
93 | }
94 | return s == null ? "" : s.Substring(0, s.Length);
95 | }
96 | }
97 | public sealed class Path
98 | {
99 | private enum TraverseResult : byte
100 | {
101 | Success = 0,
102 | NotApath = 1,
103 | NotRpath = 2,
104 | APathRdirs = 3,
105 | RpathInvalidRdirs = 4,
106 | TraverseBeyondRoot = 5
107 | }
108 |
109 | private List Location { get; set; }
110 | public string FullPath { get; private set; }
111 | public string Directory { get; private set; }
112 | public string Entry { get; private set; }
113 |
114 | private void AssembleStringified()
115 | {
116 | FullPath = "/";
117 | Directory = "/";
118 | Entry = "";
119 | int i = 0;
120 | for (; i < Location.Count - 1; i++)
121 | FullPath = Directory += Location[i] + "/";
122 | FullPath += Location.Count > 0 ? (Entry += Location[i++]) : "";
123 | }
124 |
125 | private TraverseResult InternalInitialize(string path)
126 | {
127 | List pathList = new List();
128 | string[] dirs = path.Split('/');
129 | if (dirs[0] != "")
130 | return TraverseResult.NotApath;
131 | for (int i = 1; i < dirs.Length; i++)
132 | {
133 | if (dirs[i] == "." || dirs[i] == "..")
134 | return TraverseResult.APathRdirs;
135 | pathList.Add(dirs[i]);
136 | }
137 | Location = pathList;
138 | AssembleStringified();
139 | return TraverseResult.Success;
140 | }
141 | public bool Initialize(string path = "/") => InternalInitialize(path) == TraverseResult.Success;
142 |
143 | private TraverseResult InternalTraverse(string path)
144 | {
145 | List pathList = new List(Location);
146 | string[] dirs = path.Split('/');
147 | if (dirs[0] == "")
148 | return TraverseResult.NotRpath;
149 | bool acceptingBackwards = true;
150 | for (int i = 1; i < dirs.Length; i++)
151 | {
152 | if (dirs[i] == "..")
153 | {
154 | if (!acceptingBackwards) return TraverseResult.RpathInvalidRdirs;
155 | pathList.RemoveAt(pathList.Count - 1);
156 | }
157 | else if (dirs[i] == ".") { if (!acceptingBackwards) return TraverseResult.RpathInvalidRdirs; }
158 | else { pathList.Add(dirs[i]); acceptingBackwards = false; }
159 | }
160 | Location = pathList;
161 | AssembleStringified();
162 | return TraverseResult.Success;
163 | }
164 | public bool Traverse(string path = "/") => InternalTraverse(path) == TraverseResult.Success;
165 |
166 | public bool Contains(Path other)
167 | {
168 | int checkLen = Math.Min(Location.Count, other.Location.Count);
169 | for (int i = 0; i < checkLen; i++) if (Location[i] != other.Location[i]) return false;
170 | return checkLen == Location.Count;
171 | }
172 |
173 | public Path() => Initialize();
174 | public Path(Path other) => Initialize(other.FullPath);
175 | public Path(string path) => Initialize(path);
176 |
177 | public override string ToString() => FullPath;
178 |
179 | public static implicit operator Path(string value) => new Path(value);
180 | public static implicit operator string(Path value) => value.FullPath;
181 | }
182 | public sealed class URL
183 | {
184 | public Path Path { get; set; }
185 | public string Hash { get; set; }
186 | public Queries Queries { get; set; }
187 | public string Query
188 | {
189 | get => Queries?.ToString();
190 | set => Queries = Queries.Parse(value);
191 | }
192 |
193 | public static URL Parse(string s)
194 | {
195 | if (!TryParse(s, out URL result))
196 | throw new ArgumentException("Invalid query");
197 | return result;
198 | }
199 | public static bool TryParse(string s, out URL result)
200 | {
201 | URL temp = new URL();
202 | result = null;
203 |
204 | string[] splitForHash = s.Split("#");
205 | if (splitForHash.Length > 1) temp.Hash = splitForHash[1];
206 | string[] splitForSearch = splitForHash[0].Split("?");
207 | if (splitForSearch.Length > 1)
208 | {
209 | if (!Queries.TryParse("?" + splitForSearch[1], out Queries searches))
210 | return false;
211 | temp.Queries = searches;
212 | }
213 | Path tempPath = new Path();
214 | if (!tempPath.Initialize(splitForSearch[0]))
215 | return false;
216 | temp.Path = tempPath;
217 | result = temp;
218 | return true;
219 | }
220 |
221 | public URL()
222 | {
223 | Path = null;
224 | Hash = null;
225 | Queries = null;
226 | }
227 | public URL(string path, string hash, string searchString)
228 | {
229 | Path.Traverse(path);
230 | Hash = hash;
231 | Query = searchString;
232 | }
233 | public URL(Path path, string hash, Queries searches)
234 | {
235 | Path = new Path(path.FullPath);
236 | Hash = hash;
237 | Queries = searches;
238 | }
239 | public URL(string query)
240 | {
241 | URL result = Parse(query);
242 | Path = result.Path;
243 | Hash = result.Hash;
244 | Queries = result.Queries;
245 | }
246 | public override string ToString() =>
247 | (Path.ToString() ?? throw new InvalidOperationException("Path in HttpQuery is null"))
248 | + (Query ?? "")
249 | + (Hash == null ? "" : "#" + Hash);
250 |
251 | public static implicit operator URL(string value) => Parse(value);
252 | public static implicit operator string(URL value) => value.ToString();
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/CSSockets/Http/Definition/Version.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace CSSockets.Http.Definition
4 | {
5 | public struct Version
6 | {
7 | public byte Major { get; }
8 | public byte Minor { get; }
9 |
10 | public static bool TryParse(string str, out Version result)
11 | {
12 | result = default(Version);
13 | if (str.Length < 5 || !str.StartsWith("HTTP/"))
14 | return false;
15 | str = str.Substring(5);
16 | string[] split = str.Split(".");
17 | if (split.Length != 2)
18 | return false;
19 | if (!byte.TryParse(split[0], out byte _1))
20 | return false;
21 | if (!byte.TryParse(split[1], out byte _2))
22 | return false;
23 | result = new Version(_1, _2);
24 | return true;
25 | }
26 | public static Version Parse(string str)
27 | {
28 | if (!TryParse(str, out Version result))
29 | throw new ArgumentException("Invalid string format");
30 | return result;
31 | }
32 |
33 | public Version(byte major, byte minor)
34 | {
35 | Major = major;
36 | Minor = minor;
37 | }
38 |
39 | public override string ToString()
40 | => "HTTP/" + Major + "." + Minor;
41 |
42 | public static implicit operator System.Version(Version version)
43 | => new System.Version(version.Major, version.Minor);
44 | public static implicit operator Version(System.Version version)
45 | {
46 | if (version.Revision != -1 || version.Build != -1 ||
47 | version.Major < 0 || version.Minor < 0 ||
48 | version.Major > 255 || version.Minor > 255)
49 | throw new InvalidOperationException("Cannot convert version " + version + " to an HTTP version");
50 | return new Version((byte)version.Major, (byte)version.Minor);
51 | }
52 | public static implicit operator Version(string str) => Parse(str);
53 | public static implicit operator string(Version version) => version.ToString();
54 |
55 | public static bool operator ==(Version a, Version b) => a.Major == b.Major && a.Minor == b.Minor;
56 | public static bool operator !=(Version a, Version b) => a.Major != b.Major || a.Minor != b.Minor;
57 | public override bool Equals(object obj)
58 | {
59 | if (obj is null) return false;
60 | if (!(obj is Version)) return false;
61 | Version actual = (Version)obj;
62 | return Major == actual.Major && Minor == actual.Minor;
63 | }
64 | public override int GetHashCode() => Major * 256 + Minor;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/Body.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using CSSockets.Streams;
4 | using System.Globalization;
5 | using CSSockets.Http.Definition;
6 |
7 | namespace CSSockets.Http.Reference
8 | {
9 | public sealed class BodySerializer : Duplex, IFinishable
10 | {
11 | private const string CRLF = "\r\n";
12 | private static readonly byte[] CRLF_BYTES = Encoding.ASCII.GetBytes(CRLF);
13 | private static readonly byte[] LAST_CHUNK = Encoding.ASCII.GetBytes("0" + CRLF + CRLF);
14 |
15 | public event ControlHandler OnFinish;
16 |
17 | public bool Finished { get; private set; } = true;
18 | public BodyType? Type { get; private set; } = null;
19 | public ulong CurrentLength { get; private set; } = 0;
20 | public ulong CurrentEncodedLength { get; private set; } = 0;
21 |
22 | private Duplex Transform = null;
23 | private bool IsTransforming = false;
24 | private bool IsTransformFinishable = false;
25 |
26 | public bool TrySetFor(BodyType type)
27 | {
28 | lock (Sync)
29 | {
30 | if (!Finished) return false;
31 | if (type.Compression == TransferCompression.Compress)
32 | return false;
33 | if (type.Encoding == TransferEncoding.None)
34 | return true;
35 | Finished = false;
36 | Type = type;
37 | switch (type.Compression)
38 | {
39 | case TransferCompression.None:
40 | IsTransforming = false;
41 | break;
42 | case TransferCompression.Deflate:
43 | IsTransforming = true;
44 | Transform = new DeflateCompressor();
45 | IsTransformFinishable = true;
46 | break;
47 | case TransferCompression.Gzip:
48 | IsTransforming = true;
49 | Transform = new GzipCompressor();
50 | IsTransformFinishable = true;
51 | break;
52 | }
53 | return true;
54 | }
55 | }
56 |
57 | protected sealed override bool HandleWritable(byte[] source)
58 | {
59 | if (IsTransforming)
60 | {
61 | if (!Transform.Write(source)) return false;
62 | if (Transform.BufferedReadable > 0) return WriteChunk(Transform.Read());
63 | return true;
64 | }
65 | return WriteChunk(source);
66 | }
67 | private bool WriteChunk(byte[] source)
68 | {
69 | switch (Type.Value.Encoding)
70 | {
71 | case TransferEncoding.None: return false;
72 | case TransferEncoding.Binary:
73 | CurrentLength = CurrentEncodedLength += (ulong)source.LongLength;
74 | if (CurrentEncodedLength > Type.Value.Length) return false;
75 | return HandleReadable(source);
76 | case TransferEncoding.Chunked:
77 | string str = source.Length.ToString("X");
78 | if (!HandleReadable(Encoding.ASCII.GetBytes(str))) return false;
79 | if (!HandleReadable(CRLF_BYTES)) return false;
80 | if (!HandleReadable(source)) return false;
81 | if (!HandleReadable(CRLF_BYTES)) return false;
82 | CurrentLength += (ulong)source.LongLength;
83 | CurrentEncodedLength += (ulong)str.Length + 2 + (ulong)source.LongLength + 2;
84 | return true;
85 | default: return false;
86 | }
87 | }
88 | private bool WriteLast()
89 | {
90 | switch (Type.Value.Encoding)
91 | {
92 | case TransferEncoding.None: return false;
93 | case TransferEncoding.Binary: return true;
94 | case TransferEncoding.Chunked: return HandleReadable(LAST_CHUNK);
95 | default: return false;
96 | }
97 | }
98 | public bool Finish()
99 | {
100 | lock (Sync)
101 | {
102 | if (Finished) return false;
103 | if (IsTransforming)
104 | {
105 | if (IsTransformFinishable && !(Transform as IFinishable).Finish())
106 | return false;
107 | if (Transform.BufferedReadable > 0 && !WriteChunk(Transform.Read()))
108 | return false;
109 | }
110 | if (!WriteLast()) return false;
111 | Transform = null;
112 | IsTransforming = false;
113 | IsTransformFinishable = false;
114 | Finished = true;
115 | Type = null;
116 | CurrentLength = CurrentEncodedLength = 0;
117 | OnFinish?.Invoke();
118 | return true;
119 | }
120 | }
121 | }
122 |
123 | public sealed class BodyParser : Duplex, IFinishable
124 | {
125 | private enum ParseState : byte
126 | {
127 | Dormant,
128 | Binary,
129 | Chunked_Length,
130 | Chunked_LengthLf,
131 | Chunked_ChunkData,
132 | Chunked_ChunkCr,
133 | Chunked_ChunkLf,
134 | Chunked_Trailer,
135 | Chunked_Lf
136 | }
137 | private const char CR = '\r';
138 | private const char LF = '\n';
139 |
140 | private ParseState State = ParseState.Dormant;
141 | public bool Finished => State == ParseState.Dormant;
142 | public bool Malformed { get; private set; } = false;
143 | public event ControlHandler OnFinish;
144 |
145 | private Duplex ExcessStore = new MemoryDuplex();
146 | public IReadable Excess => ExcessStore;
147 |
148 | private Duplex Transform = null;
149 | private bool IsTransforming = false;
150 | private bool IsTransformFinishable = false;
151 |
152 | private string ChunkLengthString = null;
153 | private ulong ChunkIndex = 0;
154 | private ulong ChunkLength = 0;
155 |
156 | public BodyType? Type { get; private set; } = null;
157 | public ulong ContentLength { get; private set; } = 0;
158 | public ulong EncodedContentLength { get; private set; } = 0;
159 |
160 | public bool TrySetFor(BodyType type)
161 | {
162 | lock (Sync)
163 | {
164 | if (State != ParseState.Dormant) return false;
165 | if (type.Compression == TransferCompression.Compress)
166 | return false;
167 | if (type.Encoding == TransferEncoding.None)
168 | return true;
169 | Type = type;
170 | switch (type.Compression)
171 | {
172 | case TransferCompression.None:
173 | IsTransforming = false;
174 | break;
175 | case TransferCompression.Deflate:
176 | IsTransforming = true;
177 | Transform = new DeflateDecompressor();
178 | IsTransformFinishable = true;
179 | break;
180 | case TransferCompression.Gzip:
181 | IsTransforming = true;
182 | Transform = new GzipDecompressor();
183 | IsTransformFinishable = true;
184 | break;
185 | }
186 | switch (type.Encoding)
187 | {
188 | case TransferEncoding.Binary: State = ParseState.Binary; break;
189 | case TransferEncoding.Chunked: State = ParseState.Chunked_Length; break;
190 | }
191 | return true;
192 | }
193 | }
194 |
195 | protected sealed override bool HandleWritable(byte[] source)
196 | {
197 | if (Malformed) return false;
198 | char c = '\0'; ulong length, i = 0, sourceLength = (ulong)source.LongLength;
199 | for (; i < sourceLength;)
200 | switch (State)
201 | {
202 | case ParseState.Dormant: return false;
203 | case ParseState.Binary:
204 | ContentLength = EncodedContentLength += sourceLength;
205 | if (!WriteChunk(source)) return false;
206 | if (Type.Value.Length.HasValue && Type.Value.Length == ContentLength)
207 | return Finish();
208 | return true;
209 | case ParseState.Chunked_Length:
210 | EncodedContentLength++;
211 | c = (char)source[i++];
212 | if (c == CR) State = ParseState.Chunked_LengthLf;
213 | else ChunkLengthString = ChunkLengthString == null ? c.ToString() : ChunkLengthString + c;
214 | break;
215 | case ParseState.Chunked_LengthLf:
216 | EncodedContentLength++;
217 | c = (char)source[i++];
218 | if (c != LF) return !(Malformed = true);
219 | ChunkIndex = 0;
220 | if (!ulong.TryParse(ChunkLengthString, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out length))
221 | return !(Malformed = true);
222 | ChunkLengthString = null;
223 | State = (ChunkLength = length) == 0 ? ParseState.Chunked_Trailer : ParseState.Chunked_ChunkData;
224 | break;
225 | case ParseState.Chunked_ChunkData:
226 | length = Math.Min(ChunkLength - ChunkIndex, sourceLength - i);
227 | WriteChunk(PrimitiveBuffer.Slice(source, i, i += length));
228 | ChunkIndex += length; ContentLength += length; EncodedContentLength += length;
229 | if (ChunkIndex >= ChunkLength) State = ParseState.Chunked_ChunkCr;
230 | break;
231 | case ParseState.Chunked_ChunkCr:
232 | EncodedContentLength++;
233 | c = (char)source[i++];
234 | if (c != CR) return !(Malformed = true);
235 | State = ParseState.Chunked_ChunkLf;
236 | break;
237 | case ParseState.Chunked_ChunkLf:
238 | EncodedContentLength++;
239 | c = (char)source[i++];
240 | if (c != LF) return !(Malformed = true);
241 | ChunkIndex = 0;
242 | State = ParseState.Chunked_Length;
243 | break;
244 | case ParseState.Chunked_Trailer:
245 | while ((c = (char)source[i++]) != CR) EncodedContentLength++;
246 | State = ParseState.Chunked_Lf;
247 | break;
248 | case ParseState.Chunked_Lf:
249 | EncodedContentLength++;
250 | c = (char)source[i++];
251 | if (c != LF) return !(Malformed = true);
252 | ExcessStore.Write(source, i);
253 | return Finish();
254 | }
255 | return true;
256 | }
257 | private bool WriteChunk(byte[] source)
258 | {
259 | if (IsTransforming)
260 | {
261 | if (!Transform.Write(source)) return false;
262 | if (Transform.BufferedReadable > 0) return HandleReadable(Transform.Read());
263 | return true;
264 | }
265 | return HandleReadable(source);
266 | }
267 |
268 | public bool Finish()
269 | {
270 | lock (Sync)
271 | {
272 | if (State == ParseState.Dormant) return false;
273 | State = ParseState.Dormant;
274 | if (IsTransforming)
275 | {
276 | if (IsTransformFinishable && !(Transform as IFinishable).Finish())
277 | return false;
278 | if (Transform.BufferedReadable > 0 && !HandleReadable(Transform.Read()))
279 | return false;
280 | }
281 | Transform = null;
282 | Malformed = false;
283 | IsTransforming = IsTransformFinishable = false;
284 | ChunkLengthString = null;
285 | ChunkIndex = ChunkLength = 0;
286 | OnFinish?.Invoke();
287 | return true;
288 | }
289 | }
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/ClientConnection.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using CSSockets.Tcp;
3 | using CSSockets.Streams;
4 | using CSSockets.Http.Definition;
5 | using System.Collections.Generic;
6 |
7 | namespace CSSockets.Http.Reference
8 | {
9 | public sealed class ClientConnection : Connection
10 | {
11 | private OutgoingRequest _Outgoing = null;
12 | private IncomingResponse _Incoming = null;
13 | public override OutgoingMessage Outgoing => _Outgoing;
14 | public override IncomingMessage Incoming => _Incoming;
15 | private readonly Queue unanswered = new Queue();
16 |
17 | public ClientConnection(EndPoint connectEndPoint, ControlHandler onOpen = null) : this(new Connection())
18 | => Base.Connect(connectEndPoint);
19 | public ClientConnection(Connection connection) : base(connection)
20 | {
21 | IncomingHead = new ResponseHeadParser();
22 | IncomingBody = new BodyParser();
23 | OutgoingHead = new RequestHeadSerializer();
24 | OutgoingBody = new BodySerializer();
25 | IncomingHead.OnFail += OnSegmentFail;
26 | IncomingBody.OnFail += OnSegmentFail;
27 | OutgoingHead.OnFail += OnSegmentFail;
28 | OutgoingBody.OnFail += OnSegmentFail;
29 | IncomingHead.Pipe(excess);
30 | IncomingBody.Excess.Pipe(excess);
31 | }
32 |
33 | private readonly MemoryDuplex excess = new MemoryDuplex();
34 | private bool gotReqHead = false;
35 | private bool hasReqBody = false;
36 | private bool pdnResHead = false;
37 | private bool gotResHead = false;
38 | private bool gotResBody = false;
39 |
40 | public OutgoingRequest Enqueue(Version version, string method, URL url)
41 | {
42 | lock (Sync)
43 | {
44 | if (_Outgoing != null) return null;
45 | gotReqHead = hasReqBody = gotResHead = gotResBody = false;
46 | OutgoingRequest newPending = new OutgoingRequest(this, version, method, url);
47 | _Outgoing = newPending;
48 | unanswered.Enqueue(newPending);
49 | return newPending;
50 | }
51 | }
52 | public override bool StartOutgoing(RequestHead head)
53 | {
54 | lock (Sync)
55 | {
56 | if (_Outgoing == null) return false;
57 |
58 | BodyType? type = BodyType.TryDetectFor(head, true);
59 | if (type == null) return false;
60 | if (!OutgoingBody.TrySetFor(type.Value)) return false;
61 |
62 | OutgoingHead.Write(head);
63 | OutgoingHead.Burst(Base);
64 |
65 | if (!OutgoingBody.Finished)
66 | {
67 | hasReqBody = true;
68 | OutgoingBody.OnFinish += FinishOutgoingMessage;
69 | OutgoingBody.Pipe(Base);
70 | _Outgoing.Pipe(OutgoingBody);
71 | }
72 | WaitIncoming();
73 | return gotReqHead = true;
74 | }
75 | }
76 | private void FinishOutgoingMessage()
77 | {
78 | if (!FinishOutgoing()) Terminate();
79 | }
80 | public override bool FinishOutgoing()
81 | {
82 | lock (Sync)
83 | {
84 | if (!hasReqBody) return true;
85 | OutgoingBody.OnFinish -= FinishOutgoingMessage;
86 | if (!OutgoingBody.Finished) OutgoingBody.Finish();
87 | _Outgoing.Finish();
88 | OutgoingBody.Unpipe();
89 | _Outgoing.Unpipe();
90 | _Outgoing = null;
91 | return !(hasReqBody = false);
92 | }
93 | }
94 | private void WaitIncoming()
95 | {
96 | lock (Sync)
97 | {
98 | if (pdnResHead || unanswered.Count == 0) return;
99 | pdnResHead = true;
100 | IncomingHead.OnCollect += PushIncoming;
101 | excess.Burst(IncomingHead);
102 | if (!gotResHead) Base.Pipe(IncomingHead);
103 | }
104 | }
105 | private void PushIncoming(ResponseHead head)
106 | {
107 | lock (Sync)
108 | {
109 | gotResHead = true;
110 | IncomingHead.OnCollect -= PushIncoming;
111 |
112 | BodyType? type = BodyType.TryDetectFor(head, true);
113 | if (type == null) { Terminate(); return; }
114 | if (!IncomingBody.TrySetFor(type.Value)) { Terminate(); return; }
115 |
116 | IncomingResponse incoming = _Incoming = new IncomingResponse(this, head);
117 | OutgoingRequest outgoing = unanswered.Dequeue();
118 |
119 | outgoing.FireResponse(incoming);
120 | if (Ended || Frozen) return;
121 |
122 | if (!IncomingBody.Finished)
123 | {
124 | IncomingBody.OnFinish += FinishIncomingMessage;
125 | IncomingBody.Pipe(_Incoming);
126 | excess.Burst(IncomingBody);
127 | if (!gotResBody) Base.Pipe(IncomingBody);
128 | }
129 | }
130 | }
131 | private void FinishIncomingMessage()
132 | {
133 | lock (Sync)
134 | {
135 | gotResBody = true;
136 | IncomingBody.OnFinish -= FinishIncomingMessage;
137 | _Incoming.Finish();
138 | _Incoming = null;
139 | Base.Unpipe();
140 | pdnResHead = false;
141 | WaitIncoming();
142 | }
143 | }
144 | private void OnSegmentFail() => Terminate();
145 | public override byte[] Freeze()
146 | {
147 | lock (Sync)
148 | {
149 | _Incoming = null; _Outgoing = null; Frozen = true; unanswered.Clear();
150 | gotReqHead = hasReqBody = pdnResHead = gotResHead = gotResBody = false;
151 | Base.Unpipe();
152 | IncomingHead.Unpipe();
153 | IncomingHead.OnFail -= OnSegmentFail;
154 | if (pdnResHead) IncomingHead.OnCollect -= PushIncoming;
155 | IncomingBody.Unpipe();
156 | IncomingBody.Excess.Unpipe();
157 | IncomingBody.OnFail -= OnSegmentFail;
158 | if (!hasReqBody) IncomingBody.OnFinish -= FinishIncomingMessage;
159 | OutgoingHead.Unpipe();
160 | OutgoingHead.OnFail -= OnSegmentFail;
161 | OutgoingBody.Unpipe();
162 | OutgoingBody.OnFail -= OnSegmentFail;
163 | if (!gotResBody) OutgoingBody.OnFinish -= FinishOutgoingMessage;
164 | return excess.Read();
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/Listener.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using CSSockets.Tcp;
3 | using System.Collections.Generic;
4 |
5 | namespace CSSockets.Http.Reference
6 | {
7 | public delegate void ConnectionHandler(ServerConnection connection);
8 | public sealed class Listener
9 | {
10 | private readonly object Sync = new object();
11 | private readonly HashSet Connections = new HashSet();
12 |
13 | public Tcp.Listener Base { get; }
14 | public bool Listening => Base.Listening;
15 | public EndPoint BindEndPoint
16 | {
17 | get => Base.BindEndPoint;
18 | set => Base.BindEndPoint = value;
19 | }
20 |
21 | public ConnectionHandler OnConnection { get; set; } = null;
22 | private IncomingRequestHandler _OnRequest = null;
23 | public IncomingRequestHandler OnRequest
24 | {
25 | get => _OnRequest;
26 | set => _OnRequest = Listening ? _OnRequest : value;
27 | }
28 |
29 | public Listener()
30 | {
31 | Base = new Tcp.Listener();
32 | Base.OnConnection += ConnectionHandler;
33 | }
34 | public Listener(EndPoint endPoint)
35 | {
36 | Base = new Tcp.Listener(endPoint);
37 | Base.OnConnection += ConnectionHandler;
38 | }
39 | public Listener(Tcp.Listener listener)
40 | {
41 | Base = listener;
42 | Base.OnConnection += ConnectionHandler;
43 | }
44 |
45 | public void Start()
46 | {
47 | lock (Sync) Base.Start();
48 | }
49 | public void Stop()
50 | {
51 | lock (Sync)
52 | {
53 | Base.Stop();
54 | Connection[] connections = new Connection[Connections.Count];
55 | Connections.CopyTo(connections);
56 | for (int i = 0; i < connections.Length; i++) connections[i].Terminate();
57 | Connections.Clear();
58 | }
59 | }
60 |
61 | private void ConnectionHandler(Connection connection)
62 | {
63 | ServerConnection upgraded;
64 | lock (Sync)
65 | {
66 | if (!Listening) { connection.Terminate(); return; }
67 | upgraded = new ServerConnection(connection, _OnRequest);
68 | Connections.Add(connection);
69 | upgraded.OnEnd += () => { lock (Sync) Connections.Remove(connection); };
70 | }
71 | OnConnection?.Invoke(upgraded);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/Messages.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Streams;
2 | using CSSockets.Http.Definition;
3 |
4 | namespace CSSockets.Http.Reference
5 | {
6 | public sealed class IncomingRequest : IncomingMessage
7 | {
8 | public new ServerConnection Connection => base.Connection as ServerConnection;
9 |
10 | public URL URL => Head.URL;
11 | public Path Path => Head.URL.Path;
12 | public string Hash => Head.URL.Hash;
13 | public string Method => Head.Method;
14 | public Queries Queries => Head.URL.Queries;
15 |
16 | public IncomingRequest(ServerConnection connection, RequestHead head) : base(connection, head) { }
17 | }
18 | public sealed class IncomingResponse : IncomingMessage
19 | {
20 | public new ClientConnection Connection => base.Connection as ClientConnection;
21 |
22 | public ushort StatusCode => Head.StatusCode;
23 | public string StatusDescription => Head.StatusDescription;
24 |
25 | public IncomingResponse(ClientConnection connection, ResponseHead head) : base(connection, head) { }
26 | }
27 |
28 | public delegate void IncomingResponseHandler(IncomingResponse response);
29 | public sealed class OutgoingRequest : OutgoingMessage
30 | {
31 | public new ClientConnection Connection => base.Connection as ClientConnection;
32 |
33 | public string Method
34 | {
35 | get { lock (Sync) return Head.Method; }
36 | set { lock (Sync) { if (SentHead) return; Head.Method = value; } }
37 | }
38 | public URL URL
39 | {
40 | get { lock (Sync) return Head.URL; }
41 | set { lock (Sync) { if (SentHead) return; Head.URL = value; } }
42 | }
43 |
44 | public bool Responded { get; private set; } = false;
45 | public event IncomingResponseHandler OnResponse;
46 | internal bool FireResponse(IncomingResponse response)
47 | {
48 | lock (Sync)
49 | {
50 | if (Responded) return false;
51 | OnResponse?.Invoke(response);
52 | return Responded = true;
53 | }
54 | }
55 |
56 | public OutgoingRequest(ClientConnection connection, Version version, string method, URL url) : base(connection, version)
57 | {
58 | Method = method;
59 | URL = url;
60 | }
61 | }
62 | public sealed class OutgoingResponse : OutgoingMessage
63 | {
64 | public new ServerConnection Connection => base.Connection as ServerConnection;
65 |
66 | public ushort StatusCode
67 | {
68 | get { lock (Sync) return Head.StatusCode; }
69 | set { lock (Sync) { if (SentHead) return; Head.StatusCode = value; } }
70 | }
71 | public string StatusDescription
72 | {
73 | get { lock (Sync) return Head.StatusDescription; }
74 | set { lock (Sync) { if (SentHead) return; Head.StatusDescription = value; } }
75 | }
76 |
77 | public OutgoingResponse(ServerConnection connection, Version version) : base(connection, version) { }
78 |
79 | public bool SendHead(ushort statusCode, string statusDescription)
80 | {
81 | lock (Sync)
82 | {
83 | if (SentHead) return false;
84 | Head.StatusCode = statusCode;
85 | Head.StatusDescription = statusDescription;
86 | return SendHead();
87 | }
88 | }
89 | public bool End(ushort statusCode, string statusDescription)
90 | {
91 | lock (Sync)
92 | {
93 | if (SentHead) return false;
94 | Head.StatusCode = statusCode;
95 | Head.StatusDescription = statusDescription;
96 | return End();
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/RequestHead.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Streams;
2 | using CSSockets.Http.Definition;
3 |
4 | namespace CSSockets.Http.Reference
5 | {
6 | public sealed class RequestHead : Head
7 | {
8 | public string Method { get; set; }
9 | public URL URL { get; set; }
10 |
11 | public RequestHead() : base() { }
12 | public RequestHead(Version version, string method, URL query) : base(version)
13 | {
14 | Method = method;
15 | URL = query;
16 | }
17 | public RequestHead(Version version, string method, URL query, HeaderCollection headers) : base(version, headers)
18 | {
19 | Method = method;
20 | URL = query;
21 | }
22 | }
23 |
24 | public sealed class RequestHeadSerializer : HeadSerializer
25 | {
26 | public override bool Write(RequestHead source)
27 | {
28 | string stringified = source.Method + WS + source.URL + WS + source.Version + CRLF;
29 | for (int i = 0; i < source.Headers.Length; i++)
30 | stringified += source.Headers[i].Key.ToLower() + COLON + WS + source.Headers[i].Value + CRLF;
31 | stringified += CRLF;
32 | return HandleReadable(System.Text.Encoding.ASCII.GetBytes(stringified));
33 | }
34 | }
35 | public sealed class RequestHeadParser : HeadParser
36 | {
37 | private enum ParseState : byte
38 | {
39 | Method,
40 | Query,
41 | Version,
42 | FirstLf,
43 | HeaderName,
44 | HeaderValue,
45 | HeaderLf,
46 | Lf
47 | }
48 |
49 | private ParseState State = ParseState.Method;
50 | public bool Malformed { get; private set; } = false;
51 |
52 | public RequestHeadParser() => Reset();
53 |
54 | private string IncomingMethod;
55 | private string IncomingQuery;
56 | private string IncomingVersion;
57 | private Version IncomingVersionValue;
58 | private HeaderCollection IncomingHeaders;
59 | private string IncomingHeaderName;
60 | private string IncomingHeaderValue;
61 |
62 | public bool Reset()
63 | {
64 | lock (Sync)
65 | {
66 | Malformed = false;
67 | State = ParseState.Method;
68 | IncomingMethod = "";
69 | IncomingQuery = "";
70 | IncomingVersion = "";
71 | IncomingVersionValue = default(Version);
72 | IncomingHeaders = new HeaderCollection();
73 | IncomingHeaderName = "";
74 | IncomingHeaderValue = "";
75 | return true;
76 | }
77 | }
78 | protected override bool HandleWritable(byte[] source)
79 | {
80 | if (Malformed) return false;
81 | ulong i = 0, sourceLength = (ulong)source.LongLength;
82 | for (char c; i < sourceLength; i++)
83 | {
84 | c = (char)source[i];
85 | switch (State)
86 | {
87 | case ParseState.Method:
88 | if (c != WS) IncomingMethod += c;
89 | else State = ParseState.Query;
90 | break;
91 | case ParseState.Query:
92 | if (c != WS) IncomingQuery += c;
93 | else
94 | {
95 | if (!URL.TryParse(IncomingQuery, out URL result))
96 | return !(Malformed = true);
97 | IncomingQuery = result;
98 | State = ParseState.Version;
99 | }
100 | break;
101 | case ParseState.Version:
102 | if (c != CR) IncomingVersion += c;
103 | else
104 | {
105 | if (!Version.TryParse(IncomingVersion, out Version result))
106 | return !(Malformed = true);
107 | IncomingVersionValue = result;
108 | State = ParseState.FirstLf;
109 | }
110 | break;
111 | case ParseState.FirstLf:
112 | if (c != LF) return !(Malformed = true);
113 | State = ParseState.HeaderName;
114 | break;
115 | case ParseState.HeaderName:
116 | if (c == CR) State = ParseState.Lf;
117 | else if (c != COLON) IncomingHeaderName += c;
118 | else State = ParseState.HeaderValue;
119 | break;
120 | case ParseState.HeaderValue:
121 | if (c != CR) IncomingHeaderValue += c;
122 | else
123 | {
124 | IncomingHeaders.Add(IncomingHeaderName, IncomingHeaderValue.TrimStart());
125 | IncomingHeaderName = "";
126 | IncomingHeaderValue = "";
127 | State = ParseState.HeaderLf;
128 | }
129 | break;
130 | case ParseState.HeaderLf:
131 | if (c != LF) return !(Malformed = true);
132 | else State = ParseState.HeaderName;
133 | break;
134 | case ParseState.Lf:
135 | if (c != LF) return !(Malformed = true);
136 | HandleReadable(PrimitiveBuffer.Slice(source, i + 1, sourceLength));
137 | Pickup(new RequestHead(IncomingVersionValue, IncomingMethod, IncomingQuery, IncomingHeaders));
138 | return Reset();
139 | }
140 | }
141 | return true;
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/ResponseHead.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Streams;
2 | using CSSockets.Http.Definition;
3 |
4 | namespace CSSockets.Http.Reference
5 | {
6 | public sealed class ResponseHead : Head
7 | {
8 | public ushort StatusCode { get; set; }
9 | public string StatusDescription { get; set; }
10 |
11 | public ResponseHead() : base() { }
12 | public ResponseHead(Version version, ushort statusCode, string statusDescription) : base(version)
13 | {
14 | StatusCode = statusCode;
15 | StatusDescription = statusDescription;
16 | }
17 | public ResponseHead(Version version, ushort statusCode, string statusDescription, HeaderCollection headers) : base(version, headers)
18 | {
19 | StatusCode = statusCode;
20 | StatusDescription = statusDescription;
21 | }
22 | }
23 | public sealed class ResponseHeadSerializer : HeadSerializer
24 | {
25 | public override bool Write(ResponseHead source)
26 | {
27 | string stringified = source.Version + WS + source.StatusCode + WS + source.StatusDescription + CRLF;
28 | for (int i = 0; i < source.Headers.Length; i++)
29 | stringified += source.Headers[i].Key.ToLower() + COLON + WS + source.Headers[i].Value + CRLF;
30 | stringified += CRLF;
31 | return HandleReadable(System.Text.Encoding.ASCII.GetBytes(stringified));
32 | }
33 | }
34 | public sealed class ResponseHeadParser : HeadParser
35 | {
36 | private enum ParseState : byte
37 | {
38 | Version,
39 | StatusCode,
40 | StatusDescription,
41 | FirstLf,
42 | HeaderName,
43 | HeaderValue,
44 | HeaderLf,
45 | Lf
46 | }
47 |
48 | private ParseState State = ParseState.Version;
49 | public bool Malformed { get; private set; } = false;
50 |
51 | public ResponseHeadParser() => Reset();
52 |
53 | private string IncomingVersion;
54 | private Version IncomingVersionValue;
55 | private string IncomingStatusCode;
56 | private string IncomingStatusDescription;
57 | private HeaderCollection IncomingHeaders;
58 | private string IncomingHeaderName;
59 | private string IncomingHeaderValue;
60 |
61 | public bool Reset()
62 | {
63 | lock (Sync)
64 | {
65 | Malformed = false;
66 | State = ParseState.Version;
67 | IncomingVersion = "";
68 | IncomingVersionValue = default(Version);
69 | IncomingStatusCode = "";
70 | IncomingStatusDescription = "";
71 | IncomingHeaders = new HeaderCollection();
72 | IncomingHeaderName = "";
73 | IncomingHeaderValue = "";
74 | return true;
75 | }
76 | }
77 | protected override bool HandleWritable(byte[] source)
78 | {
79 | if (Malformed) return false;
80 | ulong i = 0, sourceLength = (ulong)source.LongLength;
81 | for (char c; i < sourceLength; i++)
82 | {
83 | c = (char)source[i];
84 | switch (State)
85 | {
86 | case ParseState.Version:
87 | if (c != WS) IncomingVersion += c;
88 | else
89 | {
90 | if (!Version.TryParse(IncomingVersion, out Version result))
91 | return !(Malformed = true);
92 | IncomingVersionValue = result;
93 | State = ParseState.StatusCode;
94 | }
95 | break;
96 | case ParseState.StatusCode:
97 | if (c == WS) State = ParseState.StatusDescription;
98 | else if (char.IsDigit(c)) IncomingStatusCode += c;
99 | else return !(Malformed = true);
100 | break;
101 | case ParseState.StatusDescription:
102 | if (c != CR) IncomingStatusDescription += c;
103 | else State = ParseState.FirstLf;
104 | break;
105 | case ParseState.FirstLf:
106 | if (c != LF) return !(Malformed = true);
107 | State = ParseState.HeaderName;
108 | break;
109 | case ParseState.HeaderName:
110 | if (c == CR) State = ParseState.Lf;
111 | else if (c != COLON) IncomingHeaderName += c;
112 | else State = ParseState.HeaderValue;
113 | break;
114 | case ParseState.HeaderValue:
115 | if (c != CR) IncomingHeaderValue += c;
116 | else
117 | {
118 | IncomingHeaders.Add(IncomingHeaderName, IncomingHeaderValue.TrimStart());
119 | IncomingHeaderName = "";
120 | IncomingHeaderValue = "";
121 | State = ParseState.HeaderLf;
122 | }
123 | break;
124 | case ParseState.HeaderLf:
125 | if (c != LF) return !(Malformed = true);
126 | else State = ParseState.HeaderName;
127 | break;
128 | case ParseState.Lf:
129 | if (c != LF) return !(Malformed = true);
130 | HandleReadable(PrimitiveBuffer.Slice(source, i + 1, sourceLength));
131 | Pickup(new ResponseHead(IncomingVersionValue, ushort.Parse(IncomingStatusCode), IncomingStatusDescription, IncomingHeaders));
132 | return Reset();
133 | }
134 | }
135 | return true;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/CSSockets/Http/Reference/ServerConnection.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Tcp;
2 | using CSSockets.Streams;
3 | using CSSockets.Http.Definition;
4 |
5 | namespace CSSockets.Http.Reference
6 | {
7 | public delegate void IncomingRequestHandler(IncomingRequest request, OutgoingResponse response);
8 | public sealed class ServerConnection : Connection
9 | {
10 | private IncomingRequest _Incoming = null;
11 | private OutgoingResponse _Outgoing = null;
12 | public override IncomingMessage Incoming => _Incoming;
13 | public override OutgoingMessage Outgoing => _Outgoing;
14 |
15 | public IncomingRequestHandler RequestHandler { get; set; }
16 |
17 | public ServerConnection(Connection connection, IncomingRequestHandler requestHandler) : base(connection)
18 | {
19 | IncomingHead = new RequestHeadParser();
20 | OutgoingHead = new ResponseHeadSerializer();
21 | IncomingBody = new BodyParser();
22 | OutgoingBody = new BodySerializer();
23 | IncomingHead.OnFail += OnSegmentFail;
24 | IncomingBody.OnFail += OnSegmentFail;
25 | OutgoingHead.OnFail += OnSegmentFail;
26 | OutgoingBody.OnFail += OnSegmentFail;
27 | IncomingHead.Pipe(excess);
28 | IncomingBody.Excess.Pipe(excess);
29 | RequestHandler = requestHandler;
30 | WaitIncoming();
31 | }
32 |
33 | private readonly MemoryDuplex excess = new MemoryDuplex();
34 | private bool gotReqHead = false;
35 | private bool gotReqBody = false;
36 | private bool gotResHead = false;
37 | private bool hasResBody = false;
38 |
39 | private void WaitIncoming()
40 | {
41 | lock (Sync)
42 | {
43 | _Incoming = null; _Outgoing = null;
44 | gotReqHead = gotReqBody = gotResHead = false;
45 | IncomingHead.OnCollect += PushIncoming;
46 | excess.Burst(IncomingHead);
47 | if (!gotReqHead) Base.Pipe(IncomingHead);
48 | }
49 | }
50 | private void PushIncoming(RequestHead head)
51 | {
52 | lock (Sync)
53 | {
54 | gotReqHead = true;
55 |
56 | BodyType? type = BodyType.TryDetectFor(head, true);
57 | if (type == null) { Terminate(); return; }
58 | if (!IncomingBody.TrySetFor(type.Value)) { Terminate(); return; }
59 |
60 | IncomingRequest incoming = _Incoming = new IncomingRequest(this, head);
61 | OutgoingResponse outgoing = _Outgoing = new OutgoingResponse(this, head.Version);
62 |
63 | RequestHandler(incoming, outgoing);
64 | if (Ended || Frozen) return;
65 |
66 | if (!IncomingBody.Finished)
67 | {
68 | IncomingBody.Pipe(_Incoming);
69 | IncomingBody.OnFinish += FinishIncomingMessage;
70 | excess.Burst(IncomingBody);
71 | if (!gotReqBody) Base.Pipe(IncomingBody);
72 | }
73 | }
74 | }
75 | private void FinishIncomingMessage()
76 | {
77 | lock (Sync)
78 | {
79 | gotReqBody = true;
80 | IncomingBody.OnFinish -= FinishIncomingMessage;
81 | IncomingBody.Unpipe();
82 | _Incoming.Finish();
83 | Base.Unpipe();
84 | }
85 | }
86 | public override bool StartOutgoing(ResponseHead head)
87 | {
88 | lock (Sync)
89 | {
90 | if (!gotReqHead) return false;
91 |
92 | BodyType? type = BodyType.TryDetectFor(head, true);
93 | if (type == null) return false;
94 | if (!OutgoingBody.TrySetFor(type.Value)) return false;
95 |
96 | OutgoingHead.Write(head);
97 | OutgoingHead.Burst(Base);
98 |
99 | if (!OutgoingBody.Finished)
100 | {
101 | hasResBody = true;
102 | OutgoingBody.OnFinish += FinishOutgoingMessage;
103 | OutgoingBody.Pipe(Base);
104 | _Outgoing.Pipe(OutgoingBody);
105 | }
106 | return gotResHead = true;
107 | }
108 | }
109 | public override bool FinishOutgoing()
110 | {
111 | lock (Sync)
112 | {
113 | if (!gotResHead) return false;
114 | OutgoingBody.OnFinish -= FinishOutgoingMessage;
115 | if (!OutgoingBody.Finished) OutgoingBody.Finish();
116 | OutgoingBody.Unpipe();
117 | _Outgoing.Finish();
118 | _Incoming = null;
119 | _Outgoing = null;
120 | WaitIncoming();
121 | return !(hasResBody = false);
122 | }
123 | }
124 | private void FinishOutgoingMessage()
125 | {
126 | if (!FinishOutgoing()) Terminate();
127 | }
128 | private void OnSegmentFail() => Terminate();
129 | public override byte[] Freeze()
130 | {
131 | lock (Sync)
132 | {
133 | _Incoming = null; _Outgoing = null; Frozen = true;
134 | gotReqHead = gotReqBody = gotResHead = hasResBody = false;
135 | Base.Unpipe();
136 | IncomingHead.Unpipe();
137 | IncomingHead.OnFail -= OnSegmentFail;
138 | if (!gotReqHead) IncomingHead.OnCollect -= PushIncoming;
139 | IncomingBody.Unpipe();
140 | IncomingBody.Excess.Unpipe();
141 | IncomingBody.OnFail -= OnSegmentFail;
142 | if (!gotReqBody) IncomingBody.OnFinish -= FinishIncomingMessage;
143 | OutgoingHead.Unpipe();
144 | OutgoingHead.OnFail -= OnSegmentFail;
145 | OutgoingBody.Unpipe();
146 | OutgoingBody.OnFail -= OnSegmentFail;
147 | if (!hasResBody) OutgoingBody.OnFinish -= FinishOutgoingMessage;
148 | return excess.Read();
149 | }
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/CSSockets/Streams/Base.cs:
--------------------------------------------------------------------------------
1 | namespace CSSockets.Streams
2 | {
3 | public delegate void ControlHandler();
4 | public delegate void DataHandler(byte[] data);
5 | public delegate void OutputHandler(T item);
6 |
7 | public interface IEndable
8 | {
9 | bool Ended { get; }
10 | bool End();
11 | }
12 | public interface IFinishable
13 | {
14 | bool Finished { get; }
15 | bool Finish();
16 | }
17 |
18 | public interface IReadable
19 | {
20 | event DataHandler OnData;
21 | event ControlHandler OnFail;
22 |
23 | IWritable PipedTo { get; }
24 | bool Pipe(IWritable to);
25 | bool Burst(IWritable to);
26 | bool Unpipe();
27 |
28 | ulong BufferedReadable { get; }
29 | ulong ReadCount { get; }
30 |
31 | ulong Read(byte[] destination, ulong start = 0);
32 | byte[] Read(ulong length);
33 | byte[] Read();
34 | }
35 | public interface IWritable
36 | {
37 | ulong WriteCount { get; }
38 |
39 | bool Unpipe(IReadable from);
40 | bool Write(byte[] source);
41 | bool Write(byte[] source, ulong start);
42 | bool Write(byte[] source, ulong start, ulong end);
43 | }
44 | public interface IDuplex : IReadable, IWritable { }
45 |
46 | public interface ICollector
47 | {
48 | event OutputHandler OnCollect;
49 | }
50 | public interface ITransform
51 | {
52 | bool Write(T source);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/CSSockets/Streams/Compressors.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 |
4 | namespace CSSockets.Streams
5 | {
6 | public class Compressor : Duplex, IFinishable where T : Stream
7 | {
8 | protected readonly SimpleStream Backstore = new SimpleStream();
9 | protected T Transformer;
10 | public CompressionLevel CompressionLevel { get; }
11 | public bool Finished { get; private set; } = false;
12 |
13 | protected Compressor(CompressionLevel compressionLevel) => CompressionLevel = compressionLevel;
14 |
15 | protected bool PipeTransformer()
16 | {
17 | byte[] ret = new byte[Backstore.Length];
18 | Backstore.Read(ret, 0, (int)Backstore.Length);
19 | return HandleReadable(ret);
20 | }
21 | protected override bool HandleWritable(byte[] source)
22 | {
23 | if (Finished) throw new System.ObjectDisposedException("Stream has ended", null as System.Exception);
24 | Transformer.Write(source, 0, source.Length);
25 | return PipeTransformer();
26 | }
27 |
28 | public bool Finish()
29 | {
30 | lock (Sync)
31 | {
32 | if (Finished) return false;
33 | Transformer.Dispose();
34 | PipeTransformer();
35 | Backstore.Dispose();
36 | return Finished = true;
37 | }
38 | }
39 | }
40 | public class DeflateCompressor : Compressor
41 | {
42 | public DeflateCompressor(CompressionLevel compressionLevel = CompressionLevel.Optimal) : base(compressionLevel)
43 | => Transformer = new DeflateStream(Backstore, compressionLevel);
44 | }
45 | public class GzipCompressor : Compressor
46 | {
47 | public GzipCompressor(CompressionLevel compressionLevel = CompressionLevel.Optimal) : base(compressionLevel)
48 | => Transformer = new GZipStream(Backstore, compressionLevel);
49 | }
50 |
51 | public class Decompressor : Duplex, IFinishable where T : Stream
52 | {
53 | public const int TRANSFER_SIZE_DEFAULT = 1024;
54 |
55 | protected readonly SimpleStream Backstore = new SimpleStream();
56 | protected T Transformer;
57 | public int TransferSize { get; set; }
58 | public bool Finished { get; private set; } = false;
59 |
60 | protected Decompressor(int transferSize) => TransferSize = transferSize;
61 |
62 | protected bool PipeTransformer()
63 | {
64 | byte[] ret = new byte[TransferSize]; int len;
65 | while ((len = Transformer.Read(ret, 0, ret.Length)) > 0)
66 | if (!HandleReadable(PrimitiveBuffer.Slice(ret, 0, len))) return false;
67 | return true;
68 | }
69 | protected override bool HandleWritable(byte[] source)
70 | {
71 | if (Finished) throw new System.ObjectDisposedException("Stream has ended", null as System.Exception);
72 | Backstore.Write(source, 0, source.Length);
73 | return PipeTransformer();
74 | }
75 |
76 | public bool Finish()
77 | {
78 | lock (Sync)
79 | {
80 | if (Finished) return false;
81 | Transformer.Dispose();
82 | Backstore.Dispose();
83 | return Finished = true;
84 | }
85 | }
86 | }
87 | public class DeflateDecompressor : Decompressor
88 | {
89 | public DeflateDecompressor(int transferSize = TRANSFER_SIZE_DEFAULT) : base(transferSize) => Transformer = new DeflateStream(Backstore, CompressionMode.Decompress);
90 | }
91 | public class GzipDecompressor : Decompressor
92 | {
93 | public GzipDecompressor(int transferSize = TRANSFER_SIZE_DEFAULT) : base(transferSize) => Transformer = new GZipStream(Backstore, CompressionMode.Decompress);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/CSSockets/Streams/Primitive.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace CSSockets.Streams
4 | {
5 | public class PrimitiveBuffer
6 | {
7 | ///
8 | /// Capacity modifier multiplier
9 | ///
10 | public const ulong CMM = 256;
11 |
12 | public byte[] Buffer = new byte[CMM];
13 | public ulong Length { get; private set; } = 0;
14 | public ulong Capacity { get; private set; } = CMM;
15 |
16 | private unsafe bool EnsureCapacity(ulong targetLen)
17 | {
18 | ulong beforeLen = Length;
19 | Length = targetLen;
20 | ulong targetCap = Capacity;
21 | if (targetCap > targetLen) while (targetCap / CMM >= CMM && targetCap / CMM > targetLen) targetCap /= CMM;
22 | else while (targetCap < targetLen) targetCap *= CMM;
23 | if (targetCap == Capacity) return false;
24 | byte[] newBuf = new byte[targetCap];
25 | fixed (byte* src = Buffer, dst = newBuf) Copy(src, 0, dst, 0, Math.Min(beforeLen, targetLen));
26 | Buffer = newBuf;
27 | Capacity = targetCap;
28 | return true;
29 | }
30 | public unsafe byte[] Read(ulong length) => Read(new byte[length]);
31 | public unsafe byte[] Read(byte[] ret) => Read(ret, (ulong)ret.LongLength);
32 | public unsafe byte[] Read(byte[] ret, ulong length, ulong start = 0)
33 | {
34 | if (ret == null) throw new ArgumentNullException(nameof(ret));
35 | if (length > Length) throw new ArgumentOutOfRangeException(nameof(length));
36 | fixed (byte* src = Buffer, dst = ret)
37 | {
38 | Copy(src, 0, dst + start, 0, length);
39 | ulong remaining = Length - length;
40 | Copy(src, length, src, 0, remaining);
41 | EnsureCapacity(remaining);
42 | }
43 | return ret;
44 | }
45 | public unsafe bool Write(byte[] data)
46 | {
47 | ulong len = (ulong)data.LongLength;
48 | ulong start = Length;
49 | ulong end = Length + len;
50 | EnsureCapacity(end);
51 | fixed (byte* src = data, dst = Buffer)
52 | Copy(src, 0, dst, start, len);
53 | return true;
54 | }
55 |
56 | private static unsafe void MEMCPY(void* dest, void* src, ulong count)
57 | => System.Buffer.MemoryCopy(src, dest, count, count);
58 | private static unsafe void MEMCPY(void* dest, void* src, int count)
59 | => System.Buffer.MemoryCopy(src, dest, count, count);
60 |
61 | public static unsafe byte[] Slice(byte[] source, ulong start, ulong end)
62 | {
63 | byte[] srcChunked = new byte[end - start];
64 | fixed (byte* src = source, dst = srcChunked)
65 | MEMCPY(dst, src + start, end - start);
66 | return srcChunked;
67 | }
68 | public static unsafe byte[] Slice(byte[] source, int start, int end)
69 | {
70 | byte[] srcChunked = new byte[end - start];
71 | fixed (byte* srcp = source, dstp = srcChunked)
72 | MEMCPY(dstp, srcp + start, end - start);
73 | return srcChunked;
74 | }
75 | public static unsafe void Copy(byte* src, ulong srcStart, byte* dst, ulong dstStart, ulong length)
76 | => MEMCPY(dst + dstStart, src + srcStart, length);
77 | public static unsafe void Copy(byte[] src, ulong srcStart, byte[] dst, ulong dstStart, ulong length)
78 | {
79 | fixed (byte* srcp = src, dstp = dst) MEMCPY(dstp + dstStart, srcp + srcStart, length);
80 | }
81 | public static unsafe void Copy(byte[] src, int srcStart, byte[] dst, int dstStart, int length)
82 | {
83 | fixed (byte* srcp = src, dstp = dst) MEMCPY(dstp + dstStart, srcp + srcStart, length);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/CSSockets/Streams/Wrappers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace CSSockets.Streams
5 | {
6 | public sealed class SimpleStream : Stream
7 | {
8 | private readonly PrimitiveBuffer Buffer = new PrimitiveBuffer();
9 | private readonly object Sync = new object();
10 |
11 | public override bool CanRead => true;
12 | public override bool CanSeek => false;
13 | public override bool CanWrite => true;
14 | public override long Length => (long)Buffer.Length;
15 | public override long Position { get => 0; set => throw new InvalidOperationException("Cannot seek"); }
16 |
17 | public override void Flush() { }
18 | public override int Read(byte[] buffer, int offset, int count)
19 | {
20 | lock (Sync)
21 | {
22 | int reading = Math.Min((int)Length, count);
23 | Buffer.Read(buffer, (ulong)reading, (uint)offset);
24 | return reading;
25 | }
26 | }
27 | public override long Seek(long offset, SeekOrigin origin)
28 | => throw new InvalidOperationException("Cannot seek");
29 | public override void SetLength(long value)
30 | => throw new InvalidOperationException("Cannot set length");
31 | public override void Write(byte[] buffer, int offset, int count)
32 | {
33 | lock (Sync) Buffer.Write(PrimitiveBuffer.Slice(buffer, offset, offset + count));
34 | }
35 | }
36 | public abstract class Readable : IReadable
37 | {
38 | protected readonly PrimitiveBuffer Buffer = new PrimitiveBuffer();
39 | protected readonly object Sync = new object();
40 |
41 | public IWritable PipedTo { get; private set; } = null;
42 | public ulong ReadCount { get; private set; } = 0;
43 | public ulong BufferedReadable => Buffer.Length;
44 |
45 | protected event DataHandler _OnData;
46 | public virtual event DataHandler OnData
47 | {
48 | add
49 | {
50 | lock (Sync)
51 | {
52 | _OnData += value;
53 | if (BufferedReadable > 0) HandleReadable(Read());
54 | }
55 | }
56 | remove
57 | {
58 | lock (Sync) _OnData -= value;
59 | }
60 | }
61 | public virtual event ControlHandler OnFail;
62 |
63 | public virtual bool Pipe(IWritable to)
64 | {
65 | lock (Sync)
66 | {
67 | if (to == null) return false;
68 | PipedTo = to;
69 | if (BufferedReadable > 0) HandleReadable(Read());
70 | return true;
71 | }
72 | }
73 | public virtual bool Burst(IWritable to)
74 | {
75 | lock (Sync)
76 | {
77 | if (to == null) return false;
78 | if (BufferedReadable > 0) return to.Write(Read());
79 | return true;
80 | }
81 | }
82 | public virtual bool Unpipe()
83 | {
84 | lock (Sync)
85 | {
86 | PipedTo = null;
87 | return true;
88 | }
89 | }
90 |
91 | protected bool HandleReadable(byte[] source)
92 | {
93 | lock (Sync)
94 | {
95 | if (PipedTo != null)
96 | {
97 | if (!PipedTo.Write(source)) OnFail?.Invoke();
98 | return true;
99 | }
100 | if (_OnData != null) { _OnData(source); return true; }
101 | return Buffer.Write(source);
102 | }
103 | }
104 | public virtual ulong Read(byte[] destination, ulong start = 0)
105 | {
106 | lock (Sync)
107 | {
108 | ulong length = BufferedReadable;
109 | byte[] data = Buffer.Read(length);
110 | PrimitiveBuffer.Copy(data, 0, destination, start, length);
111 | ReadCount += length;
112 | return length;
113 | }
114 | }
115 | public virtual byte[] Read(ulong length)
116 | {
117 | lock (Sync)
118 | {
119 | ulong retLength = Math.Min(BufferedReadable, length);
120 | if (retLength != length) return null;
121 | ReadCount += retLength;
122 | return Buffer.Read(length);
123 | }
124 | }
125 | public virtual byte[] Read()
126 | {
127 | lock (Sync)
128 | {
129 | ReadCount += BufferedReadable;
130 | return Buffer.Read(BufferedReadable);
131 | }
132 | }
133 | }
134 |
135 | public abstract class Writable : IWritable
136 | {
137 | protected readonly object Sync = new object();
138 |
139 | public ulong WriteCount { get; private set; } = 0;
140 |
141 | public virtual bool Unpipe(IReadable from)
142 | {
143 | lock (Sync) return from.PipedTo == this ? from.Unpipe() : false;
144 | }
145 |
146 | protected abstract bool HandleWritable(byte[] source);
147 | public virtual bool Write(byte[] source)
148 | {
149 | lock (Sync) return HandleWritable(source);
150 | }
151 | public virtual bool Write(byte[] source, ulong start)
152 | => Write(PrimitiveBuffer.Slice(source, start, (ulong)source.LongLength));
153 | public virtual bool Write(byte[] source, ulong start, ulong end)
154 | => Write(PrimitiveBuffer.Slice(source, start, end));
155 | }
156 |
157 | public abstract class Duplex : IDuplex
158 | {
159 | protected readonly PrimitiveBuffer Readable = new PrimitiveBuffer();
160 | protected readonly object Sync = new object();
161 |
162 | public IWritable PipedTo { get; private set; } = null;
163 | public ulong ReadCount { get; private set; } = 0;
164 | public ulong WriteCount { get; private set; } = 0;
165 | public ulong BufferedReadable => Readable.Length;
166 |
167 | protected event DataHandler _OnData;
168 | public virtual event DataHandler OnData
169 | {
170 | add
171 | {
172 | lock (Sync)
173 | {
174 | _OnData += value;
175 | if (BufferedReadable > 0) HandleReadable(Read());
176 | }
177 | }
178 | remove
179 | {
180 | lock (Sync) _OnData -= value;
181 | }
182 | }
183 | public virtual event ControlHandler OnFail;
184 |
185 | public virtual bool Pipe(IWritable to)
186 | {
187 | lock (Sync)
188 | {
189 | if (to == null) return false;
190 | PipedTo = to;
191 | if (BufferedReadable > 0) HandleReadable(Read());
192 | return true;
193 | }
194 | }
195 | public virtual bool Burst(IWritable to)
196 | {
197 | lock (Sync)
198 | {
199 | if (to == null) return false;
200 | if (BufferedReadable > 0) return to.Write(Read());
201 | return true;
202 | }
203 | }
204 | public virtual bool Unpipe()
205 | {
206 | lock (Sync)
207 | {
208 | PipedTo = null;
209 | return true;
210 | }
211 | }
212 | public virtual bool Unpipe(IReadable from)
213 | {
214 | lock (Sync) return from.PipedTo == this ? from.Unpipe() : false;
215 | }
216 |
217 | public virtual ulong Read(byte[] destination, ulong start = 0)
218 | {
219 | lock (Sync)
220 | {
221 | ulong length = Math.Min(BufferedReadable, (ulong)destination.LongLength);
222 | byte[] data = Readable.Read(length);
223 | PrimitiveBuffer.Copy(data, 0, destination, start, length);
224 | ReadCount += length;
225 | return length;
226 | }
227 | }
228 | public virtual byte[] Read(ulong length)
229 | {
230 | lock (Sync)
231 | {
232 | ulong retLength = Math.Min(BufferedReadable, length);
233 | if (retLength != length) return null;
234 | ReadCount += retLength;
235 | return Readable.Read(length);
236 | }
237 | }
238 | public virtual byte[] Read()
239 | {
240 | lock (Sync)
241 | {
242 | ReadCount += BufferedReadable;
243 | return Readable.Read(BufferedReadable);
244 | }
245 | }
246 |
247 | protected bool HandleReadable(byte[] source)
248 | {
249 | lock (Sync)
250 | {
251 | if (PipedTo != null)
252 | {
253 | if (!PipedTo.Write(source)) OnFail?.Invoke();
254 | return true;
255 | }
256 | if (_OnData != null) { _OnData(source); return true; }
257 | return Readable.Write(source);
258 | }
259 | }
260 | protected abstract bool HandleWritable(byte[] source);
261 | public virtual bool Write(byte[] source)
262 | {
263 | lock (Sync) return HandleWritable(source);
264 | }
265 | public virtual bool Write(byte[] source, ulong start)
266 | => Write(PrimitiveBuffer.Slice(source, start, (ulong)source.LongLength));
267 | public virtual bool Write(byte[] source, ulong start, ulong end)
268 | => Write(PrimitiveBuffer.Slice(source, start, end));
269 | }
270 |
271 | public abstract class Transform : Readable, ITransform
272 | {
273 | public abstract bool Write(T source);
274 | }
275 | public abstract class Collector : Writable, ICollector
276 | {
277 | public event OutputHandler OnCollect;
278 |
279 | protected bool Pickup(T item)
280 | {
281 | if (OnCollect != null) { OnCollect(item); return true; }
282 | return false;
283 | }
284 | }
285 | public abstract class Translator : Duplex, ICollector
286 | {
287 | public event OutputHandler OnCollect;
288 |
289 | protected bool Pickup(T item)
290 | {
291 | if (OnCollect != null) { OnCollect(item); return true; }
292 | return false;
293 | }
294 | }
295 |
296 | public class MemoryDuplex : Duplex
297 | {
298 | protected override bool HandleWritable(byte[] source) => HandleReadable(source);
299 | }
300 | public class VoidWritable : Writable
301 | {
302 | public static readonly VoidWritable Default = new VoidWritable();
303 | protected override bool HandleWritable(byte[] source) => true;
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/CSSockets/Tcp/Connection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using CSSockets.Streams;
4 | using System.Net.Sockets;
5 |
6 | namespace CSSockets.Tcp
7 | {
8 | public enum TcpSocketState : byte
9 | {
10 | Dormant,
11 | Connecting,
12 | Open,
13 | Closing,
14 | Closed
15 | }
16 | public sealed class Connection : Duplex, IEndable
17 | {
18 | private static readonly TcpSocketState[] STATE_TRANSLATE = new TcpSocketState[]
19 | {
20 | TcpSocketState.Dormant,
21 | TcpSocketState.Dormant,
22 | TcpSocketState.Closed,
23 | TcpSocketState.Closed,
24 | TcpSocketState.Closed,
25 | TcpSocketState.Dormant,
26 | TcpSocketState.Connecting,
27 | TcpSocketState.Open,
28 | TcpSocketState.Open,
29 | TcpSocketState.Open,
30 | TcpSocketState.Closing,
31 | TcpSocketState.Closed
32 | };
33 |
34 | public SocketWrapper Base { get; private set; }
35 |
36 | public event ControlHandler OnOpen;
37 | public event ControlHandler OnTimeout;
38 | public event ControlHandler OnDrain;
39 | public event ControlHandler OnEnd;
40 | public event ControlHandler OnClose;
41 | public event SocketCodeHandler OnError;
42 |
43 | public bool AllowHalfOpen
44 | {
45 | get => Base.ClientAllowHalfOpen;
46 | set => Base.ClientAllowHalfOpen = value;
47 | }
48 | public bool Ended => Base.State == WrapperState.Destroyed;
49 | public ulong BufferedWritable => Writable.Length;
50 | public TcpSocketState State => STATE_TRANSLATE[(int)Base.State];
51 | public TimeSpan? TimeoutAfter
52 | {
53 | get => Base.ClientTimeoutAfter;
54 | set => Base.ClientTimeoutAfter = value;
55 | }
56 | public IPAddress LocalAddress => Base.Local?.Address;
57 | public IPAddress RemoteAddress => Base.Remote?.Address;
58 |
59 | internal Connection(SocketWrapper wrapper)
60 | {
61 | Base = wrapper;
62 | Base.ClientOnConnect = _OnOpen;
63 | Base.ClientOnTimeout = _OnTimeout;
64 | Base.ClientOnShutdown = _OnRemoteShutdown;
65 | Base.ClientOnClose = _OnClose;
66 | Base.WrapperOnSocketError = _OnError;
67 | }
68 | public Connection() : this(new SocketWrapper())
69 | {
70 | Base.WrapperBind();
71 | Base.WrapperAddClient(this);
72 | }
73 |
74 | internal readonly PrimitiveBuffer Writable = new PrimitiveBuffer();
75 | private void _OnOpen() => OnOpen?.Invoke();
76 | private void _OnTimeout()
77 | {
78 | lock (Sync)
79 | {
80 | if (OnTimeout != null) OnTimeout();
81 | else if (!End()) Terminate();
82 | }
83 | }
84 | private void _OnClose() => OnClose?.Invoke();
85 | private void _OnRemoteShutdown() => OnEnd?.Invoke();
86 | private void _OnError(SocketError error)
87 | {
88 | lock (Sync)
89 | {
90 | OnError?.Invoke(error);
91 | Terminate();
92 | }
93 | }
94 |
95 | internal bool _ReadableWrite(byte[] source) => HandleReadable(source);
96 | internal byte[] _WritableRead(ulong maximum)
97 | {
98 | lock (Sync)
99 | {
100 | ulong length = Math.Min(maximum, BufferedWritable);
101 | if (length == BufferedWritable) OnDrain?.Invoke();
102 | return Writable.Read(length);
103 | }
104 | }
105 |
106 | public bool CanRead => Base.State == WrapperState.ClientOpen || Base.State == WrapperState.ClientReadonly;
107 | public override byte[] Read() => Readable.Read(BufferedReadable);
108 | public override byte[] Read(ulong length) => Readable.Read(length);
109 |
110 | public bool CanWrite => Base.State == WrapperState.ClientOpen || Base.State == WrapperState.ClientWriteonly;
111 | protected override bool HandleWritable(byte[] data) => Writable.Write(data);
112 | public override bool Write(byte[] source)
113 | {
114 | lock (Sync)
115 | {
116 | if (!CanWrite) return false;
117 | return base.Write(source);
118 | }
119 | }
120 |
121 | public bool End()
122 | {
123 | lock (Sync)
124 | {
125 | switch (Base.State)
126 | {
127 | case WrapperState.ClientOpen:
128 | case WrapperState.ClientWriteonly:
129 | Base.ClientShutdown();
130 | break;
131 | case WrapperState.ClientDormant:
132 | case WrapperState.ClientConnecting:
133 | Base.ClientTerminate();
134 | break;
135 | default: return false;
136 | }
137 | return true;
138 | }
139 | }
140 | public bool Terminate()
141 | {
142 | lock (Sync)
143 | {
144 | if (State == TcpSocketState.Closed) return false;
145 | Base.ClientTerminate();
146 | return true;
147 | }
148 | }
149 |
150 | public bool Connect(EndPoint endPoint)
151 | {
152 | lock (Sync)
153 | {
154 | if (Base.State > WrapperState.ClientDormant) return false;
155 | Base.ClientConnect(endPoint);
156 | return true;
157 | }
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/CSSockets/Tcp/IOControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading;
4 | using System.Net.Sockets;
5 | using System.Collections.Generic;
6 | using System.Collections.Concurrent;
7 |
8 | namespace CSSockets.Tcp
9 | {
10 | public static class IOControl
11 | {
12 | public static int OperationsPerThread = 2147483647;
13 | public static int SocketsPerThread = 200;
14 | public static int PollTimeMillisecs = 1;
15 |
16 | private static readonly List threads = new List();
17 | private static readonly object m_lock = new object();
18 |
19 | public static int ThreadCount
20 | {
21 | get
22 | {
23 | lock (m_lock) return threads.Count;
24 | }
25 | }
26 | public static int SocketCount
27 | {
28 | get
29 | {
30 | lock (m_lock)
31 | {
32 | int count = 0;
33 | for (int i = 0; i < threads.Count; i++) count += threads[i].SocketCount;
34 | return count;
35 | }
36 | }
37 | }
38 | public static int ConnectionCount
39 | {
40 | get
41 | {
42 | lock (m_lock)
43 | {
44 | int count = 0;
45 | for (int i = 0; i < threads.Count; i++) count += threads[i].ClientCount;
46 | return count;
47 | }
48 | }
49 | }
50 | public static int ListenerCount
51 | {
52 | get
53 | {
54 | lock (m_lock)
55 | {
56 | int count = 0;
57 | for (int i = 0; i < threads.Count; i++) count += threads[i].ServerCount;
58 | return count;
59 | }
60 | }
61 | }
62 |
63 | internal static IOThread GetBest()
64 | {
65 | lock (m_lock)
66 | {
67 | IOThread best = null;
68 | for (int i = 0; i < threads.Count; i++)
69 | {
70 | if (threads[i].Closing) continue;
71 | if (threads[i].OperationCount >= OperationsPerThread) continue;
72 | if (threads[i].SocketCount >= SocketsPerThread) continue;
73 | if (best == null || best.SocketCount < threads[i].SocketCount)
74 | best = threads[i];
75 | }
76 | return best ?? Create();
77 | }
78 | }
79 |
80 | internal static IOThread Create()
81 | {
82 | IOThread thread = new IOThread();
83 | threads.Add(thread);
84 | return thread;
85 | }
86 | internal static bool Remove(IOThread thread)
87 | {
88 | lock (m_lock) return threads.Remove(thread);
89 | }
90 | }
91 | internal class IOThread
92 | {
93 | public volatile int OperationCount = 0;
94 | public volatile int SocketCount = 0;
95 | public volatile int ServerCount = 0;
96 | public volatile int ClientCount = 0;
97 | public volatile bool Closing = false;
98 | public volatile bool GotFirst = false;
99 | public Thread Thread = null;
100 |
101 | private List WrapperList = new List();
102 | private Dictionary NativeLookup = new Dictionary();
103 | private Dictionary ClientLookup = new Dictionary();
104 | private Dictionary ServerLookup = new Dictionary();
105 |
106 | private ConcurrentQueue OperationQueue = new ConcurrentQueue();
107 | public bool Enqueue(IOOperation op)
108 | {
109 | OperationCount++;
110 | OperationQueue.Enqueue(op);
111 | return true;
112 | }
113 |
114 | private IPEndPoint Resolve(EndPoint endPoint)
115 | {
116 | if (endPoint is IPEndPoint) return endPoint as IPEndPoint;
117 | else if (endPoint is DnsEndPoint)
118 | {
119 | IPAddress[] addresses = Dns.GetHostAddresses((endPoint as DnsEndPoint).Host);
120 | if (addresses.Length == 0) return null;
121 | return new IPEndPoint(addresses[0].IsIPv4MappedToIPv6 ? addresses[0].MapToIPv4() : addresses[0], (endPoint as DnsEndPoint).Port);
122 | }
123 | return null;
124 | }
125 | private bool Bind(SocketWrapper wrapper)
126 | {
127 | SocketCount++;
128 | WrapperList.Add(wrapper);
129 | NativeLookup.Add(wrapper.Socket, wrapper);
130 | return GotFirst = true;
131 | }
132 | private bool Bind(Connection connection, SocketWrapper wrapper)
133 | {
134 | ClientCount++;
135 | ClientLookup.Add(wrapper, connection);
136 | return true;
137 | }
138 | private bool Bind(Listener listener, SocketWrapper wrapper)
139 | {
140 | ServerCount++;
141 | ServerLookup.Add(wrapper, listener);
142 | return true;
143 | }
144 | private bool Unbind(SocketWrapper wrapper, SocketError? code = null)
145 | {
146 | if (code != null) wrapper.WrapperOnSocketError?.Invoke(code.Value);
147 | if (!WrapperList.Remove(wrapper)) return false;
148 | NativeLookup.Remove(wrapper.Socket);
149 | wrapper.Local = wrapper.Remote = null;
150 | SocketCount--;
151 | if (ClientLookup.Remove(wrapper, out Connection connection))
152 | {
153 | ClientCount--;
154 | wrapper.EndedReadable = true;
155 | wrapper.EndedWritable = true;
156 | wrapper.ClientOnClose?.Invoke();
157 | }
158 | wrapper.State = WrapperState.Destroyed;
159 | if (ServerLookup.Remove(wrapper)) ServerCount--;
160 | return false;
161 | }
162 |
163 | public IOThread() => (Thread = new Thread(Loop) { Name = "TCP I/O thread" }).Start();
164 |
165 | private void Loop()
166 | {
167 | List pollR = new List(), pollW = new List(), pollE = new List();
168 | while (true)
169 | {
170 | while (OperationQueue.TryDequeue(out IOOperation operation))
171 | ExecuteOperation(operation);
172 |
173 | pollR.Clear(); pollW.Clear(); pollE.Clear();
174 |
175 | for (int i = 0; i < SocketCount; i++)
176 | {
177 | SocketWrapper wrapper = WrapperList[i];
178 | Socket socket = wrapper.Socket;
179 | switch (wrapper.State)
180 | {
181 | case WrapperState.ClientConnecting:
182 | case WrapperState.ClientOpen:
183 | case WrapperState.ClientReadonly:
184 | case WrapperState.ClientWriteonly:
185 | ClientCheckTimeout(wrapper);
186 | break;
187 | }
188 | switch (wrapper.State)
189 | {
190 | case WrapperState.ServerListening:
191 | pollR.Add(socket);
192 | break;
193 | case WrapperState.ClientConnecting:
194 | pollW.Add(socket);
195 | pollE.Add(socket);
196 | break;
197 | case WrapperState.ClientOpen:
198 | pollR.Add(socket);
199 | if (ClientLookup[wrapper].BufferedWritable > 0)
200 | pollW.Add(socket);
201 | break;
202 | case WrapperState.ClientReadonly:
203 | pollR.Add(socket);
204 | if (!wrapper.EndedWritable && ClientLookup[wrapper].BufferedWritable > 0)
205 | pollW.Add(socket);
206 | else if (!wrapper.EndedWritable) ClientShutdown(wrapper, false, true);
207 | break;
208 | case WrapperState.ClientWriteonly:
209 | if (ClientLookup[wrapper].BufferedWritable > 0)
210 | pollW.Add(socket);
211 | break;
212 | case WrapperState.ClientLastWrite:
213 | if (!wrapper.EndedWritable && ClientLookup[wrapper].BufferedWritable > 0)
214 | pollW.Add(socket);
215 | else { ClientShutdown(wrapper, false, true); i--; }
216 | break;
217 | }
218 | }
219 |
220 | if (SocketCount == 0 && GotFirst) break;
221 |
222 | if (pollR.Count == 0 && pollW.Count == 0 && pollE.Count == 0)
223 | {
224 | Thread.Sleep(IOControl.PollTimeMillisecs);
225 | continue;
226 | }
227 | else Socket.Select(pollR, pollW, pollE, 1000 * IOControl.PollTimeMillisecs);
228 |
229 | for (int i = 0; i < pollR.Count; i++)
230 | {
231 | Socket socket = pollR[i];
232 | SocketWrapper wrapper = NativeLookup[socket];
233 | switch (wrapper.State)
234 | {
235 | case WrapperState.ServerListening:
236 | ServerAccept(wrapper);
237 | break;
238 | case WrapperState.ClientOpen:
239 | case WrapperState.ClientReadonly:
240 | ClientReceive(wrapper);
241 | break;
242 | }
243 | }
244 |
245 | for (int i = 0; i < pollW.Count; i++)
246 | {
247 | Socket socket = pollW[i];
248 | SocketWrapper wrapper = NativeLookup[socket];
249 | switch (wrapper.State)
250 | {
251 | case WrapperState.ClientConnecting:
252 | ClientOpen(wrapper);
253 | break;
254 | case WrapperState.ClientOpen:
255 | case WrapperState.ClientWriteonly:
256 | case WrapperState.ClientLastWrite:
257 | ClientSend(wrapper);
258 | break;
259 | }
260 | }
261 |
262 | for (int i = 0; i < pollE.Count; i++)
263 | {
264 | Socket socket = pollE[i];
265 | SocketWrapper wrapper = NativeLookup[socket];
266 | switch (wrapper.State)
267 | {
268 | case WrapperState.ClientConnecting:
269 | Unbind(wrapper, (SocketError)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error));
270 | break;
271 | }
272 | }
273 | }
274 | Closing = true;
275 | IOControl.Remove(this);
276 | }
277 |
278 | private bool SucceedOperation(SocketWrapper wrapper, WrapperState? state = null)
279 | {
280 | if (state != null) wrapper.State = state.Value;
281 | return true;
282 | }
283 | private bool FailOperation(SocketWrapper wrapper, WrapperState state)
284 | {
285 | wrapper.State = state;
286 | return false;
287 | }
288 | private bool ExecuteOperation(IOOperation operation)
289 | {
290 | OperationCount--;
291 | SocketWrapper wrapper = operation.Callee;
292 | Socket socket = wrapper.Socket;
293 | IPEndPoint endPoint;
294 | SocketError code = SocketError.Success;
295 | switch (operation.Type)
296 | {
297 | case OperationType.Noop:
298 | return SucceedOperation(wrapper);
299 |
300 | case OperationType.WrapperBind:
301 | if (operation.AdvanceFrom != WrapperState.Unset) return Unbind(wrapper);
302 | return Bind(wrapper)
303 | ? SucceedOperation(wrapper, WrapperState.Dormant)
304 | : FailOperation(wrapper, WrapperState.Unset);
305 |
306 | case OperationType.WrapperAddServer:
307 | return Bind(operation.Listener, wrapper)
308 | ? SucceedOperation(wrapper, WrapperState.ServerDormant)
309 | : FailOperation(wrapper, WrapperState.Dormant);
310 |
311 | case OperationType.WrapperAddClient:
312 | if (operation.AdvanceFrom != WrapperState.Dormant) return Unbind(wrapper);
313 | return Bind(operation.Connection, wrapper)
314 | ? SucceedOperation(wrapper, WrapperState.ClientDormant)
315 | : FailOperation(wrapper, WrapperState.Dormant);
316 |
317 | case OperationType.ServerLookup:
318 | if (operation.AdvanceFrom != WrapperState.ServerDormant) return Unbind(wrapper);
319 | wrapper.Local = endPoint = Resolve(operation.Lookup);
320 | if (endPoint == null) return FailOperation(wrapper, WrapperState.ServerDormant);
321 | return SucceedOperation(wrapper, WrapperState.ServerBound);
322 |
323 | case OperationType.ServerListen:
324 | if (operation.AdvanceFrom == WrapperState.ServerDormant) return FailOperation(wrapper, WrapperState.ServerDormant);
325 | else if (operation.AdvanceFrom != WrapperState.ServerBound) return Unbind(wrapper);
326 | try
327 | {
328 | socket.ExclusiveAddressUse = wrapper.ServerExclusive;
329 | socket.Bind(wrapper.Local);
330 | socket.Listen(wrapper.ServerBacklog);
331 | }
332 | catch (SocketException ex) { code = ex.SocketErrorCode; }
333 | return code != SocketError.Success
334 | ? Unbind(wrapper, code)
335 | : SucceedOperation(wrapper, WrapperState.ServerListening);
336 |
337 | case OperationType.ServerTerminate:
338 | return Unbind(wrapper);
339 |
340 | case OperationType.ClientOpen:
341 | if (operation.AdvanceFrom != WrapperState.ClientDormant) return Unbind(wrapper);
342 | ClientOpen(wrapper, operation.Referer, operation.Connection);
343 | return true;
344 |
345 | case OperationType.ClientConnect:
346 | if (operation.AdvanceFrom != WrapperState.ClientDormant) return Unbind(wrapper);
347 | wrapper.Remote = endPoint = Resolve(operation.Lookup);
348 | if (endPoint == null) return Unbind(wrapper, SocketError.NetworkUnreachable);
349 | try { socket.Connect(endPoint); }
350 | catch (SocketException ex) { code = ex.SocketErrorCode; }
351 | return (code != SocketError.Success && code != SocketError.WouldBlock)
352 | ? Unbind(wrapper, code)
353 | : SucceedOperation(wrapper, WrapperState.ClientConnecting) && ClientExtendTimeout(wrapper);
354 |
355 | case OperationType.ClientShutdown:
356 | return ClientShutdown(wrapper, false, true);
357 |
358 | case OperationType.ClientTerminate:
359 | return Unbind(wrapper);
360 |
361 | default: throw new Exception();
362 | }
363 | }
364 |
365 | private bool ServerAccept(SocketWrapper wrapper)
366 | {
367 | do
368 | {
369 | SocketWrapper newWrapper = new SocketWrapper(wrapper.Socket.Accept());
370 | Connection connection = new Connection(newWrapper);
371 | newWrapper.WrapperBind();
372 | newWrapper.WrapperAddClient(connection);
373 | newWrapper.ClientOpen(wrapper, connection);
374 | }
375 | while (wrapper.Socket.Poll(0, SelectMode.SelectRead));
376 | return true;
377 | }
378 |
379 | private bool ClientExtendTimeout(SocketWrapper wrapper)
380 | {
381 | wrapper.ClientLastActivity = DateTime.UtcNow;
382 | wrapper.ClientCalledTimeout = false;
383 | return true;
384 | }
385 | private bool ClientCheckTimeout(SocketWrapper wrapper)
386 | {
387 | if (wrapper.ClientTimeoutAfter == null || wrapper.ClientCalledTimeout) return true;
388 | if (DateTime.UtcNow - wrapper.ClientLastActivity < wrapper.ClientTimeoutAfter) return true;
389 | wrapper.ClientOnTimeout?.Invoke();
390 | wrapper.ClientCalledTimeout = true;
391 | return true;
392 | }
393 | private bool ClientOpen(SocketWrapper wrapper)
394 | {
395 | SucceedOperation(wrapper, WrapperState.ClientOpen);
396 | wrapper.Local = Resolve(wrapper.Socket.LocalEndPoint);
397 | wrapper.Remote = Resolve(wrapper.Socket.RemoteEndPoint);
398 | wrapper.ClientOnConnect?.Invoke();
399 | return ClientExtendTimeout(wrapper);
400 | }
401 | private bool ClientOpen(SocketWrapper wrapper, SocketWrapper referer, Connection connection)
402 | {
403 | SucceedOperation(wrapper, WrapperState.ClientOpen);
404 | wrapper.Local = Resolve(wrapper.Socket.LocalEndPoint);
405 | wrapper.Remote = Resolve(wrapper.Socket.RemoteEndPoint);
406 | referer.ServerOnConnection?.Invoke(connection);
407 | return ClientExtendTimeout(wrapper);
408 | }
409 | private bool ClientReceive(SocketWrapper wrapper)
410 | {
411 | int available = wrapper.Socket.Available;
412 | if (available == 0) return ClientShutdown(wrapper, true, false);
413 | byte[] data = new byte[available];
414 | wrapper.Socket.Receive(data, 0, available, SocketFlags.None, out SocketError code);
415 | if (code == SocketError.Success)
416 | return ClientLookup[wrapper]._ReadableWrite(data) && ClientExtendTimeout(wrapper);
417 | else return Unbind(wrapper, code);
418 | }
419 | private bool ClientSend(SocketWrapper wrapper)
420 | {
421 | byte[] data = ClientLookup[wrapper]._WritableRead(65536);
422 | wrapper.Socket.Send(data, 0, data.Length, SocketFlags.None, out SocketError code);
423 | if (code != SocketError.Success) return Unbind(wrapper, code);
424 | return ClientExtendTimeout(wrapper);
425 | }
426 | private SocketError ClientSocketShutdown(Socket socket, SocketShutdown how)
427 | {
428 | try { socket.Shutdown(how); }
429 | catch (SocketException ex) { return ex.SocketErrorCode; }
430 | return SocketError.Success;
431 | }
432 | private bool ClientShutdown(SocketWrapper wrapper, bool r, bool w)
433 | {
434 | SocketError code = SocketError.Success;
435 | bool halfOpen = wrapper.ClientAllowHalfOpen;
436 | switch (wrapper.State)
437 | {
438 | case WrapperState.ClientOpen:
439 | if (r && w) return Unbind(wrapper);
440 | if (r) return (wrapper.EndedReadable = true) && SucceedOperation(wrapper, halfOpen ? WrapperState.ClientWriteonly : WrapperState.ClientLastWrite);
441 | return SucceedOperation(wrapper, halfOpen ? WrapperState.ClientReadonly : WrapperState.ClientLastWrite);
442 |
443 | case WrapperState.ClientReadonly:
444 | if (w)
445 | {
446 | if (wrapper.EndedWritable) return Unbind(wrapper);
447 | ClientSocketShutdown(wrapper.Socket, SocketShutdown.Send);
448 | }
449 | code = ClientSocketShutdown(wrapper.Socket, SocketShutdown.Receive);
450 | if (code != SocketError.Success) return Unbind(wrapper, code);
451 | wrapper.ClientOnShutdown?.Invoke();
452 | return (wrapper.EndedReadable = true) && SucceedOperation(wrapper, WrapperState.ClientLastWrite);
453 |
454 | case WrapperState.ClientWriteonly:
455 | if (r) return Unbind(wrapper);
456 | return SucceedOperation(wrapper, WrapperState.ClientLastWrite);
457 |
458 | case WrapperState.ClientLastWrite:
459 | if (!wrapper.EndedWritable) ClientSocketShutdown(wrapper.Socket, SocketShutdown.Send);
460 | return Unbind(wrapper);
461 |
462 | default: return Unbind(wrapper);
463 | }
464 | }
465 | }
466 | }
467 |
--------------------------------------------------------------------------------
/CSSockets/Tcp/Listener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 |
5 | namespace CSSockets.Tcp
6 | {
7 | public class Listener
8 | {
9 | private readonly object sync = new object();
10 | private SocketWrapper _base = null;
11 | public SocketWrapper Base => (_base?.State == WrapperState.Destroyed ? Create() : _base) ?? Create();
12 |
13 | private SocketWrapper Create()
14 | {
15 | IPEndPoint endPoint = _base?.Local;
16 | _base = new SocketWrapper();
17 | _base.WrapperBind();
18 | _base.WrapperAddServer(this);
19 | _base.ServerExclusive = Exclusive;
20 | _base.ServerBacklog = Backlog;
21 | _base.WrapperOnSocketError = _OnError;
22 | _base.ServerOnConnection = _OnConnection;
23 | if (endPoint != null) _base.ServerLookup(endPoint);
24 | return _base;
25 | }
26 |
27 | public bool Exclusive { get; set; } = SocketWrapper.SERVER_EXCLUSIVE;
28 | public int Backlog { get; set; } = SocketWrapper.SERVER_BACKLOG;
29 | public bool Bound => _base.State >= WrapperState.ServerBound;
30 | public bool Listening => _base.State == WrapperState.ServerListening;
31 | public EndPoint BindEndPoint
32 | {
33 | get => _base.Local;
34 | set => _base.ServerLookup(value ?? throw new ArgumentNullException(nameof(value)));
35 | }
36 |
37 | public event ConnectionHandler OnConnection;
38 | public event SocketCodeHandler OnError;
39 |
40 | public Listener() => Create();
41 | public Listener(EndPoint endPoint) : this() => BindEndPoint = endPoint;
42 |
43 | private void _OnError(SocketError error) => OnError?.Invoke(error);
44 | private void _OnConnection(Connection newConnection) => OnConnection?.Invoke(newConnection);
45 |
46 | public void Start()
47 | {
48 | lock (sync) _base.ServerListen();
49 | }
50 | public void Stop()
51 | {
52 | lock (sync) _base.ServerTerminate();
53 | }
54 | public void Reset()
55 | {
56 | lock (sync) Create();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CSSockets/Tcp/SocketWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using CSSockets.Streams;
4 | using System.Net.Sockets;
5 |
6 | namespace CSSockets.Tcp
7 | {
8 | public class SocketWrapper
9 | {
10 | public const bool SERVER_EXCLUSIVE = true;
11 | public const int SERVER_BACKLOG = 511;
12 |
13 | public Socket Socket { get; }
14 | internal IOThread BoundThread = null;
15 | public WrapperState State { get; internal set; } = WrapperState.Unset;
16 | public IPEndPoint Local { get; internal set; } = null;
17 | public IPEndPoint Remote { get; internal set; } = null;
18 | public bool EndedReadable { get; internal set; } = false;
19 | public bool EndedWritable { get; internal set; } = false;
20 |
21 | public SocketWrapper() : this(new Socket(SocketType.Stream, ProtocolType.Tcp)) { }
22 | public SocketWrapper(Socket socket)
23 | {
24 | Socket = socket;
25 | Socket.Blocking = false;
26 | Socket.NoDelay = true;
27 | }
28 |
29 | public void WrapperBind()
30 | => (BoundThread = IOControl.GetBest()).Enqueue(new IOOperation()
31 | {
32 | Callee = this,
33 | Type = OperationType.WrapperBind,
34 | });
35 | public void WrapperAddClient(Connection connection)
36 | => BoundThread.Enqueue(new IOOperation()
37 | {
38 | Callee = this,
39 | Connection = connection,
40 | Type = OperationType.WrapperAddClient,
41 | });
42 | public void WrapperAddServer(Listener listener)
43 | => BoundThread.Enqueue(new IOOperation()
44 | {
45 | Callee = this,
46 | Listener = listener,
47 | Type = OperationType.WrapperAddServer,
48 | });
49 |
50 | public void ServerLookup(EndPoint endPoint)
51 | => BoundThread.Enqueue(new IOOperation()
52 | {
53 | Callee = this,
54 | Lookup = endPoint,
55 | Type = OperationType.ServerLookup,
56 | });
57 | public void ServerListen()
58 | => BoundThread.Enqueue(new IOOperation()
59 | {
60 | Callee = this,
61 | Type = OperationType.ServerListen,
62 | });
63 | public void ServerTerminate()
64 | => BoundThread.Enqueue(new IOOperation()
65 | {
66 | Callee = this,
67 | Type = OperationType.ServerTerminate,
68 | });
69 |
70 | public void ClientConnect(EndPoint endPoint)
71 | => BoundThread.Enqueue(new IOOperation()
72 | {
73 | Callee = this,
74 | Lookup = endPoint,
75 | Type = OperationType.ClientConnect,
76 | });
77 | public void ClientOpen(SocketWrapper referer, Connection connection)
78 | => BoundThread.Enqueue(new IOOperation()
79 | {
80 | Callee = this,
81 | Referer = referer,
82 | Connection = connection,
83 | Type = OperationType.ClientOpen,
84 | });
85 | public void ClientShutdown()
86 | => BoundThread.Enqueue(new IOOperation()
87 | {
88 | Callee = this,
89 | Type = OperationType.ClientShutdown,
90 | });
91 | public void ClientTerminate()
92 | => BoundThread.Enqueue(new IOOperation()
93 | {
94 | Callee = this,
95 | Type = OperationType.ClientTerminate,
96 | });
97 |
98 | public SocketCodeHandler WrapperOnSocketError { get; set; }
99 | public ControlHandler WrapperOnUnbind { get; set; }
100 |
101 | public ConnectionHandler ServerOnConnection { get; set; }
102 |
103 | public ControlHandler ClientOnConnect { get; set; }
104 | public ControlHandler ClientOnTimeout { get; set; }
105 | public ControlHandler ClientOnShutdown { get; set; }
106 | public ControlHandler ClientOnClose { get; set; }
107 |
108 | public int ServerBacklog { get; set; } = SERVER_BACKLOG;
109 | public bool ServerExclusive { get; set; } = SERVER_EXCLUSIVE;
110 |
111 | public bool ClientAllowHalfOpen { get; set; } = false;
112 | public DateTime ClientLastActivity { get; internal set; } = DateTime.UtcNow;
113 | public TimeSpan? ClientTimeoutAfter { get; set; } = null;
114 | public bool ClientCalledTimeout { get; internal set; } = false;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/CSSockets/Tcp/Types.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 |
4 | namespace CSSockets.Tcp
5 | {
6 | public delegate void SocketCodeHandler(SocketError error);
7 | public delegate void ConnectionHandler(Connection newConnection);
8 |
9 | public enum WrapperState : byte
10 | {
11 | Unset, // no owner
12 | Dormant, // no type
13 | ServerDormant, // server, no bind
14 | ServerBound, // server, has bind
15 | ServerListening, // server, listening
16 | ClientDormant, // client, no socket
17 | ClientConnecting, // client, has socket, is connecting
18 | ClientOpen, // client, has socket, can read/write
19 | ClientReadonly, // client, has socket, can only read
20 | ClientWriteonly, // client, has socket, can only write
21 | ClientLastWrite, // client, has socket, emptyin buffer
22 | Destroyed // nothing available
23 | }
24 |
25 | public enum OperationType : byte
26 | {
27 | Noop = 0,
28 | WrapperBind = 1,
29 | WrapperAddServer = 2,
30 | WrapperAddClient = 3,
31 | ServerLookup = 4,
32 | ServerListen = 5,
33 | ServerTerminate = 6,
34 | ClientOpen = 7,
35 | ClientConnect = 8,
36 | ClientShutdown = 9,
37 | ClientTerminate = 10
38 | }
39 | public struct IOOperation
40 | {
41 | public Socket Socket => Callee.Socket;
42 | public SocketWrapper Callee { get; set; }
43 | public SocketWrapper Referer { get; set; }
44 | public OperationType Type { get; set; }
45 | public EndPoint Lookup { get; set; }
46 | public Connection Connection { get; set; }
47 | public Listener Listener { get; set; }
48 | public WrapperState AdvanceFrom => Callee.State;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Connection.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using CSSockets.Tcp;
3 | using CSSockets.Streams;
4 |
5 | namespace CSSockets.WebSockets.Definition
6 | {
7 | public delegate void BinaryMessageHandler(byte[] data);
8 | public delegate void StringMessageHandler(string data);
9 | public delegate void CloseMessageHandler(ushort code, string reason);
10 | public abstract partial class Connection
11 | {
12 | public IMode Mode { get; }
13 | public Tcp.Connection Base { get; }
14 | public string Subprotocol { get; protected set; }
15 | public bool Opening => Base.State == TcpSocketState.Connecting;
16 | public bool Open => Base.State == TcpSocketState.Open;
17 | public bool Closing => SentClose;
18 | public bool Closed => RecvClose;
19 |
20 | public virtual event ControlHandler OnOpen;
21 | public virtual event BinaryMessageHandler OnBinary;
22 | public virtual event StringMessageHandler OnString;
23 | public virtual event BinaryMessageHandler OnPing;
24 | public virtual event BinaryMessageHandler OnPong;
25 | public virtual event CloseMessageHandler OnClose;
26 |
27 | public Connection(Tcp.Connection connection, IMode mode)
28 | {
29 | Mode = mode;
30 | Base = connection;
31 | Base.OnClose += FinishClose;
32 | Parser.OnCollect += OnParserCollect;
33 | Merger.OnCollect += OnMergerCollect;
34 | }
35 | public void SetSubprotocol(string subprotocol)
36 | {
37 | lock (Sync) Subprotocol = subprotocol;
38 | }
39 | public bool Initiate(byte[] trail)
40 | {
41 | lock (Sync)
42 | {
43 | if (Mode.FireOpen) OnOpen?.Invoke();
44 | if (!Parser.Write(trail)) return false;
45 | return Base.Pipe(Parser);
46 | }
47 | }
48 |
49 | protected readonly FrameParser Parser = new FrameParser();
50 | protected readonly FrameMerger Merger = new FrameMerger();
51 |
52 | protected readonly object Sync = new object();
53 | protected ushort CloseCode = 1006;
54 | protected string CloseReason = null;
55 | protected bool RecvClose = false;
56 | protected bool SentClose = false;
57 | protected bool StartClose(ushort code, string reason)
58 | {
59 | lock (Sync)
60 | {
61 | if (RecvClose || SentClose) return false;
62 | CloseCode = code;
63 | CloseReason = reason;
64 | return SentClose = true;
65 | }
66 | }
67 | protected void FinishClose()
68 | {
69 | lock (Sync)
70 | {
71 | RecvClose = true;
72 | OnClose?.Invoke(CloseCode = 1006, CloseReason = "");
73 | Base.OnClose -= FinishClose;
74 | }
75 | }
76 | protected bool FinishClose(ushort code)
77 | {
78 | lock (Sync)
79 | {
80 | if (!SentClose || RecvClose) return false;
81 | if (CloseCode != code) return false;
82 | OnClose?.Invoke(CloseCode, CloseReason);
83 | Base.OnClose -= FinishClose;
84 | Base.End();
85 | return RecvClose = true;
86 | }
87 | }
88 | protected bool FinishClose(ushort code, string reason)
89 | {
90 | lock (Sync)
91 | {
92 | if (RecvClose) return false;
93 | OnClose?.Invoke(CloseCode = code, CloseReason = reason);
94 | Base.OnClose -= FinishClose;
95 | SendClose(code);
96 | return Base.End() && (RecvClose = true);
97 | }
98 | }
99 |
100 | protected abstract void OnParserCollect(Frame frame);
101 | protected abstract void OnMergerCollect(Message message);
102 | protected bool HandleMessage(Message message)
103 | {
104 | lock (Sync) switch (message.Opcode)
105 | {
106 | case 1: OnString?.Invoke(Encoding.UTF8.GetString(message.Payload)); return true;
107 | case 2: OnBinary?.Invoke(message.Payload); return true;
108 | case 8:
109 | if (message.Length < 2) { FinishClose(); return true; }
110 | ushort code = (ushort)(message.Payload[0] * 256 + message.Payload[1]);
111 | string reason = Encoding.UTF8.GetString(PrimitiveBuffer.Slice(message.Payload, 2, message.Length));
112 | if (RecvClose && message.Length > 2) return false;
113 | if (message.Length > 2) return FinishClose(code, reason);
114 | return FinishClose(code);
115 | case 9: OnPing?.Invoke(message.Payload); return true;
116 | case 10: OnPong?.Invoke(message.Payload); return true;
117 | default: return false;
118 | }
119 | }
120 |
121 | protected bool Send(Frame frame)
122 | {
123 | lock (Sync)
124 | {
125 | if (SentClose || RecvClose || !Base.CanWrite) return false;
126 | frame.SerializeTo(Base);
127 | return true;
128 | }
129 | }
130 | public abstract bool SendBinary(byte[] payload);
131 | public virtual bool SendBinary(byte[] payload, ushort start) => SendBinary(PrimitiveBuffer.Slice(payload, start, (ulong)payload.LongLength));
132 | public virtual bool SendBinary(byte[] payload, ushort start, ulong end) => SendBinary(PrimitiveBuffer.Slice(payload, start, end));
133 | public abstract bool SendString(string payload);
134 | public abstract bool SendPing(byte[] payload = null);
135 | public virtual bool SendPing(byte[] payload, ushort start) => SendPing(PrimitiveBuffer.Slice(payload, start, (ulong)payload.LongLength));
136 | public virtual bool SendPing(byte[] payload, ushort start, ulong end) => SendPing(PrimitiveBuffer.Slice(payload, start, end));
137 | protected abstract bool SendPong(byte[] payload);
138 | public abstract bool SendClose(ushort code, string reason = "");
139 | protected abstract bool SendClose(ushort code);
140 |
141 | protected virtual bool Terminate(ushort code, string reason)
142 | {
143 | lock (Sync)
144 | {
145 | if (!Opening && !Open && !Closing) return false;
146 | Base.Terminate();
147 | FinishClose(code, reason);
148 | return true;
149 | }
150 | }
151 | public virtual bool Terminate()
152 | {
153 | lock (Sync)
154 | {
155 | if (!Opening && !Open && !Closing) return false;
156 | Base.Terminate();
157 | FinishClose();
158 | return true;
159 | }
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/ConnectionFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using CSSockets.Tcp;
3 | using CSSockets.Http.Reference;
4 | using CSSockets.Http.Definition;
5 | using System.Collections.Generic;
6 | using System.Security.Cryptography;
7 |
8 | namespace CSSockets.WebSockets.Definition
9 | {
10 | public abstract class ConnectionFactory : Negotiator where TConnection : Connection
11 | {
12 | protected override IEnumerable RespondExtensions(NegotiatingExtension[] requested) => null;
13 |
14 | public virtual TConnection Generate(Tcp.Connection connection, URL url, params string[] reqSubprotocols)
15 | {
16 | ClientConnection httpClient = new ClientConnection(connection);
17 | OutgoingRequest req = httpClient.Enqueue("HTTP/1.1", "GET", url);
18 | TConnection newConnection = GenerateConnection(connection, req.Head);
19 |
20 | string reqExtensions = NegotiatingExtension.Stringify(RequestExtensions());
21 | string key = Secret.GenerateKey();
22 |
23 | req["Connection"] = "Upgrade";
24 | req["Upgrade"] = "websocket";
25 | req["Sec-WebSocket-Version"] = "13";
26 | req["Sec-WebSocket-Key"] = key;
27 | if (reqSubprotocols.Length > 0) req["Sec-WebSocket-Protocol"] = SubprotocolNegotiation.Stringify(reqSubprotocols);
28 | if (reqExtensions.Length > 0) req["Sec-WebSocket-Extensions"] = reqExtensions;
29 |
30 | req.OnResponse += (res) =>
31 | {
32 | if (!VerifyResponseHead(res, key)) { httpClient.Terminate(); return; }
33 | string subprotocol = res["Sec-WebSocket-Protocol"];
34 | if (subprotocol != null)
35 | {
36 | bool validSubprotocol = false;
37 | for (int i = 0; i < reqSubprotocols.Length; i++)
38 | if (reqSubprotocols[i] == subprotocol) { validSubprotocol = true; break; }
39 | if (!validSubprotocol) { httpClient.Terminate(); return; }
40 | }
41 | newConnection.SetSubprotocol(subprotocol);
42 | if (
43 | !NegotiatingExtension.TryParse(res["Sec-WebSocket-Extensions"] ?? "", out NegotiatingExtension[] resExtensions)
44 | || !CheckExtensions(resExtensions)
45 | ) { httpClient.Terminate(); return; }
46 |
47 | byte[] trail = httpClient.Freeze();
48 | if (!httpClient.End()) return;
49 | newConnection.Initiate(trail);
50 | };
51 |
52 | return req.End() ? newConnection : null;
53 | }
54 | protected abstract TConnection GenerateConnection(Tcp.Connection connection, RequestHead req);
55 | protected virtual bool VerifyResponseHead(IncomingResponse res, string key)
56 | {
57 | if (res.StatusCode != 101) return false;
58 | if (res["Connection"] != "Upgrade") return false;
59 | if (res["Upgrade"] != "websocket") return false;
60 | if (res["Sec-WebSocket-Version"] != "13") return false;
61 | if (res["Sec-WebSocket-Accept"] != Secret.ComputeAccept(key)) return false;
62 | return true;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/ConnectionMode.cs:
--------------------------------------------------------------------------------
1 | namespace CSSockets.WebSockets.Definition
2 | {
3 | public abstract partial class Connection
4 | {
5 | public interface IMode
6 | {
7 | bool FireOpen { get; }
8 | bool IncomingMasked { get; }
9 | bool OutgoingMasked { get; }
10 | }
11 | public struct ClientMode : IMode
12 | {
13 | public bool FireOpen => true;
14 | public bool IncomingMasked => false;
15 | public bool OutgoingMasked => true;
16 | }
17 | public struct ServerMode : IMode
18 | {
19 | public bool FireOpen => false;
20 | public bool IncomingMasked => true;
21 | public bool OutgoingMasked => false;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Headers.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.Specialized;
3 |
4 | namespace CSSockets.WebSockets.Definition
5 | {
6 | public class NegotiatingExtension
7 | {
8 | public StringDictionary Parameters { get; }
9 | public string ExtensionName { get; set; }
10 |
11 | public static bool TryParse(string s, out NegotiatingExtension[] result)
12 | {
13 | result = null;
14 | if (s == "") { result = new NegotiatingExtension[0]; return true; }
15 | List parsed = new List();
16 | string[] spl = s.Split(",");
17 | for (int i = 0; i < spl.Length; i++)
18 | {
19 | string ext = spl[i].Trim();
20 | if (ext.Length == 0) return false;
21 | string[] spl2 = ext.Split(";");
22 | NegotiatingExtension current = new NegotiatingExtension();
23 | if ((current.ExtensionName = spl2[0].Trim()).Length == 0)
24 | return false;
25 | for (int j = 1; j < spl2.Length; j++)
26 | {
27 | string[] spl3 = spl2[j].Split("=");
28 | if (spl3.Length > 2) return false;
29 | current.Parameters.Add(spl3[0].Trim(), spl3.Length == 2 ? spl3[1].Trim() : "");
30 | }
31 | parsed.Add(current);
32 | }
33 | result = parsed.ToArray();
34 | return true;
35 | }
36 |
37 | public static string Stringify(IEnumerable extensions)
38 | {
39 | string s = "";
40 | foreach (NegotiatingExtension ext in extensions) s += ext + ", ";
41 | return s.Length == 0 ? s : s.Substring(0, s.Length - 2);
42 | }
43 |
44 | public NegotiatingExtension()
45 | {
46 | Parameters = new StringDictionary();
47 | ExtensionName = null;
48 | }
49 |
50 | public override string ToString()
51 | {
52 | string s = ExtensionName;
53 | if (Parameters.Count == 0) return s;
54 | s += "; ";
55 | string[] keys = new string[Parameters.Count];
56 | Parameters.Keys.CopyTo(keys, 0);
57 | for (int i = 0; i < Parameters.Count; i++)
58 | {
59 | string param = Parameters[keys[i]];
60 | s += keys[i] + (param == "" ? "" : "=" + param) + (i == Parameters.Count - 1 ? "" : "; ");
61 | }
62 | return s;
63 | }
64 | }
65 | public static class SubprotocolNegotiation
66 | {
67 | public static bool TryParse(string s, out string[] result)
68 | {
69 | result = null;
70 | string[] spl = s.Split(',');
71 | for (int i = 0; i < spl.Length; i++) spl[i] = spl[i].Trim();
72 | result = spl;
73 | return true;
74 | }
75 |
76 | public static string Stringify(IEnumerable subprotocols)
77 | {
78 | string s = "";
79 | foreach (string subp in subprotocols) s += subp + ", ";
80 | return s.Length == 0 ? s : s.Substring(0, s.Length - 2);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Listener.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Text;
3 | using CSSockets.Http.Reference;
4 | using CSSockets.Http.Definition;
5 | using System.Security.Cryptography;
6 | using System.Collections.Generic;
7 |
8 | namespace CSSockets.WebSockets.Definition
9 | {
10 | public delegate bool ClientVerifier(IPAddress remote, string[] subprotocols, RequestHead head);
11 | public delegate string SubprotocolChooser(IPAddress remote, string[] subprotocols, RequestHead head);
12 | public abstract class Listener : Negotiator
13 | {
14 | public Http.Reference.Listener Base { get; }
15 |
16 | protected readonly object Sync = new object();
17 | protected SHA1 Hasher;
18 | public ClientVerifier ClientVerifier { get; set; } = null;
19 | public SubprotocolChooser SubprotocolChooser { get; set; } = null;
20 |
21 | public Listener()
22 | {
23 | Base = new Http.Reference.Listener();
24 | Base.OnRequest = _RequestHandler;
25 | }
26 | public Listener(EndPoint endPoint)
27 | {
28 | Base = new Http.Reference.Listener(endPoint);
29 | Base.OnRequest = _RequestHandler;
30 | }
31 | public Listener(Tcp.Listener listener)
32 | {
33 | Base = new Http.Reference.Listener(listener);
34 | Base.OnRequest = _RequestHandler;
35 | }
36 | public Listener(Http.Reference.Listener listener)
37 | {
38 | Base = new Http.Reference.Listener();
39 | Base.OnRequest = _RequestHandler;
40 | }
41 |
42 | protected void _RequestHandler(IncomingRequest req, OutgoingResponse res) => RequestHandler(req, res);
43 | protected virtual bool RequestHandler(IncomingRequest req, OutgoingResponse res)
44 | {
45 | if (!CheckHeaders(req, res)) return false;
46 | IPAddress remote = res.Connection.Base.RemoteAddress;
47 |
48 | if (!SubprotocolNegotiation.TryParse(req["Sec-WebSocket-Protocol"] ?? "", out string[] subprotocols))
49 | return DropRequest(res, 400, "Bad Request", "Could not parse subprotocols");
50 | if (ClientVerifier != null && !ClientVerifier(remote, subprotocols, req.Head))
51 | return DropRequest(res, 403, "Forbidden");
52 | if (!NegotiatingExtension.TryParse(req["Sec-WebSocket-Extensions"] ?? "", out NegotiatingExtension[] requestedExtensions))
53 | return DropRequest(res, 400, "Bad Request", "Could not parse extensions");
54 | if (!CheckExtensions(requestedExtensions))
55 | return DropRequest(res, 400, "Bad Request", "Extensions have been rejected");
56 |
57 | string subprotocol = null;
58 | if (subprotocols.Length > 0 && SubprotocolChooser != null)
59 | {
60 | subprotocol = SubprotocolChooser(remote, subprotocols, req.Head);
61 | if (subprotocol == null) DropRequest(res, 501, "Not Implemented");
62 | res["Sec-WebSocket-Protocol"] = subprotocol;
63 | }
64 | string respondedExtensions = NegotiatingExtension.Stringify(RespondExtensions(requestedExtensions));
65 | if (respondedExtensions.Length > 0)
66 | res["Sec-WebSocket-Extensions"] = respondedExtensions;
67 |
68 | res["Connection"] = "Upgrade";
69 | res["Upgrade"] = "websocket";
70 | res["Sec-WebSocket-Version"] = "13";
71 | res["Sec-WebSocket-Accept"] = Secret.ComputeAccept(req["Sec-WebSocket-Key"]);
72 | res.End(101, "Switching Protocols");
73 |
74 | byte[] trail = res.Connection.Freeze();
75 | if (!res.Connection.End()) return false;
76 | FireConnection(res.Connection.Base, req.Head, subprotocol, trail);
77 | return true;
78 | }
79 | protected abstract void FireConnection(Tcp.Connection connection, RequestHead req, string subprotocol, byte[] trail);
80 | protected override IEnumerable RequestExtensions() => null;
81 |
82 | protected bool DropRequest(OutgoingResponse res, ushort code, string reason, string asciiBody = null, params Header[] headers)
83 | {
84 | for (int i = 0; i < headers.Length; i++) res[headers[i].Key] = headers[i].Value;
85 | res["Content-Length"] = asciiBody?.Length.ToString() ?? "0";
86 | res.SendHead(code, reason);
87 | if (asciiBody != null) res.Write(Encoding.ASCII.GetBytes(asciiBody));
88 | return false;
89 | }
90 | protected bool CheckHeaders(IncomingRequest req, OutgoingResponse res)
91 | {
92 | if (req.Version != "HTTP/1.1")
93 | return DropRequest(res, 505, "HTTP Version Not Supported", "Use HTTP/1.1");
94 | if (req.Method != "GET")
95 | return DropRequest(res, 405, "Method Not Allowed", "Use GET");
96 |
97 | BodyType? type = BodyType.TryDetectFor(req.Head, true);
98 | if (type == null)
99 | return DropRequest(res, 400, "Bad Request", "Cannot detect body type");
100 | if (type.Value.Encoding != TransferEncoding.None)
101 | return DropRequest(res, 400, "Bad Request", "No body allowed");
102 |
103 | if (req["Connection"] != "Upgrade")
104 | return DropRequest(res, 426, "Upgrade Required", "Upgrade required");
105 | if (req["Upgrade"] != "websocket")
106 | return DropRequest(res, 400, "Bad Request", "Upgrade to websocket");
107 |
108 | if (req["Sec-WebSocket-Version"] != "13")
109 | return DropRequest(res, 400, "Bad Request", "Unsupported WebSocket version", new Header("Sec-WebSocket-Version", "13"));
110 | if (req["Sec-WebSocket-Key"] == null)
111 | return DropRequest(res, 400, "Bad Request", "Sec-WebSocket-Key not given");
112 | return true;
113 | }
114 |
115 | public void Start()
116 | {
117 | lock (Sync)
118 | {
119 | Hasher = SHA1.Create();
120 | Base.Start();
121 | }
122 | }
123 | public void Stop()
124 | {
125 | lock (Sync)
126 | {
127 | Base.Stop();
128 | Hasher.Dispose();
129 | Hasher = null;
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Negotiator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace CSSockets.WebSockets.Definition
4 | {
5 | public abstract class Negotiator
6 | {
7 | protected abstract bool CheckExtensions(NegotiatingExtension[] extensions);
8 | protected abstract IEnumerable RequestExtensions();
9 | protected abstract IEnumerable RespondExtensions(NegotiatingExtension[] requested);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Protocol.cs:
--------------------------------------------------------------------------------
1 | using CSSockets.Binary;
2 | using CSSockets.Streams;
3 |
4 | namespace CSSockets.WebSockets.Definition
5 | {
6 | public struct Frame
7 | {
8 | public static byte GetXLengthSize(byte length)
9 | => length == 127 ? (byte)8 : length == 126 ? (byte)2 : (byte)0;
10 | public static byte GetLengthFromXLength(ulong xLength)
11 | => xLength >= 65536 ? (byte)127 : xLength >= 126 ? (byte)126 : (byte)xLength;
12 |
13 | public bool FIN { get; set; }
14 | public byte Opcode { get; set; }
15 | public bool RSV1 { get; set; }
16 | public bool RSV2 { get; set; }
17 | public bool RSV3 { get; set; }
18 | public byte Length { get; set; }
19 | public ulong ExtendedLength { get; set; }
20 | public bool Masked { get; set; }
21 | public byte[] Mask { get; set; }
22 | public byte[] Payload { get; set; }
23 |
24 | public Frame(byte head1, byte head2) : this()
25 | {
26 | FIN = (head1 & 128) == 128;
27 | RSV1 = (head1 & 64) == 64;
28 | RSV2 = (head1 & 32) == 32;
29 | RSV3 = (head1 & 16) == 16;
30 | Opcode = (byte)(head1 - (FIN ? 128 : 0) - (RSV1 ? 64 : 0) - (RSV2 ? 32 : 0) - (RSV3 ? 16 : 0));
31 | Masked = (head2 & 128) == 128;
32 | Length = (byte)(head2 - (Masked ? 128 : 0));
33 | Payload = null;
34 | }
35 | public Frame(bool fin, byte opcode, bool rsv1, bool rsv2, bool rsv3, bool masked, byte[] payload) : this()
36 | {
37 | FIN = fin;
38 | Opcode = opcode;
39 | RSV1 = rsv1;
40 | RSV2 = rsv2;
41 | RSV3 = rsv3;
42 | ExtendedLength = (ulong)payload.LongLength;
43 | Length = GetLengthFromXLength(ExtendedLength);
44 | if (Masked = masked) Mask = Secret.GenerateMask();
45 | Payload = payload;
46 | }
47 | public Frame(bool fin, byte opcode, bool rsv1, bool rsv2, bool rsv3, byte[] mask, byte[] payload) : this()
48 | {
49 | FIN = fin;
50 | Opcode = opcode;
51 | RSV1 = rsv1;
52 | RSV2 = rsv2;
53 | RSV3 = rsv3;
54 | ExtendedLength = (ulong)payload.LongLength;
55 | Length = GetLengthFromXLength(ExtendedLength);
56 | Masked = true;
57 | Mask = mask;
58 | Payload = payload;
59 | }
60 |
61 | public void SerializeTo(IWritable writable)
62 | {
63 | StreamWriter writer = new StreamWriter(writable);
64 | writer.WriteUInt8((byte)(Opcode + (FIN ? 128 : 0) + (RSV1 ? 64 : 0) + (RSV2 ? 32 : 0) + (RSV3 ? 16 : 0)));
65 | writer.WriteUInt8((byte)(Length + (Mask != null ? 128 : 0)));
66 | switch (GetXLengthSize(Length))
67 | {
68 | case 0: break;
69 | case 2: writer.WriteUInt16LE((ushort)ExtendedLength); break;
70 | case 8: writer.WriteUInt64LE(ExtendedLength); break;
71 | }
72 | if (Mask != null) { writable.Write(Mask); writable.Write(FlipMask(Payload, Mask)); }
73 | else writable.Write(Payload);
74 | }
75 |
76 | public static byte[] FlipMask(byte[] payload, byte[] mask)
77 | {
78 | byte[] copied = PrimitiveBuffer.Slice(payload, 0, (ulong)payload.LongLength);
79 | for (long i = 0; i < copied.LongLength; i++) copied[i] ^= mask[i & 3];
80 | return copied;
81 | }
82 | }
83 |
84 | public struct Message
85 | {
86 | public byte Opcode { get; }
87 | public byte[] Payload { get; }
88 | public ulong Length { get; }
89 |
90 | public Message(byte opcode, byte[] payload, ulong length) : this()
91 | {
92 | Opcode = opcode;
93 | Payload = payload;
94 | Length = length;
95 | }
96 | }
97 |
98 | public sealed class FrameParser : Collector
99 | {
100 | enum ParseState : byte
101 | {
102 | Head,
103 | ExtendedLen2,
104 | ExtendedLen8,
105 | Mask,
106 | Payload
107 | }
108 |
109 | private Frame Incoming = new Frame();
110 | private readonly MemoryDuplex Buffer;
111 | private readonly StreamReader Reader;
112 | private ParseState State = ParseState.Head;
113 |
114 | public FrameParser() => Reader = new StreamReader(Buffer = new MemoryDuplex());
115 |
116 | protected override bool HandleWritable(byte[] source)
117 | {
118 | Buffer.Write(source); ulong buffered;
119 | while ((buffered = Buffer.BufferedReadable) > 0)
120 | switch (State)
121 | {
122 | case ParseState.Head:
123 | if (buffered < 2) return true;
124 | Incoming = new Frame(Reader.ReadUInt8(), Reader.ReadUInt8());
125 | switch (Frame.GetXLengthSize(Incoming.Length))
126 | {
127 | case 0:
128 | Incoming.ExtendedLength = Incoming.Length;
129 | if (Incoming.Masked) State = ParseState.Mask;
130 | else if (Incoming.Length > 0) State = ParseState.Payload;
131 | else Reset();
132 | break;
133 | case 2: State = ParseState.ExtendedLen2; break;
134 | case 8: State = ParseState.ExtendedLen8; break;
135 | }
136 | break;
137 | case ParseState.ExtendedLen2:
138 | if (buffered < 2) return true;
139 | Incoming.ExtendedLength = Reader.ReadUInt16LE();
140 | if (Incoming.Masked) State = ParseState.Mask;
141 | else if (Incoming.ExtendedLength > 0) State = ParseState.Payload;
142 | else Reset();
143 | break;
144 | case ParseState.ExtendedLen8:
145 | if (buffered < 8) return true;
146 | Incoming.ExtendedLength = Reader.ReadUInt64LE();
147 | if (Incoming.Masked) State = ParseState.Mask;
148 | else if (Incoming.ExtendedLength > 0) State = ParseState.Payload;
149 | else Reset();
150 | break;
151 | case ParseState.Mask:
152 | if (buffered < 4) return true;
153 | Incoming.Mask = Buffer.Read(4);
154 | State = ParseState.Payload;
155 | break;
156 | case ParseState.Payload:
157 | if (buffered < Incoming.ExtendedLength) return true;
158 | Incoming.Payload = Buffer.Read(Incoming.ExtendedLength);
159 | Reset();
160 | break;
161 | }
162 | return true;
163 | }
164 |
165 | private bool Reset()
166 | {
167 | Incoming.Payload = Incoming.Payload ?? new byte[0];
168 | Pickup(Incoming);
169 | Incoming = new Frame();
170 | State = ParseState.Head;
171 | return true;
172 | }
173 | }
174 |
175 | public delegate void MessageHandler(Message message);
176 | public sealed class FrameMerger
177 | {
178 | public enum Response : byte
179 | {
180 | OK,
181 | Reset,
182 | MessageNotStarted,
183 | MessageNotFinished
184 | }
185 | private readonly object Sync = new object();
186 |
187 | public event MessageHandler OnCollect;
188 | private byte IncomingOpcode = 0;
189 | private readonly MemoryDuplex IncomingMessage = new MemoryDuplex();
190 |
191 | public Response Push(Frame frame)
192 | {
193 | lock (Sync)
194 | {
195 | if (frame.Opcode == 0 && IncomingOpcode == 0)
196 | return Response.MessageNotStarted;
197 | if (frame.Opcode != 0 && IncomingOpcode != 0)
198 | return Response.MessageNotFinished;
199 | if (frame.Opcode != 0) IncomingOpcode = frame.Opcode;
200 | IncomingMessage.Write(frame.Masked ? Frame.FlipMask(frame.Payload, frame.Mask) : frame.Payload);
201 | if (!frame.FIN) return Response.OK;
202 | ulong length = IncomingMessage.BufferedReadable;
203 | OnCollect?.Invoke(new Message(IncomingOpcode, IncomingMessage.Read(), length));
204 | IncomingOpcode = 0;
205 | return Response.Reset;
206 | }
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Definition/Secret.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Security.Cryptography;
4 |
5 | namespace CSSockets.WebSockets.Definition
6 | {
7 | internal static class Secret
8 | {
9 | public static readonly SHA1 Hasher = SHA1.Create();
10 | public static readonly RandomNumberGenerator RNG = RandomNumberGenerator.Create();
11 | public static readonly string Magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
12 |
13 | public static byte[] GenerateMask()
14 | {
15 | byte[] key = new byte[4];
16 | RNG.GetBytes(key);
17 | return key;
18 | }
19 | public static string GenerateKey()
20 | {
21 | byte[] key = new byte[4];
22 | RNG.GetBytes(key);
23 | return Convert.ToBase64String(key);
24 | }
25 | public static string ComputeAccept(string key)
26 | {
27 | byte[] accept = Hasher.ComputeHash(Encoding.UTF8.GetBytes(key + Magic));
28 | return Convert.ToBase64String(accept);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Primitive/Connection.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using CSSockets.Streams;
3 | using CSSockets.Http.Reference;
4 | using CSSockets.WebSockets.Definition;
5 |
6 | namespace CSSockets.WebSockets.Primitive
7 | {
8 | public class Connection : Definition.Connection
9 | {
10 | public RequestHead Req { get; }
11 |
12 | public Connection(Tcp.Connection connection, RequestHead req, IMode mode) : base(connection, mode) => Req = req;
13 |
14 | public override bool SendBinary(byte[] payload)
15 | {
16 | lock (Sync) return Send(new Frame(true, 2, false, false, false, Mode.OutgoingMasked, payload));
17 | }
18 | public override bool SendClose(ushort code, string reason = "")
19 | {
20 | lock (Sync)
21 | {
22 | MemoryDuplex buffer = new MemoryDuplex();
23 | buffer.Write(new byte[2] { (byte)(code / 256), (byte)(code % 256) });
24 | buffer.Write(Encoding.UTF8.GetBytes(reason ?? ""));
25 | return Send(new Frame(true, 8, false, false, false, Mode.OutgoingMasked, buffer.Read())) && StartClose(code, reason);
26 | }
27 | }
28 | protected override bool SendClose(ushort code)
29 | {
30 | lock (Sync) return Send(new Frame(true, 8, false, false, false, Mode.OutgoingMasked, new byte[2] { (byte)(code / 256), (byte)(code % 256) }));
31 | }
32 | public override bool SendPing(byte[] payload)
33 | {
34 | lock (Sync) return Send(new Frame(true, 9, false, false, false, Mode.OutgoingMasked, payload ?? new byte[0]));
35 | }
36 | public override bool SendString(string payload)
37 | {
38 | lock (Sync) return Send(new Frame(true, 1, false, false, false, Mode.OutgoingMasked, Encoding.UTF8.GetBytes(payload)));
39 | }
40 | protected override void OnMergerCollect(Message message)
41 | {
42 | lock (Sync) if (!HandleMessage(message)) Terminate(1002, "");
43 | }
44 | protected override void OnParserCollect(Frame frame)
45 | {
46 | lock (Sync)
47 | {
48 | if (frame.Masked != Mode.IncomingMasked) { Terminate(1002, ""); return; }
49 | if (frame.RSV1 || frame.RSV2 || frame.RSV3) { Terminate(1002, ""); return; }
50 | switch (Merger.Push(frame))
51 | {
52 | case FrameMerger.Response.OK:
53 | case FrameMerger.Response.Reset:
54 | break;
55 | default: Terminate(1002, ""); break;
56 | }
57 | }
58 | }
59 | protected override bool SendPong(byte[] payload)
60 | {
61 | lock (Sync) return Send(new Frame(true, 10, false, false, false, Mode.OutgoingMasked, payload));
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Primitive/ConnectionFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using CSSockets.Http.Reference;
3 | using System.Collections.Generic;
4 | using CSSockets.WebSockets.Definition;
5 |
6 | namespace CSSockets.WebSockets.Primitive
7 | {
8 | public class ConnectionFactory : ConnectionFactory
9 | {
10 | public static readonly ConnectionFactory Default = new ConnectionFactory();
11 |
12 | protected override bool CheckExtensions(NegotiatingExtension[] requested) => requested.Length == 0;
13 | protected override IEnumerable RequestExtensions() => Enumerable.Empty();
14 | protected override Connection GenerateConnection(Tcp.Connection connection, RequestHead req) => new Connection(connection, req, new Definition.Connection.ClientMode());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/CSSockets/WebSockets/Primitive/Listener.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Linq;
3 | using CSSockets.Http.Reference;
4 | using System.Collections.Generic;
5 | using CSSockets.WebSockets.Definition;
6 |
7 | namespace CSSockets.WebSockets.Primitive
8 | {
9 | public delegate void ConnectionHandler(Connection connection);
10 | public class Listener : Definition.Listener
11 | {
12 | public event ConnectionHandler OnConnection;
13 |
14 | public Listener() : base() { }
15 | public Listener(EndPoint endPoint) : base(endPoint) { }
16 | public Listener(Tcp.Listener listener) : base(listener) { }
17 | public Listener(Http.Reference.Listener listener) : base(listener) { }
18 |
19 | protected override bool CheckExtensions(NegotiatingExtension[] extensions) => true;
20 | protected override IEnumerable RespondExtensions(NegotiatingExtension[] requested) => Enumerable.Empty();
21 | protected override void FireConnection(Tcp.Connection connection, RequestHead req, string subprotocol, byte[] trail)
22 | {
23 | Connection newConnection = new Connection(connection, req, new Definition.Connection.ServerMode());
24 | newConnection.SetSubprotocol(subprotocol);
25 | OnConnection?.Invoke(newConnection);
26 | newConnection.Initiate(trail);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2018 Luka
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSSockets
2 |
3 | An implementation of event-based sockets for .NET Core 2.0.
4 | Includes highly scalable, wrapped TCP, low-level HTTP and raw WebSockets, all being thread-safe, yet internally use the least amount of threads.
5 | Data handling is done with Node.js-inspired reinvented streams - see CSSockets.Streams.
6 | This is a shaky but functioning library - bug hunting is encouraged.
7 |
8 | This project uses object-oriented programming to a large extent to enable heavy customization:
9 | - All streams inherit either interfaces directly or base classes.
10 | - Sockets are wrapped then accessed with events and stream methods however it's left exposed if you want to do magic.
11 | - All the base HTTP classes are built on generics so you can even make your own HTTP version, albeit not in the form of HTTP/2.
12 |
13 | The performance focus is around parallelization of heavy workloads but with minimal cross-thread tampering:
14 | - Calls to Readable, Writable, Duplex and Compressors stream implemenatations don't cross or make new threads.
15 | - Accepted sockets made by CSSockets.Tcp.Listener are handled with Socket.Select to ensure minimal thread usage.
16 | - Multiple socket I/O processor threads will be opened for more than X sockets - even that is open to change.
17 |
18 | Implementations:
19 |
20 | - [X] Base stream classes
21 | - [X] Readable
22 | - [X] Writable
23 | - [X] Duplex
24 | - [X] Transform (UnifiedDuplex is capable of this)
25 | - [X] TCP
26 | - [X] Client
27 | - [X] Server
28 | - [X] HTTP
29 | - [X] Base HTTP classes
30 | - [X] Header
31 | - [X] Version
32 | - [X] Query tokens
33 | - [X] Paths
34 | - [X] Head parsing
35 | - [X] Request head
36 | - [X] Response head
37 | - [X] Head serializing
38 | - [X] Request head
39 | - [X] Response head
40 | - [X] Body parsing
41 | - [X] Binary
42 | - [X] Chunked
43 | - [X] Compressed binary
44 | - [X] Compressed chunked
45 | - [X] Body serializing
46 | - [X] Binary
47 | - [X] Chunked
48 | - [X] Compressed binary
49 | - [X] Compressed chunked
50 | - [X] HTTP connections
51 | - [X] Client-side connection
52 | - [X] Server-side connection
53 | - [X] Support for custom upgrading
54 | - [X] HTTP listener
55 | - [X] WebSockets
56 | - [X] Message parsing
57 | - [X] Message serialization
58 | - [X] WebSocket connections
59 | - [X] Client-side connection
60 | - [X] Server-side connection
61 | - [X] Support for custom extensions
62 | - Implement permessage-deflate?
63 | - HTTPS?
64 | - Secure WebSockets?
65 |
--------------------------------------------------------------------------------