Loading WindowsDevicePortalWrapper/UnitTestProject/UnitTestProject.csproj +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ <Compile Include="Core\OsInformationTests.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TestHelpers.cs" /> <Compile Include="WDPMockImplementations\WebSocket.cs" /> <Compile Include="Xbox\UserManagementTests.cs" /> <Compile Include="Xbox\XboxAppDeploymentTests.cs" /> <Compile Include="Xbox\XboxSettingsTests.cs" /> Loading WindowsDevicePortalWrapper/UnitTestProject/WDPMockImplementations/WebSocket.cs 0 → 100644 +50 −0 Original line number Diff line number Diff line //---------------------------------------------------------------------------------------------- // <copyright file="WebSocket.cs" company="Microsoft Corporation"> // Licensed under the MIT License. See LICENSE.TXT in the project root license information. // </copyright> //---------------------------------------------------------------------------------------------- using System; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace Microsoft.Tools.WindowsDevicePortal { /// <summary> /// MOCK implementation of HTTP web socket wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal partial class WebSocket<T> { /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> /// <param name="serverCertificateValidationHandler">Server certificate handler.</param> public WebSocket(IDevicePortalConnection connection, Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler) { throw new NotImplementedException(); } /// <summary> /// Stops listneing for messages from the websocket and closes the connection to the websocket. /// </summary> /// <returns>The task of closing the websocket connection.</returns> private async Task StopListeningForMessagesInternal() { throw new NotImplementedException(); } /// <summary> /// Connects to the websocket and starts listening for messages from the websocket. /// Once they are received they are parsed and the WebSocketMessageReceived event is raised. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of listening for messages from the websocket.</returns> private async Task StartListeningForMessagesInternal(Uri endpoint) { throw new NotImplementedException(); } } } WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/Core/PerformanceData.cs +14 −19 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ namespace Microsoft.Tools.WindowsDevicePortal /// </summary> private static readonly string SystemPerfApi = "api/resourcemanager/systemperf"; #if !WINDOWS_UWP /// <summary> /// Web socket to get running processes. /// </summary> Loading Loading @@ -55,8 +54,6 @@ namespace Microsoft.Tools.WindowsDevicePortal set; } #endif // !WINDOWS_UWP /// <summary> /// Gets the collection of processes running on the device. /// </summary> Loading @@ -66,7 +63,6 @@ namespace Microsoft.Tools.WindowsDevicePortal return await this.Get<RunningProcesses>(RunningProcessApi); } #if !WINDOWS_UWP /// <summary> /// Starts listening for the running processes on the device with them being returned via the RunningProcessesMessageReceived event handler. /// </summary> Loading @@ -75,7 +71,11 @@ namespace Microsoft.Tools.WindowsDevicePortal { if (this.deviceProcessesWebSocket == null) { #if WINDOWS_UWP this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(this.deviceConnection); #else this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(this.deviceConnection, this.ServerCertificateValidation); #endif this.deviceProcessesWebSocket.WebSocketMessageReceived += this.RunningProcessesReceivedHandler; } Loading @@ -87,10 +87,7 @@ namespace Microsoft.Tools.WindowsDevicePortal } } await this.deviceProcessesWebSocket.OpenConnection(RunningProcessApi); // Do not await on the actual listening. Task listenTask = this.deviceProcessesWebSocket.StartListeningForMessages(); await this.deviceProcessesWebSocket.StartListeningForMessages(RunningProcessApi); } /// <summary> Loading @@ -104,9 +101,8 @@ namespace Microsoft.Tools.WindowsDevicePortal return; } await this.deviceProcessesWebSocket.CloseConnection(); await this.deviceProcessesWebSocket.StopListeningForMessages(); } #endif // !WINDOWS_UWP /// <summary> /// Gets system performance information for the device. Loading @@ -117,7 +113,6 @@ namespace Microsoft.Tools.WindowsDevicePortal return await this.Get<SystemPerformanceInformation>(SystemPerfApi); } #if !WINDOWS_UWP /// <summary> /// Starts listening for the system performance information for the device with it being returned via the SystemPerfMessageReceived event handler. /// </summary> Loading @@ -126,7 +121,11 @@ namespace Microsoft.Tools.WindowsDevicePortal { if (this.systemPerfWebSocket == null) { #if WINDOWS_UWP this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(this.deviceConnection); #else this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(this.deviceConnection, this.ServerCertificateValidation); #endif this.systemPerfWebSocket.WebSocketMessageReceived += this.SystemPerfMessageReceived; } Loading @@ -138,10 +137,7 @@ namespace Microsoft.Tools.WindowsDevicePortal } } await this.systemPerfWebSocket.OpenConnection(SystemPerfApi); // Do not await on the actual listening. Task listenTask = this.systemPerfWebSocket.StartListeningForMessages(); await this.systemPerfWebSocket.StartListeningForMessages(SystemPerfApi); } /// <summary> Loading @@ -155,7 +151,7 @@ namespace Microsoft.Tools.WindowsDevicePortal return; } await this.systemPerfWebSocket.CloseConnection(); await this.systemPerfWebSocket.StopListeningForMessages(); } /// <summary> Loading Loading @@ -192,7 +188,6 @@ namespace Microsoft.Tools.WindowsDevicePortal } } #endif // !WINDOWS_UWP #region Device contract /// <summary> Loading WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/HttpRest/WebSocket.cs +31 −153 Original line number Diff line number Diff line Loading @@ -4,17 +4,9 @@ // </copyright> //---------------------------------------------------------------------------------------------- // TODO: enable this once the rest of the code is UWP friendly #if !WINDOWS_UWP using System; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Net.WebSockets; using System.Runtime.Serialization.Json; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Tools.WindowsDevicePortal Loading @@ -23,50 +15,13 @@ namespace Microsoft.Tools.WindowsDevicePortal /// HTTP Websocket Wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal class WebSocket<T> internal partial class WebSocket<T> { /// <summary> /// The hresult for the connection being reset by the peer. /// </summary> private const int WSAECONNRESET = 0x2746; /// <summary> /// The maximum number of bytes that can be received in a single chunk. /// </summary> private static readonly uint MaxChunkSizeInBytes = 1024; /// <summary> /// The device that the <see cref="WebSocket{T}" /> is connected to. /// </summary> private IDevicePortalConnection deviceConnection; /// <summary> /// The <see cref="ClientWebSocket" /> that is being wrapped. /// </summary> private ClientWebSocket websocket = new ClientWebSocket(); /// <summary> /// <see cref="ManualResetEvent" /> used to indicate that the <see cref="WebSocket{T}" /> has stopped receiving messages. /// </summary> private ManualResetEvent stoppedReceivingMessages = new ManualResetEvent(false); /// <summary> /// The handler used to validate server certificates. /// </summary> private Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler; /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> /// <param name="serverCertificateValidationHandler">Server certificate handler.</param> public WebSocket(IDevicePortalConnection connection, Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler) { this.deviceConnection = connection; this.IsListeningForMessages = false; this.serverCertificateValidationHandler = serverCertificateValidationHandler; } /// <summary> /// Gets a value indicating whether the web socket is listening for messages. /// </summary> Loading @@ -86,105 +41,50 @@ namespace Microsoft.Tools.WindowsDevicePortal } /// <summary> /// Opens a connection to the specified websocket API with the provided payload. /// </summary> /// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param> /// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param> /// <returns>The task of opening a connection to the websocket.</returns> internal async Task OpenConnection( string apiPath, string payload = null) { Uri uri = Utilities.BuildEndpoint( this.deviceConnection.WebSocketConnection, apiPath, payload); this.websocket.Options.UseDefaultCredentials = false; this.websocket.Options.Credentials = this.deviceConnection.Credentials; this.websocket.Options.SetRequestHeader("Origin", this.deviceConnection.Connection.AbsoluteUri); // There is no way to set a ServerCertificateValidationCallback for a single web socket, hence the workaround. ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors) { return this.serverCertificateValidationHandler(sender, cert, chain, policyErrors); }; await this.websocket.ConnectAsync(uri, CancellationToken.None); } /// <summary> /// Closes the connection to the websocket. /// Closes the connection to the websocket and stop listening for messages. /// </summary> /// <returns>The task of closing the websocket connection.</returns> internal async Task CloseConnection() { if (this.websocket.State != WebSocketState.Closed) internal async Task StopListeningForMessages() { await this.websocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); // Wait for web socket to no longer be receiving messages. if (this.IsListeningForMessages) { this.stoppedReceivingMessages.WaitOne(); this.stoppedReceivingMessages.Reset(); } // Reset websocket as WDP will abort the connection once it receives the close message. this.websocket = new ClientWebSocket(); await this.StopListeningForMessagesInternal(); } } /// <summary> /// Starts listening for messages from the websocket. Once they are received they are parsed and the WebSocketMessageReceived event is raised. /// </summary> /// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param> /// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param> /// <returns>The task of listening for messages from the websocket.</returns> internal async Task StartListeningForMessages() { if (this.websocket.State != WebSocketState.Open || this.IsListeningForMessages) { return; } this.IsListeningForMessages = true; try { ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[MaxChunkSizeInBytes]); // Once close message is sent do not try to get any more messages as WDP will abort the web socket connection. while (this.websocket.State == WebSocketState.Open) { // Receive single message in chunks. using (var ms = new MemoryStream()) { WebSocketReceiveResult result; do internal async Task StartListeningForMessages( string apiPath, string payload = null) { result = await this.websocket.ReceiveAsync(buffer, CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) if (!this.IsListeningForMessages) { await this.websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); return; } Uri uri = Utilities.BuildEndpoint( this.deviceConnection.WebSocketConnection, apiPath, payload); if (result.Count > MaxChunkSizeInBytes) { throw new InvalidOperationException("Buffer not large enough"); await this.StartListeningForMessagesInternal(uri); } ms.Write(buffer.Array, buffer.Offset, result.Count); } while (!result.EndOfMessage); ms.Seek(0, SeekOrigin.Begin); if (result.MessageType == WebSocketMessageType.Text) /// <summary> /// Converts received stream to a parsed message and passes it to the WebSocketMessageReceived handler. /// </summary> /// <param name="stream">The received stream.</param> private void ConvertStreamToMessage(Stream stream) { if (stream != null && stream.Length != 0) { using (stream) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); T message = (T)serializer.ReadObject(ms); T message = (T)serializer.ReadObject(stream); this.WebSocketMessageReceived?.Invoke( this, Loading @@ -193,26 +93,4 @@ namespace Microsoft.Tools.WindowsDevicePortal } } } catch (WebSocketException e) { // If WDP aborted the web socket connection ignore the exception. SocketException socketException = e.InnerException?.InnerException as SocketException; if (socketException != null) { if (socketException.NativeErrorCode == WSAECONNRESET) { return; } } throw; } finally { this.stoppedReceivingMessages.Set(); this.IsListeningForMessages = false; } } } } No newline at end of file #endif // !WINDOWS_UWP No newline at end of file WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.UniversalWindows/HttpRest/WebSocket.cs 0 → 100644 +137 −0 Original line number Diff line number Diff line //---------------------------------------------------------------------------------------------- // <copyright file="WebSocket.cs" company="Microsoft Corporation"> // Licensed under the MIT License. See LICENSE.TXT in the project root license information. // </copyright> //---------------------------------------------------------------------------------------------- using System; using System.IO; using System.Threading.Tasks; using Windows.Networking.Sockets; using Windows.Security.Credentials; using Windows.Storage.Streams; namespace Microsoft.Tools.WindowsDevicePortal { /// <summary> /// HTTP Websocket Wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal partial class WebSocket<T> { /// <summary> /// The <see cref="MessageWebSocket" /> that is being wrapped. /// </summary> private MessageWebSocket websocket = null; /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> public WebSocket(IDevicePortalConnection connection) { this.deviceConnection = connection; this.IsListeningForMessages = false; } /// <summary> /// Opens a connection to the specified websocket API and starts listening for messages. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of opening a connection to the websocket.</returns> private async Task StartListeningForMessagesInternal( Uri endpoint) { this.websocket = new MessageWebSocket(); this.websocket.Control.MessageType = SocketMessageType.Utf8; this.websocket.MessageReceived += this.MessageReceived; this.websocket.Closed += (senderSocket, args) => { if (this.websocket != null) { this.websocket.Dispose(); this.websocket = null; this.IsListeningForMessages = false; } }; PasswordCredential cred = new PasswordCredential(); cred.UserName = this.deviceConnection.Credentials.UserName; cred.Password = this.deviceConnection.Credentials.Password; this.websocket.Control.ServerCredential = cred; this.websocket.SetRequestHeader("Origin", this.deviceConnection.Connection.AbsoluteUri); // Do not wait on receiving messages. Task connectTask = this.ConnectAsync(endpoint); } /// <summary> /// Opens a connection to the specified websocket API. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of opening a connection to the websocket.</returns> private async Task ConnectAsync( Uri endpoint) { bool connecting = true; try { await this.websocket.ConnectAsync(endpoint); connecting = false; this.IsListeningForMessages = true; } catch (Exception) { // Error happened during connect operation. if (connecting && this.websocket != null) { this.websocket.Dispose(); this.websocket = null; this.IsListeningForMessages = false; } } } /// <summary> /// Converts received stream to a parsed message and passes it to the WebSocketMessageReceived handler. /// </summary> /// <param name="sender">The <see cref="MessageWebSocket" /> that sent the message.</param> /// <param name="args">The message from the web socket.</param> private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) { using (IInputStream inputStream = args.GetDataStream()) { Stream stream = new MemoryStream(); Task copyTask = inputStream.AsStreamForRead().CopyToAsync(stream); copyTask.Wait(); // Ensure we return with the stream pointed at the origin. stream.Position = 0; this.ConvertStreamToMessage(stream); } } /// <summary> /// Closes the connection to the websocket. /// </summary> /// <returns>The task of closing the websocket connection.</returns> private async Task StopListeningForMessagesInternal() { if (this.IsListeningForMessages) { if (this.websocket != null) { this.websocket.Close(1000, "Closed due to user request."); this.websocket = null; this.IsListeningForMessages = false; } } } } } Loading
WindowsDevicePortalWrapper/UnitTestProject/UnitTestProject.csproj +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ <Compile Include="Core\OsInformationTests.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TestHelpers.cs" /> <Compile Include="WDPMockImplementations\WebSocket.cs" /> <Compile Include="Xbox\UserManagementTests.cs" /> <Compile Include="Xbox\XboxAppDeploymentTests.cs" /> <Compile Include="Xbox\XboxSettingsTests.cs" /> Loading
WindowsDevicePortalWrapper/UnitTestProject/WDPMockImplementations/WebSocket.cs 0 → 100644 +50 −0 Original line number Diff line number Diff line //---------------------------------------------------------------------------------------------- // <copyright file="WebSocket.cs" company="Microsoft Corporation"> // Licensed under the MIT License. See LICENSE.TXT in the project root license information. // </copyright> //---------------------------------------------------------------------------------------------- using System; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace Microsoft.Tools.WindowsDevicePortal { /// <summary> /// MOCK implementation of HTTP web socket wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal partial class WebSocket<T> { /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> /// <param name="serverCertificateValidationHandler">Server certificate handler.</param> public WebSocket(IDevicePortalConnection connection, Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler) { throw new NotImplementedException(); } /// <summary> /// Stops listneing for messages from the websocket and closes the connection to the websocket. /// </summary> /// <returns>The task of closing the websocket connection.</returns> private async Task StopListeningForMessagesInternal() { throw new NotImplementedException(); } /// <summary> /// Connects to the websocket and starts listening for messages from the websocket. /// Once they are received they are parsed and the WebSocketMessageReceived event is raised. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of listening for messages from the websocket.</returns> private async Task StartListeningForMessagesInternal(Uri endpoint) { throw new NotImplementedException(); } } }
WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/Core/PerformanceData.cs +14 −19 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ namespace Microsoft.Tools.WindowsDevicePortal /// </summary> private static readonly string SystemPerfApi = "api/resourcemanager/systemperf"; #if !WINDOWS_UWP /// <summary> /// Web socket to get running processes. /// </summary> Loading Loading @@ -55,8 +54,6 @@ namespace Microsoft.Tools.WindowsDevicePortal set; } #endif // !WINDOWS_UWP /// <summary> /// Gets the collection of processes running on the device. /// </summary> Loading @@ -66,7 +63,6 @@ namespace Microsoft.Tools.WindowsDevicePortal return await this.Get<RunningProcesses>(RunningProcessApi); } #if !WINDOWS_UWP /// <summary> /// Starts listening for the running processes on the device with them being returned via the RunningProcessesMessageReceived event handler. /// </summary> Loading @@ -75,7 +71,11 @@ namespace Microsoft.Tools.WindowsDevicePortal { if (this.deviceProcessesWebSocket == null) { #if WINDOWS_UWP this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(this.deviceConnection); #else this.deviceProcessesWebSocket = new WebSocket<RunningProcesses>(this.deviceConnection, this.ServerCertificateValidation); #endif this.deviceProcessesWebSocket.WebSocketMessageReceived += this.RunningProcessesReceivedHandler; } Loading @@ -87,10 +87,7 @@ namespace Microsoft.Tools.WindowsDevicePortal } } await this.deviceProcessesWebSocket.OpenConnection(RunningProcessApi); // Do not await on the actual listening. Task listenTask = this.deviceProcessesWebSocket.StartListeningForMessages(); await this.deviceProcessesWebSocket.StartListeningForMessages(RunningProcessApi); } /// <summary> Loading @@ -104,9 +101,8 @@ namespace Microsoft.Tools.WindowsDevicePortal return; } await this.deviceProcessesWebSocket.CloseConnection(); await this.deviceProcessesWebSocket.StopListeningForMessages(); } #endif // !WINDOWS_UWP /// <summary> /// Gets system performance information for the device. Loading @@ -117,7 +113,6 @@ namespace Microsoft.Tools.WindowsDevicePortal return await this.Get<SystemPerformanceInformation>(SystemPerfApi); } #if !WINDOWS_UWP /// <summary> /// Starts listening for the system performance information for the device with it being returned via the SystemPerfMessageReceived event handler. /// </summary> Loading @@ -126,7 +121,11 @@ namespace Microsoft.Tools.WindowsDevicePortal { if (this.systemPerfWebSocket == null) { #if WINDOWS_UWP this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(this.deviceConnection); #else this.systemPerfWebSocket = new WebSocket<SystemPerformanceInformation>(this.deviceConnection, this.ServerCertificateValidation); #endif this.systemPerfWebSocket.WebSocketMessageReceived += this.SystemPerfMessageReceived; } Loading @@ -138,10 +137,7 @@ namespace Microsoft.Tools.WindowsDevicePortal } } await this.systemPerfWebSocket.OpenConnection(SystemPerfApi); // Do not await on the actual listening. Task listenTask = this.systemPerfWebSocket.StartListeningForMessages(); await this.systemPerfWebSocket.StartListeningForMessages(SystemPerfApi); } /// <summary> Loading @@ -155,7 +151,7 @@ namespace Microsoft.Tools.WindowsDevicePortal return; } await this.systemPerfWebSocket.CloseConnection(); await this.systemPerfWebSocket.StopListeningForMessages(); } /// <summary> Loading Loading @@ -192,7 +188,6 @@ namespace Microsoft.Tools.WindowsDevicePortal } } #endif // !WINDOWS_UWP #region Device contract /// <summary> Loading
WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.Shared/HttpRest/WebSocket.cs +31 −153 Original line number Diff line number Diff line Loading @@ -4,17 +4,9 @@ // </copyright> //---------------------------------------------------------------------------------------------- // TODO: enable this once the rest of the code is UWP friendly #if !WINDOWS_UWP using System; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Net.WebSockets; using System.Runtime.Serialization.Json; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Tools.WindowsDevicePortal Loading @@ -23,50 +15,13 @@ namespace Microsoft.Tools.WindowsDevicePortal /// HTTP Websocket Wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal class WebSocket<T> internal partial class WebSocket<T> { /// <summary> /// The hresult for the connection being reset by the peer. /// </summary> private const int WSAECONNRESET = 0x2746; /// <summary> /// The maximum number of bytes that can be received in a single chunk. /// </summary> private static readonly uint MaxChunkSizeInBytes = 1024; /// <summary> /// The device that the <see cref="WebSocket{T}" /> is connected to. /// </summary> private IDevicePortalConnection deviceConnection; /// <summary> /// The <see cref="ClientWebSocket" /> that is being wrapped. /// </summary> private ClientWebSocket websocket = new ClientWebSocket(); /// <summary> /// <see cref="ManualResetEvent" /> used to indicate that the <see cref="WebSocket{T}" /> has stopped receiving messages. /// </summary> private ManualResetEvent stoppedReceivingMessages = new ManualResetEvent(false); /// <summary> /// The handler used to validate server certificates. /// </summary> private Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler; /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> /// <param name="serverCertificateValidationHandler">Server certificate handler.</param> public WebSocket(IDevicePortalConnection connection, Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> serverCertificateValidationHandler) { this.deviceConnection = connection; this.IsListeningForMessages = false; this.serverCertificateValidationHandler = serverCertificateValidationHandler; } /// <summary> /// Gets a value indicating whether the web socket is listening for messages. /// </summary> Loading @@ -86,105 +41,50 @@ namespace Microsoft.Tools.WindowsDevicePortal } /// <summary> /// Opens a connection to the specified websocket API with the provided payload. /// </summary> /// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param> /// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param> /// <returns>The task of opening a connection to the websocket.</returns> internal async Task OpenConnection( string apiPath, string payload = null) { Uri uri = Utilities.BuildEndpoint( this.deviceConnection.WebSocketConnection, apiPath, payload); this.websocket.Options.UseDefaultCredentials = false; this.websocket.Options.Credentials = this.deviceConnection.Credentials; this.websocket.Options.SetRequestHeader("Origin", this.deviceConnection.Connection.AbsoluteUri); // There is no way to set a ServerCertificateValidationCallback for a single web socket, hence the workaround. ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors) { return this.serverCertificateValidationHandler(sender, cert, chain, policyErrors); }; await this.websocket.ConnectAsync(uri, CancellationToken.None); } /// <summary> /// Closes the connection to the websocket. /// Closes the connection to the websocket and stop listening for messages. /// </summary> /// <returns>The task of closing the websocket connection.</returns> internal async Task CloseConnection() { if (this.websocket.State != WebSocketState.Closed) internal async Task StopListeningForMessages() { await this.websocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); // Wait for web socket to no longer be receiving messages. if (this.IsListeningForMessages) { this.stoppedReceivingMessages.WaitOne(); this.stoppedReceivingMessages.Reset(); } // Reset websocket as WDP will abort the connection once it receives the close message. this.websocket = new ClientWebSocket(); await this.StopListeningForMessagesInternal(); } } /// <summary> /// Starts listening for messages from the websocket. Once they are received they are parsed and the WebSocketMessageReceived event is raised. /// </summary> /// <param name="apiPath">The relative portion of the uri path that specifies the API to call.</param> /// <param name="payload">The query string portion of the uri path that provides the parameterized data.</param> /// <returns>The task of listening for messages from the websocket.</returns> internal async Task StartListeningForMessages() { if (this.websocket.State != WebSocketState.Open || this.IsListeningForMessages) { return; } this.IsListeningForMessages = true; try { ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[MaxChunkSizeInBytes]); // Once close message is sent do not try to get any more messages as WDP will abort the web socket connection. while (this.websocket.State == WebSocketState.Open) { // Receive single message in chunks. using (var ms = new MemoryStream()) { WebSocketReceiveResult result; do internal async Task StartListeningForMessages( string apiPath, string payload = null) { result = await this.websocket.ReceiveAsync(buffer, CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) if (!this.IsListeningForMessages) { await this.websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); return; } Uri uri = Utilities.BuildEndpoint( this.deviceConnection.WebSocketConnection, apiPath, payload); if (result.Count > MaxChunkSizeInBytes) { throw new InvalidOperationException("Buffer not large enough"); await this.StartListeningForMessagesInternal(uri); } ms.Write(buffer.Array, buffer.Offset, result.Count); } while (!result.EndOfMessage); ms.Seek(0, SeekOrigin.Begin); if (result.MessageType == WebSocketMessageType.Text) /// <summary> /// Converts received stream to a parsed message and passes it to the WebSocketMessageReceived handler. /// </summary> /// <param name="stream">The received stream.</param> private void ConvertStreamToMessage(Stream stream) { if (stream != null && stream.Length != 0) { using (stream) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); T message = (T)serializer.ReadObject(ms); T message = (T)serializer.ReadObject(stream); this.WebSocketMessageReceived?.Invoke( this, Loading @@ -193,26 +93,4 @@ namespace Microsoft.Tools.WindowsDevicePortal } } } catch (WebSocketException e) { // If WDP aborted the web socket connection ignore the exception. SocketException socketException = e.InnerException?.InnerException as SocketException; if (socketException != null) { if (socketException.NativeErrorCode == WSAECONNRESET) { return; } } throw; } finally { this.stoppedReceivingMessages.Set(); this.IsListeningForMessages = false; } } } } No newline at end of file #endif // !WINDOWS_UWP No newline at end of file
WindowsDevicePortalWrapper/WindowsDevicePortalWrapper.UniversalWindows/HttpRest/WebSocket.cs 0 → 100644 +137 −0 Original line number Diff line number Diff line //---------------------------------------------------------------------------------------------- // <copyright file="WebSocket.cs" company="Microsoft Corporation"> // Licensed under the MIT License. See LICENSE.TXT in the project root license information. // </copyright> //---------------------------------------------------------------------------------------------- using System; using System.IO; using System.Threading.Tasks; using Windows.Networking.Sockets; using Windows.Security.Credentials; using Windows.Storage.Streams; namespace Microsoft.Tools.WindowsDevicePortal { /// <summary> /// HTTP Websocket Wrapper /// </summary> /// <typeparam name="T">Return type for the websocket messages.</typeparam> internal partial class WebSocket<T> { /// <summary> /// The <see cref="MessageWebSocket" /> that is being wrapped. /// </summary> private MessageWebSocket websocket = null; /// <summary> /// Initializes a new instance of the <see cref="WebSocket{T}" /> class. /// </summary> /// <param name="connection">Implementation of a connection object.</param> public WebSocket(IDevicePortalConnection connection) { this.deviceConnection = connection; this.IsListeningForMessages = false; } /// <summary> /// Opens a connection to the specified websocket API and starts listening for messages. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of opening a connection to the websocket.</returns> private async Task StartListeningForMessagesInternal( Uri endpoint) { this.websocket = new MessageWebSocket(); this.websocket.Control.MessageType = SocketMessageType.Utf8; this.websocket.MessageReceived += this.MessageReceived; this.websocket.Closed += (senderSocket, args) => { if (this.websocket != null) { this.websocket.Dispose(); this.websocket = null; this.IsListeningForMessages = false; } }; PasswordCredential cred = new PasswordCredential(); cred.UserName = this.deviceConnection.Credentials.UserName; cred.Password = this.deviceConnection.Credentials.Password; this.websocket.Control.ServerCredential = cred; this.websocket.SetRequestHeader("Origin", this.deviceConnection.Connection.AbsoluteUri); // Do not wait on receiving messages. Task connectTask = this.ConnectAsync(endpoint); } /// <summary> /// Opens a connection to the specified websocket API. /// </summary> /// <param name="endpoint">The uri that the weboscket should connect to</param> /// <returns>The task of opening a connection to the websocket.</returns> private async Task ConnectAsync( Uri endpoint) { bool connecting = true; try { await this.websocket.ConnectAsync(endpoint); connecting = false; this.IsListeningForMessages = true; } catch (Exception) { // Error happened during connect operation. if (connecting && this.websocket != null) { this.websocket.Dispose(); this.websocket = null; this.IsListeningForMessages = false; } } } /// <summary> /// Converts received stream to a parsed message and passes it to the WebSocketMessageReceived handler. /// </summary> /// <param name="sender">The <see cref="MessageWebSocket" /> that sent the message.</param> /// <param name="args">The message from the web socket.</param> private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) { using (IInputStream inputStream = args.GetDataStream()) { Stream stream = new MemoryStream(); Task copyTask = inputStream.AsStreamForRead().CopyToAsync(stream); copyTask.Wait(); // Ensure we return with the stream pointed at the origin. stream.Position = 0; this.ConvertStreamToMessage(stream); } } /// <summary> /// Closes the connection to the websocket. /// </summary> /// <returns>The task of closing the websocket connection.</returns> private async Task StopListeningForMessagesInternal() { if (this.IsListeningForMessages) { if (this.websocket != null) { this.websocket.Close(1000, "Closed due to user request."); this.websocket = null; this.IsListeningForMessages = false; } } } } }