├── grunt ├── .gitignore ├── packages.config ├── .jshintrc ├── config.json ├── package.json ├── tasks │ ├── install-npm.js │ ├── nuget.js │ ├── test-client.js │ ├── start-server.js │ ├── build-test.js │ ├── build-client.js │ └── createNugetPackage.js ├── templates │ ├── AssemblyInfo.cs │ ├── EngineIoClientDotNet.nuspec │ └── EngineIoClientDotNet - with xamarin.nuspec └── Gruntfile.js ├── TestServer ├── .gitignore ├── testme.quobject.com.p7b ├── packages.config ├── package.json ├── .jshintrc ├── testme.quobject.com.cert ├── testme.quobject.com.key └── server.js ├── Src ├── EngineIoClientDotNet │ ├── Parser │ │ ├── IEncodeCallback.cs │ │ ├── IDecodePayloadCallback.cs │ │ ├── Buffer.cs │ │ ├── Parser.cs │ │ ├── ByteBuffer.cs │ │ └── Packet.cs │ ├── Modules │ │ ├── UTF8Exception.cs │ │ ├── ServerCertificate_netstandard.cs │ │ ├── ParseQS.cs │ │ ├── LogManager.cs │ │ ├── Global.cs │ │ └── UTF8.cs │ ├── Client │ │ ├── EngineIOException.cs │ │ ├── HandshakeData.cs │ │ ├── Transport.cs │ │ └── Transports │ │ │ ├── WebSocket.cs │ │ │ ├── Polling.cs │ │ │ └── PollingXHR.cs │ ├── EngineIoClientDotNet.csproj │ ├── Thread │ │ ├── Heartbeat.cs │ │ ├── TriggeredLoopTimer.cs │ │ └── EasyTimer.cs │ └── ComponentEmitter │ │ └── Emitter.cs ├── EngineIoClientDotNet.Tests │ ├── ClientTests │ │ ├── ConnectionConstants.cs │ │ ├── HandshakeDataTests.cs │ │ ├── Connection.cs │ │ ├── UsageTest.cs │ │ ├── SocketTest.cs │ │ ├── BinaryWebSocketTest.cs │ │ ├── BinaryPollingTest.cs │ │ ├── TransportTest.cs │ │ ├── ConnectionTest.cs │ │ └── SSLServerConnectionTest.cs │ ├── EngineIoClientDotNet.Tests.csproj │ ├── ModulesTests │ │ ├── ParseQSTests.cs │ │ └── UTF8Tests.cs │ ├── ParserTests │ │ └── DecodeTests.cs │ └── ComponentEmitterTests │ │ └── EmitterTests.cs └── EngineIoClientDotNet.sln ├── LICENSE.md ├── README.md └── .gitignore /grunt/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test.xml -------------------------------------------------------------------------------- /TestServer/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test.xml 3 | *.pem 4 | -------------------------------------------------------------------------------- /TestServer/testme.quobject.com.p7b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quobject/EngineIoClientDotNet/HEAD/TestServer/testme.quobject.com.p7b -------------------------------------------------------------------------------- /grunt/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TestServer/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/IEncodeCallback.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Quobject.EngineIoClientDotNet.Parser 3 | { 4 | public interface IEncodeCallback 5 | { 6 | void Call(object data); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/IDecodePayloadCallback.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Quobject.EngineIoClientDotNet.Parser 3 | { 4 | 5 | public interface IDecodePayloadCallback 6 | { 7 | bool Call(Packet packet, int index, int total); 8 | } 9 | } -------------------------------------------------------------------------------- /TestServer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "engine.io-client.java-test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "engine.io": "^1.8.4", 7 | "express": "^4.15.3", 8 | "strip-json-comments": "^2.0.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/UTF8Exception.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Quobject.EngineIoClientDotNet.Modules 4 | { 5 | public class UTF8Exception : Exception 6 | { 7 | public UTF8Exception(string message) : base(message) 8 | {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /grunt/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "devel": true, 4 | "indent": 2, 5 | "maxerr": 50, 6 | "newcap": true, 7 | "nomen": true, 8 | "plusplus": false, 9 | "regexp": true, 10 | "white": false, 11 | "curly": true, 12 | "eqnull": true, 13 | "eqeqeq": true, 14 | "undef": true 15 | 16 | } -------------------------------------------------------------------------------- /TestServer/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "devel": true, 4 | "indent": 2, 5 | "maxerr": 50, 6 | "newcap": true, 7 | "nomen": true, 8 | "plusplus": false, 9 | "regexp": true, 10 | "white": false, 11 | "curly": true, 12 | "eqnull": true, 13 | "eqeqeq": true, 14 | "undef": true 15 | 16 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/ConnectionConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 2 | { 3 | public static class ConnectionConstants 4 | { 5 | public static int PORT = 80; 6 | public static string HOSTNAME = "localhost"; 7 | public static int SSL_PORT = 443; 8 | public static readonly int TIMEOUT = 300000; 9 | } 10 | } -------------------------------------------------------------------------------- /grunt/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.2", 3 | "server": { 4 | "port": 80, 5 | "ssl_port": 443, 6 | "hostname": "testme.quobject.com" 7 | }, 8 | "win": { 9 | "powershell": "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe", 10 | "msbuild": "C:/Windows/Microsoft.NET/Framework/v4.0.30319/msbuild.exe", 11 | "nuget": "C:/ProgramData/chocolatey/bin/nuget.exe" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/ServerCertificate_netstandard.cs: -------------------------------------------------------------------------------- 1 | namespace Quobject.EngineIoClientDotNet.Modules 2 | { 3 | public class ServerCertificate 4 | { 5 | public static bool Ignore { get; set; } 6 | 7 | static ServerCertificate() 8 | { 9 | Ignore = false; 10 | } 11 | 12 | public static void IgnoreServerCertificateValidation() 13 | { 14 | Ignore = true; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/EngineIOException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Quobject.EngineIoClientDotNet.Client 4 | { 5 | public class EngineIOException : Exception 6 | { 7 | public string Transport; 8 | public object code; 9 | 10 | public EngineIOException(string message) 11 | : base(message) 12 | { 13 | } 14 | 15 | 16 | public EngineIOException(Exception cause) 17 | : base("", cause) 18 | { 19 | } 20 | 21 | public EngineIOException(string message, Exception cause) 22 | : base(message, cause) 23 | { 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /grunt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EngineIoClientDotNet", 3 | "version": "0.1.0", 4 | "author": "Matthias Ludwig", 5 | "description": "Engine.IO Client Library for .Net", 6 | "repository": { 7 | "type": "git", 8 | "url": "" 9 | }, 10 | "readme": "This is the Engine.IO Client Library for C#, which is ported from the [JavaScript client](https://github.com/LearnBoost/engine.io-client).", 11 | "devDependencies": { 12 | "grunt": "^1.0.1", 13 | "grunt-shell": "^2.1.0", 14 | "grunt-contrib-jshint": "^1.1.0" 15 | }, 16 | "dependencies": { 17 | "string": "^3.3.3", 18 | "string-formatter": "^0.2.5", 19 | "grunt-contrib-clean": "^0.6.0", 20 | "grunt-contrib-copy": "^1.0.0", 21 | "rimraf": "^2.6.1", 22 | "strip-json-comments": "^2.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/EngineIoClientDotNet.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/HandshakeDataTests.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Xunit; 3 | 4 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 5 | { 6 | public class HandshakeDataTests 7 | { 8 | [Fact] 9 | public void Test() 10 | { 11 | var json = @"{ 12 | sid: 'nne323', 13 | upgrades: ['u1','u2'], 14 | pingInterval: 12, 15 | pingTimeout: 23 16 | }"; 17 | 18 | var handshakeData = new HandshakeData(json); 19 | Assert.Equal("u1", handshakeData.Upgrades[0]); 20 | Assert.Equal("u2", handshakeData.Upgrades[1]); 21 | 22 | Assert.Equal(12, handshakeData.PingInterval); 23 | Assert.Equal(23, handshakeData.PingTimeout); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/HandshakeData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System.Collections.Immutable; 3 | 4 | 5 | namespace Quobject.EngineIoClientDotNet.Client 6 | { 7 | public class HandshakeData 8 | { 9 | public string Sid; 10 | public ImmutableList Upgrades = ImmutableList.Empty; 11 | public long PingInterval; 12 | public long PingTimeout; 13 | 14 | public HandshakeData(string data) 15 | : this(JObject.Parse(data)) 16 | { 17 | } 18 | 19 | public HandshakeData(JObject data) 20 | { 21 | var upgrades = data.GetValue("upgrades"); 22 | 23 | foreach (var e in upgrades) 24 | { 25 | Upgrades = Upgrades.Add(e.ToString()); 26 | } 27 | 28 | Sid = data.GetValue("sid").Value(); 29 | PingInterval = data.GetValue("pingInterval").Value(); 30 | PingTimeout = data.GetValue("pingTimeout").Value(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/Buffer.cs: -------------------------------------------------------------------------------- 1 | namespace Quobject.EngineIoClientDotNet.Parser 2 | { 3 | internal class Buffer 4 | { 5 | 6 | private Buffer() 7 | { 8 | } 9 | 10 | public static byte[] Concat(byte[][] list) 11 | { 12 | int length = 0; 13 | foreach (var buf in list) 14 | { 15 | length += buf.Length; 16 | } 17 | 18 | return Concat(list, length); 19 | } 20 | 21 | public static byte[] Concat(byte[][] list, int length) 22 | { 23 | if (list.Length == 0) 24 | { 25 | return new byte[0]; 26 | } 27 | if (list.Length == 1) 28 | { 29 | return list[0]; 30 | } 31 | 32 | ByteBuffer buffer = ByteBuffer.Allocate(length); 33 | foreach (var buf in list) 34 | { 35 | buffer.Put(buf); 36 | } 37 | 38 | return buffer.Array(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /grunt/tasks/install-npm.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('installNpm', 4 | 'install node modules', function () { 5 | var 6 | string = require('string-formatter'), 7 | server_path2 = grunt.config('server_path'), 8 | os = grunt.config('os'), 9 | config = grunt.config('config'), 10 | win_pwd_command = string.format('{0} pwd', config.win.powershell); 11 | 12 | grunt.log.writeln('server_path = "%s"', server_path2); 13 | grunt.log.writeln('win_pwd_command = "%s"', win_pwd_command); 14 | 15 | if (os === 'win') { 16 | grunt.config('shell.exec.options.execOptions.cwd', '<%= server_path %>'); 17 | 18 | grunt.config('shell.exec.command', [win_pwd_command, 19 | 'npm install'].join('&&')); 20 | grunt.task.run('shell'); 21 | 22 | } else { 23 | 24 | grunt.config('shell.exec.options.execOptions.cwd', '<%= server_path %>'); 25 | grunt.config('shell.exec.command', ['pwd', 'npm install'].join('&&')); 26 | grunt.task.run('shell'); 27 | } 28 | 29 | }); 30 | }; 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Quobject 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 | -------------------------------------------------------------------------------- /TestServer/testme.quobject.com.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDDzCCAfegAwIBAgIJANW6WUwZpQs+MA0GCSqGSIb3DQEBBQUAMB4xHDAaBgNV 3 | BAMME3Rlc3RtZS5xdW9iamVjdC5jb20wHhcNMTQwODI2MTkwMDQzWhcNMjQwODIz 4 | MTkwMDQzWjAeMRwwGgYDVQQDDBN0ZXN0bWUucXVvYmplY3QuY29tMIIBIjANBgkq 5 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtJCU+74bnPPYg0SPBCws1WYdT7+lXDaf 6 | IZdCLXU3RT/hAszD/Hi8oCijD1BvfjRcPr9XAqKBWMeLTorV8YL/I5g+5Nzmcaep 7 | LjnmQV3YDR+ioBfx+PRwF8gx/ZGdmK/hcoFq27xbF6cLI4mbvddlwUdKEGgZ+g/a 8 | B+CzFF9xCKoll6zqnnHS+DImGNbH4+ex33vQj4yoQrRT5E85s7/nSwvbDve+AlJ3 9 | ChJVod4kepwixhV90ENP0u65lpgi7ipIDCNxtf/7ZazsSj33eSKioz3xy2mFX7WO 10 | Fqtg1f3h/njH4uI6RkPUbuFyj3IOqv6OQwbl7NXbzuPfkmGC7QBGqwIDAQABo1Aw 11 | TjAdBgNVHQ4EFgQUE28o7tGA1Aw53KhiC0PyTDlA29EwHwYDVR0jBBgwFoAUE28o 12 | 7tGA1Aw53KhiC0PyTDlA29EwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC 13 | AQEAqbOxXwelUcV9psZl8fr+FIbkl5/mLqZV1RdrHCkUD2OwGH5M8AlCqj42hmxi 14 | n6KIgE45MOo9UYHWNQ1Aem3ziEGPRDVZpsoNW1GfG6XnAH5r1DK34Td7lU1JebNN 15 | hxqV3AfVfeqrW1ZOmqEFJ95VwCoN1RPPh3MgFI1zjOjEJyk0pPxFNFRtpIHfLgve 16 | TFe88aVMAbDLVzGyDkkS2DxNvyZ5153W3JRh2u8PqhLSzCIGF+IcCOrwZya+VC63 17 | wWg8AckPXIGmhU/6P4zdQ/WCZ/tqErFYls49zwp6xAfvvfdTbqYCSNyOqsTKbYyP 18 | qAd5L9YKITYYa8IupRyIJGbXnw== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/Connection.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Quobject.EngineIoClientDotNet.Modules; 3 | 4 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 5 | { 6 | public class Connection 7 | { 8 | static Connection() 9 | { 10 | LogManager.SetupLogManager(); 11 | } 12 | 13 | protected static Socket.Options CreateOptions() 14 | { 15 | var options = new Socket.Options 16 | { 17 | Port = ConnectionConstants.PORT, 18 | Hostname = ConnectionConstants.HOSTNAME 19 | }; 20 | //log.Info("Please add to your hosts file: 127.0.0.1 " + options.Hostname); 21 | 22 | return options; 23 | } 24 | 25 | protected static Socket.Options CreateOptionsSecure() 26 | { 27 | var options = new Socket.Options 28 | { 29 | Port = ConnectionConstants.SSL_PORT, 30 | Hostname = ConnectionConstants.HOSTNAME, 31 | //log.Info("Please add to your hosts file: 127.0.0.1 " + options.Hostname); 32 | Secure = true, 33 | IgnoreServerCertificateValidation = true 34 | }; 35 | return options; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /grunt/tasks/nuget.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('nuget', 4 | 'get nuget assemblies', function () { 5 | var 6 | //fs = require('fs'), 7 | //S = require('string'), 8 | string = require('string-formatter'), 9 | os = grunt.config('os'), 10 | config = grunt.config('config'), 11 | //configuration = grunt.config('msbuild_configuration'), 12 | nuget_builds = grunt.config('nuget_builds'), 13 | nuget_path = os === 'win' ? 14 | config.win.nuget : config.linux.nuget, 15 | format_str = os === 'win' ? 16 | '{0} restore "{1}"' : 17 | 'mono --runtime=v4.0.30319 {0} restore {1}', 18 | tasks = [], 19 | i; 20 | 21 | function restorePackagesWithTitle(title) { 22 | var 23 | sln = string.format('{0}/../../Src/{1}/{2}.sln',__dirname, title,title), 24 | restore = string.format(format_str, nuget_path, sln); 25 | 26 | tasks.push(restore); 27 | } 28 | 29 | if (os === 'win') { 30 | for (i = 0; i < nuget_builds.length; i++) { 31 | restorePackagesWithTitle(nuget_builds[i].Name); 32 | } 33 | } 34 | 35 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 36 | grunt.config('shell.exec.command', tasks.join('&&')); 37 | grunt.task.run('shell'); 38 | }); 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /grunt/tasks/test-client.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('testClient', 4 | 'test cs', function () { 5 | var 6 | string = require('string-formatter'), 7 | os = grunt.config('os'), 8 | config = grunt.config('config'), 9 | tasks = [], 10 | configuration = grunt.config('msbuild_configuration'), 11 | test_format_str = os === 'win' ? 12 | '{0}/xunit.console.exe {1} -nunit test.xml -parallel none' : 13 | 'mono {0}/xunit.console.clr4.exe {1}', 14 | 15 | xunit_path = os === 'win' ? 16 | config.win.xunit_path : config.linux.xunit_path; 17 | 18 | function addTestDllWithTitle(title) { 19 | var 20 | dir_path = string.format('{0}/../../Src/{1}/', __dirname, title), 21 | test_dll = string.format('{0}bin/{1}/{2}.dll', dir_path, configuration, title); 22 | 23 | tasks.push( string.format(test_format_str,xunit_path, test_dll) ); 24 | } 25 | 26 | if (os === 'win') { 27 | addTestDllWithTitle('EngineIoClientDotNet.Tests.net45'); 28 | } else { 29 | addTestDllWithTitle('EngineIoClientDotNet.Tests.mono'); 30 | } 31 | 32 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 33 | grunt.config('shell.exec.command', tasks.join('&&')); 34 | grunt.task.run('shell'); 35 | 36 | }); 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/EngineIoClientDotNet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 0.0.6 7 | EngineIoClientDotNet.Standard 8 | 9 | Joe Wen 10 | false 11 | 12 | https://github.com/joewen/EngineIoClientDotNet 13 | Engine.IO Client Library for .NET Standard 14 | LICENSE.md 15 | 16 | https://github.com/joewen/EngineIoClientDotNet 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | True 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/UsageTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 6 | { 7 | public class UsageTest : Connection 8 | { 9 | [Fact] 10 | public void Usage1() 11 | { 12 | var options = CreateOptions(); 13 | var socket = new Socket(options); 14 | 15 | //You can use `Socket` to connect: 16 | //var socket = new Socket("ws://localhost"); 17 | socket.On(Socket.EVENT_OPEN, () => 18 | { 19 | socket.Send("hi"); 20 | socket.Close(); 21 | }); 22 | socket.Open(); 23 | 24 | //System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); 25 | } 26 | 27 | [Fact] 28 | public void Usage2() 29 | { 30 | var options = CreateOptions(); 31 | var socket = new Socket(options); 32 | 33 | //Receiving data 34 | //var socket = new Socket("ws://localhost:3000"); 35 | socket.On(Socket.EVENT_OPEN, () => 36 | { 37 | socket.On(Socket.EVENT_MESSAGE, (data) => Console.WriteLine((string)data)); 38 | }); 39 | socket.Open(); 40 | 41 | System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); 42 | socket.Close(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /grunt/tasks/start-server.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('startServer', 4 | 'install node modules', function () { 5 | var 6 | server_path = grunt.config('server_path'), 7 | os = grunt.config('os'), 8 | string = require('string-formatter'), 9 | config = grunt.config('config'), 10 | tasks = [], 11 | start, 12 | pwd; 13 | 14 | grunt.log.writeln('server_path = "%s"', server_path); 15 | 16 | if (os === 'win') { 17 | 18 | start = '{0} start-process ' + 19 | '-NoNewWindow ' + 20 | //'-WindowStyle Normal ' + //-WindowStyle (Hidden | Normal) | -NoNewWindow 21 | '-FilePath node ' + 22 | '-ArgumentList \' server.js \' '; 23 | start = string.format(start, config.win.powershell); 24 | pwd = string.format('{0} pwd',config.win.powershell); 25 | 26 | tasks.push(pwd); 27 | tasks.push(start); 28 | 29 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 30 | grunt.config('shell.exec.options.execOptions.cwd', '<%= server_path %>'); 31 | grunt.config('shell.exec.command', tasks.join('&&')); 32 | grunt.task.run('shell'); 33 | 34 | } else { 35 | 36 | grunt.config('shell.exec.options.execOptions.cwd', '<%= server_path %>'); 37 | grunt.config('shell.exec.command', ['pwd', 'node server.js'].join('&&')); 38 | grunt.task.run('shell'); 39 | } 40 | 41 | }); 42 | }; 43 | 44 | 45 | -------------------------------------------------------------------------------- /grunt/templates/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("EngineIoClientDotNet")] 8 | [assembly: AssemblyDescription("Engine.IO Client Library for .Net")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Quobject Software")] 11 | [assembly: AssemblyProduct("EngineIoClientDotNet")] 12 | [assembly: AssemblyCopyright("Copyright © 2017")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("a95e75cd-35e6-4e88-9e22-631e3fd01546")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("@VERSION@")] 35 | [assembly: AssemblyFileVersion("@VERSION@")] 36 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/Parser.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Quobject.EngineIoClientDotNet.Parser 3 | { 4 | /// 5 | /// This is the JavaScript parser for the engine.io protocol encoding, 6 | /// shared by both engine.io-client and engine.io. 7 | /// https://github.com/Automattic/engine.io-parser 8 | /// 9 | public class Parser 10 | { 11 | 12 | public static readonly int Protocol = 3; 13 | 14 | 15 | private Parser() 16 | { 17 | } 18 | 19 | public static void EncodePacket(Packet packet, IEncodeCallback callback) 20 | { 21 | packet.Encode(callback); 22 | } 23 | 24 | public static Packet DecodePacket(string data, bool utf8decode = false) 25 | { 26 | return Packet.DecodePacket(data, utf8decode); 27 | } 28 | 29 | public static Packet DecodePacket(byte[] data) 30 | { 31 | return Packet.DecodePacket(data); 32 | } 33 | 34 | public static void EncodePayload(Packet[] packets, IEncodeCallback callback) 35 | { 36 | Packet.EncodePayload(packets, callback); 37 | } 38 | 39 | 40 | public static void DecodePayload(string data, IDecodePayloadCallback callback) 41 | { 42 | Packet.DecodePayload(data, callback); 43 | } 44 | 45 | public static void DecodePayload(byte[] data, IDecodePayloadCallback callback) 46 | { 47 | Packet.DecodePayload(data, callback); 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EngineIoClientDotNet 2 | 3 | Engine.IO Client Library for .Net 4 | 5 | Fork from [https://github.com/joewen/EngineIoClientDotNet](https://github.com/joewen/EngineIoClientDotNet). 6 | 7 | This is the Engine.IO Client Library for C#, which is ported from the [JavaScript client](https://github.com/Automattic/engine.io-client). 8 | 9 | 10 | ##### Installation 11 | Nuget install: 12 | ``` 13 | Install-Package EngineIoClientDotNet.Standard 14 | ``` 15 | 16 | * NuGet Package: [![EngineIoClientDotNet.Standard](https://img.shields.io/nuget/v/EngineIoClientDotNet.Standard.svg?maxAge=2592000)](https://www.nuget.org/packages/EngineIoClientDotNet.Standard/) 17 | 18 | 19 | ##### Usage 20 | EngineIoClientDotNet has a similar api to those of the [JavaScript client](https://github.com/Automattic/engine.io-client). 21 | 22 | You can use `Socket` to connect: 23 | 24 | ```cs 25 | var socket = new Socket("ws://localhost"); 26 | socket.On(Socket.EVENT_OPEN, () => 27 | { 28 | socket.Send("hi", () => 29 | { 30 | socket.Close(); 31 | }); 32 | }); 33 | socket.Open(); 34 | ``` 35 | 36 | Receiving data 37 | ```cs 38 | var socket = new Socket("ws://localhost"); 39 | socket.On(Socket.EVENT_OPEN, () => 40 | { 41 | socket.On(Socket.EVENT_MESSAGE, (data) => 42 | { 43 | Console.WriteLine((string)data); 44 | }); 45 | }); 46 | socket.Open(); 47 | ``` 48 | 49 | #### Features 50 | This library supports all of the features the JS client does, including events, options and upgrading transport. 51 | 52 | #### Framework Versions 53 | .Net Standard 2.0 54 | 55 | ## License 56 | 57 | [MIT](http://opensource.org/licenses/MIT) 58 | 59 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Thread/Heartbeat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | 5 | namespace Quobject.EngineIoClientDotNet.Thread 6 | { 7 | public class Heartbeat 8 | { 9 | private volatile bool gotHeartbeat = false; 10 | private BackgroundWorker heartBeatTimer; 11 | private CancellationTokenSource ts; 12 | 13 | private Heartbeat() 14 | { 15 | ts = new CancellationTokenSource(); 16 | } 17 | 18 | public static Heartbeat Start(Action onTimeout, int timeout) 19 | { 20 | Heartbeat heartbeat = new Heartbeat(); 21 | heartbeat.Run(onTimeout, timeout); 22 | return heartbeat; 23 | } 24 | 25 | public void OnHeartbeat() 26 | { 27 | gotHeartbeat = true; 28 | } 29 | 30 | private void Run(Action onTimeout, int timeout) 31 | { 32 | heartBeatTimer = new BackgroundWorker(); 33 | 34 | heartBeatTimer.DoWork += (s, e) => 35 | { 36 | while (!ts.IsCancellationRequested) 37 | { 38 | System.Threading.Thread.Sleep(timeout); 39 | if (!gotHeartbeat && !ts.IsCancellationRequested) 40 | { 41 | onTimeout(); 42 | break; 43 | } 44 | } 45 | }; 46 | 47 | heartBeatTimer.RunWorkerAsync(); 48 | } 49 | 50 | public void Stop() 51 | { 52 | ts.Cancel(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.87 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EngineIoClientDotNet", "EngineIoClientDotNet\EngineIoClientDotNet.csproj", "{2596BA35-6391-4955-9439-AAB7D0FE668A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineIoClientDotNet.Tests", "EngineIoClientDotNet.Tests\EngineIoClientDotNet.Tests.csproj", "{05954C40-D0B5-4557-ADFD-02A091064F44}" 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 | {2596BA35-6391-4955-9439-AAB7D0FE668A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2596BA35-6391-4955-9439-AAB7D0FE668A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2596BA35-6391-4955-9439-AAB7D0FE668A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2596BA35-6391-4955-9439-AAB7D0FE668A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {05954C40-D0B5-4557-ADFD-02A091064F44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {05954C40-D0B5-4557-ADFD-02A091064F44}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {05954C40-D0B5-4557-ADFD-02A091064F44}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {05954C40-D0B5-4557-ADFD-02A091064F44}.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 = {ABCEAB3D-05C7-4ACC-99B3-64BCECF1C2EE} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /TestServer/testme.quobject.com.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtJCU+74bnPPYg0SPBCws1WYdT7+lXDafIZdCLXU3RT/hAszD 3 | /Hi8oCijD1BvfjRcPr9XAqKBWMeLTorV8YL/I5g+5NzmcaepLjnmQV3YDR+ioBfx 4 | +PRwF8gx/ZGdmK/hcoFq27xbF6cLI4mbvddlwUdKEGgZ+g/aB+CzFF9xCKoll6zq 5 | nnHS+DImGNbH4+ex33vQj4yoQrRT5E85s7/nSwvbDve+AlJ3ChJVod4kepwixhV9 6 | 0ENP0u65lpgi7ipIDCNxtf/7ZazsSj33eSKioz3xy2mFX7WOFqtg1f3h/njH4uI6 7 | RkPUbuFyj3IOqv6OQwbl7NXbzuPfkmGC7QBGqwIDAQABAoIBAQCy2/0YGUqVAF7a 8 | ONFKGtAWWt5yHq6YV2ruBT0CdnfXWt1yvo7sylReeaJ8CvtGEmvFpBd2fq6N2Ku/ 9 | k3s1jsNY6Ph0D/UdZC0Lo0LYQTNAXLPkzZNdPhTDGgWa3eE0XBSALn5BR6UcGtXH 10 | 0Am71V/wQsO02MnSkF0zLHt3lMsM/oPoJx8S6Tw+PpxGOhwQipdLMjKH49vyy5IM 11 | pS5OiGuAjmIq7bB0QPbgeZkQgSVgvZ+XP4OLuRt7I6GwMnChrVucybYWuetTSoUI 12 | uyRmOtlwUFBVJdSwEY5RfdF71kOH9DPpuTY6M4UJGdprEM1N1dX0WF2y/5D+ExeA 13 | rb06SXrxAoGBANfSja+YFn5+M8N/favt2/nGP6ePQJutjeiz0PgYOpe/85MrqNK/ 14 | 5rlU89QNDm616xEJnsPO/J/ZFDf+2rhuTfNCeNtzA/10j07xhHYN62Us9S5bsaXC 15 | JSMgdLcAnbUAXRJVrxPoUYgIoJIR65IG9UAHshoAysGPfBKQqR4hWKsHAoGBANYt 16 | wMnPA2sZH651pIe9lMETHeY5AIx7QGEDtu12raTgHaQanJFTXz5oXsEkJYUJKVvK 17 | XS2I74ZbIjvruASj2Tf7/L21xxo2JmxJCXVMlgyrVRKBIb9d0Ea7tUqLsIukYV3X 18 | iXrSabVtLLJA/SCtZME0tHrc/4RJwLIG0XVltMo9AoGARrQl0qbCh8IUdzFnHFIa 19 | RKOb6urVQasD2H5AMWbOmzQ5ObeN4S0ZCxI3pvp4BfD3B2fdaUyAGmXlZ8rIIK+S 20 | PeVC7rGpVvk+kaAxwvMgcM7fq8ZCVolZ3T4evm0nPUrXMtB7QMxVGXmqEPBp+jbp 21 | VYav5DDqO6sj/HkDzmkiQTUCgYEAxXMUonfITPmybWFjNwidlImNLOssCFav+UA1 22 | aiHY34EFkn4+DPPxgFUz1Zb/R/A0Qr0CvbHaL+DgZKFg2lY7MROL41Erpox5S6bh 23 | o1PhmPhyy0Zk2Ekic7Mk5P522aXHZX4I7kQA1BM7+3FSasevdTajlAkdPtXHYdhL 24 | TZFf5HkCgYB4qGARdPa0JJ+YbNilsykkIKs602Faen7qR1uVYFzbM+GUOjMrHkxt 25 | mmLE4+IrkhrWKQTW4vDAlIvTBZYJYeXZtu0IRilKccSExcKDhBmWhIDyFqRoyl3V 26 | OYmrskNT5YtBTEFKerAOKJ5LXxVQQ7k2YDz3uSli3NNsCMpfqo7Lxg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Thread/TriggeredLoopTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | 5 | namespace Quobject.EngineIoClientDotNet.Thread 6 | { 7 | public class TriggeredLoopTimer 8 | { 9 | private ManualResetEvent trigger; 10 | private CancellationTokenSource ts; 11 | 12 | private TriggeredLoopTimer() 13 | { 14 | trigger = new ManualResetEvent(false); 15 | ts = new CancellationTokenSource(); 16 | } 17 | 18 | public static TriggeredLoopTimer Start (Action method, int delayInMilliseconds) 19 | { 20 | TriggeredLoopTimer ping = new TriggeredLoopTimer(); 21 | ping.Run (method, delayInMilliseconds); 22 | return ping; 23 | } 24 | 25 | 26 | public void Trigger() 27 | { 28 | trigger.Set(); 29 | } 30 | 31 | private void Run (Action method, int delayInMilliseconds) 32 | { 33 | var worker = new BackgroundWorker(); 34 | 35 | worker.DoWork += (s, e) => 36 | { 37 | while (!ts.IsCancellationRequested) 38 | { 39 | System.Threading.Thread.Sleep (delayInMilliseconds); 40 | if (!ts.IsCancellationRequested) 41 | { 42 | method(); 43 | trigger.WaitOne(); 44 | trigger.Reset(); 45 | } 46 | } 47 | }; 48 | 49 | worker.RunWorkerAsync(); 50 | } 51 | 52 | public void Stop() 53 | { 54 | if (ts != null) 55 | { 56 | ts.Cancel(); 57 | trigger.Set(); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ModulesTests/ParseQSTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Quobject.EngineIoClientDotNet.Modules; 3 | using System.Collections.Generic; 4 | using Xunit; 5 | 6 | 7 | namespace Quobject.EngineIoClientDotNet_Tests.ModulesTests 8 | { 9 | public class ParseQsTests 10 | { 11 | //should parse a querystring and return an object 12 | [Fact] 13 | public void Decode() 14 | { 15 | // Single assignment 16 | var queryObj = ParseQS.Decode("foo=bar"); 17 | Assert.Equal("bar", queryObj["foo"]); 18 | 19 | // Multiple assignments 20 | queryObj = ParseQS.Decode("france=grenoble&germany=mannheim"); 21 | Assert.Equal("grenoble", queryObj["france"]); 22 | Assert.Equal("mannheim", queryObj["germany"]); 23 | 24 | // Assignments containing non-alphanumeric characters 25 | queryObj = ParseQS.Decode("india=new%20delhi"); 26 | Assert.Equal("new delhi", queryObj["india"]); 27 | } 28 | 29 | //should construct a query string from an object' 30 | [Fact] 31 | public void Encode() 32 | { 33 | 34 | Dictionary obj; 35 | 36 | obj = new Dictionary {{"a", "b"}}; 37 | var imObj = ImmutableDictionary.Create().AddRange(obj); 38 | Assert.Equal("a=b", ParseQS.Encode(imObj)); 39 | 40 | obj = new Dictionary {{"a", "b"}, {"c", "d"}}; 41 | imObj = ImmutableDictionary.Create().AddRange(obj); 42 | Assert.Equal("a=b&c=d", ParseQS.Encode(imObj)); 43 | 44 | obj = new Dictionary {{"a", "b"}, {"c", "tobi rocks"}}; 45 | imObj = ImmutableDictionary.Create().AddRange(obj); 46 | Assert.Equal("a=b&c=tobi%20rocks", ParseQS.Encode(imObj)); 47 | 48 | } 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /grunt/templates/EngineIoClientDotNet.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EngineIoClientDotNet 5 | @VERSION@-beta1 6 | EngineIoClientDotNet 7 | Matthias Ludwig 8 | http://opensource.org/licenses/MIT 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | https://github.com/Quobject/EngineIoClientDotNet/ 35 | This is the Engine.IO Client Library for C#. 36 | This library supports all of the features the JS client does, including events, options and upgrading transport. EngineIoClientDotNet has a similar api to those of the JavaScript client. 37 | engine.io.client 38 | 39 | 40 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Thread/EasyTimer.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Quobject.EngineIoClientDotNet.Modules; 7 | using System; 8 | 9 | 10 | 11 | namespace Quobject.EngineIoClientDotNet.Thread 12 | { 13 | public class EasyTimer 14 | { 15 | 16 | 17 | private CancellationTokenSource ts; 18 | 19 | 20 | public EasyTimer(CancellationTokenSource ts) 21 | { 22 | this.ts = ts; 23 | } 24 | 25 | public static EasyTimer SetTimeout(Action method, int delayInMilliseconds) 26 | { 27 | var ts = new CancellationTokenSource(); 28 | var ct = ts.Token; 29 | 30 | 31 | var worker = new BackgroundWorker(); 32 | 33 | worker.DoWork += (s, e) => System.Threading.Thread.Sleep(delayInMilliseconds); 34 | 35 | worker.RunWorkerCompleted += (s, e) => 36 | { 37 | if (!ts.IsCancellationRequested) 38 | { 39 | Task.Factory.StartNew(method, ct, TaskCreationOptions.None, TaskScheduler.Default); 40 | } 41 | }; 42 | 43 | worker.RunWorkerAsync(); 44 | 45 | 46 | 47 | // Returns a stop handle which can be used for stopping 48 | // the timer, if required 49 | return new EasyTimer(ts); 50 | } 51 | 52 | public void Stop() 53 | { 54 | var log = LogManager.GetLogger(Global.CallerName()); 55 | log.Info("EasyTimer stop"); 56 | if (ts != null) 57 | { 58 | ts.Cancel(); 59 | } 60 | } 61 | 62 | public static void TaskRun(Action action) 63 | { 64 | var t = new Task(action); 65 | t.RunSynchronously(); 66 | if (t.IsFaulted) 67 | { 68 | if (t.Exception != null) 69 | { 70 | throw t.Exception; 71 | } 72 | throw new Exception(); 73 | } 74 | //Task.Run(action).Wait(); 75 | } 76 | 77 | public static Task TaskRunNoWait(Action action) 78 | { 79 | var t = new Task(action); 80 | t.Start(); 81 | return t; 82 | } 83 | 84 | } 85 | 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/SocketTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using System.Collections.Immutable; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 7 | { 8 | public class SocketTest : Connection 9 | { 10 | private Socket socket; 11 | public string Message; 12 | 13 | [Fact] 14 | public void FilterUpgrades() 15 | { 16 | var options = CreateOptions(); 17 | options.Transports = ImmutableList.Empty.Add("polling"); 18 | 19 | socket = new Socket(options); 20 | 21 | var immutablelist = socket.FilterUpgrades(ImmutableList.Empty.Add("polling").Add("websocket")); 22 | 23 | Assert.Equal("polling", immutablelist[0]); 24 | Assert.Equal(1, immutablelist.Count); 25 | } 26 | 27 | [Fact] 28 | public async Task SocketClosing() 29 | { 30 | var closed = false; 31 | var error = false; 32 | 33 | var options = CreateOptions(); 34 | 35 | socket = new Socket("ws://0.0.0.0:8080", options); 36 | socket.On(Socket.EVENT_OPEN, () => 37 | { 38 | //socket.Send("test send"); 39 | }); 40 | socket.On(Socket.EVENT_CLOSE, () => 41 | { 42 | //log.Info("EVENT_CLOSE = "); 43 | closed = true; 44 | }); 45 | 46 | socket.Once(Socket.EVENT_ERROR, () => 47 | { 48 | //log.Info("EVENT_ERROR = "); 49 | error = true; 50 | }); 51 | 52 | socket.Open(); 53 | //System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); 54 | await Task.Delay(1000); 55 | Assert.True(closed); 56 | Assert.True(error); 57 | } 58 | 59 | [Fact] 60 | public void SocketOptionCookies() 61 | { 62 | var options = new Socket.Options(); 63 | options.Cookies.Add("foo", "bar"); 64 | Assert.Equal("foo=bar", options.GetCookiesAsString()); 65 | options.Cookies.Add("name2", "value2"); 66 | Assert.Equal("foo=bar; name2=value2", options.GetCookiesAsString()); 67 | } 68 | 69 | [Fact] 70 | public void DefaultProtocol() 71 | { 72 | var socket = new Socket("testme.quobject.com"); 73 | Assert.NotNull(socket); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /grunt/tasks/build-test.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('buildTest', 4 | 'test modules', function () { 5 | var 6 | fs = require('fs'), 7 | S = require('string'), 8 | string = require('string-formatter'), 9 | os = grunt.config('os'), 10 | config = grunt.config('config'), 11 | configuration = grunt.config('msbuild_configuration'), 12 | tasks = [], 13 | clean_format = os === 'win' ? '{0} start-process ' + 14 | '-NoNewWindow ' + 15 | //'-WindowStyle Normal ' + //-WindowStyle (Hidden | Normal) | -NoNewWindow 16 | '-FilePath {1} ' + 17 | '-ArgumentList \' {2} /t:clean /p:Configuration={3} \' ' : 18 | '{0} {1} /t:clean /p:Configuration={2}', 19 | build_format = os === 'win' ? '{0} start-process ' + 20 | '-NoNewWindow ' + 21 | //'-WindowStyle Normal ' + //-WindowStyle (Hidden | Normal) | -NoNewWindow 22 | '-FilePath {1} ' + 23 | '-ArgumentList \' {2} /p:Configuration={3} \' ' : 24 | '{0} {1} /p:Configuration={2}'; 25 | 26 | function addBuildWithTitle(title) { 27 | var 28 | dir_path = string.format('{0}/../../Src/{1}/', __dirname,title), 29 | csproj = string.format('{0}{1}.csproj', dir_path, title), 30 | clean = os === 'win' ? string.format(clean_format, config.win.powershell, config.win.msbuild, csproj, configuration ): 31 | string.format(clean_format, config.linux.msbuild, csproj, configuration), 32 | build = os === 'win' ? string.format(build_format, config.win.powershell, config.win.msbuild, csproj, configuration ): 33 | string.format(build_format, config.linux.msbuild, csproj, configuration), 34 | template_file_content = fs.readFileSync('./templates/AssemblyInfo.cs'); 35 | 36 | template_file_content = S(template_file_content).replaceAll('@VERSION@', config.version).s; 37 | //grunt.log.writeln('template_file_content = "%s"', template_file_content); 38 | fs.writeFileSync(string.format('{0}Properties/AssemblyInfo.cs', dir_path), template_file_content); 39 | 40 | tasks.push(clean); 41 | tasks.push(build); 42 | } 43 | 44 | if (os === 'win') { 45 | addBuildWithTitle('EngineIoClientDotNet.Tests.net45'); 46 | } else { 47 | addBuildWithTitle('EngineIoClientDotNet.Tests.mono'); 48 | } 49 | 50 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 51 | grunt.config('shell.exec.command', tasks.join('&&')); 52 | grunt.task.run('shell'); 53 | }); 54 | }; 55 | 56 | 57 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ParserTests/DecodeTests.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Modules; 2 | using Quobject.EngineIoClientDotNet.Parser; 3 | using Xunit; 4 | 5 | namespace Quobject.EngineIoClientDotNet_Tests.ParserTests 6 | { 7 | public class DecodeTests 8 | { 9 | private const string PARSER_ERROR = "parser error"; 10 | 11 | [Fact] 12 | public void DecodeBadFormat() 13 | { 14 | LogManager.SetupLogManager(); 15 | var log = LogManager.GetLogger("DecodeTests DecodeBadFormat"); 16 | 17 | 18 | Packet p = Parser.DecodePacket(":::"); 19 | Assert.Equal(Packet.ERROR, p.Type); 20 | Assert.Equal(PARSER_ERROR, p.Data); 21 | } 22 | 23 | [Fact] 24 | public void DecodeInexistingTypes() 25 | { 26 | 27 | Packet p = Parser.DecodePacket("94103"); 28 | Assert.Equal(Packet.ERROR, p.Type); 29 | Assert.Equal(PARSER_ERROR, p.Data); 30 | } 31 | 32 | [Fact] 33 | public void DecodeInvalidUTF8() 34 | { 35 | 36 | Packet p = Parser.DecodePacket("4\uffff", true); 37 | Assert.Equal(Packet.ERROR, p.Type); 38 | Assert.Equal(PARSER_ERROR, p.Data); 39 | } 40 | 41 | 42 | public class DecodePayloadBadFormat_DecodeCallback : IDecodePayloadCallback 43 | { 44 | 45 | public bool Call(Packet packet, int index, int total) 46 | { 47 | var isLast = index + 1 == total; 48 | Assert.True(isLast); 49 | Assert.Equal(Packet.ERROR, packet.Type); 50 | Assert.Equal(PARSER_ERROR, packet.Data); 51 | return true; 52 | } 53 | } 54 | 55 | [Fact] 56 | public void EncodeAndDecodeEmptyPayloads() 57 | { 58 | 59 | Packet.DecodePayload("1!", new DecodePayloadBadFormat_DecodeCallback()); 60 | Packet.DecodePayload("", new DecodePayloadBadFormat_DecodeCallback()); 61 | Packet.DecodePayload("))", new DecodePayloadBadFormat_DecodeCallback()); 62 | } 63 | 64 | [Fact] 65 | public void DecodePayloadBadPacketFormat() 66 | { 67 | 68 | Packet.DecodePayload("3:99", new DecodePayloadBadFormat_DecodeCallback()); 69 | Packet.DecodePayload("1:aa", new DecodePayloadBadFormat_DecodeCallback()); 70 | Packet.DecodePayload("1:a2:b", new DecodePayloadBadFormat_DecodeCallback()); 71 | } 72 | 73 | [Fact] 74 | public void DecodePayloadInvalidUTF8() 75 | { 76 | Packet.DecodePayload("2:4\uffff", new DecodePayloadBadFormat_DecodeCallback()); 77 | } 78 | 79 | 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/ParseQS.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Quobject.EngineIoClientDotNet.Modules 7 | { 8 | /// 9 | /// Provides methods for parsing a query string into an object, and vice versa. 10 | /// Ported from the JavaScript module. 11 | /// https://www.npmjs.org/package/parseqs 12 | /// 13 | public class ParseQS 14 | { 15 | /// 16 | /// Compiles a querystring 17 | /// Returns string representation of the object 18 | /// 19 | /// 20 | /// 21 | public static string Encode(ImmutableDictionary obj) 22 | { 23 | var sb = new StringBuilder(); 24 | foreach (var key in obj.Keys.OrderBy(x=>x)) 25 | { 26 | if (sb.Length > 0) 27 | { 28 | sb.Append("&"); 29 | } 30 | sb.Append(Global.EncodeURIComponent(key)); 31 | sb.Append("="); 32 | sb.Append(Global.EncodeURIComponent(obj[key])); 33 | } 34 | return sb.ToString(); 35 | } 36 | 37 | /// 38 | /// Compiles a querystring 39 | /// Returns string representation of the object 40 | /// 41 | /// 42 | /// 43 | internal static string Encode(System.Collections.Generic.Dictionary obj) 44 | { 45 | var sb = new StringBuilder(); 46 | foreach (var key in obj.Keys) 47 | { 48 | if (sb.Length > 0) 49 | { 50 | sb.Append("&"); 51 | } 52 | sb.Append(Global.EncodeURIComponent(key)); 53 | sb.Append("="); 54 | sb.Append(Global.EncodeURIComponent(obj[key])); 55 | } 56 | return sb.ToString(); 57 | } 58 | 59 | /// 60 | /// Parses a simple querystring into an object 61 | /// 62 | /// 63 | /// 64 | public static Dictionary Decode(string qs) 65 | { 66 | var qry = new Dictionary(); 67 | var pairs = qs.Split('&'); 68 | for (int i = 0; i < pairs.Length; i++) 69 | { 70 | var pair = pairs[i].Split('='); 71 | 72 | qry.Add(Global.DecodeURIComponent(pair[0]), Global.DecodeURIComponent(pair[1])); 73 | } 74 | return qry; 75 | } 76 | 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /grunt/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (grunt) { 3 | var 4 | node_os = require('os'), 5 | fs = require('fs'), 6 | strip_json = require('strip-json-comments'), 7 | config = JSON.parse(strip_json(String(fs.readFileSync('./config.json')))), 8 | util = require('util'), 9 | os = node_os.platform() === 'win32' ? 'win' : 'linux', 10 | nuget_builds = [ 11 | { "Name": "EngineIoClientDotNet.net35", "NuGetDir": "net35", "SourceDir": "net35", copyOnly: true }, 12 | { "Name": "EngineIoClientDotNet.net40", "NuGetDir": "net40", "SourceDir": "net40", copyOnly: true }, 13 | { "Name": "EngineIoClientDotNet.net45", "NuGetDir": "net45", "SourceDir": "net45", copyOnly: true }, 14 | { "Name": "EngineIoClientDotNet.netstandard1.3", "NuGetDir": "netstandard1.3", "SourceDir": "netstandard1.3", copyOnly: true }, 15 | ]; 16 | 17 | grunt.log.writeln(util.inspect(config)); 18 | grunt.log.writeln( 'os = "%s"', os ); 19 | 20 | grunt.loadTasks('./tasks'); 21 | 22 | grunt.initConfig({ 23 | os: os, 24 | config: config, 25 | //msbuild_configuration: 'Debug', 26 | msbuild_configuration: 'Release', 27 | nuget_builds: nuget_builds, 28 | release_path: './../Releases/<%= config.version %>/', 29 | working_path: './../Working/', 30 | server_path: '../TestServer/', 31 | shell: { 32 | exec: { 33 | options: { 34 | stdout: true, 35 | stderr: true 36 | } 37 | } 38 | }, 39 | jshint: { 40 | options: { 41 | jshintrc: true, 42 | }, 43 | target: [ 44 | 'Gruntfile.js', 45 | '<%= server_path %>server.js', 46 | 'tasks/**/*.js' 47 | ] 48 | }, 49 | clean: { 50 | release: ['<%= release_path %>/*'], 51 | working: ['<%= working_path %>/*'], 52 | options: { force: true } 53 | }, 54 | copy: { 55 | release: { 56 | files: [ 57 | { 58 | expand: true, 59 | cwd: './../EngineIoClientDotNet/bin/Release', 60 | src: '*', 61 | dest: '<%= release_path %>/net45' 62 | } 63 | ] 64 | }, 65 | release_mono: { 66 | files: [ 67 | { 68 | expand: true, 69 | cwd: './../EngineIoClientDotNet_Mono/bin/Release', 70 | src: '*', 71 | dest: '<%= release_path %>/mono' 72 | } 73 | ] 74 | }, 75 | } 76 | }); 77 | 78 | grunt.loadNpmTasks('grunt-contrib-copy'); 79 | grunt.loadNpmTasks('grunt-contrib-clean'); 80 | grunt.loadNpmTasks('grunt-shell'); 81 | grunt.loadNpmTasks('grunt-contrib-jshint'); 82 | grunt.registerTask('default', ['jshint', 'installNpm', 'nuget', 'buildClient', 'buildTest', 'startServer', 'testClient']); 83 | grunt.registerTask('test', ['jshint', 'buildClient', 'buildTest', 'testClient']); 84 | grunt.registerTask('makeNuget', ['jshint','clean:working','createNugetPackage']); 85 | }; 86 | -------------------------------------------------------------------------------- /grunt/templates/EngineIoClientDotNet - with xamarin.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EngineIoClientDotNet 5 | @VERSION@ 6 | EngineIoClientDotNet 7 | Matthias Ludwig 8 | http://opensource.org/licenses/MIT 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | https://github.com/Quobject/EngineIoClientDotNet/ 52 | This is the Engine.IO Client Library for C#. 53 | This library supports all of the features the JS client does, including events, options and upgrading transport. EngineIoClientDotNet has a similar api to those of the JavaScript client. 54 | engine.io.client 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /grunt/tasks/build-client.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('buildClient', 4 | 'build cs modules', function () { 5 | var 6 | fs = require('fs'), 7 | S = require('string'), 8 | string = require('string-formatter'), 9 | os = grunt.config('os'), 10 | config = grunt.config('config'), 11 | configuration = grunt.config('msbuild_configuration'), 12 | output_path_base = 'bin\\'+ configuration +'\\', 13 | nuget_builds = grunt.config('nuget_builds'), 14 | tasks = [], 15 | clean_format = os === 'win' ? '{0} start-process ' + 16 | '-NoNewWindow ' + 17 | //'-WindowStyle Normal ' + //-WindowStyle (Hidden | Normal) | -NoNewWindow 18 | '-FilePath {1} ' + 19 | '-ArgumentList \' {2} /t:clean;Rebuild /p:Configuration={3} /p:OutputPath={4} \' ' : 20 | '{0} {1} /t:Rebuild /p:Configuration={2} ', 21 | //build_format = os === 'win' ? '{0} start-process ' + 22 | // '-NoNewWindow ' + 23 | // //'-WindowStyle Normal ' + //-WindowStyle (Hidden | Normal) | -NoNewWindow 24 | // '-FilePath {1} ' + 25 | // '-ArgumentList \' {2} /p:Configuration={3} \' ' : 26 | // '{0} {1} /p:Configuration={2}', 27 | i; 28 | 29 | function addBuildWithTitle(title, dir, copyOnly) { 30 | var 31 | dir_path = string.format('{0}/../../Src/{1}/', __dirname, title), 32 | csproj = string.format('{0}{1}.csproj', dir_path, title), 33 | output_path = output_path_base + dir +'\\', 34 | clean = os === 'win' ? string.format(clean_format, config.win.powershell, config.win.msbuild, csproj, configuration, output_path) : 35 | string.format(clean_format, config.linux.msbuild, csproj, configuration), 36 | //build = os === 'win' ? string.format(build_format, config.win.powershell, config.win.msbuild, csproj, configuration ): 37 | // string.format(build_format, config.linux.msbuild, csproj, configuration), 38 | template_file_content = fs.readFileSync('./templates/AssemblyInfo.cs'); 39 | 40 | //template_file_content = S(template_file_content).replaceAll('@TITLE@', title).s; 41 | template_file_content = S(template_file_content).replaceAll('@VERSION@', config.version).s; 42 | //grunt.log.writeln('template_file_content = "%s"', template_file_content); 43 | fs.writeFileSync(string.format('{0}Properties/AssemblyInfo.cs', dir_path), template_file_content); 44 | if (!copyOnly) { 45 | tasks.push(clean); 46 | } 47 | } 48 | 49 | for (i = 0; i < nuget_builds.length; i++) { 50 | if (nuget_builds[i].Name !== 'EngineIoClientDotNet.netstandard1.3') { 51 | addBuildWithTitle(nuget_builds[i].Name, nuget_builds[i].NuGetDir, nuget_builds[i].copyOnly); 52 | } 53 | } 54 | 55 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 56 | grunt.config('shell.exec.command', tasks.join('&&')); 57 | grunt.task.run('shell'); 58 | 59 | if (configuration === 'Release') { 60 | //grunt.task.run('clean:release'); 61 | if (os === 'win') { 62 | grunt.task.run('copy:release'); 63 | } else { 64 | grunt.task.run('copy:release_mono'); 65 | } 66 | } 67 | }); 68 | }; 69 | 70 | 71 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/LogManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace Quobject.EngineIoClientDotNet.Modules 8 | { 9 | public class LogManager 10 | { 11 | private const string LogFilePath = "XunitTrace.log"; 12 | 13 | private static readonly LogManager EmptyLogger = new LogManager(null); 14 | 15 | private static StreamWriter writer; 16 | 17 | private readonly string type; 18 | 19 | #region Statics 20 | 21 | public static void SetupLogManager() 22 | {} 23 | 24 | public static LogManager GetLogger(string type) 25 | { 26 | return new LogManager(type); 27 | } 28 | 29 | public static LogManager GetLogger(Type type) 30 | { 31 | return GetLogger(type.ToString()); 32 | } 33 | 34 | public static LogManager GetLogger(MethodBase methodBase) 35 | { 36 | #if DEBUG 37 | string declaringType = methodBase.DeclaringType != null 38 | ? methodBase.DeclaringType.ToString() 39 | : string.Empty; 40 | string fullType = string.Format("{0}#{1}", declaringType, methodBase.Name); 41 | return GetLogger(fullType); 42 | #else 43 | return EmptyLogger; 44 | #endif 45 | } 46 | 47 | #endregion 48 | 49 | public LogManager(string type) 50 | { 51 | this.type = type; 52 | } 53 | 54 | public static bool Enabled { get; set; } 55 | 56 | private static StreamWriter Writer 57 | { 58 | get 59 | { 60 | if (writer == null) 61 | { 62 | FileStream fs = new FileStream( 63 | LogFilePath, FileMode.Append, FileAccess.Write, FileShare.Read); 64 | writer = new StreamWriter(fs, Encoding.UTF8) 65 | { 66 | AutoFlush = true 67 | }; 68 | } 69 | 70 | return writer; 71 | } 72 | } 73 | 74 | [Conditional("DEBUG")] 75 | public void Info(string msg) 76 | { 77 | if (!Enabled) 78 | { 79 | return; 80 | } 81 | 82 | Writer.WriteLine( 83 | "{0:yyyy-MM-dd HH:mm:ss fff} [] {1} {2}", 84 | DateTime.Now, 85 | this.type, 86 | Global.StripInvalidUnicodeCharacters(msg)); 87 | } 88 | 89 | [Conditional("DEBUG")] 90 | public void Error(string p, Exception exception) 91 | { 92 | this.Info($"ERROR {p} {exception.Message} {exception.StackTrace}"); 93 | if (exception.InnerException != null) 94 | { 95 | this.Info($"ERROR exception.InnerException {p} {exception.InnerException.Message} {exception.InnerException.StackTrace}"); 96 | } 97 | } 98 | 99 | 100 | [Conditional("DEBUG")] 101 | internal void Error(Exception e) 102 | { 103 | this.Error("", e); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /TestServer/server.js: -------------------------------------------------------------------------------- 1 | var 2 | ssl = true, 3 | express = require('express'), 4 | fs = require('fs'), 5 | strip_json = require('strip-json-comments'), 6 | config = JSON.parse(strip_json(String(fs.readFileSync('./../grunt/config.json')))), 7 | util = require('util'), 8 | app = express(), 9 | fs = require('fs'), 10 | options = { 11 | key: fs.readFileSync(__dirname + '/testme.quobject.com.key'), 12 | cert: fs.readFileSync(__dirname + '/testme.quobject.com.cert'), 13 | requestCert: true 14 | }, 15 | server, 16 | https, 17 | http, 18 | primus_server, 19 | ssl_server ; 20 | 21 | 22 | console.log("https port = " + config.server.ssl_port); 23 | https = require('https').createServer(options, app); 24 | ssl_server = require('engine.io').attach(https, {'pingInterval': 500}); 25 | https.listen(config.server.ssl_port, function(d) { 26 | console.log('Engine.IO server listening on port', config.server.ssl_port); 27 | }); 28 | 29 | console.log("http port = " + config.server.port); 30 | http = require('http').createServer(app); 31 | server = require('engine.io').attach(http, { 'pingInterval': 500 }); 32 | primus_server = require('engine.io').attach(http, { 'pingInterval': 500, 'path' : '/primus/engine.io' }); 33 | http.listen( config.server.port, function() { 34 | console.log('Engine.IO server listening on port', config.server.port); 35 | }); 36 | 37 | 38 | primus_server.on('connection', function (socket) { 39 | console.log('primus_server connection'); 40 | socket.send('hi'); 41 | }); 42 | 43 | 44 | http.on('request', function(request, response) { 45 | //console.log('request ' +util.inspect( request.headers)); 46 | 47 | }); 48 | 49 | 50 | 51 | 52 | server.on('connection', function(socket){ 53 | socket.send('hi'); 54 | 55 | 56 | 57 | 58 | // Bounce any received messages back 59 | socket.on('message', function (data) { 60 | 61 | console.log('got message1 data = "' + data + '"'); 62 | console.log('got message data stringify = "' + JSON.stringify(data) + '"'); 63 | var result = new Int8Array(data); 64 | console.log('got message data Int8Array = "' + JSON.stringify(result) + '"\n\n'); 65 | 66 | if (data === 'give binary') { 67 | var abv = new Int8Array(5); 68 | for (var i = 0; i < 5; i++) { 69 | abv[i] = i; 70 | } 71 | socket.send(abv); 72 | return; 73 | } 74 | 75 | if (data === 'cookie') { 76 | console.log('cookie ' + util.inspect(socket.request.headers)); 77 | if (socket.request.headers !== undefined) { 78 | if (socket.request.headers.cookie === "foo=bar") { 79 | socket.send('got cookie'); 80 | return; 81 | } 82 | } 83 | socket.send('no cookie'); 84 | return; 85 | } 86 | 87 | 88 | socket.send(data); 89 | 90 | }); 91 | 92 | }); 93 | 94 | 95 | 96 | 97 | ssl_server.on('connection', function(socket){ 98 | socket.send('hi'); 99 | 100 | // Bounce any received messages back 101 | socket.on('message', function (data) { 102 | if (data === 'give binary') { 103 | var abv = new Int8Array(5); 104 | for (var i = 0; i < 5; i++) { 105 | abv[i] = i; 106 | } 107 | socket.send(abv); 108 | return; 109 | } 110 | console.log('got message data = "' + data + '"'); 111 | console.log('got message data stringify = "' + JSON.stringify(data) + '"'); 112 | var result = new Int8Array(data); 113 | console.log('got message data Int8Array = "' + JSON.stringify(result) + '"\n\n'); 114 | 115 | socket.send(data); 116 | 117 | }); 118 | }); -------------------------------------------------------------------------------- /grunt/tasks/createNugetPackage.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.registerTask('createNugetPackage', 4 | 'create package ', function () { 5 | var 6 | fs = require('fs'), 7 | S = require('string'), 8 | string = require('string-formatter'), 9 | os = grunt.config('os'), 10 | config = grunt.config('config'), 11 | working_path = grunt.config('working_path'), 12 | package_path = working_path + '/NuGet/', 13 | package_lib_path = working_path + '/NuGet/lib/', 14 | //configuration = grunt.config('msbuild_configuration'), 15 | configuration = grunt.config('msbuild_configuration'), 16 | output_path_base = 'bin\\' + configuration + '\\', 17 | nuget_builds = grunt.config('nuget_builds'), 18 | nuget_path = os === 'win' ? 19 | config.win.nuget : config.linux.nuget, 20 | dst_path, 21 | template_file_content, 22 | i, 23 | tasks = []; 24 | 25 | //function createPackageWithTitle(title) { 26 | // var 27 | // dir_path = string.format('{0}/../../{1}/', __dirname, title), 28 | // csproj = string.format('{0}{1}.csproj', dir_path, title), 29 | // pack = string.format('{0} pack {1}', nuget_path, csproj); 30 | 31 | // tasks.push(pack); 32 | //} 33 | 34 | if (os !== 'win') { 35 | return; 36 | } 37 | if (configuration !== 'Release') { 38 | grunt.log.writeln('wrong configuration = ' + configuration); 39 | return; 40 | } 41 | 42 | //createPackageWithTitle('EngineIoClientDotNet'); 43 | 44 | 45 | if (! fs.existsSync(working_path)) { 46 | fs.mkdirSync(working_path); 47 | fs.mkdirSync(package_path); 48 | fs.mkdirSync(package_lib_path); 49 | } 50 | if (!fs.existsSync(package_path)) { 51 | fs.mkdirSync(package_path); 52 | fs.mkdirSync(package_lib_path); 53 | } 54 | if (!fs.existsSync(package_lib_path)) { 55 | fs.mkdirSync(package_lib_path); 56 | } 57 | 58 | for (i = 0; i < nuget_builds.length; i++) { 59 | dst_path = package_lib_path + nuget_builds[i].NuGetDir + '/'; 60 | //files = fs.readdirSync(dst_path); 61 | grunt.log.writeln(string.format('dst_path={0}', dst_path)); 62 | fs.mkdirSync(dst_path); 63 | } 64 | 65 | 66 | template_file_content = fs.readFileSync('./templates/EngineIoClientDotNet.nuspec'); 67 | template_file_content = S(template_file_content).replaceAll('@VERSION@', config.version).s; 68 | fs.writeFileSync(string.format('{0}EngineIoClientDotNet.nuspec', package_path), template_file_content); 69 | 70 | 71 | 72 | function addBuildWithTitle(title, destsubdir, srcsubdir) { 73 | var src_path = string.format('{0}/../../Src/{1}/{2}{3}/', __dirname, title, output_path_base, srcsubdir); 74 | var dst_path = package_lib_path + destsubdir + '/'; 75 | var filename = 'EngineIoClientDotNet.dll'; 76 | var src_file = string.format('{0}{1}', src_path, filename); 77 | var dst_file = string.format('{0}EngineIoClientDotNet.dll', dst_path); 78 | 79 | grunt.log.writeln(string.format('src_file={0} dst_file={1}', src_file, dst_file)); 80 | fs.writeFileSync(dst_file, fs.readFileSync(src_file)); 81 | 82 | //src_file = src_path + string.format('{0}.xml', title); 83 | //dst_file = string.format('{0}EngineIoClientDotNet.xml', dst_path); 84 | //grunt.log.writeln(string.format('src_file={0} dst_file={1}', src_file, dst_file)); 85 | //fs.writeFileSync(dst_file, fs.readFileSync(src_file)); 86 | } 87 | 88 | for (i = 0; i < nuget_builds.length; i++) { 89 | addBuildWithTitle(nuget_builds[i].Name, nuget_builds[i].NuGetDir, nuget_builds[i].SourceDir); 90 | } 91 | tasks.push('C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe pwd'); 92 | tasks.push(string.format('{0} pack EngineIoClientDotNet.nuspec', config.win.nuget)); 93 | grunt.log.writeln('tasks = %s', JSON.stringify(tasks)); 94 | grunt.config('shell.exec.options.execOptions.cwd', package_path); 95 | grunt.config('shell.exec.command', tasks.join('&&')); 96 | grunt.task.run('shell'); 97 | }); 98 | }; -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/BinaryWebSocketTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using Xunit; 5 | 6 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 7 | { 8 | public class BinaryWebSocketTest : Connection 9 | { 10 | private ManualResetEvent _manualResetEvent = null; 11 | 12 | [Fact] 13 | public void ReceiveBinaryData() 14 | { 15 | _manualResetEvent = new ManualResetEvent(false); 16 | 17 | var events = new ConcurrentQueue(); 18 | 19 | var binaryData = new byte[5]; 20 | for (int i = 0; i < binaryData.Length; i++) 21 | { 22 | binaryData[i] = (byte)(i + 0); 23 | } 24 | 25 | var options = CreateOptions(); 26 | 27 | var socket = new Socket(options); 28 | 29 | socket.On(Socket.EVENT_OPEN, () => 30 | { 31 | //log.Info(Socket.EVENT_OPEN); 32 | }); 33 | 34 | socket.On(Socket.EVENT_UPGRADE, () => 35 | { 36 | //log.Info(Socket.EVENT_UPGRADE); 37 | socket.Send(binaryData); 38 | }); 39 | 40 | socket.On(Socket.EVENT_MESSAGE, (d) => 41 | { 42 | var data = d as string; 43 | //log.Info(string.Format("EVENT_MESSAGE data ={0} d = {1} ", data, d)); 44 | 45 | if (data == "hi") 46 | { 47 | return; 48 | } 49 | events.Enqueue(d); 50 | _manualResetEvent.Set(); 51 | }); 52 | 53 | socket.Open(); 54 | _manualResetEvent.WaitOne(); 55 | socket.Close(); 56 | //log.Info("ReceiveBinaryData end"); 57 | 58 | var binaryData2 = new byte[5]; 59 | for (int i = 0; i < binaryData2.Length; i++) 60 | { 61 | binaryData2[i] = (byte)(i + 1); 62 | } 63 | 64 | object result; 65 | events.TryDequeue(out result); 66 | Assert.Equal(binaryData, result); 67 | } 68 | 69 | [Fact] 70 | public void ReceiveBinaryDataAndMultibyteUTF8String() 71 | { 72 | _manualResetEvent = new ManualResetEvent(false); 73 | 74 | var events = new ConcurrentQueue(); 75 | 76 | var binaryData = new byte[5]; 77 | for (int i = 0; i < binaryData.Length; i++) 78 | { 79 | binaryData[i] = (byte)i; 80 | } 81 | const string stringData = "Ä ä Ü ü ß"; 82 | 83 | var options = CreateOptions(); 84 | 85 | var socket = new Socket(options); 86 | 87 | socket.On(Socket.EVENT_OPEN, () => 88 | { 89 | }); 90 | 91 | socket.On(Socket.EVENT_UPGRADE, () => 92 | { 93 | //log.Info(Socket.EVENT_UPGRADE); 94 | socket.Send(binaryData); 95 | socket.Send(stringData); 96 | }); 97 | 98 | socket.On(Socket.EVENT_MESSAGE, (d) => 99 | { 100 | var data = d as string; 101 | //log.Info(string.Format("EVENT_MESSAGE data ={0} d = {1} ", data, d)); 102 | 103 | if (data == "hi") 104 | { 105 | return; 106 | } 107 | events.Enqueue(d); 108 | if (events.Count > 1) 109 | { 110 | _manualResetEvent.Set(); 111 | } 112 | }); 113 | 114 | socket.Open(); 115 | _manualResetEvent.WaitOne(); 116 | socket.Close(); 117 | 118 | var binaryData2 = new byte[5]; 119 | for (int i = 0; i < binaryData2.Length; i++) 120 | { 121 | binaryData2[i] = (byte)(i + 1); 122 | } 123 | 124 | object result; 125 | events.TryDequeue(out result); 126 | Assert.Equal(binaryData, result); 127 | events.TryDequeue(out result); 128 | Assert.Equal(stringData, (string)result); 129 | //log.Info("ReceiveBinaryDataAndMultibyteUTF8String end"); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ModulesTests/UTF8Tests.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Modules; 2 | using System; 3 | using Xunit; 4 | 5 | 6 | namespace Quobject.EngineIoClientDotNet_Tests.ModulesTests 7 | { 8 | public class Utf8Tests 9 | { 10 | private static readonly Data[] DATA = new Data[] 11 | { 12 | // 1-byte 13 | new Data(0x0000, "\x00", "\x00"), 14 | new Data(0x005c, "\u005C\u005C", "\u005C\u005C"), // = backslash 15 | new Data(0x007f, "\u007F", "\u007F"), 16 | // 2-byte 17 | new Data(0x0080, "\u0080", "\u00C2\u0080"), 18 | new Data(0x05CA, "\u05CA", "\u00D7\u008A"), 19 | new Data(0x07FF, "\u07FF", "\u00DF\u00BF"), 20 | // 3-byte 21 | new Data(0x0800, "\u0800", "\u00E0\u00A0\u0080"), 22 | new Data(0x2C3C, "\u2C3C", "\u00E2\u00B0\u00BC"), 23 | new Data(0x07FF, "\uFFFF", "\u00EF\u00BF\u00BF"), 24 | // unmatched surrogate halves 25 | // high surrogates: 0xD800 to 0xDBFF 26 | new Data(0xD800, "\uD800", "\u00ED\u00A0\u0080"), 27 | new Data("High surrogate followed by another high surrogate", 28 | "\uD800\uD800", "\u00ED\u00A0\u0080\u00ED\u00A0\u0080"), 29 | new Data("High surrogate followed by a symbol that is not a surrogate", 30 | "\uD800A", "\u00ED\u00A0\u0080A"), 31 | new Data( 32 | "Unmatched high surrogate, followed by a surrogate pair, followed by an unmatched high surrogate", 33 | "\uD800\uD834\uDF06\uD800", "\u00ED\u00A0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00A0\u0080"), 34 | new Data(0xD9AF, "\uD9AF", "\u00ED\u00A6\u00AF"), 35 | new Data(0xDBFF, "\uDBFF", "\u00ED\u00AF\u00BF"), 36 | // low surrogates: 0xDC00 to 0xDFFF 37 | new Data(0xDC00, "\uDC00", "\u00ED\u00B0\u0080"), 38 | new Data("Low surrogate followed by another low surrogate", 39 | "\uDC00\uDC00", "\u00ED\u00B0\u0080\u00ED\u00B0\u0080"), 40 | new Data("Low surrogate followed by a symbol that is not a surrogate", 41 | "\uDC00A", "\u00ED\u00B0\u0080A"), 42 | new Data( 43 | "Unmatched low surrogate, followed by a surrogate pair, followed by an unmatched low surrogate", 44 | "\uDC00\uD834\uDF06\uDC00", "\u00ED\u00B0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00B0\u0080"), 45 | new Data(0xDEEE, "\uDEEE", "\u00ED\u00BB\u00AE"), 46 | new Data(0xDFFF, "\uDFFF", "\u00ED\u00BF\u00BF"), 47 | // 4-byte 48 | new Data(0x010000, "\uD800\uDC00", "\u00F0\u0090\u0080\u0080"), 49 | new Data(0x01D306, "\uD834\uDF06", "\u00F0\u009D\u008C\u0086"), 50 | new Data(0x010FFF, "\uDBFF\uDFFF", "\u00F4\u008F\u00BF\u00BF"), 51 | }; 52 | 53 | // 54 | [Fact] 55 | public void EncodeAndDecode() 56 | { 57 | foreach (var data in DATA) 58 | { 59 | data.Test(); 60 | } 61 | } 62 | 63 | 64 | 65 | private class Data 66 | { 67 | private readonly int _codePoint = -1; 68 | private String Description { get; set; } 69 | private String Decoded { get; set; } 70 | private String Encoded { get; set; } 71 | 72 | public Data(int codePoint, String decoded, String encoded) 73 | { 74 | this._codePoint = codePoint; 75 | this.Decoded = decoded; 76 | this.Encoded = encoded; 77 | } 78 | 79 | public Data(String description, String decoded, String encoded) 80 | { 81 | this.Description = description; 82 | this.Decoded = decoded; 83 | this.Encoded = encoded; 84 | } 85 | 86 | public void Test() 87 | { 88 | EncodingTest(); 89 | DecodingTest(); 90 | ExceptionTest(); 91 | } 92 | 93 | private void EncodingTest() 94 | { 95 | var value = UTF8.Encode(Decoded); 96 | Assert.Equal(Encoded, value); 97 | } 98 | 99 | private void DecodingTest() 100 | { 101 | Assert.Equal(Decoded, UTF8.Decode(Encoded)); 102 | } 103 | 104 | private void ExceptionTest() 105 | { 106 | Assert.Throws( 107 | delegate 108 | { 109 | UTF8.Decode("\uFFFF"); 110 | }); 111 | 112 | Assert.Throws( 113 | delegate 114 | { 115 | UTF8.Decode("\xE9\x00\x00"); 116 | }); 117 | 118 | Assert.Throws( 119 | delegate 120 | { 121 | UTF8.Decode("\xC2\uFFFF"); 122 | }); 123 | 124 | Assert.Throws( 125 | delegate 126 | { 127 | UTF8.Decode("\xF0\x9D"); 128 | }); 129 | 130 | } 131 | 132 | 133 | private string Reason 134 | { 135 | get { return Description ?? "U+" + _codePoint.ToString("X4").ToUpper(); } 136 | } 137 | } 138 | 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/BinaryPollingTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Quobject.EngineIoClientDotNet.Client.Transports; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Immutable; 5 | using System.Threading; 6 | using Xunit; 7 | 8 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 9 | { 10 | public class BinaryPollingTest : Connection 11 | { 12 | //[Fact] 13 | //public void PingTest() 14 | //{ 15 | // var log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod()); 16 | // log.Info("Start"); 17 | 18 | // var binaryData = new byte[5]; 19 | // for (int i = 0; i < binaryData.Length; i++) 20 | // { 21 | // binaryData[i] = (byte)i; 22 | // } 23 | 24 | // var events = new ConcurrentQueue(); 25 | 26 | // var options = CreateOptions(); 27 | // options.Transports = ImmutableList.Create(Polling.NAME); 28 | 29 | // var socket = new Socket(options); 30 | 31 | // socket.On(Socket.EVENT_OPEN, () => 32 | // { 33 | // log.Info("EVENT_OPEN"); 34 | 35 | // socket.Send(binaryData); 36 | // socket.Send("cash money €€€"); 37 | // }); 38 | 39 | // socket.On(Socket.EVENT_MESSAGE, (d) => 40 | // { 41 | // var data = d as string; 42 | // log.Info(string.Format("EVENT_MESSAGE data ={0} d = {1} ", data, d)); 43 | 44 | // if (data == "hi") 45 | // { 46 | // return; 47 | // } 48 | // events.Enqueue(d); 49 | // //socket.Close(); 50 | // }); 51 | 52 | // socket.Open(); 53 | // Task.Delay(20000).Wait(); 54 | // socket.Close(); 55 | // log.Info("ReceiveBinaryData end"); 56 | 57 | // var binaryData2 = new byte[5]; 58 | // for (int i = 0; i < binaryData2.Length; i++) 59 | // { 60 | // binaryData2[i] = (byte)(i + 1); 61 | // } 62 | 63 | // object result; 64 | // events.TryDequeue(out result); 65 | // Assert.Equal("1", "1"); 66 | //} 67 | 68 | private ManualResetEvent _manualResetEvent = null; 69 | 70 | [Fact] 71 | public void ReceiveBinaryData() 72 | { 73 | _manualResetEvent = new ManualResetEvent(false); 74 | 75 | var events = new ConcurrentQueue(); 76 | 77 | var binaryData = new byte[5]; 78 | for (int i = 0; i < binaryData.Length; i++) 79 | { 80 | binaryData[i] = (byte)i; 81 | } 82 | 83 | var options = CreateOptions(); 84 | options.Transports = ImmutableList.Create(Polling.NAME); 85 | 86 | var socket = new Socket(options); 87 | 88 | socket.On(Socket.EVENT_OPEN, () => 89 | { 90 | socket.Send(binaryData); 91 | //socket.Send("cash money €€€"); 92 | }); 93 | 94 | socket.On(Socket.EVENT_MESSAGE, (d) => 95 | { 96 | var data = d as string; 97 | //log.Info(string.Format("EVENT_MESSAGE data ={0} d = {1} ", data, d)); 98 | 99 | if (data == "hi") 100 | { 101 | return; 102 | } 103 | events.Enqueue(d); 104 | _manualResetEvent.Set(); 105 | }); 106 | 107 | socket.Open(); 108 | _manualResetEvent.WaitOne(); 109 | socket.Close(); 110 | //log.Info("ReceiveBinaryData end"); 111 | 112 | var binaryData2 = new byte[5]; 113 | for (int i = 0; i < binaryData2.Length; i++) 114 | { 115 | binaryData2[i] = (byte)(i + 1); 116 | } 117 | 118 | object result; 119 | events.TryDequeue(out result); 120 | Assert.Equal(binaryData, result); 121 | } 122 | 123 | [Fact] 124 | public void ReceiveBinaryDataAndMultibyteUTF8String() 125 | { 126 | _manualResetEvent = new ManualResetEvent(false); 127 | 128 | var events = new ConcurrentQueue(); 129 | 130 | var binaryData = new byte[5]; 131 | for (int i = 0; i < binaryData.Length; i++) 132 | { 133 | binaryData[i] = (byte)i; 134 | } 135 | const string stringData = "cash money €€€"; 136 | 137 | var options = CreateOptions(); 138 | options.Transports = ImmutableList.Create(Polling.NAME); 139 | 140 | var socket = new Socket(options); 141 | 142 | socket.On(Socket.EVENT_OPEN, () => 143 | { 144 | socket.On(Socket.EVENT_MESSAGE, (d) => 145 | { 146 | var data = d as string; 147 | //log.Info(string.Format("EVENT_MESSAGE data ={0} d = {1} ", data, d)); 148 | 149 | if (data == "hi") 150 | { 151 | return; 152 | } 153 | events.Enqueue(d); 154 | if (events.Count > 1) 155 | { 156 | _manualResetEvent.Set(); 157 | } 158 | }); 159 | socket.Send(binaryData); 160 | socket.Send(stringData); 161 | }); 162 | 163 | socket.Open(); 164 | _manualResetEvent.WaitOne(); 165 | socket.Close(); 166 | var binaryData2 = new byte[5]; 167 | for (int i = 0; i < binaryData2.Length; i++) 168 | { 169 | binaryData2[i] = (byte)(i + 1); 170 | } 171 | 172 | object result; 173 | events.TryDequeue(out result); 174 | Assert.Equal(binaryData, result); 175 | events.TryDequeue(out result); 176 | Assert.Equal(stringData, (string)result); 177 | socket.Close(); 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/ByteBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Quobject.EngineIoClientDotNet.Parser 5 | { 6 | public class ByteBuffer 7 | { 8 | private readonly MemoryStream _memoryStream; 9 | 10 | private long _limit = 0; 11 | 12 | 13 | public ByteBuffer(int length) 14 | { 15 | this._memoryStream = new MemoryStream(); 16 | _memoryStream.SetLength(length); 17 | _memoryStream.Capacity = length; 18 | _limit = length; 19 | } 20 | 21 | 22 | public static ByteBuffer Allocate(int length) 23 | { 24 | return new ByteBuffer(length); 25 | } 26 | 27 | internal void Put(byte[] buf) 28 | { 29 | _memoryStream.Write(buf,0,buf.Length); 30 | } 31 | 32 | internal byte[] Array() 33 | { 34 | return _memoryStream.ToArray(); 35 | } 36 | 37 | internal static ByteBuffer Wrap(byte[] data) 38 | { 39 | var result = new ByteBuffer(data.Length); 40 | result.Put(data); 41 | return result; 42 | } 43 | 44 | /// 45 | /// A buffer's capacity is the number of elements it contains. The capacity of a 46 | /// buffer is never negative and never changes. 47 | /// 48 | public int Capacity 49 | { 50 | get { return _memoryStream.Capacity; } 51 | } 52 | 53 | /// 54 | /// Absolute get method. Reads the byte at the given index. 55 | /// 56 | /// The index from which the byte will be read 57 | /// The byte at the given index 58 | internal byte Get(long index) 59 | { 60 | if (index > Capacity) 61 | { 62 | throw new IndexOutOfRangeException(); 63 | } 64 | 65 | _memoryStream.Position = index; 66 | return (byte) _memoryStream.ReadByte(); 67 | } 68 | 69 | /// 70 | /// Relative bulk get method. 71 | /// 72 | /// This method transfers bytes from this buffer into the given destination array. If there are fewer bytes 73 | /// remaining in the buffer than are required to satisfy the request, that is, if length > remaining(), then 74 | /// no bytes are transferred and a BufferUnderflowException is thrown. 75 | /// 76 | /// Otherwise, this method copies length bytes from this buffer into the given array, starting at the current 77 | /// position of this buffer and at the given offset in the array. The position of this buffer is then 78 | /// incremented by length. 79 | /// 80 | /// In other words, an invocation of this method of the form src.get(dst, off, len) has exactly the same effect as the loop 81 | /// 82 | /// for (int i = off; i < off + len; i++) 83 | /// dst[i] = src.get(); 84 | /// 85 | /// except that it first checks that there are sufficient bytes in this buffer and it is potentially much more efficient. 86 | /// 87 | /// 88 | /// 89 | /// 90 | /// This buffer 91 | internal ByteBuffer Get(byte[] dst, int offset, int length) 92 | { 93 | _memoryStream.Read(dst, offset, length); 94 | return this; 95 | } 96 | 97 | 98 | /// 99 | /// Relative bulk get method. 100 | /// 101 | /// This method transfers bytes from this buffer into the given destination array. 102 | /// An invocation of this method of the form src.get(a) behaves in exactly the same 103 | /// way as the invocation src.get(a, 0, a.length) 104 | /// 105 | /// 106 | /// This buffer 107 | internal ByteBuffer Get(byte[] dst) 108 | { 109 | return Get(dst, 0, dst.Length); 110 | } 111 | 112 | /// 113 | /// Sets this buffer's position. If the mark is defined and larger than the new 114 | /// position then it is discarded. 115 | /// 116 | /// The new position value; must be non-negative and no larger than the current limit 117 | internal void Position(long newPosition) 118 | { 119 | _memoryStream.Position = newPosition; 120 | } 121 | 122 | 123 | /// 124 | /// Sets this buffer's limit. If the position is larger than the new limit then it is set to the new limit. 125 | /// If the mark is defined and larger than the new limit then it is discarded. 126 | /// 127 | /// A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity. 128 | /// 129 | /// The new limit value; must be non-negative and no larger than this buffer's capacity 130 | internal void Limit(long newLimit) 131 | { 132 | _limit = newLimit; 133 | if (_memoryStream.Position > newLimit) 134 | { 135 | _memoryStream.Position = newLimit; 136 | } 137 | } 138 | 139 | /// 140 | /// Returns the number of elements between the current position and the limit. 141 | /// 142 | /// The number of elements remaining in this buffer 143 | internal long Remaining() 144 | { 145 | return (_limit - _memoryStream.Position); 146 | } 147 | 148 | 149 | 150 | /// 151 | /// Clears this buffer. The position is set to zero, the limit is set to the capacity, and the mark is discarded. 152 | /// 153 | /// This method does not actually erase the data in the buffer, but it is named as if 154 | /// it did because it will most often be used in situations in which that might as well be the case. 155 | /// 156 | internal void Clear() 157 | { 158 | Position(0); 159 | Limit(Capacity); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/Global.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Runtime.CompilerServices; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Quobject.EngineIoClientDotNet.Modules 11 | { 12 | public static class Global 13 | { 14 | public static string EncodeURIComponent(string str) 15 | { 16 | //http://stackoverflow.com/a/4550600/1109316 17 | return Uri.EscapeDataString(str); 18 | } 19 | 20 | public static string DecodeURIComponent(string str) 21 | { 22 | return Uri.UnescapeDataString(str); 23 | } 24 | 25 | public static string CallerName([CallerMemberName]string caller = "", [CallerLineNumber]int number = 0, [CallerFilePath]string path = "") 26 | { 27 | var s = path.Split('\\'); 28 | var fileName = s.LastOrDefault(); 29 | if (path.Contains("SocketIoClientDotNet.Tests")) 30 | { 31 | path = "SocketIoClientDotNet.Tests"; 32 | } 33 | else if (path.Contains("SocketIoClientDotNet")) 34 | { 35 | path = "SocketIoClientDotNet"; 36 | } 37 | else if (path.Contains("EngineIoClientDotNet")) 38 | { 39 | path = "EngineIoClientDotNet"; 40 | } 41 | 42 | return string.Format("{0}-{1}:{2}#{3}", path, fileName, caller, number); 43 | } 44 | 45 | //from http://stackoverflow.com/questions/8767103/how-to-remove-invalid-code-points-from-a-string 46 | public static string StripInvalidUnicodeCharacters(string str) 47 | { 48 | var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((? item) 58 | { 59 | var oldContext = SynchronizationContext.Current; 60 | var synch = new ExclusiveSynchronizationContext(); 61 | SynchronizationContext.SetSynchronizationContext(synch); 62 | synch.Post(async _ => 63 | { 64 | try 65 | { 66 | await item(); 67 | } 68 | catch (Exception e) 69 | { 70 | synch.InnerException = e; 71 | throw; 72 | } 73 | finally 74 | { 75 | synch.EndMessageLoop(); 76 | } 77 | }, null); 78 | synch.BeginMessageLoop(); 79 | SynchronizationContext.SetSynchronizationContext(oldContext); 80 | } 81 | 82 | public static T Run(Func> item) 83 | { 84 | var oldContext = SynchronizationContext.Current; 85 | var synch = new ExclusiveSynchronizationContext(); 86 | SynchronizationContext.SetSynchronizationContext(synch); 87 | T ret = default(T); 88 | synch.Post(async _ => 89 | { 90 | try 91 | { 92 | ret = await 93 | item(); 94 | } 95 | catch (Exception e) 96 | { 97 | synch.InnerException = e; 98 | throw; 99 | } 100 | finally 101 | { 102 | synch.EndMessageLoop(); 103 | } 104 | }, null); 105 | synch.BeginMessageLoop(); 106 | SynchronizationContext.SetSynchronizationContext(oldContext); 107 | return ret; 108 | } 109 | 110 | private class ExclusiveSynchronizationContext : SynchronizationContext 111 | { 112 | private bool done; 113 | public Exception InnerException { get; set; } 114 | readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false); 115 | readonly Queue> items = 116 | new Queue>(); 117 | 118 | public override void Send(SendOrPostCallback d, object state) 119 | { 120 | throw new NotSupportedException("We cannot send to our same thread"); 121 | } 122 | public override void Post(SendOrPostCallback d, object state) 123 | { 124 | lock (items) 125 | { 126 | items.Enqueue(Tuple.Create(d, state)); 127 | } 128 | workItemsWaiting.Set(); 129 | } 130 | public void EndMessageLoop() 131 | { 132 | Post(_ => done = true, null); 133 | } 134 | public void BeginMessageLoop() 135 | { 136 | while (!done) 137 | { 138 | Tuple task = null; 139 | lock (items) 140 | { 141 | if (items.Count > 0) 142 | { 143 | task = items.Dequeue(); 144 | } 145 | } 146 | if (task != null) 147 | { 148 | task.Item1(task.Item2); 149 | if (InnerException != null) // the method threw an exeption 150 | { 151 | throw new AggregateException("AsyncInline.Run method threw an exception.", 152 | InnerException); 153 | } 154 | } 155 | else 156 | { 157 | workItemsWaiting.WaitOne(); 158 | } 159 | } 160 | } 161 | public override SynchronizationContext CreateCopy() 162 | { 163 | return this; 164 | } 165 | } 166 | } 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/TransportTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Quobject.EngineIoClientDotNet.Client.Transports; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text.RegularExpressions; 6 | using Xunit; 7 | 8 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 9 | { 10 | // NOTE: tests for the rememberUpgrade option are on ServerConnectionTest. 11 | 12 | public class TransportTest : Connection 13 | { 14 | [Fact] 15 | public void Constructors() 16 | { 17 | var socket = new Socket(CreateOptions()); 18 | 19 | socket.Open(); 20 | System.Threading.Thread.Sleep(TimeSpan.FromSeconds(3)); 21 | Assert.NotNull(socket.Transport); 22 | 23 | socket.Close(); 24 | } 25 | 26 | [Fact] 27 | public void Uri() 28 | { 29 | var options = new Transport.Options 30 | { 31 | Path = "/engine.io", 32 | Hostname = Connection.CreateOptions().Hostname, 33 | Secure = false, 34 | Query = new Dictionary { { "sid", "test" } }, 35 | TimestampRequests = false 36 | }; 37 | var polling = new Polling(options); 38 | var expected = string.Format("http://{0}/engine.io?sid=test&b64=1", options.Hostname); 39 | Assert.Contains(expected, polling.Uri()); 40 | } 41 | 42 | [Fact] 43 | public void UriWithDefaultPort() 44 | { 45 | var options = new Transport.Options 46 | { 47 | Path = "/engine.io", 48 | Hostname = Connection.CreateOptions().Hostname, 49 | Secure = false, 50 | Query = new Dictionary { { "sid", "test" } }, 51 | TimestampRequests = false, 52 | Port = 80 53 | }; 54 | var polling = new Polling(options); 55 | //Assert.Contains("http://localhost/engine.io?sid=test&b64=1", polling.Uri()); 56 | var expected = string.Format("http://{0}/engine.io?sid=test&b64=1", options.Hostname); 57 | Assert.Contains(expected, polling.Uri()); 58 | } 59 | 60 | [Fact] 61 | public void UriWithPort() 62 | { 63 | var options = new Transport.Options 64 | { 65 | Path = "/engine.io", 66 | Hostname = Connection.CreateOptions().Hostname, 67 | Secure = false, 68 | Query = new Dictionary { { "sid", "test" } }, 69 | TimestampRequests = false, 70 | Port = 3000 71 | }; 72 | var polling = new Polling(options); 73 | //Assert.Contains("http://localhost:3000/engine.io?sid=test&b64=1", polling.Uri()); 74 | var expected = string.Format("http://{0}:{1}/engine.io?sid=test&b64=1", options.Hostname, options.Port); 75 | Assert.Contains(expected, polling.Uri()); 76 | } 77 | 78 | [Fact] 79 | public void HttpsUriWithDefaultPort() 80 | { 81 | var options = new Transport.Options 82 | { 83 | Path = "/engine.io", 84 | Hostname = Connection.CreateOptions().Hostname, 85 | Secure = true, 86 | Query = new Dictionary { { "sid", "test" } }, 87 | TimestampRequests = false, 88 | Port = 443 89 | }; 90 | var polling = new Polling(options); 91 | //Assert.Contains("https://localhost/engine.io?sid=test&b64=1", polling.Uri()); 92 | var expected = string.Format("https://{0}/engine.io?sid=test&b64=1", options.Hostname); 93 | Assert.Contains(expected, polling.Uri()); 94 | } 95 | 96 | [Fact] 97 | public void TimestampedUri() 98 | { 99 | var options = new Transport.Options 100 | { 101 | Path = "/engine.io", 102 | Hostname = "test", 103 | Secure = false, 104 | Query = new Dictionary { { "sid", "test" } }, 105 | TimestampRequests = true, 106 | TimestampParam = "t" 107 | }; 108 | var polling = new Polling(options); 109 | 110 | var pat = @"http://test/engine.io\?sid=test&(t=[0-9]+-[0-9]+)"; 111 | var r = new Regex(pat, RegexOptions.IgnoreCase); 112 | var test = polling.Uri(); 113 | //log.Info(test); 114 | var m = r.Match(test); 115 | Assert.True(m.Success); 116 | } 117 | 118 | [Fact] 119 | public void WsUri() 120 | { 121 | var options = new Transport.Options 122 | { 123 | Path = "/engine.io", 124 | Hostname = "test", 125 | Secure = false, 126 | Query = new Dictionary { { "transport", "websocket" } }, 127 | TimestampRequests = false 128 | }; 129 | var ws = new WebSocket(options); 130 | Assert.Contains("ws://test/engine.io?transport=websocket", ws.Uri()); 131 | } 132 | 133 | [Fact] 134 | public void WssUri() 135 | { 136 | var options = new Transport.Options 137 | { 138 | Path = "/engine.io", 139 | Hostname = "test", 140 | Secure = true, 141 | Query = new Dictionary { { "transport", "websocket" } }, 142 | TimestampRequests = false 143 | }; 144 | var ws = new WebSocket(options); 145 | Assert.Contains("wss://test/engine.io?transport=websocket", ws.Uri()); 146 | } 147 | 148 | [Fact] 149 | public void WsTimestampedUri() 150 | { 151 | var options = new Transport.Options 152 | { 153 | Path = "/engine.io", 154 | Hostname = "test", 155 | Secure = false, 156 | Query = new Dictionary { { "sid", "test" } }, 157 | TimestampRequests = true, 158 | TimestampParam = "woot" 159 | }; 160 | var ws = new WebSocket(options); 161 | 162 | var pat = @"ws://test/engine.io\?sid=test&(woot=[0-9]+-[0-9]+)"; 163 | var r = new Regex(pat, RegexOptions.IgnoreCase); 164 | var test = ws.Uri(); 165 | //log.Info(test); 166 | var m = r.Match(test); 167 | Assert.True(m.Success); 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/ConnectionTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Quobject.EngineIoClientDotNet.Client.Transports; 3 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 4 | using System.Collections.Immutable; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 10 | { 11 | public class ConnectionTest : Connection 12 | { 13 | private ManualResetEvent _manualResetEvent = null; 14 | private Socket socket; 15 | public string Message; 16 | 17 | [Fact] 18 | public void ConnectToLocalhost() 19 | { 20 | _manualResetEvent = new ManualResetEvent(false); 21 | 22 | socket = new Socket(CreateOptions()); 23 | socket.On(Socket.EVENT_OPEN, new TestListener()); 24 | socket.On(Socket.EVENT_MESSAGE, new MessageListener(socket, this)); 25 | socket.Open(); 26 | _manualResetEvent.WaitOne(); 27 | socket.Close(); 28 | Assert.Equal("hi", this.Message); 29 | } 30 | 31 | public class TestListener : IListener 32 | { 33 | public void Call(params object[] args) 34 | { 35 | //log.Info("open"); 36 | } 37 | 38 | public int CompareTo(IListener other) 39 | { 40 | return this.GetId().CompareTo(other.GetId()); 41 | } 42 | 43 | public int GetId() 44 | { 45 | return 0; 46 | } 47 | } 48 | 49 | public class MessageListener : IListener 50 | { 51 | private Socket socket; 52 | private ConnectionTest connectionTest; 53 | 54 | public MessageListener(Socket socket) 55 | { 56 | this.socket = socket; 57 | } 58 | 59 | public MessageListener(Socket socket, ConnectionTest connectionTest) 60 | { 61 | this.socket = socket; 62 | this.connectionTest = connectionTest; 63 | } 64 | 65 | public void Call(params object[] args) 66 | { 67 | //log.Info("message = " + args[0]); 68 | connectionTest.Message = (string)args[0]; 69 | connectionTest._manualResetEvent.Set(); 70 | } 71 | 72 | public int CompareTo(IListener other) 73 | { 74 | return this.GetId().CompareTo(other.GetId()); 75 | } 76 | 77 | public int GetId() 78 | { 79 | return 0; 80 | } 81 | } 82 | 83 | [Fact] 84 | public void ConnectToLocalhost2() 85 | { 86 | _manualResetEvent = new ManualResetEvent(false); 87 | this.Message = ""; 88 | 89 | var options = CreateOptions(); 90 | options.Transports = ImmutableList.Create(Polling.NAME); 91 | socket = new Socket(options); 92 | 93 | //socket = new Socket(CreateOptions()); 94 | socket.On(Socket.EVENT_OPEN, () => 95 | { 96 | //log.Info("open"); 97 | //socket.Send("test send"); 98 | }); 99 | socket.On(Socket.EVENT_MESSAGE, (d) => 100 | { 101 | var data = (string)d; 102 | 103 | //log.Info("message2 = " + data); 104 | this.Message = data; 105 | _manualResetEvent.Set(); 106 | }); 107 | 108 | socket.Open(); 109 | _manualResetEvent.WaitOne(); 110 | socket.Close(); 111 | Assert.Equal("hi", this.Message); 112 | } 113 | 114 | [Fact] 115 | public void TestmultibyteUtf8StringsWithPolling() 116 | { 117 | _manualResetEvent = new ManualResetEvent(false); 118 | 119 | const string SendMessage = "cash money €€€"; 120 | 121 | socket = new Socket(CreateOptions()); 122 | socket.On(Socket.EVENT_OPEN, () => 123 | { 124 | //log.Info("open"); 125 | 126 | socket.Send(SendMessage); 127 | }); 128 | socket.On(Socket.EVENT_MESSAGE, (d) => 129 | { 130 | var data = (string)d; 131 | 132 | //log.Info("TestMessage data = " + data); 133 | 134 | if (data == "hi") 135 | { 136 | return; 137 | } 138 | 139 | this.Message = data; 140 | _manualResetEvent.Set(); 141 | }); 142 | 143 | socket.Open(); 144 | _manualResetEvent.WaitOne(); 145 | socket.Close(); 146 | //log.Info("TestmultibyteUtf8StringsWithPolling this.Message = " + this.Message); 147 | Assert.Equal(SendMessage, this.Message); 148 | } 149 | 150 | [Fact] 151 | public void Testemoji() 152 | { 153 | _manualResetEvent = new ManualResetEvent(false); 154 | const string SendMessage = "\uD800-\uDB7F\uDB80-\uDBFF\uDC00-\uDFFF\uE000-\uF8FF"; 155 | 156 | var options = CreateOptions(); 157 | socket = new Socket(options); 158 | 159 | socket.On(Socket.EVENT_OPEN, () => 160 | { 161 | //log.Info("open"); 162 | 163 | socket.Send(SendMessage); 164 | }); 165 | 166 | socket.On(Socket.EVENT_MESSAGE, (d) => 167 | { 168 | var data = (string)d; 169 | 170 | //log.Info(Socket.EVENT_MESSAGE); 171 | 172 | if (data == "hi") 173 | { 174 | return; 175 | } 176 | 177 | this.Message = data; 178 | _manualResetEvent.Set(); 179 | }); 180 | 181 | socket.Open(); 182 | _manualResetEvent.WaitOne(); 183 | socket.Close(); 184 | 185 | Assert.True(SendMessage == this.Message); 186 | } 187 | 188 | [Fact] 189 | public async Task NotSendPacketsIfSocketCloses() 190 | { 191 | var noPacket = true; 192 | 193 | socket = new Socket(CreateOptions()); 194 | socket.On(Socket.EVENT_OPEN, () => 195 | { 196 | noPacket = true; 197 | }); 198 | 199 | socket.Open(); 200 | socket.On(Socket.EVENT_PACKET_CREATE, () => 201 | { 202 | noPacket = false; 203 | //log.Info("NotSendPacketsIfSocketCloses EVENT_PACKET_CREATE noPacket = " + noPacket); 204 | }); 205 | socket.Close(); 206 | await Task.Delay(1000); 207 | //log.Info("NotSendPacketsIfSocketCloses end noPacket = " + noPacket); 208 | Assert.True(noPacket); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/Transport.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Text; 3 | using System.Collections.Immutable; 4 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 5 | using Quobject.EngineIoClientDotNet.Modules; 6 | using Quobject.EngineIoClientDotNet.Parser; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | 11 | namespace Quobject.EngineIoClientDotNet.Client 12 | { 13 | public abstract class Transport : Emitter 14 | { 15 | protected enum ReadyStateEnum 16 | { 17 | OPENING, 18 | OPEN, 19 | CLOSED, 20 | PAUSED 21 | } 22 | 23 | public static readonly string EVENT_OPEN = "open"; 24 | public static readonly string EVENT_CLOSE = "close"; 25 | public static readonly string EVENT_PACKET = "packet"; 26 | public static readonly string EVENT_DRAIN = "drain"; 27 | public static readonly string EVENT_ERROR = "error"; 28 | public static readonly string EVENT_SUCCESS = "success"; 29 | public static readonly string EVENT_DATA = "data"; 30 | public static readonly string EVENT_REQUEST_HEADERS = "requestHeaders"; 31 | public static readonly string EVENT_RESPONSE_HEADERS = "responseHeaders"; 32 | 33 | protected static int Timestamps = 0; 34 | 35 | private bool _writeable ; 36 | public bool Writable { 37 | get { return _writeable; } 38 | set 39 | { 40 | var log = LogManager.GetLogger(Global.CallerName()); 41 | log.Info(string.Format("Writable: {0} sid={1}", value, this.Socket.Id)); 42 | _writeable = value; 43 | } 44 | } 45 | 46 | private int myVar; 47 | 48 | public int MyProperty 49 | { 50 | get { return myVar; } 51 | set { myVar = value; } 52 | } 53 | 54 | public string Name; 55 | public Dictionary Query; 56 | 57 | protected bool Secure; 58 | protected bool TimestampRequests; 59 | protected int Port; 60 | protected string Path; 61 | protected string Hostname; 62 | protected string TimestampParam; 63 | protected Socket Socket; 64 | protected bool Agent = false; 65 | protected bool ForceBase64 = false; 66 | protected bool ForceJsonp = false; 67 | protected string Cookie; 68 | 69 | protected Dictionary ExtraHeaders; 70 | 71 | 72 | protected ReadyStateEnum ReadyState = ReadyStateEnum.CLOSED; 73 | 74 | protected Transport(Options options) 75 | { 76 | this.Path = options.Path; 77 | this.Hostname = options.Hostname; 78 | this.Port = options.Port; 79 | this.Secure = options.Secure; 80 | this.Query = options.Query; 81 | this.TimestampParam = options.TimestampParam; 82 | this.TimestampRequests = options.TimestampRequests; 83 | this.Socket = options.Socket; 84 | this.Agent = options.Agent; 85 | this.ForceBase64 = options.ForceBase64; 86 | this.ForceJsonp = options.ForceJsonp; 87 | this.Cookie = options.GetCookiesAsString(); 88 | this.ExtraHeaders = options.ExtraHeaders; 89 | } 90 | 91 | protected Transport OnError(string message, Exception exception) 92 | { 93 | Exception err = new EngineIOException(message, exception); 94 | this.Emit(EVENT_ERROR, err); 95 | return this; 96 | } 97 | 98 | protected void OnOpen() 99 | { 100 | ReadyState = ReadyStateEnum.OPEN; 101 | Writable = true; 102 | Emit(EVENT_OPEN); 103 | } 104 | 105 | protected void OnClose() 106 | { 107 | ReadyState = ReadyStateEnum.CLOSED; 108 | Emit(EVENT_CLOSE); 109 | } 110 | 111 | 112 | protected virtual void OnData(string data) 113 | { 114 | this.OnPacket(Parser.Parser.DecodePacket(data)); 115 | } 116 | 117 | protected virtual void OnData(byte[] data) 118 | { 119 | this.OnPacket(Parser.Parser.DecodePacket(data)); 120 | } 121 | 122 | protected void OnPacket(Packet packet) 123 | { 124 | this.Emit(EVENT_PACKET, packet); 125 | } 126 | 127 | 128 | public Transport Open() 129 | { 130 | if (ReadyState == ReadyStateEnum.CLOSED) 131 | { 132 | ReadyState = ReadyStateEnum.OPENING; 133 | DoOpen(); 134 | } 135 | return this; 136 | } 137 | 138 | public Transport Close() 139 | { 140 | if (ReadyState == ReadyStateEnum.OPENING || ReadyState == ReadyStateEnum.OPEN) 141 | { 142 | DoClose(); 143 | OnClose(); 144 | } 145 | return this; 146 | } 147 | 148 | public Transport Send(ImmutableList packets) 149 | { 150 | var log = LogManager.GetLogger(Global.CallerName()); 151 | log.Info("Send called with packets.Count: " + packets.Count); 152 | var count = packets.Count; 153 | if (ReadyState == ReadyStateEnum.OPEN) 154 | { 155 | //PollTasks.Exec((n) => 156 | //{ 157 | Write(packets); 158 | //}); 159 | } 160 | else 161 | { 162 | throw new EngineIOException("Transport not open"); 163 | //log.Info("Transport not open"); 164 | } 165 | return this; 166 | } 167 | 168 | 169 | 170 | protected abstract void DoOpen(); 171 | 172 | protected abstract void DoClose(); 173 | 174 | protected abstract void Write(ImmutableList packets); 175 | 176 | 177 | public class Options 178 | { 179 | public bool Agent = false; 180 | public bool ForceBase64 = false; 181 | public bool ForceJsonp = false; 182 | public string Hostname; 183 | public string Path; 184 | public string TimestampParam; 185 | public bool Secure = false; 186 | public bool TimestampRequests = true; 187 | public int Port; 188 | public int PolicyPort; 189 | public Dictionary Query; 190 | public bool IgnoreServerCertificateValidation = false; 191 | internal Socket Socket; 192 | public Dictionary Cookies = new Dictionary(); 193 | public Dictionary ExtraHeaders = new Dictionary(); 194 | 195 | public string GetCookiesAsString() 196 | { 197 | var result = new StringBuilder(); 198 | var first = true; 199 | foreach (var item in Cookies) 200 | { 201 | if (!first) 202 | { 203 | result.Append("; "); 204 | } 205 | result.Append(string.Format("{0}={1}", item.Key, item.Value)); 206 | first = false; 207 | } 208 | return result.ToString(); 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Coverlet is a free, cross platform Code Coverage Tool 141 | coverage*[.json, .xml, .info] 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Modules/UTF8.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace Quobject.EngineIoClientDotNet.Modules 5 | { 6 | /// 7 | /// UTF-8 encoder/decoder ported from utf8.js. 8 | /// Ported from the JavaScript module. 9 | /// https://github.com/mathiasbynens/utf8.js 10 | /// 11 | public class UTF8 12 | { 13 | private static List byteArray; 14 | private static int byteCount; 15 | private static int byteIndex; 16 | 17 | public static string Encode(string str) 18 | { 19 | List codePoints = Ucs2Decode(str); 20 | var length = codePoints.Count; 21 | var index = -1; 22 | var byteString = new StringBuilder(); 23 | while (++index < length) 24 | { 25 | var codePoint = codePoints[index]; 26 | byteString.Append(EncodeCodePoint(codePoint)); 27 | } 28 | return byteString.ToString(); 29 | } 30 | 31 | public static string Decode(string byteString) 32 | { 33 | byteArray = Ucs2Decode(byteString); 34 | byteCount = byteArray.Count; 35 | byteIndex = 0; 36 | 37 | var codePoints = new List(); 38 | int tmp; 39 | while ((tmp = DecodeSymbol()) != -1) 40 | { 41 | codePoints.Add(tmp); 42 | } 43 | return Ucs2Encode(codePoints); 44 | } 45 | 46 | private static int DecodeSymbol() 47 | { 48 | int byte1; 49 | int byte2; 50 | int byte3; 51 | int byte4; 52 | int codePoint; 53 | 54 | if (byteIndex > byteCount) 55 | { 56 | throw new UTF8Exception("Invalid byte index"); 57 | } 58 | 59 | if (byteIndex == byteCount) 60 | { 61 | return -1; 62 | } 63 | 64 | byte1 = byteArray[byteIndex] & 0xFF; 65 | byteIndex++; 66 | 67 | if ((byte1 & 0x80) == 0) 68 | { 69 | return byte1; 70 | } 71 | 72 | if ((byte1 & 0xE0) == 0xC0) 73 | { 74 | byte2 = ReadContinuationByte(); 75 | codePoint = ((byte1 & 0x1F) << 6) | byte2; 76 | if (codePoint >= 0x80) 77 | { 78 | return codePoint; 79 | } 80 | else 81 | { 82 | throw new UTF8Exception("Invalid continuation byte"); 83 | } 84 | } 85 | 86 | if ((byte1 & 0xF0) == 0xE0) 87 | { 88 | byte2 = ReadContinuationByte(); 89 | byte3 = ReadContinuationByte(); 90 | codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; 91 | if (codePoint >= 0x0800) 92 | { 93 | return codePoint; 94 | } 95 | else 96 | { 97 | throw new UTF8Exception("Invalid continuation byte"); 98 | } 99 | } 100 | 101 | if ((byte1 & 0xF8) == 0xF0) 102 | { 103 | byte2 = ReadContinuationByte(); 104 | byte3 = ReadContinuationByte(); 105 | byte4 = ReadContinuationByte(); 106 | codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | (byte3 << 0x06) | byte4; 107 | if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) 108 | { 109 | return codePoint; 110 | } 111 | } 112 | 113 | throw new UTF8Exception("Invalid continuation byte"); 114 | } 115 | 116 | private static int ReadContinuationByte() 117 | { 118 | if (byteIndex >= byteCount) 119 | { 120 | throw new UTF8Exception("Invalid byte index"); 121 | } 122 | 123 | int continuationByte = byteArray[byteIndex] & 0xFF; 124 | byteIndex++; 125 | 126 | if ((continuationByte & 0xC0) == 0x80) 127 | { 128 | return continuationByte & 0x3F; 129 | } 130 | 131 | throw new UTF8Exception("Invalid continuation byte"); 132 | } 133 | 134 | 135 | private static string EncodeCodePoint(int codePoint) 136 | { 137 | var sb = new StringBuilder(); 138 | if ((codePoint & 0xFFFFFF80) == 0) 139 | { 140 | // 1-byte sequence 141 | sb.Append((char) codePoint); 142 | return sb.ToString(); 143 | } 144 | if ((codePoint & 0xFFFFF800) == 0) 145 | { 146 | // 2-byte sequence 147 | sb.Append((char) (((codePoint >> 6) & 0x1F) | 0xC0)); 148 | } 149 | else if ((codePoint & 0xFFFF0000) == 0) 150 | { 151 | // 3-byte sequence 152 | sb.Append((char) (((codePoint >> 12) & 0x0F) | 0xE0)); 153 | sb.Append( CreateByte(codePoint, 6)); 154 | } 155 | else if ((codePoint & 0xFFE00000) == 0) 156 | { 157 | // 4-byte sequence 158 | sb.Append((char) (((codePoint >> 18) & 0x07) | 0xF0)); 159 | sb.Append( CreateByte(codePoint, 12)); 160 | sb.Append( CreateByte(codePoint, 6)); 161 | } 162 | sb.Append((char) ((codePoint & 0x3F) | 0x80)); 163 | return sb.ToString(); 164 | } 165 | 166 | private static char CreateByte(int codePoint, int shift) 167 | { 168 | return (char)(((codePoint >> shift) & 0x3F) | 0x80); 169 | } 170 | 171 | 172 | 173 | private static List Ucs2Decode(string str) 174 | { 175 | var output = new List(); 176 | var counter = 0; 177 | var length = str.Length; 178 | 179 | while (counter < length) 180 | { 181 | var value = (int)str[counter++]; 182 | 183 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) 184 | { 185 | // high surrogate, and there is a next character 186 | var extra = (int)str[counter++]; 187 | if ((extra & 0xFC00) == 0xDC00) 188 | { 189 | // low surrogate 190 | output.Add(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 191 | } 192 | else 193 | { 194 | // unmatched surrogate; only append this code unit, in case the next 195 | // code unit is the high surrogate of a surrogate pair 196 | output.Add(value); 197 | counter--; 198 | } 199 | } 200 | else 201 | { 202 | output.Add(value); 203 | } 204 | } 205 | return output; 206 | } 207 | 208 | private static string Ucs2Encode(List array) 209 | { 210 | var sb = new StringBuilder(); 211 | var index = -1; 212 | while (++index < array.Count) 213 | { 214 | var value = array[index]; 215 | if (value > 0xFFFF) 216 | { 217 | value -= 0x10000; 218 | sb.Append((char)(((int)((uint)value >> 10)) & 0x3FF | 0xD800)); 219 | value = 0xDC00 | value & 0x3FF; 220 | } 221 | sb.Append((char)value); 222 | } 223 | return sb.ToString(); 224 | } 225 | 226 | 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/Transports/WebSocket.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Modules; 2 | using Quobject.EngineIoClientDotNet.Parser; 3 | using System; 4 | using System.Net; 5 | using System.Collections.Generic; 6 | using WebSocket4Net; 7 | using SuperSocket.ClientEngine.Proxy; 8 | 9 | namespace Quobject.EngineIoClientDotNet.Client.Transports 10 | { 11 | public class WebSocket : Transport 12 | { 13 | public static readonly string NAME = "websocket"; 14 | 15 | private WebSocket4Net.WebSocket ws; 16 | private List> Cookies; 17 | private List> MyExtraHeaders; 18 | 19 | public WebSocket(Options opts) 20 | : base(opts) 21 | { 22 | Name = NAME; 23 | Cookies = new List>(); 24 | foreach (var cookie in opts.Cookies) 25 | { 26 | Cookies.Add(new KeyValuePair(cookie.Key, cookie.Value)); 27 | } 28 | MyExtraHeaders = new List>(); 29 | foreach (var header in opts.ExtraHeaders) 30 | { 31 | MyExtraHeaders.Add(new KeyValuePair(header.Key, header.Value)); 32 | } 33 | } 34 | 35 | protected override void DoOpen() 36 | { 37 | var log = LogManager.GetLogger(Global.CallerName()); 38 | log.Info("DoOpen uri =" + this.Uri()); 39 | 40 | ws = new WebSocket4Net.WebSocket(this.Uri(), String.Empty, Cookies, MyExtraHeaders) 41 | { 42 | EnableAutoSendPing = false 43 | }; 44 | if (ServerCertificate.Ignore) 45 | { 46 | var security = ws.Security; 47 | 48 | if (security != null) 49 | { 50 | security.AllowUnstrustedCertificate = true; 51 | security.AllowNameMismatchCertificate = true; 52 | } 53 | } 54 | ws.Opened += ws_Opened; 55 | ws.Closed += ws_Closed; 56 | ws.MessageReceived += ws_MessageReceived; 57 | ws.DataReceived += ws_DataReceived; 58 | ws.Error += ws_Error; 59 | 60 | var destUrl = new UriBuilder(this.Uri()); 61 | if (this.Secure) 62 | destUrl.Scheme = "https"; 63 | else 64 | destUrl.Scheme = "http"; 65 | var useProxy = !WebRequest.DefaultWebProxy.IsBypassed(destUrl.Uri); 66 | if (useProxy) 67 | { 68 | var proxyUrl = WebRequest.DefaultWebProxy.GetProxy(destUrl.Uri); 69 | if (proxyUrl != null) 70 | { 71 | var proxy = new HttpConnectProxy(new DnsEndPoint(proxyUrl.Host, proxyUrl.Port), destUrl.Host); 72 | ws.Proxy = proxy; 73 | } 74 | } 75 | ws.Open(); 76 | } 77 | 78 | void ws_DataReceived(object sender, DataReceivedEventArgs e) 79 | { 80 | var log = LogManager.GetLogger(Global.CallerName()); 81 | log.Info("ws_DataReceived " + e.Data); 82 | this.OnData(e.Data); 83 | } 84 | 85 | private void ws_Opened(object sender, EventArgs e) 86 | { 87 | var log = LogManager.GetLogger(Global.CallerName()); 88 | log.Info("ws_Opened " + ws.SupportBinary); 89 | this.OnOpen(); 90 | } 91 | 92 | void ws_Closed(object sender, EventArgs e) 93 | { 94 | var log = LogManager.GetLogger(Global.CallerName()); 95 | log.Info("ws_Closed"); 96 | ws.Opened -= ws_Opened; 97 | ws.Closed -= ws_Closed; 98 | ws.MessageReceived -= ws_MessageReceived; 99 | ws.DataReceived -= ws_DataReceived; 100 | ws.Error -= ws_Error; 101 | this.OnClose(); 102 | } 103 | 104 | void ws_MessageReceived(object sender, MessageReceivedEventArgs e) 105 | { 106 | var log = LogManager.GetLogger(Global.CallerName()); 107 | log.Info("ws_MessageReceived e.Message= " + e.Message); 108 | this.OnData(e.Message); 109 | } 110 | 111 | void ws_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e) 112 | { 113 | this.OnError("websocket error", e.Exception); 114 | } 115 | 116 | protected override void Write(System.Collections.Immutable.ImmutableList packets) 117 | { 118 | Writable = false; 119 | foreach (var packet in packets) 120 | { 121 | Parser.Parser.EncodePacket(packet, new WriteEncodeCallback(this)); 122 | } 123 | 124 | // fake drain 125 | // defer to next tick to allow Socket to clear writeBuffer 126 | //EasyTimer.SetTimeout(() => 127 | //{ 128 | Writable = true; 129 | Emit(EVENT_DRAIN); 130 | //}, 1); 131 | } 132 | 133 | public class WriteEncodeCallback : IEncodeCallback 134 | { 135 | private WebSocket webSocket; 136 | 137 | public WriteEncodeCallback(WebSocket webSocket) 138 | { 139 | this.webSocket = webSocket; 140 | } 141 | 142 | public void Call(object data) 143 | { 144 | //var log = LogManager.GetLogger(Global.CallerName()); 145 | 146 | if (data is string) 147 | { 148 | webSocket.ws.Send((string)data); 149 | } 150 | else if (data is byte[]) 151 | { 152 | var d = (byte[])data; 153 | 154 | //try 155 | //{ 156 | // var dataString = BitConverter.ToString(d); 157 | // //log.Info(string.Format("WriteEncodeCallback byte[] data {0}", dataString)); 158 | //} 159 | //catch (Exception e) 160 | //{ 161 | // log.Error(e); 162 | //} 163 | 164 | webSocket.ws.Send(d, 0, d.Length); 165 | } 166 | } 167 | } 168 | 169 | 170 | 171 | protected override void DoClose() 172 | { 173 | if (ws != null) 174 | { 175 | 176 | try 177 | { 178 | ws.Close(); 179 | } 180 | catch (Exception e) 181 | { 182 | var log = LogManager.GetLogger(Global.CallerName()); 183 | log.Info("DoClose ws.Close() Exception= " + e.Message); 184 | } 185 | } 186 | } 187 | 188 | 189 | 190 | public string Uri() 191 | { 192 | Dictionary query = null; 193 | query = this.Query == null ? new Dictionary() : new Dictionary(this.Query); 194 | var schema = this.Secure ? "wss" : "ws"; 195 | var portString = ""; 196 | 197 | if (this.TimestampRequests) 198 | { 199 | query.Add(this.TimestampParam, DateTime.Now.Ticks.ToString() + "-" + Transport.Timestamps++); 200 | } 201 | 202 | var _query = ParseQS.Encode(query); 203 | 204 | if (this.Port > 0 && (("wss" == schema && this.Port != 443) 205 | || ("ws" == schema && this.Port != 80))) 206 | { 207 | portString = ":" + this.Port; 208 | } 209 | 210 | if (_query.Length > 0) 211 | { 212 | _query = "?" + _query; 213 | } 214 | 215 | return schema + "://" + this.Hostname + portString + this.Path + _query; 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ClientTests/SSLServerConnectionTest.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Client; 2 | using Quobject.EngineIoClientDotNet.Client.Transports; 3 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.IO; 7 | using System.Threading; 8 | using Xunit; 9 | 10 | namespace Quobject.EngineIoClientDotNet_Tests.ClientTests 11 | { 12 | public class SSLServerConnectionTest : Connection 13 | { 14 | private ManualResetEvent _manualResetEvent = null; 15 | 16 | [Fact] 17 | public void OpenAndClose() 18 | { 19 | _manualResetEvent = new ManualResetEvent(false); 20 | 21 | var events = new ConcurrentQueue(); 22 | 23 | var socket = new Socket(CreateOptionsSecure()); 24 | Console.WriteLine(Directory.GetCurrentDirectory()); 25 | socket.On(Socket.EVENT_OPEN, () => 26 | { 27 | Console.WriteLine("EVENT_OPEN"); 28 | events.Enqueue(Socket.EVENT_OPEN); 29 | socket.Close(); 30 | }); 31 | socket.On(Socket.EVENT_CLOSE, () => 32 | { 33 | Console.WriteLine("EVENT_CLOSE"); 34 | events.Enqueue(Socket.EVENT_CLOSE); 35 | _manualResetEvent.Set(); 36 | }); 37 | 38 | socket.Open(); 39 | _manualResetEvent.WaitOne(); 40 | socket.Close(); 41 | string result; 42 | events.TryDequeue(out result); 43 | Assert.Equal(Socket.EVENT_OPEN, result); 44 | events.TryDequeue(out result); 45 | Assert.Equal(Socket.EVENT_CLOSE, result); 46 | } 47 | 48 | [Fact] 49 | public void Messages() 50 | { 51 | _manualResetEvent = new ManualResetEvent(false); 52 | 53 | var events = new ConcurrentQueue(); 54 | 55 | var socket = new Socket(CreateOptionsSecure()); 56 | socket.On(Socket.EVENT_OPEN, () => 57 | { 58 | socket.Send("hello"); 59 | }); 60 | socket.On(Socket.EVENT_MESSAGE, (d) => 61 | { 62 | var data = (string)d; 63 | //log.Info("EVENT_MESSAGE data = " + data); 64 | events.Enqueue(data); 65 | if (events.Count > 1) 66 | { 67 | _manualResetEvent.Set(); 68 | } 69 | }); 70 | socket.Open(); 71 | _manualResetEvent.WaitOne(); 72 | socket.Close(); 73 | 74 | string result; 75 | events.TryDequeue(out result); 76 | Assert.Equal("hi", result); 77 | events.TryDequeue(out result); 78 | Assert.Equal("hello", result); 79 | } 80 | 81 | [Fact] 82 | public void Handshake() 83 | { 84 | _manualResetEvent = new ManualResetEvent(false); 85 | 86 | HandshakeData handshake_data = null; 87 | 88 | var socket = new Socket(CreateOptionsSecure()); 89 | 90 | socket.On(Socket.EVENT_HANDSHAKE, (data) => 91 | { 92 | //log.Info(Socket.EVENT_HANDSHAKE + string.Format(" data = {0}", data)); 93 | handshake_data = data as HandshakeData; 94 | _manualResetEvent.Set(); 95 | }); 96 | 97 | socket.Open(); 98 | _manualResetEvent.WaitOne(); 99 | socket.Close(); 100 | 101 | Assert.NotNull(handshake_data); 102 | Assert.NotNull(handshake_data.Upgrades); 103 | Assert.True(handshake_data.Upgrades.Count > 0); 104 | Assert.True(handshake_data.PingInterval > 0); 105 | Assert.True(handshake_data.PingTimeout > 0); 106 | } 107 | 108 | public class TestHandshakeListener : IListener 109 | { 110 | public HandshakeData HandshakeData; 111 | private SSLServerConnectionTest serverConnectionTest; 112 | 113 | public TestHandshakeListener(SSLServerConnectionTest serverConnectionTest) 114 | { 115 | this.serverConnectionTest = serverConnectionTest; 116 | } 117 | 118 | public void Call(params object[] args) 119 | { 120 | //log.Info(string.Format("open args[0]={0} args.Length={1}", args[0], args.Length)); 121 | HandshakeData = args[0] as HandshakeData; 122 | serverConnectionTest._manualResetEvent.Set(); 123 | } 124 | 125 | public int CompareTo(IListener other) 126 | { 127 | return this.GetId().CompareTo(other.GetId()); 128 | } 129 | 130 | public int GetId() 131 | { 132 | return 0; 133 | } 134 | } 135 | 136 | [Fact] 137 | public void Handshake2() 138 | { 139 | _manualResetEvent = new ManualResetEvent(false); 140 | 141 | var socket = new Socket(CreateOptionsSecure()); 142 | var testListener = new TestHandshakeListener(this); 143 | socket.On(Socket.EVENT_HANDSHAKE, testListener); 144 | socket.Open(); 145 | _manualResetEvent.WaitOne(); 146 | socket.Close(); 147 | 148 | Assert.NotNull(testListener.HandshakeData); 149 | Assert.NotNull(testListener.HandshakeData.Upgrades); 150 | Assert.True(testListener.HandshakeData.Upgrades.Count > 0); 151 | Assert.True(testListener.HandshakeData.PingInterval > 0); 152 | Assert.True(testListener.HandshakeData.PingTimeout > 0); 153 | } 154 | 155 | [Fact] 156 | public void Upgrade() 157 | { 158 | _manualResetEvent = new ManualResetEvent(false); 159 | 160 | var events = new ConcurrentQueue(); 161 | 162 | var socket = new Socket(CreateOptionsSecure()); 163 | 164 | socket.On(Socket.EVENT_UPGRADING, (data) => 165 | { 166 | //log.Info(Socket.EVENT_UPGRADING + string.Format(" data = {0}", data)); 167 | events.Enqueue(data); 168 | }); 169 | socket.On(Socket.EVENT_UPGRADE, (data) => 170 | { 171 | //log.Info(Socket.EVENT_UPGRADE + string.Format(" data = {0}", data)); 172 | events.Enqueue(data); 173 | _manualResetEvent.Set(); 174 | }); 175 | 176 | socket.Open(); 177 | _manualResetEvent.WaitOne(); 178 | 179 | object test = null; 180 | events.TryDequeue(out test); 181 | Assert.NotNull(test); 182 | Assert.IsAssignableFrom(test); 183 | 184 | events.TryDequeue(out test); 185 | Assert.NotNull(test); 186 | Assert.IsAssignableFrom(test); 187 | } 188 | 189 | [Fact] 190 | public void RememberWebsocket() 191 | { 192 | _manualResetEvent = new ManualResetEvent(false); 193 | 194 | var socket1 = new Socket(CreateOptionsSecure()); 195 | string socket1TransportName = null; 196 | string socket2TransportName = null; 197 | 198 | socket1.On(Socket.EVENT_OPEN, () => 199 | { 200 | socket1TransportName = socket1.Transport.Name; 201 | }); 202 | 203 | socket1.On(Socket.EVENT_UPGRADE, (data) => 204 | { 205 | //log.Info(Socket.EVENT_UPGRADE + string.Format(" data = {0}", data)); 206 | var transport = (Transport)data; 207 | socket1.Close(); 208 | if (WebSocket.NAME == transport.Name) 209 | { 210 | var options = CreateOptionsSecure(); 211 | options.RememberUpgrade = true; 212 | var socket2 = new Socket(options); 213 | socket2.Open(); 214 | socket2TransportName = socket2.Transport.Name; 215 | socket2.Close(); 216 | _manualResetEvent.Set(); 217 | } 218 | }); 219 | 220 | socket1.Open(); 221 | _manualResetEvent.WaitOne(); 222 | Assert.Equal(Polling.NAME, socket1TransportName); 223 | Assert.Equal(WebSocket.NAME, socket2TransportName); 224 | } 225 | 226 | [Fact] 227 | public void NotRememberWebsocket() 228 | { 229 | _manualResetEvent = new ManualResetEvent(false); 230 | 231 | var socket1 = new Socket(CreateOptionsSecure()); 232 | string socket1TransportName = null; 233 | string socket2TransportName = null; 234 | 235 | socket1.On(Socket.EVENT_OPEN, () => 236 | { 237 | socket1TransportName = socket1.Transport.Name; 238 | }); 239 | 240 | socket1.On(Socket.EVENT_UPGRADE, (data) => 241 | { 242 | //log.Info(Socket.EVENT_UPGRADE + string.Format(" data = {0}", data)); 243 | var transport = (Transport)data; 244 | if (WebSocket.NAME == transport.Name) 245 | { 246 | socket1.Close(); 247 | var options = CreateOptionsSecure(); 248 | options.RememberUpgrade = false; 249 | var socket2 = new Socket(options); 250 | socket2.On(Socket.EVENT_OPEN, () => 251 | { 252 | //log.Info("EVENT_OPEN socket 2"); 253 | socket2TransportName = socket2.Transport.Name; 254 | socket2.Close(); 255 | _manualResetEvent.Set(); 256 | }); 257 | socket2.Open(); 258 | } 259 | }); 260 | 261 | socket1.Open(); 262 | _manualResetEvent.WaitOne(); 263 | Assert.Equal(Polling.NAME, socket1TransportName); 264 | Assert.Equal(Polling.NAME, socket2TransportName); 265 | } 266 | } 267 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet.Tests/ComponentEmitterTests/EmitterTests.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 2 | using Quobject.EngineIoClientDotNet.Modules; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace Quobject.EngineIoClientDotNet_Tests.ComponentEmitterTests 9 | { 10 | public class EmitterTests 11 | { 12 | public class TestListener1 : IListener 13 | { 14 | private readonly List _calls; 15 | 16 | public TestListener1(List calls) 17 | { 18 | this._calls = calls; 19 | } 20 | 21 | public void Call(params object[] args) 22 | { 23 | _calls.Add("one"); 24 | _calls.Add(args[0]); 25 | } 26 | 27 | public int CompareTo(IListener other) 28 | { 29 | return this.GetId().CompareTo(other.GetId()); 30 | } 31 | 32 | public int GetId() 33 | { 34 | return 0; 35 | } 36 | } 37 | 38 | public class TestListener2 : IListener 39 | { 40 | private readonly List _calls; 41 | 42 | public TestListener2(List calls) 43 | { 44 | this._calls = calls; 45 | } 46 | 47 | public void Call(params object[] args) 48 | { 49 | _calls.Add("two"); 50 | _calls.Add(args[0]); 51 | } 52 | 53 | public int CompareTo(IListener other) 54 | { 55 | return this.GetId().CompareTo(other.GetId()); 56 | } 57 | 58 | public int GetId() 59 | { 60 | return 0; 61 | } 62 | 63 | } 64 | 65 | 66 | [Fact] 67 | public void On() 68 | { 69 | 70 | 71 | 72 | 73 | var emitter = new Emitter(); 74 | var calls = new List(); 75 | 76 | var listener1 = new TestListener1(calls); 77 | emitter.On("foo", listener1); 78 | 79 | var listener2 = new TestListener2(calls); 80 | emitter.On("foo", listener2); 81 | 82 | emitter.Emit("foo", 1); 83 | emitter.Emit("bar", 1); 84 | emitter.Emit("foo", 2); 85 | 86 | var expected = new Object[] {"one", 1, "two", 1, "one", 2, "two", 2}; 87 | Assert.Equal(expected, calls.ToArray()); 88 | } 89 | 90 | [Fact] 91 | public void Once() 92 | { 93 | 94 | 95 | 96 | 97 | var emitter = new Emitter(); 98 | var calls = new List(); 99 | 100 | var listener1 = new TestListener1(calls); 101 | emitter.Once("foo", listener1); 102 | 103 | emitter.Emit("foo", 1); 104 | emitter.Emit("foo", 2); 105 | emitter.Emit("foo", 3); 106 | emitter.Emit("bar", 1); 107 | 108 | var expected = new Object[] {"one", 1}; 109 | Assert.Equal(expected, calls.ToArray()); 110 | } 111 | 112 | 113 | public class TestListener3 : IListener 114 | { 115 | private readonly List _calls; 116 | 117 | public TestListener3(List calls) 118 | { 119 | this._calls = calls; 120 | } 121 | 122 | public void Call(params object[] args) 123 | { 124 | _calls.Add("one"); 125 | } 126 | 127 | public int CompareTo(IListener other) 128 | { 129 | return this.GetId().CompareTo(other.GetId()); 130 | } 131 | 132 | public int GetId() 133 | { 134 | return 0; 135 | } 136 | 137 | } 138 | 139 | public class TestListener4 : IListener 140 | { 141 | private readonly List _calls; 142 | 143 | public TestListener4(List calls) 144 | { 145 | this._calls = calls; 146 | } 147 | 148 | public void Call(params object[] args) 149 | { 150 | _calls.Add("two"); 151 | } 152 | 153 | public int CompareTo(IListener other) 154 | { 155 | return this.GetId().CompareTo(other.GetId()); 156 | } 157 | 158 | public int GetId() 159 | { 160 | return 0; 161 | } 162 | 163 | } 164 | 165 | [Fact] 166 | public void Off() 167 | { 168 | 169 | 170 | 171 | 172 | var emitter = new Emitter(); 173 | var calls = new List(); 174 | 175 | var listener3 = new TestListener3(calls); 176 | emitter.On("foo", listener3); 177 | 178 | var listener4 = new TestListener4(calls); 179 | emitter.On("foo", listener4); 180 | emitter.Off("foo", listener4); 181 | 182 | emitter.Emit("foo"); 183 | 184 | var expected = new Object[] {"one"}; 185 | Assert.Equal(expected, calls.ToArray()); 186 | } 187 | 188 | [Fact] 189 | public void OffWithOnce() 190 | { 191 | 192 | 193 | 194 | 195 | var emitter = new Emitter(); 196 | var calls = new List(); 197 | 198 | var listener3 = new TestListener3(calls); 199 | 200 | emitter.Once("foo", listener3); 201 | emitter.Off("foo", listener3); 202 | 203 | emitter.Emit("foo"); 204 | 205 | var expected = new Object[] {}; 206 | Assert.Equal(expected, calls.ToArray()); 207 | } 208 | 209 | 210 | public class TestListener5 : IListener 211 | { 212 | private readonly List _called; 213 | 214 | public TestListener5(List called) 215 | { 216 | this._called = called; 217 | } 218 | 219 | public void Call(params object[] args) 220 | { 221 | this._called[0] = true; 222 | } 223 | 224 | public int CompareTo(IListener other) 225 | { 226 | return this.GetId().CompareTo(other.GetId()); 227 | } 228 | 229 | public int GetId() 230 | { 231 | return 0; 232 | } 233 | 234 | } 235 | 236 | public class TestListener6 : IListener 237 | { 238 | private readonly Emitter _emitter; 239 | private readonly IListener _bListener; 240 | 241 | public TestListener6(Emitter emitter, IListener bListener) 242 | { 243 | this._emitter = emitter; 244 | this._bListener = bListener; 245 | } 246 | 247 | public void Call(params object[] args) 248 | { 249 | _emitter.Off("tobi", _bListener); 250 | } 251 | 252 | public int CompareTo(IListener other) 253 | { 254 | return this.GetId().CompareTo(other.GetId()); 255 | } 256 | 257 | public int GetId() 258 | { 259 | return 0; 260 | } 261 | 262 | } 263 | 264 | [Fact] 265 | public void OffWhenCalledfromEvent() 266 | { 267 | 268 | 269 | 270 | 271 | var emitter = new Emitter(); 272 | var called = new List {false}; 273 | 274 | 275 | var listener5 = new TestListener5(called); 276 | var listener6 = new TestListener6(emitter, listener5); 277 | emitter.On("tobi", listener6); 278 | 279 | emitter.Once("tobi", listener5); 280 | emitter.Emit("tobi"); 281 | Assert.True(called[0]); 282 | called[0] = false; 283 | emitter.Emit("tobi"); 284 | Assert.False(called[0]); 285 | } 286 | 287 | [Fact] 288 | public void OffEvent() 289 | { 290 | 291 | 292 | 293 | 294 | var emitter = new Emitter(); 295 | var calls = new List(); 296 | 297 | var listener3 = new TestListener3(calls); 298 | emitter.On("foo", listener3); 299 | 300 | var listener4 = new TestListener4(calls); 301 | 302 | emitter.On("foo", listener3); 303 | emitter.On("foo", listener4); 304 | emitter.Off("foo"); 305 | 306 | emitter.Emit("foo"); 307 | emitter.Emit("foo"); 308 | 309 | var expected = new Object[] {}; 310 | Assert.Equal(expected, calls.ToArray()); 311 | } 312 | 313 | 314 | [Fact] 315 | public void OffAll() 316 | { 317 | 318 | 319 | 320 | 321 | var emitter = new Emitter(); 322 | var calls = new List(); 323 | 324 | var listener3 = new TestListener3(calls); 325 | 326 | var listener4 = new TestListener4(calls); 327 | 328 | 329 | 330 | emitter.On("foo", listener3); 331 | emitter.On("bar", listener4); 332 | 333 | emitter.Emit("foo"); 334 | emitter.Emit("bar"); 335 | 336 | emitter.Off(); 337 | 338 | emitter.Emit("foo"); 339 | emitter.Emit("bar"); 340 | 341 | 342 | var expected = new Object[] {"one", "two"}; 343 | Assert.Equal(expected, calls.ToArray()); 344 | } 345 | 346 | [Fact] 347 | public void Listeners() 348 | { 349 | 350 | 351 | 352 | 353 | var emitter = new Emitter(); 354 | var calls = new List(); 355 | 356 | var listener3 = new TestListener3(calls); 357 | emitter.On("foo", listener3); 358 | var expected = new IListener[] {listener3}; 359 | Assert.Equal(expected, emitter.Listeners("foo").ToArray()); 360 | } 361 | 362 | [Fact] 363 | public void ListenersWithoutHandlers() 364 | { 365 | 366 | var emitter = new Emitter(); 367 | var expected = new IListener[] {}; 368 | Assert.Equal(expected, emitter.Listeners("foo").ToArray()); 369 | } 370 | 371 | [Fact] 372 | public void HasListeners() 373 | { 374 | 375 | 376 | 377 | 378 | var emitter = new Emitter(); 379 | var calls = new List(); 380 | Assert.False(emitter.HasListeners("foo")); 381 | 382 | var listener3 = new TestListener3(calls); 383 | emitter.On("foo", listener3); 384 | Assert.True(emitter.HasListeners("foo")); 385 | } 386 | 387 | [Fact] 388 | public void HasListenersWithoutHandlers() 389 | { 390 | 391 | 392 | 393 | 394 | var emitter = new Emitter(); 395 | Assert.False(emitter.HasListeners("foo")); 396 | } 397 | 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/ComponentEmitter/Emitter.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Collections.Immutable; 3 | using Quobject.EngineIoClientDotNet.Modules; 4 | using System; 5 | 6 | namespace Quobject.EngineIoClientDotNet.ComponentEmitter 7 | { 8 | 9 | /// 10 | /// The event emitter which is ported from the JavaScript module. 11 | /// https://github.com/component/emitter 12 | /// 13 | public class Emitter 14 | { 15 | private ImmutableDictionary> callbacks; 16 | 17 | private ImmutableDictionary _onceCallbacks; 18 | 19 | 20 | public Emitter() 21 | { 22 | this.Off(); 23 | } 24 | 25 | /// 26 | /// Executes each of listeners with the given args. 27 | /// 28 | /// an event name. 29 | /// 30 | /// a reference to this object. 31 | public virtual Emitter Emit(string eventString, params object[] args) 32 | { 33 | //var log = LogManager.GetLogger(Global.CallerName()); 34 | //log.Info("Emitter emit event = " + eventString); 35 | if (this.callbacks.TryGetValue(eventString, out var callbacksLocal)) 36 | { 37 | foreach (var fn in callbacksLocal) 38 | { 39 | fn.Call(args); 40 | } 41 | } 42 | return this; 43 | } 44 | 45 | /// 46 | /// Listens on the event. 47 | /// 48 | /// event name 49 | /// 50 | /// a reference to this object 51 | public Emitter On(string eventString, IListener fn) 52 | { 53 | if (!this.callbacks.ContainsKey(eventString)) 54 | { 55 | //this.callbacks[eventString] = ImmutableList.Empty; 56 | this.callbacks = this.callbacks.Add(eventString, ImmutableList.Empty); 57 | } 58 | 59 | ImmutableList callbacksLocal = this.callbacks[eventString]; 60 | callbacksLocal = callbacksLocal.Add(fn); 61 | //this.callbacks[eventString] = callbacksLocal; 62 | this.callbacks = this.callbacks.Remove(eventString).Add(eventString, callbacksLocal); 63 | return this; 64 | } 65 | 66 | /// 67 | /// Listens on the event. 68 | /// 69 | /// event name 70 | /// 71 | /// a reference to this object 72 | public Emitter On(string eventString, Action fn) 73 | { 74 | var listener = new ListenerImpl(fn); 75 | return this.On(eventString, listener); 76 | } 77 | 78 | /// 79 | /// Listens on the event. 80 | /// 81 | /// event name 82 | /// 83 | /// a reference to this object 84 | public Emitter On(string eventString, Action fn) 85 | { 86 | var listener = new ListenerImpl(fn); 87 | return this.On(eventString, listener); 88 | } 89 | 90 | 91 | /// 92 | /// Adds a one time listener for the event. 93 | /// 94 | /// an event name. 95 | /// 96 | /// a reference to this object 97 | public Emitter Once(string eventString, IListener fn) 98 | { 99 | var on = new OnceListener(eventString, fn, this); 100 | 101 | _onceCallbacks = _onceCallbacks.Add(fn, on); 102 | this.On(eventString, on); 103 | return this; 104 | 105 | } 106 | 107 | /// 108 | /// Adds a one time listener for the event. 109 | /// 110 | /// an event name. 111 | /// 112 | /// a reference to this object 113 | public Emitter Once(string eventString, Action fn) 114 | { 115 | var listener = new ListenerImpl(fn); 116 | return this.Once(eventString, listener); 117 | } 118 | 119 | /// 120 | /// Removes all registered listeners. 121 | /// 122 | /// a reference to this object. 123 | public Emitter Off() 124 | { 125 | callbacks = ImmutableDictionary.Create>(); 126 | _onceCallbacks = ImmutableDictionary.Create(); 127 | return this; 128 | } 129 | 130 | /// 131 | /// Removes all listeners of the specified event. 132 | /// 133 | /// an event name 134 | /// a reference to this object. 135 | public Emitter Off(string eventString) 136 | { 137 | try 138 | { 139 | 140 | ImmutableList retrievedValue; 141 | if (!callbacks.TryGetValue(eventString, out retrievedValue)) 142 | { 143 | var log = LogManager.GetLogger(Global.CallerName()); 144 | log.Info(string.Format("Emitter.Off Could not remove {0}", eventString)); 145 | } 146 | 147 | if (retrievedValue != null) 148 | { 149 | callbacks = callbacks.Remove(eventString); 150 | 151 | foreach (var listener in retrievedValue) 152 | { 153 | _onceCallbacks.Remove(listener); 154 | } 155 | } 156 | } 157 | catch (Exception) 158 | { 159 | this.Off(); 160 | } 161 | 162 | return this; 163 | } 164 | 165 | 166 | /// 167 | /// Removes the listener 168 | /// 169 | /// an event name 170 | /// 171 | /// a reference to this object. 172 | public Emitter Off(string eventString, IListener fn) 173 | { 174 | try 175 | { 176 | if (this.callbacks.TryGetValue(eventString, out var callbacksLocal)) 177 | { 178 | IListener offListener; 179 | _onceCallbacks.TryGetValue(fn, out offListener); 180 | _onceCallbacks = _onceCallbacks.Remove(fn); 181 | 182 | 183 | if (callbacksLocal.Count > 0 && callbacksLocal.Contains(offListener ?? fn)) 184 | { 185 | callbacksLocal = callbacksLocal.Remove(offListener ?? fn); 186 | this.callbacks = this.callbacks.Remove(eventString); 187 | this.callbacks = this.callbacks.Add(eventString, callbacksLocal); 188 | } 189 | } 190 | 191 | } 192 | catch(Exception) 193 | { 194 | this.Off(); 195 | } 196 | 197 | return this; 198 | } 199 | 200 | /// 201 | /// Returns a list of listeners for the specified event. 202 | /// 203 | /// an event name. 204 | /// a reference to this object 205 | public ImmutableList Listeners(string eventString) 206 | { 207 | if (this.callbacks.TryGetValue(eventString, out var callbacksLocal)) 208 | { 209 | return callbacksLocal ?? ImmutableList.Empty; 210 | } 211 | return ImmutableList.Empty; 212 | } 213 | 214 | /// 215 | /// Check if this emitter has listeners for the specified event. 216 | /// 217 | /// an event name 218 | /// bool 219 | public bool HasListeners(string eventString) 220 | { 221 | return this.Listeners(eventString).Count > 0; 222 | } 223 | } 224 | 225 | public interface IListener: System.IComparable 226 | { 227 | int GetId(); 228 | void Call(params object[] args); 229 | } 230 | 231 | public class ListenerImpl : IListener 232 | { 233 | private static int id_counter = 0; 234 | private int Id; 235 | private readonly Action fn1; 236 | private readonly Action fn; 237 | 238 | public ListenerImpl(Action fn) 239 | { 240 | 241 | this.fn = fn; 242 | this.Id = id_counter++; 243 | } 244 | 245 | public ListenerImpl(Action fn) 246 | { 247 | 248 | this.fn1 = fn; 249 | this.Id = id_counter++; 250 | } 251 | 252 | public void Call(params object[] args) 253 | { 254 | if (fn != null) 255 | { 256 | var arg = args.Length > 0 ? args[0] : null; 257 | fn(arg); 258 | } 259 | else 260 | { 261 | fn1(); 262 | } 263 | } 264 | 265 | public int CompareTo(IListener other) 266 | { 267 | return this.GetId().CompareTo(other.GetId()); 268 | } 269 | 270 | public int GetId() 271 | { 272 | return Id; 273 | } 274 | } 275 | 276 | public class OnceListener : IListener 277 | { 278 | private static int id_counter = 0; 279 | private int Id; 280 | private readonly string _eventString; 281 | private readonly IListener _fn; 282 | private readonly Emitter _emitter; 283 | 284 | public OnceListener(string eventString, IListener fn, Emitter emitter) 285 | { 286 | this._eventString = eventString; 287 | this._fn = fn; 288 | this._emitter = emitter; 289 | Id = id_counter++; 290 | } 291 | 292 | void IListener.Call(params object[] args) 293 | { 294 | _emitter.Off(_eventString, this); 295 | _fn.Call(args); 296 | } 297 | 298 | public int CompareTo(IListener other) 299 | { 300 | return this.GetId().CompareTo(other.GetId()); 301 | } 302 | 303 | public int GetId() 304 | { 305 | return Id; 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/Transports/Polling.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Collections.Immutable; 3 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 4 | using Quobject.EngineIoClientDotNet.Modules; 5 | using Quobject.EngineIoClientDotNet.Parser; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace Quobject.EngineIoClientDotNet.Client.Transports 11 | { 12 | public class Polling : Transport 13 | { 14 | public static readonly string NAME = "polling"; 15 | public static readonly string EVENT_POLL = "poll"; 16 | public static readonly string EVENT_POLL_COMPLETE = "pollComplete"; 17 | 18 | private bool IsPolling = false; 19 | 20 | public Polling(Options opts) : base(opts) 21 | { 22 | Name = NAME; 23 | } 24 | 25 | private bool FirstTimePoll = true; 26 | private bool OnDataReceived = false; 27 | 28 | protected override void DoOpen() 29 | { 30 | var log = LogManager.GetLogger(Global.CallerName()); 31 | log.Info("DoOpen: Entry"); 32 | 33 | do 34 | { 35 | if (FirstTimePoll) 36 | { 37 | log.Info("DoOpen: Initial Poll - ReadyState=" + ReadyState.ToString()); 38 | FirstTimePoll = false; 39 | Poll(); 40 | IsPolling = false; 41 | Emit(EVENT_POLL_COMPLETE); 42 | } 43 | else if (OnDataReceived && ReadyState == ReadyStateEnum.OPEN) 44 | { 45 | log.Info("DoOpen: General Poll - ReadyState=" + ReadyState.ToString()); 46 | OnDataReceived = false;// Don't poll again, unless signaled by _onData 47 | Poll(); 48 | IsPolling = false; 49 | Emit(EVENT_POLL_COMPLETE); 50 | } 51 | else 52 | { 53 | log.Info(string.Format("DoOpen: ignoring poll - transport state {0}", ReadyState)); 54 | } 55 | System.Threading.Thread.Sleep(100); 56 | } 57 | while (ReadyState != ReadyStateEnum.CLOSED); 58 | } 59 | 60 | public void Pause(Action onPause) 61 | { 62 | //var log = LogManager.GetLogger(Global.CallerName()); 63 | 64 | ReadyState = ReadyStateEnum.PAUSED; 65 | Action pause = () => 66 | { 67 | //log.Info("paused"); 68 | ReadyState = ReadyStateEnum.PAUSED; 69 | onPause(); 70 | }; 71 | 72 | if (IsPolling || !Writable) 73 | { 74 | var total = new[] {0}; 75 | 76 | 77 | if (IsPolling) 78 | { 79 | //log.Info("we are currently polling - waiting to pause"); 80 | total[0]++; 81 | Once(EVENT_POLL_COMPLETE, new PauseEventPollCompleteListener(total, pause)); 82 | 83 | } 84 | 85 | if (!Writable) 86 | { 87 | //log.Info("we are currently writing - waiting to pause"); 88 | total[0]++; 89 | Once(EVENT_DRAIN, new PauseEventDrainListener(total, pause)); 90 | } 91 | 92 | } 93 | else 94 | { 95 | pause(); 96 | } 97 | } 98 | 99 | public void Resume() 100 | { 101 | if (ReadyState == ReadyStateEnum.PAUSED) 102 | ReadyState = ReadyStateEnum.OPEN; 103 | } 104 | 105 | private class PauseEventDrainListener : IListener 106 | { 107 | private int[] total; 108 | private Action pause; 109 | 110 | public PauseEventDrainListener(int[] total, Action pause) 111 | { 112 | this.total = total; 113 | this.pause = pause; 114 | } 115 | 116 | public void Call(params object[] args) 117 | { 118 | //var log = LogManager.GetLogger(Global.CallerName()); 119 | 120 | //log.Info("pre-pause writing complete"); 121 | if (--total[0] == 0) 122 | { 123 | pause(); 124 | } 125 | } 126 | 127 | public int CompareTo(IListener other) 128 | { 129 | return this.GetId().CompareTo(other.GetId()); 130 | } 131 | 132 | public int GetId() 133 | { 134 | return 0; 135 | } 136 | } 137 | 138 | class PauseEventPollCompleteListener : IListener 139 | { 140 | private int[] total; 141 | private Action pause; 142 | 143 | public PauseEventPollCompleteListener(int[] total, Action pause) 144 | { 145 | 146 | this.total = total; 147 | this.pause = pause; 148 | } 149 | 150 | public void Call(params object[] args) 151 | { 152 | //var log = LogManager.GetLogger(Global.CallerName()); 153 | 154 | //log.Info("pre-pause polling complete"); 155 | if (--total[0] == 0) 156 | { 157 | pause(); 158 | } 159 | } 160 | 161 | public int CompareTo(IListener other) 162 | { 163 | return this.GetId().CompareTo(other.GetId()); 164 | } 165 | 166 | public int GetId() 167 | { 168 | return 0; 169 | } 170 | 171 | 172 | } 173 | 174 | 175 | private void Poll() 176 | { 177 | //var log = LogManager.GetLogger(Global.CallerName()); 178 | 179 | //log.Info("polling"); 180 | IsPolling = true; 181 | DoPoll(); 182 | Emit(EVENT_POLL); 183 | } 184 | 185 | 186 | 187 | protected override void OnData(string data) 188 | { 189 | _onData(data); 190 | } 191 | 192 | protected override void OnData(byte[] data) 193 | { 194 | _onData(data); 195 | } 196 | 197 | 198 | private class DecodePayloadCallback : IDecodePayloadCallback 199 | { 200 | private Polling polling; 201 | 202 | public DecodePayloadCallback(Polling polling) 203 | { 204 | this.polling = polling; 205 | } 206 | public bool Call(Packet packet, int index, int total) 207 | { 208 | if (polling.ReadyState == ReadyStateEnum.OPENING) 209 | { 210 | polling.OnOpen(); 211 | } 212 | 213 | if (packet.Type == Packet.CLOSE) 214 | { 215 | polling.OnClose(); 216 | return false; 217 | } 218 | 219 | polling.OnPacket(packet); 220 | return true; 221 | } 222 | } 223 | 224 | 225 | private void _onData(object data) 226 | { 227 | var log = LogManager.GetLogger(Global.CallerName()); 228 | 229 | log.Info(string.Format("polling got data {0}",data)); 230 | var callback = new DecodePayloadCallback(this); 231 | if (data is string) 232 | { 233 | Parser.Parser.DecodePayload((string)data, callback); 234 | } 235 | else if (data is byte[]) 236 | { 237 | Parser.Parser.DecodePayload((byte[])data, callback); 238 | } 239 | 240 | OnDataReceived = true; 241 | } 242 | 243 | private class CloseListener : IListener 244 | { 245 | private Polling polling; 246 | 247 | public CloseListener(Polling polling) 248 | { 249 | this.polling = polling; 250 | } 251 | 252 | public void Call(params object[] args) 253 | { 254 | //var log = LogManager.GetLogger(Global.CallerName()); 255 | 256 | //log.Info("writing close packet"); 257 | ImmutableList packets = ImmutableList.Empty; 258 | packets = packets.Add(new Packet(Packet.CLOSE)); 259 | polling.Write(packets); 260 | } 261 | 262 | public int CompareTo(IListener other) 263 | { 264 | return this.GetId().CompareTo(other.GetId()); 265 | } 266 | 267 | public int GetId() 268 | { 269 | return 0; 270 | } 271 | } 272 | 273 | protected override void DoClose() 274 | { 275 | var log = LogManager.GetLogger(Global.CallerName()); 276 | 277 | var closeListener = new CloseListener(this); 278 | 279 | if (ReadyState == ReadyStateEnum.OPEN) 280 | { 281 | log.Info("transport open - closing"); 282 | closeListener.Call(); 283 | } 284 | else 285 | { 286 | // in case we're trying to close while 287 | // handshaking is in progress (engine.io-client GH-164) 288 | log.Info("transport not open - deferring close"); 289 | this.Once(EVENT_OPEN, closeListener); 290 | } 291 | } 292 | 293 | 294 | public class SendEncodeCallback : IEncodeCallback 295 | { 296 | private Polling polling; 297 | 298 | public SendEncodeCallback(Polling polling) 299 | { 300 | this.polling = polling; 301 | } 302 | 303 | public void Call(object data) 304 | { 305 | //var log = LogManager.GetLogger(Global.CallerName()); 306 | //log.Info("SendEncodeCallback data = " + data); 307 | 308 | var byteData = (byte[]) data; 309 | polling.DoWrite(byteData, () => 310 | { 311 | polling.Writable = true; 312 | polling.Emit(EVENT_DRAIN); 313 | }); 314 | } 315 | 316 | } 317 | 318 | 319 | protected override void Write(ImmutableList packets) 320 | { 321 | var log = LogManager.GetLogger(Global.CallerName()); 322 | log.Info("Write packets.Count = " + packets.Count); 323 | 324 | Writable = false; 325 | 326 | var callback = new SendEncodeCallback(this); 327 | Parser.Parser.EncodePayload(packets.ToArray(), callback); 328 | } 329 | 330 | public string Uri() 331 | { 332 | //var query = this.Query; 333 | var query = new Dictionary(Query); 334 | //if (Query == null) 335 | //{ 336 | // query = new Dictionary(); 337 | //} 338 | string schema = this.Secure ? "https" : "http"; 339 | string portString = ""; 340 | 341 | if (this.TimestampRequests) 342 | { 343 | query.Add(this.TimestampParam, DateTime.Now.Ticks + "-" + Transport.Timestamps++); 344 | } 345 | 346 | query.Add("b64", "1"); 347 | 348 | 349 | 350 | string _query = ParseQS.Encode(query); 351 | 352 | if (this.Port > 0 && (("https" == schema && this.Port != 443) 353 | || ("http" == schema && this.Port != 80))) 354 | { 355 | portString = ":" + this.Port; 356 | } 357 | 358 | if (_query.Length > 0) 359 | { 360 | _query = "?" + _query; 361 | } 362 | 363 | return schema + "://" + this.Hostname + portString + this.Path + _query; 364 | } 365 | 366 | protected virtual void DoWrite(byte[] data, Action action) 367 | { 368 | 369 | } 370 | 371 | protected virtual void DoPoll() 372 | { 373 | 374 | } 375 | 376 | 377 | 378 | 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Client/Transports/PollingXHR.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.ComponentEmitter; 2 | using Quobject.EngineIoClientDotNet.Modules; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | namespace Quobject.EngineIoClientDotNet.Client.Transports 13 | { 14 | public class PollingXHR : Polling 15 | { 16 | private XHRRequest sendXhr; 17 | 18 | public PollingXHR(Options options) 19 | : base(options) 20 | { 21 | } 22 | 23 | protected XHRRequest Request() 24 | { 25 | return Request(null); 26 | } 27 | 28 | protected XHRRequest Request(XHRRequest.RequestOptions opts) 29 | { 30 | if (opts == null) 31 | { 32 | opts = new XHRRequest.RequestOptions(); 33 | } 34 | opts.Uri = Uri(); 35 | 36 | var req = new XHRRequest(opts); 37 | 38 | req.On(EVENT_REQUEST_HEADERS, new EventRequestHeadersListener(this)). 39 | On(EVENT_RESPONSE_HEADERS, new EventResponseHeadersListener(this)); 40 | 41 | return req; 42 | } 43 | 44 | private class EventRequestHeadersListener : IListener 45 | { 46 | private PollingXHR pollingXHR; 47 | 48 | public EventRequestHeadersListener(PollingXHR pollingXHR) 49 | { 50 | this.pollingXHR = pollingXHR; 51 | } 52 | 53 | public void Call(params object[] args) 54 | { 55 | // Never execute asynchronously for support to modify headers. 56 | pollingXHR.Emit(EVENT_RESPONSE_HEADERS, args[0]); 57 | } 58 | 59 | public int CompareTo(IListener other) 60 | { 61 | return this.GetId().CompareTo(other.GetId()); 62 | } 63 | 64 | public int GetId() 65 | { 66 | return 0; 67 | } 68 | } 69 | 70 | private class EventResponseHeadersListener : IListener 71 | { 72 | private PollingXHR pollingXHR; 73 | 74 | public EventResponseHeadersListener(PollingXHR pollingXHR) 75 | { 76 | this.pollingXHR = pollingXHR; 77 | } 78 | 79 | public void Call(params object[] args) 80 | { 81 | pollingXHR.Emit(EVENT_REQUEST_HEADERS, args[0]); 82 | } 83 | 84 | public int CompareTo(IListener other) 85 | { 86 | return this.GetId().CompareTo(other.GetId()); 87 | } 88 | 89 | public int GetId() 90 | { 91 | return 0; 92 | } 93 | } 94 | 95 | protected override void DoWrite(byte[] data, Action action) 96 | { 97 | var opts = new XHRRequest.RequestOptions { Method = "POST", Data = data, CookieHeaderValue = Cookie }; 98 | var log = LogManager.GetLogger(Global.CallerName()); 99 | log.Info("DoWrite data = " + data); 100 | //try 101 | //{ 102 | // var dataString = BitConverter.ToString(data); 103 | // log.Info(string.Format("DoWrite data {0}", dataString)); 104 | //} 105 | //catch (Exception e) 106 | //{ 107 | // log.Error(e); 108 | //} 109 | 110 | sendXhr = Request(opts); 111 | sendXhr.On(EVENT_SUCCESS, new SendEventSuccessListener(action)); 112 | sendXhr.On(EVENT_ERROR, new SendEventErrorListener(this)); 113 | sendXhr.Create(); 114 | } 115 | 116 | private class SendEventErrorListener : IListener 117 | { 118 | private PollingXHR pollingXHR; 119 | 120 | public SendEventErrorListener(PollingXHR pollingXHR) 121 | { 122 | this.pollingXHR = pollingXHR; 123 | } 124 | 125 | public void Call(params object[] args) 126 | { 127 | var err = args.Length > 0 && args[0] is Exception ? (Exception)args[0] : null; 128 | pollingXHR.OnError("xhr post error", err); 129 | } 130 | 131 | public int CompareTo(IListener other) 132 | { 133 | return this.GetId().CompareTo(other.GetId()); 134 | } 135 | 136 | public int GetId() 137 | { 138 | return 0; 139 | } 140 | } 141 | 142 | private class SendEventSuccessListener : IListener 143 | { 144 | private Action action; 145 | 146 | public SendEventSuccessListener(Action action) 147 | { 148 | this.action = action; 149 | } 150 | 151 | public void Call(params object[] args) 152 | { 153 | action?.Invoke(); 154 | } 155 | 156 | public int CompareTo(IListener other) 157 | { 158 | return this.GetId().CompareTo(other.GetId()); 159 | } 160 | 161 | public int GetId() 162 | { 163 | return 0; 164 | } 165 | } 166 | 167 | protected override void DoPoll() 168 | { 169 | var log = LogManager.GetLogger(Global.CallerName()); 170 | log.Info("xhr poll"); 171 | var opts = new XHRRequest.RequestOptions { CookieHeaderValue = Cookie }; 172 | sendXhr = Request(opts); 173 | sendXhr.On(EVENT_DATA, new DoPollEventDataListener(this)); 174 | sendXhr.On(EVENT_ERROR, new DoPollEventErrorListener(this)); 175 | //sendXhr.Create(); 176 | sendXhr.Create(); 177 | } 178 | 179 | private class DoPollEventDataListener : IListener 180 | { 181 | private PollingXHR pollingXHR; 182 | 183 | public DoPollEventDataListener(PollingXHR pollingXHR) 184 | { 185 | this.pollingXHR = pollingXHR; 186 | } 187 | 188 | public void Call(params object[] args) 189 | { 190 | var arg = args.Length > 0 ? args[0] : null; 191 | if (arg is string) 192 | { 193 | pollingXHR.OnData((string)arg); 194 | } 195 | else if (arg is byte[]) 196 | { 197 | pollingXHR.OnData((byte[])arg); 198 | } 199 | } 200 | 201 | public int CompareTo(IListener other) 202 | { 203 | return this.GetId().CompareTo(other.GetId()); 204 | } 205 | 206 | public int GetId() 207 | { 208 | return 0; 209 | } 210 | } 211 | 212 | private class DoPollEventErrorListener : IListener 213 | { 214 | private PollingXHR pollingXHR; 215 | 216 | public DoPollEventErrorListener(PollingXHR pollingXHR) 217 | { 218 | this.pollingXHR = pollingXHR; 219 | } 220 | 221 | public void Call(params object[] args) 222 | { 223 | var err = args.Length > 0 && args[0] is Exception ? (Exception)args[0] : null; 224 | pollingXHR.OnError("xhr poll error", err); 225 | } 226 | 227 | public int CompareTo(IListener other) 228 | { 229 | return this.GetId().CompareTo(other.GetId()); 230 | } 231 | 232 | public int GetId() 233 | { 234 | return 0; 235 | } 236 | } 237 | 238 | public class XHRRequest : Emitter 239 | { 240 | private string Method; 241 | private string Uri; 242 | private byte[] Data; 243 | private string CookieHeaderValue; 244 | private Dictionary ExtraHeaders; 245 | 246 | public XHRRequest(RequestOptions options) 247 | { 248 | Method = options.Method ?? "GET"; 249 | Uri = options.Uri; 250 | Data = options.Data; 251 | CookieHeaderValue = options.CookieHeaderValue; 252 | ExtraHeaders = options.ExtraHeaders; 253 | } 254 | 255 | public void Create() 256 | { 257 | var httpMethod = Method == "POST" ? HttpMethod.Post : HttpMethod.Get; 258 | var dataToSend = Data == null ? Encoding.UTF8.GetBytes("") : Data; 259 | 260 | Task.Run(async () => 261 | { 262 | try 263 | { 264 | using (var httpClientHandler = new HttpClientHandler()) 265 | { 266 | if (ServerCertificate.Ignore) 267 | { 268 | httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; 269 | } 270 | 271 | using (var client = new HttpClient(httpClientHandler)) 272 | { 273 | using (var httpContent = new ByteArrayContent(dataToSend)) 274 | { 275 | if (Method == "POST") 276 | { 277 | httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); 278 | } 279 | 280 | var request = new HttpRequestMessage(httpMethod, Uri) 281 | { 282 | Content = httpContent 283 | }; 284 | 285 | if (!string.IsNullOrEmpty(CookieHeaderValue)) 286 | { 287 | httpContent.Headers.Add(@"Cookie", CookieHeaderValue); 288 | } 289 | if (ExtraHeaders != null) 290 | { 291 | foreach (var header in ExtraHeaders) 292 | { 293 | httpContent.Headers.Add(header.Key, header.Value); 294 | } 295 | } 296 | 297 | if (Method == "GET") 298 | { 299 | using (HttpResponseMessage response = await client.GetAsync(request.RequestUri)) 300 | { 301 | var responseContent = await response.Content.ReadAsStringAsync(); 302 | OnData(responseContent); 303 | } 304 | } 305 | else 306 | { 307 | using (HttpResponseMessage response = await client.SendAsync(request)) 308 | { 309 | response.EnsureSuccessStatusCode(); 310 | var contentType = response.Content.Headers.GetValues("Content-Type").Aggregate("", (acc, x) => acc + x).Trim(); 311 | 312 | if (contentType.Equals("application/octet-stream", StringComparison.OrdinalIgnoreCase)) 313 | { 314 | var responseContent = await response.Content.ReadAsByteArrayAsync(); 315 | OnData(responseContent); 316 | } 317 | else 318 | { 319 | var responseContent = await response.Content.ReadAsStringAsync(); 320 | OnData(responseContent); 321 | } 322 | 323 | } 324 | } 325 | } 326 | } 327 | } 328 | } 329 | catch (Exception e) 330 | { 331 | OnError(e); 332 | } 333 | 334 | }).Wait(); 335 | 336 | 337 | } 338 | 339 | private void OnSuccess() 340 | { 341 | this.Emit(EVENT_SUCCESS); 342 | } 343 | 344 | private void OnData(string data) 345 | { 346 | //var log = LogManager.GetLogger(Global.CallerName()); 347 | //log.Info("OnData string = " + data); 348 | this.Emit(EVENT_DATA, data); 349 | this.OnSuccess(); 350 | } 351 | 352 | private void OnData(byte[] data) 353 | { 354 | //var log = LogManager.GetLogger(Global.CallerName()); 355 | //log.Info(string.Format("OnData byte[] ={0}", System.Text.Encoding.UTF8.GetString(data, 0, data.Length))); 356 | this.Emit(EVENT_DATA, data); 357 | this.OnSuccess(); 358 | } 359 | 360 | private void OnError(Exception err) 361 | { 362 | this.Emit(EVENT_ERROR, err); 363 | } 364 | 365 | private void OnRequestHeaders(Dictionary headers) 366 | { 367 | this.Emit(EVENT_REQUEST_HEADERS, headers); 368 | } 369 | 370 | private void OnResponseHeaders(Dictionary headers) 371 | { 372 | this.Emit(EVENT_RESPONSE_HEADERS, headers); 373 | } 374 | 375 | public class RequestOptions 376 | { 377 | public string Uri; 378 | public string Method; 379 | public byte[] Data; 380 | public string CookieHeaderValue; 381 | public Dictionary ExtraHeaders; 382 | } 383 | } 384 | } 385 | } -------------------------------------------------------------------------------- /Src/EngineIoClientDotNet/Parser/Packet.cs: -------------------------------------------------------------------------------- 1 | using Quobject.EngineIoClientDotNet.Modules; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Quobject.EngineIoClientDotNet.Parser 8 | { 9 | /// 10 | /// Packet type which is ported from the JavaScript module. 11 | /// This is the JavaScript parser for the engine.io protocol encoding, 12 | /// shared by both engine.io-client and engine.io. 13 | /// https://github.com/Automattic/engine.io-parser 14 | /// 15 | public class Packet 16 | { 17 | public static readonly string OPEN = "open"; 18 | public static readonly string CLOSE = "close"; 19 | public static readonly string PING = "ping"; 20 | public static readonly string PONG = "pong"; 21 | public static readonly string UPGRADE = "upgrade"; 22 | public static readonly string MESSAGE = "message"; 23 | public static readonly string NOOP = "noop"; 24 | public static readonly string ERROR = "error"; 25 | 26 | private static readonly int MAX_INT_CHAR_LENGTH = int.MaxValue.ToString().Length; 27 | 28 | //TODO: suport binary? 29 | private bool SupportsBinary = false; 30 | 31 | private static readonly Dictionary _packets = new Dictionary() 32 | { 33 | {Packet.OPEN, 0}, 34 | {Packet.CLOSE, 1}, 35 | {Packet.PING, 2}, 36 | {Packet.PONG, 3}, 37 | {Packet.MESSAGE, 4}, 38 | {Packet.UPGRADE, 5}, 39 | {Packet.NOOP, 6} 40 | }; 41 | 42 | private static readonly Dictionary _packetsList = new Dictionary(); 43 | 44 | static Packet() 45 | { 46 | foreach (var entry in _packets) 47 | { 48 | _packetsList.Add(entry.Value,entry.Key); 49 | } 50 | } 51 | 52 | private static readonly Packet _err = new Packet(Packet.ERROR,"parser error"); 53 | 54 | public string Type { get; set; } 55 | public object Data { get; set; } 56 | 57 | 58 | public Packet(string type, object data) 59 | { 60 | this.Type = type; 61 | this.Data = data; 62 | } 63 | 64 | public Packet(string type) 65 | { 66 | this.Type = type; 67 | this.Data = null; 68 | } 69 | 70 | internal void Encode(IEncodeCallback callback,bool utf8encode = false) 71 | { 72 | if ( Data is byte[]) 73 | { 74 | if (!SupportsBinary) 75 | { 76 | EncodeBase64Packet(callback); 77 | return; 78 | } 79 | EncodeByteArray(callback); 80 | return; 81 | } 82 | var encodedStringBuilder = new StringBuilder(); 83 | encodedStringBuilder.Append(_packets[Type]); 84 | 85 | if (Data != null) 86 | { 87 | encodedStringBuilder.Append( utf8encode ? UTF8.Encode((string) Data): (string)Data); 88 | } 89 | 90 | callback.Call(encodedStringBuilder.ToString()); 91 | } 92 | 93 | private void EncodeBase64Packet(IEncodeCallback callback) 94 | { 95 | var byteData = Data as byte[]; 96 | if (byteData != null) 97 | { 98 | var result = new StringBuilder(); 99 | result.Append("b"); 100 | result.Append(_packets[Type]); 101 | result.Append(Convert.ToBase64String(byteData)); 102 | callback.Call(result.ToString()); 103 | return; 104 | } 105 | throw new Exception("byteData == null"); 106 | } 107 | 108 | private void EncodeByteArray(IEncodeCallback callback) 109 | { 110 | var byteData = Data as byte[]; 111 | if (byteData != null) 112 | { 113 | var resultArray = new byte[1 + byteData.Length]; 114 | resultArray[0] = _packets[Type]; 115 | Array.Copy(byteData, 0, resultArray, 1, byteData.Length); 116 | callback.Call(resultArray); 117 | return; 118 | } 119 | throw new Exception("byteData == null"); 120 | } 121 | 122 | internal static Packet DecodePacket(string data, bool utf8decode = false) 123 | { 124 | if (data.StartsWith("b")) 125 | { 126 | return DecodeBase64Packet(data.Substring(1)); 127 | } 128 | 129 | int type; 130 | var s = data.Substring(0, 1); 131 | if (!int.TryParse(s, out type)) 132 | { 133 | type = -1; 134 | } 135 | 136 | if (utf8decode) 137 | { 138 | try 139 | { 140 | data = UTF8.Decode(data); 141 | } 142 | catch (Exception) 143 | { 144 | 145 | return _err; 146 | } 147 | } 148 | 149 | if (type < 0 || type >= _packetsList.Count) 150 | { 151 | return _err; 152 | } 153 | 154 | if (data.Length > 1) 155 | { 156 | return new Packet(_packetsList[(byte) type], data.Substring(1)); 157 | } 158 | return new Packet(_packetsList[(byte) type], null); 159 | } 160 | 161 | private static Packet DecodeBase64Packet(string msg) 162 | { 163 | int type; 164 | var s = msg.Substring(0, 1); 165 | if (!int.TryParse(s, out type)) 166 | { 167 | type = -1; 168 | } 169 | if (type < 0 || type >= _packetsList.Count) 170 | { 171 | return _err; 172 | } 173 | msg = msg.Substring(1); 174 | byte[] decodedFromBase64 = Convert.FromBase64String(msg); 175 | return new Packet(_packetsList[(byte)type], decodedFromBase64); 176 | } 177 | 178 | internal static Packet DecodePacket(byte[] data) 179 | { 180 | int type = data[0]; 181 | var byteArray = new byte[data.Length - 1]; 182 | Array.Copy(data,1,byteArray,0, byteArray.Length); 183 | return new Packet(_packetsList[(byte)type], byteArray); 184 | } 185 | 186 | 187 | 188 | internal static void EncodePayload(Packet[] packets, IEncodeCallback callback) 189 | { 190 | if (packets.Length == 0) 191 | { 192 | callback.Call(new byte[0]); 193 | return; 194 | } 195 | 196 | var results = new List(packets.Length); 197 | var encodePayloadCallback = new EncodePayloadCallback(results); 198 | foreach (var packet in packets) 199 | { 200 | packet.Encode(encodePayloadCallback, true); 201 | } 202 | 203 | callback.Call(Buffer.Concat(results.ToArray()));//new byte[results.Count][] 204 | } 205 | 206 | /// 207 | /// Decodes data when a payload is maybe expected. 208 | /// 209 | /// 210 | /// 211 | public static void DecodePayload(string data, IDecodePayloadCallback callback) 212 | { 213 | if (String.IsNullOrEmpty(data)) 214 | { 215 | callback.Call(_err, 0, 1); 216 | return; 217 | } 218 | 219 | var length = new StringBuilder(); 220 | for (int i = 0, l = data.Length; i < l; i++) 221 | { 222 | var chr = Convert.ToChar(data.Substring(i, 1)); 223 | 224 | if (chr != ':') 225 | { 226 | length.Append(chr); 227 | } 228 | else 229 | { 230 | int n; 231 | if (!int.TryParse(length.ToString(), out n)) 232 | { 233 | callback.Call(_err, 0, 1); 234 | return; 235 | } 236 | 237 | string msg; 238 | try 239 | { 240 | msg = data.Substring(i + 1, n); 241 | } 242 | catch (ArgumentOutOfRangeException) 243 | { 244 | callback.Call(_err, 0, 1); 245 | return; 246 | } 247 | 248 | if (msg.Length != 0) 249 | { 250 | Packet packet = DecodePacket(msg, false); 251 | if (_err.Type == packet.Type && _err.Data == packet.Data) 252 | { 253 | callback.Call(_err, 0, 1); 254 | return; 255 | } 256 | 257 | bool ret = callback.Call(packet, i + n, l); 258 | if (!ret) 259 | { 260 | return; 261 | } 262 | 263 | } 264 | 265 | i += n; 266 | length = new StringBuilder(); 267 | } 268 | } 269 | if (length.Length > 0) 270 | { 271 | callback.Call(_err, 0, 1); 272 | } 273 | } 274 | 275 | /// 276 | /// Decodes data when a payload is maybe expected. 277 | /// 278 | /// 279 | /// 280 | public static void DecodePayload(byte[] data, IDecodePayloadCallback callback) 281 | { 282 | var bufferTail = ByteBuffer.Wrap(data); 283 | 284 | var buffers = new List(); 285 | int bufferTail_offset = 0; 286 | while (bufferTail.Capacity - bufferTail_offset > 0) 287 | { 288 | var strLen = new StringBuilder(); 289 | var isString = (bufferTail.Get(0 + bufferTail_offset) & 0xFF) == 0; 290 | var numberTooLong = false; 291 | for (int i = 1;; i++) 292 | { 293 | int b = bufferTail.Get(i + bufferTail_offset) & 0xFF; 294 | if (b == 255) 295 | { 296 | break; 297 | } 298 | // support only integer 299 | if (strLen.Length > MAX_INT_CHAR_LENGTH) 300 | { 301 | numberTooLong = true; 302 | break; 303 | } 304 | strLen.Append(b); 305 | } 306 | if (numberTooLong) 307 | { 308 | callback.Call(_err, 0, 1); 309 | return; 310 | } 311 | bufferTail_offset += strLen.Length + 1; 312 | 313 | int msgLength = int.Parse(strLen.ToString()); 314 | 315 | bufferTail.Position(1 + bufferTail_offset); 316 | bufferTail.Limit(msgLength + 1 + bufferTail_offset); 317 | var msg = new byte[bufferTail.Remaining()]; 318 | bufferTail.Get(msg, 0, msg.Length); 319 | 320 | if (isString) 321 | { 322 | buffers.Add(ByteArrayToString(msg)); 323 | } 324 | else 325 | { 326 | buffers.Add(msg); 327 | } 328 | bufferTail.Clear(); 329 | bufferTail.Position(msgLength + 1 + bufferTail_offset); 330 | bufferTail_offset += msgLength + 1; 331 | } 332 | 333 | var total = buffers.Count; 334 | for (int i = 0; i < total; i++) 335 | { 336 | var buffer = buffers[i]; 337 | if (buffer is string) 338 | { 339 | callback.Call(DecodePacket((string) buffer, true), i, total); 340 | } 341 | else if (buffer is byte[]) 342 | { 343 | callback.Call(DecodePacket((byte[])buffer), i, total); 344 | } 345 | } 346 | 347 | } 348 | 349 | 350 | internal static byte[] StringToByteArray(string str) 351 | { 352 | int len = str.Length; 353 | var bytes = new byte[len]; 354 | for (int i = 0; i < len; i++) 355 | { 356 | bytes[i] = (byte)str[i]; 357 | } 358 | return bytes; 359 | } 360 | 361 | internal static string ByteArrayToString(byte[] bytes) 362 | { 363 | //return Encoding.ASCII.GetString(bytes); 364 | //http://stackoverflow.com/questions/7750850/encoding-ascii-getstring-in-windows-phone-platform 365 | return string.Concat(bytes.Select(b => b <= 0x7f ? (char)b : '?')); 366 | } 367 | 368 | private class EncodePayloadCallback : IEncodeCallback 369 | { 370 | private readonly List _results; 371 | 372 | public EncodePayloadCallback(List results) 373 | { 374 | this._results = results; 375 | } 376 | 377 | public void Call(object data) 378 | { 379 | if (data is string) 380 | { 381 | var packet = (string) data; 382 | var encodingLength = packet.Length.ToString(); 383 | var sizeBuffer = new byte[encodingLength.Length + 2]; 384 | sizeBuffer[0] = (byte) 0; // is a string 385 | for (var i = 0; i < encodingLength.Length; i++) 386 | { 387 | sizeBuffer[i + 1] = byte.Parse(encodingLength.Substring(i,1)); 388 | } 389 | sizeBuffer[sizeBuffer.Length - 1] = (byte) 255; 390 | _results.Add(Buffer.Concat(new byte[][] { sizeBuffer, StringToByteArray(packet) })); 391 | return; 392 | } 393 | 394 | var packet1 = (byte[]) data; 395 | var encodingLength1 = packet1.Length.ToString(); 396 | var sizeBuffer1 = new byte[encodingLength1.Length + 2]; 397 | sizeBuffer1[0] = (byte)1; // is binary 398 | for (var i = 0; i < encodingLength1.Length; i++) 399 | { 400 | sizeBuffer1[i + 1] = byte.Parse(encodingLength1.Substring(i, 1)); 401 | } 402 | sizeBuffer1[sizeBuffer1.Length - 1] = (byte)255; 403 | _results.Add(Buffer.Concat(new byte[][] { sizeBuffer1, packet1 })); 404 | } 405 | } 406 | 407 | } 408 | } 409 | --------------------------------------------------------------------------------