| /****************************************************************************  | 
| **  | 
| ** Copyright (C) 2018 The Qt Company Ltd.  | 
| ** Contact: https://www.qt.io/licensing/  | 
| **  | 
| ** This file is part of the Qt VS Tools.  | 
| **  | 
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$  | 
| ** Commercial License Usage  | 
| ** Licensees holding valid commercial Qt licenses may use this file in  | 
| ** accordance with the commercial license agreement provided with the  | 
| ** Software or, alternatively, in accordance with the terms contained in  | 
| ** a written agreement between you and The Qt Company. For licensing terms  | 
| ** and conditions see https://www.qt.io/terms-conditions. For further  | 
| ** information use the contact form at https://www.qt.io/contact-us.  | 
| **  | 
| ** GNU General Public License Usage  | 
| ** Alternatively, this file may be used under the terms of the GNU  | 
| ** General Public License version 3 as published by the Free Software  | 
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT  | 
| ** included in the packaging of this file. Please review the following  | 
| ** information to ensure the GNU General Public License requirements will  | 
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html.  | 
| **  | 
| ** $QT_END_LICENSE$  | 
| **  | 
| ****************************************************************************/  | 
|   | 
| using System.Collections.Concurrent;  | 
| using System.Collections.Generic;  | 
| using System.Linq;  | 
| using System.Threading;  | 
| using System.Threading.Tasks;  | 
|   | 
| namespace QtVsTools.Qml.Debug.V4  | 
| {  | 
|     interface IMessageEventSink  | 
|     {  | 
|         bool QueryRuntimeFrozen();  | 
|   | 
|         void NotifyStateTransition(  | 
|             DebugClient client,  | 
|             DebugClientState oldState,  | 
|             DebugClientState newState);  | 
|   | 
|         void NotifyRequestResponded(Request req);  | 
|         void NotifyEvent(Event evt);  | 
|         void NotifyMessage(Message msg);  | 
|     }  | 
|   | 
|     class ProtocolDriver : Finalizable, IConnectionEventSink  | 
|     {  | 
|         IMessageEventSink sink;  | 
|         DebugClient client;  | 
|         int nextRequestSeq = 0;  | 
|         Dictionary<int, PendingRequest> pendingRequests = new Dictionary<int, PendingRequest>();  | 
|         Task eventHandlingThread;  | 
|         EventWaitHandle eventReceived = new EventWaitHandle(false, EventResetMode.AutoReset);  | 
|         ConcurrentQueue<Event> eventQueue = new ConcurrentQueue<Event>();  | 
|   | 
|         public uint? ThreadId { get { return client.ThreadId; } }  | 
|   | 
|         public static ProtocolDriver Create(IMessageEventSink sink)  | 
|         {  | 
|             var _this = new ProtocolDriver();  | 
|             return _this.Initialize(sink) ? _this : null;  | 
|         }  | 
|   | 
|         private ProtocolDriver()  | 
|         { }  | 
|   | 
|         private bool Initialize(IMessageEventSink sink)  | 
|         {  | 
|             this.sink = sink;  | 
|             eventHandlingThread = Task.Run(() => EventHandlingThread());  | 
|             client = DebugClient.Create(this);  | 
|             if (client == null)  | 
|                 return false;  | 
|   | 
|             return true;  | 
|         }  | 
|   | 
|         protected override void DisposeManaged()  | 
|         {  | 
|             foreach (var req in ThreadSafe(() => pendingRequests.Values.ToList()))  | 
|                 req.Dispose();  | 
|   | 
|             ThreadSafe(() => pendingRequests.Clear());  | 
|             client.Dispose();  | 
|         }  | 
|   | 
|         protected override void DisposeFinally()  | 
|         {  | 
|             eventReceived.Set();  | 
|             eventHandlingThread.Wait();  | 
|             eventReceived.Dispose();  | 
|         }  | 
|   | 
|         private void EventHandlingThread()  | 
|         {  | 
|             while (!Disposing) {  | 
|                 eventReceived.WaitOne();  | 
|                 Event evt;  | 
|                 while (!Disposing && eventQueue.TryDequeue(out evt))  | 
|                     sink.NotifyEvent(evt);  | 
|             }  | 
|         }  | 
|   | 
|         public DebugClientState ConnectionState  | 
|         {  | 
|             get { return client.State; }  | 
|         }  | 
|   | 
|         bool IConnectionEventSink.QueryRuntimeFrozen()  | 
|         {  | 
|             return sink.QueryRuntimeFrozen();  | 
|         }  | 
|   | 
|         public EventWaitHandle Connect(string hostName, ushort hostPort)  | 
|         {  | 
|             return client.Connect(hostName, hostPort);  | 
|         }  | 
|   | 
|         public EventWaitHandle StartLocalServer(string fileName)  | 
|         {  | 
|             return client.StartLocalServer(fileName);  | 
|         }  | 
|   | 
|         public bool Disconnect()  | 
|         {  | 
|             return client.Disconnect();  | 
|         }  | 
|   | 
|         public bool SendMessage(Message message)  | 
|         {  | 
|             if (client.State != DebugClientState.Connected)  | 
|                 return false;  | 
|   | 
|             var messageParams = message.Serialize();  | 
|   | 
|             System.Diagnostics.Debug  | 
|                 .Assert(messageParams != null, "Empty message data");  | 
|   | 
|             return client.SendMessage(message.Type, messageParams);  | 
|         }  | 
|   | 
|         public PendingRequest SendRequest(Request request)  | 
|         {  | 
|             ThreadSafe(() => request.SequenceNum = nextRequestSeq++);  | 
|   | 
|             var pendingRequest = new PendingRequest(request);  | 
|             ThreadSafe(() => pendingRequests[request.SequenceNum] = pendingRequest);  | 
|   | 
|             if (!SendMessage(request as Message)) {  | 
|                 ThreadSafe(() => pendingRequests.Remove(request.SequenceNum));  | 
|                 pendingRequest.Dispose();  | 
|                 return new PendingRequest();  | 
|             }  | 
|   | 
|             return pendingRequest;  | 
|         }  | 
|   | 
|         void IConnectionEventSink.NotifyStateTransition(  | 
|             DebugClient client,  | 
|             DebugClientState oldState,  | 
|             DebugClientState newState)  | 
|         {  | 
|             sink.NotifyStateTransition(client, oldState, newState);  | 
|         }  | 
|   | 
|         void IConnectionEventSink.NotifyMessageReceived(  | 
|             DebugClient client,  | 
|             string messageType,  | 
|             byte[] messageParams)  | 
|         {  | 
|             if (client != this.client || Disposing)  | 
|                 return;  | 
|   | 
|             var msg = Message.Deserialize(messageType, messageParams);  | 
|             if (msg == null)  | 
|                 return;  | 
|   | 
|             if (msg is Response) {  | 
|                 var msgResponse = msg as Response;  | 
|                 EnterCriticalSection();  | 
|                 PendingRequest pendingRequest = null;  | 
|                 if (pendingRequests.TryGetValue(msgResponse.RequestSeq, out pendingRequest)) {  | 
|                     pendingRequests.Remove(msgResponse.RequestSeq);  | 
|                     LeaveCriticalSection();  | 
|                     pendingRequest.SetResponse(msgResponse);  | 
|                     sink.NotifyRequestResponded(pendingRequest.Request);  | 
|                     pendingRequest.Dispose();  | 
|                 } else {  | 
|                     LeaveCriticalSection();  | 
|                     sink.NotifyMessage(msg);  | 
|                 }  | 
|   | 
|             } else if (msg is Event) {  | 
|                 var msgEvent = msg as Event;  | 
|                 eventQueue.Enqueue(msgEvent);  | 
|                 eventReceived.Set();  | 
|   | 
|             } else {  | 
|                 sink.NotifyMessage(msg);  | 
|             }  | 
|         }  | 
|   | 
|         #region //////////////////// PendingRequest ///////////////////////////////////////////////  | 
|   | 
|         public class PendingRequest : Finalizable  | 
|         {  | 
|             public Request Request { get; private set; }  | 
|             EventWaitHandle responded;  | 
|   | 
|             public PendingRequest()  | 
|             {  | 
|                 Request = null;  | 
|                 responded = null;  | 
|             }  | 
|   | 
|             public PendingRequest(Request req)  | 
|             {  | 
|                 responded = new EventWaitHandle(false, EventResetMode.ManualReset);  | 
|                 Request = req;  | 
|             }  | 
|   | 
|             public bool RequestSent  | 
|             {  | 
|                 get { return Request != null; }  | 
|             }  | 
|   | 
|             public bool ResponseReceived  | 
|             {  | 
|                 get { return Request != null && Request.Response != null; }  | 
|             }  | 
|   | 
|             public Response WaitForResponse()  | 
|             {  | 
|                 if (Request == null)  | 
|                     return null;  | 
|                 if (responded != null)  | 
|                     responded.WaitOne();  | 
|                 return Request.Response;  | 
|             }  | 
|   | 
|             protected override void DisposeManaged()  | 
|             {  | 
|                 if (Request == null)  | 
|                     return;  | 
|   | 
|                 if (responded != null)  | 
|                     responded.Dispose();  | 
|             }  | 
|   | 
|             public void SetResponse(Response res)  | 
|             {  | 
|                 if (Request == null || Disposing)  | 
|                     return;  | 
|   | 
|                 Request.Response = res;  | 
|                 if (responded != null)  | 
|                     responded.Set();  | 
|             }  | 
|         }  | 
|   | 
|         #endregion //////////////////// PendingRequest ////////////////////////////////////////////  | 
|   | 
|     }  | 
| }  |