diff --git a/SafeExamBrowser.Client.UnitTests/Communication/ClientHostTests.cs b/SafeExamBrowser.Client.UnitTests/Communication/ClientHostTests.cs index 4d374bc0..8cf41eae 100644 --- a/SafeExamBrowser.Client.UnitTests/Communication/ClientHostTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Communication/ClientHostTests.cs @@ -105,7 +105,12 @@ namespace SafeExamBrowser.Client.UnitTests.Communication sut.AuthenticationToken = token; var connectionResponse = sut.Connect(token); - var response = sut.Disconnect(new DisconnectionMessage { CommunicationToken = connectionResponse.CommunicationToken.Value }); + var message = new DisconnectionMessage + { + CommunicationToken = connectionResponse.CommunicationToken.Value, + Interlocutor = Interlocutor.Runtime + }; + var response = sut.Disconnect(message); Assert.IsNotNull(response); Assert.IsTrue(response.ConnectionTerminated); @@ -118,8 +123,13 @@ namespace SafeExamBrowser.Client.UnitTests.Communication { var token = sut.AuthenticationToken = Guid.NewGuid(); var response = sut.Connect(token); + var message = new DisconnectionMessage + { + CommunicationToken = response.CommunicationToken.Value, + Interlocutor = Interlocutor.Runtime + }; - sut.Disconnect(new DisconnectionMessage { CommunicationToken = response.CommunicationToken.Value }); + sut.Disconnect(message); sut.AuthenticationToken = token = Guid.NewGuid(); response = sut.Connect(token); @@ -274,7 +284,7 @@ namespace SafeExamBrowser.Client.UnitTests.Communication sut.Send(new PasswordRequestMessage(default(PasswordRequestPurpose), Guid.Empty) { CommunicationToken = token }); sut.Send(new ReconfigurationDeniedMessage("") { CommunicationToken = token }); sut.Send(new SimpleMessage(SimpleMessagePurport.Shutdown) { CommunicationToken = token }); - sut.Disconnect(new DisconnectionMessage { CommunicationToken = token }); + sut.Disconnect(new DisconnectionMessage { CommunicationToken = token, Interlocutor = Interlocutor.Runtime }); } private class TestMessage : Message { }; diff --git a/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs index abaa62fb..1d5af574 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/RuntimeConnectionOperationTests.cs @@ -65,12 +65,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations { runtime.Setup(r => r.Connect(It.IsAny(), It.IsAny())).Returns(true); runtime.Setup(r => r.Disconnect()).Returns(true); + runtime.SetupGet(r => r.IsConnected).Returns(true); sut.Perform(); var result = sut.Revert(); runtime.Verify(r => r.Connect(It.IsAny(), It.IsAny()), Times.Once); runtime.Verify(r => r.Disconnect(), Times.Once); + runtime.VerifyGet(r => r.IsConnected, Times.Once); runtime.VerifyNoOtherCalls(); Assert.AreEqual(OperationResult.Success, result); @@ -81,12 +83,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations { runtime.Setup(r => r.Connect(It.IsAny(), It.IsAny())).Returns(true); runtime.Setup(r => r.Disconnect()).Returns(false); + runtime.SetupGet(r => r.IsConnected).Returns(true); sut.Perform(); var result = sut.Revert(); runtime.Verify(r => r.Connect(It.IsAny(), It.IsAny()), Times.Once); runtime.Verify(r => r.Disconnect(), Times.Once); + runtime.VerifyGet(r => r.IsConnected, Times.Once); runtime.VerifyNoOtherCalls(); Assert.AreEqual(OperationResult.Failed, result); @@ -97,6 +101,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations { var result = sut.Revert(); + runtime.VerifyGet(r => r.IsConnected, Times.Once); runtime.VerifyNoOtherCalls(); Assert.AreEqual(OperationResult.Success, result); diff --git a/SafeExamBrowser.Client/Communication/ClientHost.cs b/SafeExamBrowser.Client/Communication/ClientHost.cs index 3f3c9b35..4a62fe5f 100644 --- a/SafeExamBrowser.Client/Communication/ClientHost.cs +++ b/SafeExamBrowser.Client/Communication/ClientHost.cs @@ -8,6 +8,7 @@ using System; using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; @@ -53,10 +54,13 @@ namespace SafeExamBrowser.Client.Communication return accepted; } - protected override void OnDisconnect() + protected override void OnDisconnect(Interlocutor interlocutor) { - RuntimeDisconnected?.Invoke(); - IsConnected = false; + if (interlocutor == Interlocutor.Runtime) + { + RuntimeDisconnected?.Invoke(); + IsConnected = false; + } } protected override Response OnReceive(Message message) diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index cfc0e21d..f8f6c06c 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -20,6 +20,7 @@ using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Configuration.Cryptography; using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Client; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; @@ -97,7 +98,7 @@ namespace SafeExamBrowser.Client powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text); processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods); uiFactory = BuildUserInterfaceFactory(); - runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy))); + runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy)), Interlocutor.Client); taskbar = BuildTaskbar(); terminationActivator = new TerminationActivator(new ModuleLogger(logger, nameof(TerminationActivator))); windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods); diff --git a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs index ff187282..dc88a3f3 100644 --- a/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs +++ b/SafeExamBrowser.Client/Operations/RuntimeConnectionOperation.cs @@ -17,7 +17,6 @@ namespace SafeExamBrowser.Client.Operations { internal class RuntimeConnectionOperation : IOperation { - private bool connected; private ILogger logger; private IRuntimeProxy runtime; private Guid token; @@ -37,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Initializing runtime connection..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection); - connected = runtime.Connect(token); + var connected = runtime.Connect(token); if (connected) { @@ -56,7 +55,7 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Closing runtime connection..."); StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection); - if (connected) + if (runtime.IsConnected) { var success = runtime.Disconnect(); diff --git a/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostStub.cs b/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostStub.cs index 641374d7..909dade4 100644 --- a/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostStub.cs +++ b/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostStub.cs @@ -7,7 +7,9 @@ */ using System; +using System.Linq; using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Logging; @@ -17,7 +19,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts internal class BaseHostStub : BaseHost { public Func OnConnectStub { get; set; } - public Action OnDisconnectStub { get; set; } + public Action OnDisconnectStub { get; set; } public Func OnReceiveStub { get; set; } public Func OnReceiveSimpleMessageStub { get; set; } @@ -27,7 +29,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts public Guid? GetCommunicationToken() { - return CommunicationToken; + return CommunicationToken.Any() ? CommunicationToken.First() : default(Guid?); } protected override bool OnConnect(Guid? token) @@ -35,9 +37,9 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts return OnConnectStub?.Invoke(token) == true; } - protected override void OnDisconnect() + protected override void OnDisconnect(Interlocutor interlocutor) { - OnDisconnectStub?.Invoke(); + OnDisconnectStub?.Invoke(interlocutor); } protected override Response OnReceive(Message message) diff --git a/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostTests.cs b/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostTests.cs index 90eafcf5..dc53285b 100644 --- a/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostTests.cs +++ b/SafeExamBrowser.Communication.UnitTests/Hosts/BaseHostTests.cs @@ -154,17 +154,19 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts [TestMethod] public void MustCorrectlyHandleDisconnectionRequest() { - var message = new DisconnectionMessage(); + var message = new DisconnectionMessage { Interlocutor = Interlocutor.Runtime }; var disconnected = false; + var interlocutor = Interlocutor.Unknown; sut.OnConnectStub = (t) => { return true; }; - sut.OnDisconnectStub = () => disconnected = true; + sut.OnDisconnectStub = (i) => { disconnected = true; interlocutor = i; }; sut.Connect(); message.CommunicationToken = sut.GetCommunicationToken().Value; var response = sut.Disconnect(message); + Assert.AreEqual(message.Interlocutor, interlocutor); Assert.IsTrue(disconnected); Assert.IsTrue(response.ConnectionTerminated); Assert.IsNull(sut.GetCommunicationToken()); @@ -176,7 +178,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts var disconnected = false; sut.OnConnectStub = (t) => { return true; }; - sut.OnDisconnectStub = () => disconnected = true; + sut.OnDisconnectStub = (i) => disconnected = true; sut.Connect(); var response = sut.Disconnect(new DisconnectionMessage()); diff --git a/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyImpl.cs b/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyImpl.cs index ef20ffa9..ea1796c1 100644 --- a/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyImpl.cs +++ b/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyImpl.cs @@ -7,16 +7,17 @@ */ using System; +using SafeExamBrowser.Communication.Proxies; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Communication.Proxies; namespace SafeExamBrowser.Communication.UnitTests.Proxies { internal class BaseProxyImpl : BaseProxy { - public BaseProxyImpl(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) + public BaseProxyImpl(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner) { } diff --git a/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyTests.cs b/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyTests.cs index 9515b6b1..b8b6b268 100644 --- a/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyTests.cs +++ b/SafeExamBrowser.Communication.UnitTests/Proxies/BaseProxyTests.cs @@ -10,6 +10,7 @@ using System; using System.ServiceModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; @@ -29,7 +30,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies proxyObjectFactory = new Mock(); logger = new Mock(); - sut = new BaseProxyImpl("net.pipe://some/address/here", proxyObjectFactory.Object, logger.Object); + sut = new BaseProxyImpl("net.pipe://some/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor)); } [TestMethod] diff --git a/SafeExamBrowser.Communication.UnitTests/Proxies/ClientProxyTests.cs b/SafeExamBrowser.Communication.UnitTests/Proxies/ClientProxyTests.cs index e88eeccf..93a4a132 100644 --- a/SafeExamBrowser.Communication.UnitTests/Proxies/ClientProxyTests.cs +++ b/SafeExamBrowser.Communication.UnitTests/Proxies/ClientProxyTests.cs @@ -11,6 +11,7 @@ using System.ServiceModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Communication.Proxies; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; @@ -43,7 +44,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); - sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); + sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor)); sut.Connect(Guid.NewGuid()); } diff --git a/SafeExamBrowser.Communication.UnitTests/Proxies/RuntimeProxyTests.cs b/SafeExamBrowser.Communication.UnitTests/Proxies/RuntimeProxyTests.cs index 6ddd2fa4..ad1fdf71 100644 --- a/SafeExamBrowser.Communication.UnitTests/Proxies/RuntimeProxyTests.cs +++ b/SafeExamBrowser.Communication.UnitTests/Proxies/RuntimeProxyTests.cs @@ -11,6 +11,7 @@ using System.ServiceModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Communication.Proxies; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; @@ -44,7 +45,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); - sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); + sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor)); sut.Connect(Guid.NewGuid()); } diff --git a/SafeExamBrowser.Communication.UnitTests/Proxies/ServiceProxyTests.cs b/SafeExamBrowser.Communication.UnitTests/Proxies/ServiceProxyTests.cs index e8ade396..80a40672 100644 --- a/SafeExamBrowser.Communication.UnitTests/Proxies/ServiceProxyTests.cs +++ b/SafeExamBrowser.Communication.UnitTests/Proxies/ServiceProxyTests.cs @@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Communication.Proxies; +using SafeExamBrowser.Contracts.Communication; namespace SafeExamBrowser.Communication.UnitTests.Proxies { @@ -42,7 +43,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); - sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); + sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor)); } [TestMethod] diff --git a/SafeExamBrowser.Communication/Hosts/BaseHost.cs b/SafeExamBrowser.Communication/Hosts/BaseHost.cs index 6c83aca7..ed4462fb 100644 --- a/SafeExamBrowser.Communication/Hosts/BaseHost.cs +++ b/SafeExamBrowser.Communication/Hosts/BaseHost.cs @@ -7,6 +7,7 @@ */ using System; +using System.Collections.Generic; using System.ServiceModel; using System.Threading; using SafeExamBrowser.Contracts.Communication; @@ -30,7 +31,7 @@ namespace SafeExamBrowser.Communication.Hosts private Thread hostThread; private int timeout_ms; - protected Guid? CommunicationToken { get; private set; } + protected IList CommunicationToken { get; private set; } protected ILogger Logger { get; private set; } public bool IsRunning @@ -47,13 +48,14 @@ namespace SafeExamBrowser.Communication.Hosts public BaseHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) { this.address = address; + this.CommunicationToken = new List(); this.factory = factory; this.Logger = logger; this.timeout_ms = timeout_ms; } protected abstract bool OnConnect(Guid? token); - protected abstract void OnDisconnect(); + protected abstract void OnDisconnect(Interlocutor interlocutor); protected abstract Response OnReceive(Message message); protected abstract Response OnReceive(SimpleMessagePurport message); @@ -61,15 +63,19 @@ namespace SafeExamBrowser.Communication.Hosts { lock (@lock) { - Logger.Debug($"Received connection request with authentication token '{token}'."); + Logger.Debug($"Received connection request {(token.HasValue ? $"with authentication token '{token}'" : "without authentication token")}."); var response = new ConnectionResponse(); var connected = OnConnect(token); if (connected) { - response.CommunicationToken = CommunicationToken = Guid.NewGuid(); + var communicationToken = Guid.NewGuid(); + + response.CommunicationToken = communicationToken; response.ConnectionEstablished = true; + + CommunicationToken.Add(communicationToken); } Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request."); @@ -88,10 +94,10 @@ namespace SafeExamBrowser.Communication.Hosts if (IsAuthorized(message?.CommunicationToken)) { - OnDisconnect(); + OnDisconnect(message.Interlocutor); - CommunicationToken = null; response.ConnectionTerminated = true; + CommunicationToken.Remove(message.CommunicationToken); } return response; @@ -166,7 +172,7 @@ namespace SafeExamBrowser.Communication.Hosts private bool IsAuthorized(Guid? token) { - return CommunicationToken == token; + return token.HasValue && CommunicationToken.Contains(token.Value); } private void TryStartHost(AutoResetEvent startedEvent, out Exception exception) diff --git a/SafeExamBrowser.Communication/Proxies/BaseProxy.cs b/SafeExamBrowser.Communication/Proxies/BaseProxy.cs index 0347d698..f3b45af5 100644 --- a/SafeExamBrowser.Communication/Proxies/BaseProxy.cs +++ b/SafeExamBrowser.Communication/Proxies/BaseProxy.cs @@ -26,21 +26,23 @@ namespace SafeExamBrowser.Communication.Proxies private readonly object @lock = new object(); private string address; + private Interlocutor owner; private IProxyObject proxy; private IProxyObjectFactory factory; private Guid? communicationToken; private Timer timer; - protected bool IsConnected { get { return communicationToken.HasValue; } } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; } + public bool IsConnected => communicationToken.HasValue; public event CommunicationEventHandler ConnectionLost; - public BaseProxy(string address, IProxyObjectFactory factory, ILogger logger) + public BaseProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) { this.address = address; this.factory = factory; this.Logger = logger; + this.owner = owner; } public virtual bool Connect(Guid? token = null, bool autoPing = true) @@ -89,7 +91,11 @@ namespace SafeExamBrowser.Communication.Proxies StopAutoPing(); - var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value }; + var message = new DisconnectionMessage + { + CommunicationToken = communicationToken.Value, + Interlocutor = owner + }; var response = proxy.Disconnect(message); var success = response.ConnectionTerminated; diff --git a/SafeExamBrowser.Communication/Proxies/ClientProxy.cs b/SafeExamBrowser.Communication/Proxies/ClientProxy.cs index 1692d187..f0e82bd3 100644 --- a/SafeExamBrowser.Communication/Proxies/ClientProxy.cs +++ b/SafeExamBrowser.Communication/Proxies/ClientProxy.cs @@ -7,6 +7,7 @@ */ using System; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; @@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies /// public class ClientProxy : BaseProxy, IClientProxy { - public ClientProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) + public ClientProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner) { } diff --git a/SafeExamBrowser.Communication/Proxies/ProxyFactory.cs b/SafeExamBrowser.Communication/Proxies/ProxyFactory.cs new file mode 100644 index 00000000..bfcd149b --- /dev/null +++ b/SafeExamBrowser.Communication/Proxies/ProxyFactory.cs @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Communication; +using SafeExamBrowser.Contracts.Communication.Proxies; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Communication.Proxies +{ + /// + /// Default implementation of the , creating instances of the default proxy implementations. + /// + public class ProxyFactory : IProxyFactory + { + private IProxyObjectFactory factory; + private IModuleLogger logger; + + public ProxyFactory(IProxyObjectFactory factory, IModuleLogger logger) + { + this.factory = factory; + this.logger = logger; + } + + public IClientProxy CreateClientProxy(string address, Interlocutor owner) + { + return new ClientProxy(address, factory, logger.CloneFor(nameof(ClientProxy)), owner); + } + } +} diff --git a/SafeExamBrowser.Communication/Proxies/RuntimeProxy.cs b/SafeExamBrowser.Communication/Proxies/RuntimeProxy.cs index ea03710d..14eb8438 100644 --- a/SafeExamBrowser.Communication/Proxies/RuntimeProxy.cs +++ b/SafeExamBrowser.Communication/Proxies/RuntimeProxy.cs @@ -7,6 +7,7 @@ */ using System; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Logging; @@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies /// public class RuntimeProxy : BaseProxy, IRuntimeProxy { - public RuntimeProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) + public RuntimeProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner) { } diff --git a/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs b/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs index 88caff61..7773977b 100644 --- a/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs +++ b/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs @@ -7,6 +7,8 @@ */ using System; +using SafeExamBrowser.Contracts.Communication; +using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; @@ -19,9 +21,8 @@ namespace SafeExamBrowser.Communication.Proxies public class ServiceProxy : BaseProxy, IServiceProxy { public bool Ignore { private get; set; } - public new bool IsConnected => base.IsConnected; - public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) + public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner) { } @@ -52,10 +53,28 @@ namespace SafeExamBrowser.Communication.Proxies return new CommunicationResult(true); } - // Implement service communication... - // Send(new StartSessionMessage { Id = sessionId, Settings = settings }); + try + { + var response = Send(new SessionStartMessage(configuration)); + var success = IsAcknowledged(response); - throw new NotImplementedException(); + if (success) + { + Logger.Debug("Service acknowledged session start."); + } + else + { + Logger.Error($"Service did not acknowledge session start! Received: {ToString(response)}."); + } + + return new CommunicationResult(success); + } + catch (Exception e) + { + Logger.Error($"Failed to perform '{nameof(StartSession)}'", e); + + return new CommunicationResult(false); + } } public CommunicationResult StopSession(Guid sessionId) @@ -65,10 +84,28 @@ namespace SafeExamBrowser.Communication.Proxies return new CommunicationResult(true); } - // Implement service communication... - // Send(new StopSessionMessage { SessionId = sessionId }); + try + { + var response = Send(new SessionStopMessage(sessionId)); + var success = IsAcknowledged(response); - throw new NotImplementedException(); + if (success) + { + Logger.Debug("Service acknowledged session stop."); + } + else + { + Logger.Error($"Service did not acknowledge session stop! Received: {ToString(response)}."); + } + + return new CommunicationResult(success); + } + catch (Exception e) + { + Logger.Error($"Failed to perform '{nameof(StopSession)}'", e); + + return new CommunicationResult(false); + } } private bool IgnoreOperation(string operationName) diff --git a/SafeExamBrowser.Communication/SafeExamBrowser.Communication.csproj b/SafeExamBrowser.Communication/SafeExamBrowser.Communication.csproj index 222c3012..a47fe46a 100644 --- a/SafeExamBrowser.Communication/SafeExamBrowser.Communication.csproj +++ b/SafeExamBrowser.Communication/SafeExamBrowser.Communication.csproj @@ -60,6 +60,7 @@ + diff --git a/SafeExamBrowser.Configuration.UnitTests/ConfigurationRepositoryTests.cs b/SafeExamBrowser.Configuration.UnitTests/ConfigurationRepositoryTests.cs index b8f91349..eb673747 100644 --- a/SafeExamBrowser.Configuration.UnitTests/ConfigurationRepositoryTests.cs +++ b/SafeExamBrowser.Configuration.UnitTests/ConfigurationRepositoryTests.cs @@ -278,7 +278,6 @@ namespace SafeExamBrowser.Configuration.UnitTests Assert.IsNull(configuration.Settings); Assert.IsInstanceOfType(configuration.AppConfig, typeof(AppConfig)); Assert.IsInstanceOfType(configuration.ClientAuthenticationToken, typeof(Guid)); - Assert.IsInstanceOfType(configuration.ServiceAuthenticationToken, typeof(Guid)); Assert.IsInstanceOfType(configuration.SessionId, typeof(Guid)); } @@ -292,14 +291,18 @@ namespace SafeExamBrowser.Configuration.UnitTests var runtimeAddress = appConfig.RuntimeAddress; var runtimeId = appConfig.RuntimeId; var runtimeLogFilePath = appConfig.RuntimeLogFilePath; + var serviceEventName = appConfig.ServiceEventName; + var configuration = sut.InitializeSessionConfiguration(); - Assert.AreNotEqual(configuration.AppConfig.ClientAddress, clientAddress); - Assert.AreNotEqual(configuration.AppConfig.ClientId, clientId); Assert.AreEqual(configuration.AppConfig.ClientLogFilePath, clientLogFilePath); Assert.AreEqual(configuration.AppConfig.RuntimeAddress, runtimeAddress); Assert.AreEqual(configuration.AppConfig.RuntimeId, runtimeId); Assert.AreEqual(configuration.AppConfig.RuntimeLogFilePath, runtimeLogFilePath); + + Assert.AreNotEqual(configuration.AppConfig.ClientAddress, clientAddress); + Assert.AreNotEqual(configuration.AppConfig.ClientId, clientId); + Assert.AreNotEqual(configuration.AppConfig.ServiceEventName, serviceEventName); } [TestMethod] @@ -312,10 +315,8 @@ namespace SafeExamBrowser.Configuration.UnitTests Assert.AreNotEqual(firstSession.SessionId, secondSession.SessionId); Assert.AreNotEqual(firstSession.ClientAuthenticationToken, secondSession.ClientAuthenticationToken); - Assert.AreNotEqual(firstSession.ServiceAuthenticationToken, secondSession.ServiceAuthenticationToken); Assert.AreNotEqual(secondSession.SessionId, thirdSession.SessionId); Assert.AreNotEqual(secondSession.ClientAuthenticationToken, thirdSession.ClientAuthenticationToken); - Assert.AreNotEqual(secondSession.ServiceAuthenticationToken, thirdSession.ServiceAuthenticationToken); } private void RegisterModules() diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs index 98c5228a..814cc09c 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs @@ -78,10 +78,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData appConfig.ClientId = Guid.NewGuid(); appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; + appConfig.ServiceEventName = $@"Global\{nameof(SafeExamBrowser)}-{Guid.NewGuid()}"; configuration.AppConfig = appConfig.Clone(); configuration.ClientAuthenticationToken = Guid.NewGuid(); - configuration.ServiceAuthenticationToken = Guid.NewGuid(); configuration.SessionId = Guid.NewGuid(); return configuration; diff --git a/SafeExamBrowser.Contracts/Communication/Data/DisconnectionMessage.cs b/SafeExamBrowser.Contracts/Communication/Data/DisconnectionMessage.cs index 358f43ff..278752b7 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/DisconnectionMessage.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/DisconnectionMessage.cs @@ -16,5 +16,9 @@ namespace SafeExamBrowser.Contracts.Communication.Data [Serializable] public class DisconnectionMessage : Message { + /// + /// Identifies the component sending the message. + /// + public Interlocutor Interlocutor { get; set; } } } diff --git a/SafeExamBrowser.Contracts/Communication/Data/SessionStartMessage.cs b/SafeExamBrowser.Contracts/Communication/Data/SessionStartMessage.cs new file mode 100644 index 00000000..9050cab0 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Data/SessionStartMessage.cs @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; +using SafeExamBrowser.Contracts.Configuration; + +namespace SafeExamBrowser.Contracts.Communication.Data +{ + /// + /// This message is transmitted to the service to request the initialization of a new session. + /// + [Serializable] + public class SessionStartMessage : Message + { + /// + /// The configuration to be used by the service. + /// + public ServiceConfiguration Configuration { get; } + + public SessionStartMessage(ServiceConfiguration configuration) + { + Configuration = configuration; + } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Data/SessionStopMessage.cs b/SafeExamBrowser.Contracts/Communication/Data/SessionStopMessage.cs new file mode 100644 index 00000000..7fb3f0e2 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Data/SessionStopMessage.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; + +namespace SafeExamBrowser.Contracts.Communication.Data +{ + /// + /// This message is transmitted to the service to request the termination of a currently running session. + /// + [Serializable] + public class SessionStopMessage : Message + { + public Guid SessionId { get; } + + public SessionStopMessage(Guid sessionId) + { + SessionId = sessionId; + } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Events/SessionStartEventArgs.cs b/SafeExamBrowser.Contracts/Communication/Events/SessionStartEventArgs.cs new file mode 100644 index 00000000..99575ed1 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Events/SessionStartEventArgs.cs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Configuration; + +namespace SafeExamBrowser.Contracts.Communication.Events +{ + /// + /// The event arguments used for the session start event fired by the . + /// + public class SessionStartEventArgs : CommunicationEventArgs + { + /// + /// The configuration to be used for the new session. + /// + public ServiceConfiguration Configuration { get; set; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Events/SessionStopEventArgs.cs b/SafeExamBrowser.Contracts/Communication/Events/SessionStopEventArgs.cs new file mode 100644 index 00000000..c1a017f3 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Events/SessionStopEventArgs.cs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; + +namespace SafeExamBrowser.Contracts.Communication.Events +{ + /// + /// The event arguments used for the session stop event fired by the . + /// + public class SessionStopEventArgs : CommunicationEventArgs + { + /// + /// The identifier of the session to be stopped. + /// + public Guid SessionId { get; set; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs index 61e69428..846c5119 100644 --- a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs @@ -24,7 +24,7 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts /// /// The token used for initial authentication. /// - Guid AuthenticationToken { set; } + Guid? AuthenticationToken { set; } /// /// Event fired when the client disconnected from the runtime. @@ -56,26 +56,6 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts /// event CommunicationEventHandler ReconfigurationRequested; - /// - /// Event fired when the service disconnected from the runtime. - /// - event CommunicationEventHandler ServiceDisconnected; - - /// - /// Event fired when the service has experienced a critical failure. - /// - event CommunicationEventHandler ServiceFailed; - - /// - /// Event fired when the service has successfully started a new session. - /// - event CommunicationEventHandler ServiceSessionStarted; - - /// - /// Event fired when the service has successfully stopped the currently running session. - /// - event CommunicationEventHandler ServiceSessionStopped; - /// /// Event fired when the client requests to shut down the application. /// diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs index c19bdf4c..a661bdc6 100644 --- a/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IServiceHost.cs @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using SafeExamBrowser.Contracts.Communication.Events; + namespace SafeExamBrowser.Contracts.Communication.Hosts { /// @@ -13,5 +15,19 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts /// public interface IServiceHost : ICommunicationHost { + /// + /// Determines whether another application component may establish a connection with the host. + /// + bool AllowConnection { get; set; } + + /// + /// Event fired when the runtime requested to start a new session. + /// + event CommunicationEventHandler SessionStartRequested; + + /// + /// Event fired when the runtime requested to stop a running session. + /// + event CommunicationEventHandler SessionStopRequested; } } diff --git a/SafeExamBrowser.Contracts/Communication/ICommunication.cs b/SafeExamBrowser.Contracts/Communication/ICommunication.cs index 15c76975..7a28b49d 100644 --- a/SafeExamBrowser.Contracts/Communication/ICommunication.cs +++ b/SafeExamBrowser.Contracts/Communication/ICommunication.cs @@ -28,6 +28,8 @@ namespace SafeExamBrowser.Contracts.Communication [ServiceKnownType(typeof(ReconfigurationMessage))] [ServiceKnownType(typeof(ReconfigurationDeniedMessage))] [ServiceKnownType(typeof(ServiceConfiguration))] + [ServiceKnownType(typeof(SessionStartMessage))] + [ServiceKnownType(typeof(SessionStopMessage))] [ServiceKnownType(typeof(SimpleMessage))] [ServiceKnownType(typeof(SimpleResponse))] public interface ICommunication diff --git a/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs b/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs index 75ff7d24..d3e894af 100644 --- a/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs @@ -17,6 +17,11 @@ namespace SafeExamBrowser.Contracts.Communication /// public interface ICommunicationProxy { + /// + /// Indicates whether a connection to the host has been established. + /// + bool IsConnected { get; } + /// /// Fired when the connection to the host was lost, e.g. if a ping request failed or a communication fault occurred. /// diff --git a/SafeExamBrowser.Contracts/Communication/Interlocutor.cs b/SafeExamBrowser.Contracts/Communication/Interlocutor.cs new file mode 100644 index 00000000..360d3e90 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Interlocutor.cs @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; + +namespace SafeExamBrowser.Contracts.Communication +{ + /// + /// Defines all possible interlocutors for inter-process communication within the application. + /// + [Serializable] + public enum Interlocutor + { + /// + /// The interlocutor is not an application component. + /// + Unknown = 0, + + /// + /// The client application component. + /// + Client, + + /// + /// The runtime application component. + /// + Runtime, + + /// + /// The service application component. + /// + Service + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Proxies/IProxyFactory.cs b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyFactory.cs index 9cf51641..47273716 100644 --- a/SafeExamBrowser.Contracts/Communication/Proxies/IProxyFactory.cs +++ b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyFactory.cs @@ -14,8 +14,8 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies public interface IProxyFactory { /// - /// Creates a new for the given endpoint address. + /// Creates a new for the given endpoint address and owner. /// - IClientProxy CreateClientProxy(string address); + IClientProxy CreateClientProxy(string address, Interlocutor owner); } } diff --git a/SafeExamBrowser.Contracts/Communication/Proxies/IServiceProxy.cs b/SafeExamBrowser.Contracts/Communication/Proxies/IServiceProxy.cs index 9f819e58..5968a642 100644 --- a/SafeExamBrowser.Contracts/Communication/Proxies/IServiceProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/Proxies/IServiceProxy.cs @@ -22,11 +22,6 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies /// bool Ignore { set; } - /// - /// Indicates whether a connection to the communication host of the service has been established. - /// - bool IsConnected { get; } - /// /// Instructs the service to start a new session according to the given configuration. /// diff --git a/SafeExamBrowser.Contracts/Configuration/AppConfig.cs b/SafeExamBrowser.Contracts/Configuration/AppConfig.cs index 07002a37..f9d57df3 100644 --- a/SafeExamBrowser.Contracts/Configuration/AppConfig.cs +++ b/SafeExamBrowser.Contracts/Configuration/AppConfig.cs @@ -116,6 +116,11 @@ namespace SafeExamBrowser.Contracts.Configuration /// public string ServiceAddress { get; set; } + /// + /// The name of the global inter-process synchronization event hosted by the service. + /// + public string ServiceEventName { get; set; } + /// /// Creates a shallow clone. /// diff --git a/SafeExamBrowser.Contracts/Configuration/ServiceConfiguration.cs b/SafeExamBrowser.Contracts/Configuration/ServiceConfiguration.cs index 1dc3eeb1..28773828 100644 --- a/SafeExamBrowser.Contracts/Configuration/ServiceConfiguration.cs +++ b/SafeExamBrowser.Contracts/Configuration/ServiceConfiguration.cs @@ -21,11 +21,6 @@ namespace SafeExamBrowser.Contracts.Configuration /// public AppConfig AppConfig { get; set; } - /// - /// The token used for initial authentication with the runtime. - /// - public Guid AuthenticationToken { get; set; } - /// /// The unique identifier for the current session. /// diff --git a/SafeExamBrowser.Contracts/Configuration/SessionConfiguration.cs b/SafeExamBrowser.Contracts/Configuration/SessionConfiguration.cs index 3ba11124..9449fa18 100644 --- a/SafeExamBrowser.Contracts/Configuration/SessionConfiguration.cs +++ b/SafeExamBrowser.Contracts/Configuration/SessionConfiguration.cs @@ -25,11 +25,6 @@ namespace SafeExamBrowser.Contracts.Configuration /// public Guid ClientAuthenticationToken { get; set; } - /// - /// The token used for initial communication authentication with the service. - /// - public Guid ServiceAuthenticationToken { get; set; } - /// /// The unique session identifier. /// diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index 37feb0ae..725a4ad9 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -56,10 +56,15 @@ + + + + + diff --git a/SafeExamBrowser.Runtime.UnitTests/Communication/RuntimeHostTests.cs b/SafeExamBrowser.Runtime.UnitTests/Communication/RuntimeHostTests.cs index 667cdd96..5f3664e3 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Communication/RuntimeHostTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Communication/RuntimeHostTests.cs @@ -43,7 +43,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication } [TestMethod] - public void MustOnlyAllowConnectionIfTokenIsValid() + public void MustAllowConnectionIfTokenIsValid() { var token = Guid.NewGuid(); @@ -70,6 +70,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication Assert.IsFalse(response.ConnectionEstablished); } + [TestMethod] + public void MustRejectConnectionIfNoAuthenticationTokenSet() + { + var token = Guid.Empty; + + sut.AllowConnection = true; + + var response = sut.Connect(token); + + Assert.IsNotNull(response); + Assert.IsFalse(response.ConnectionEstablished); + } + [TestMethod] public void MustOnlyAllowOneConcurrentConnection() { @@ -94,7 +107,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication } [TestMethod] - public void MustCorrectlyDisconnect() + public void MustCorrectlyDisconnectClient() { var disconnected = false; var token = Guid.NewGuid(); @@ -104,7 +117,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication sut.ClientDisconnected += () => disconnected = true; var connectionResponse = sut.Connect(token); - var response = sut.Disconnect(new DisconnectionMessage { CommunicationToken = connectionResponse.CommunicationToken.Value }); + var message = new DisconnectionMessage + { + CommunicationToken = connectionResponse.CommunicationToken.Value, + Interlocutor = Interlocutor.Client + }; + var response = sut.Disconnect(message); Assert.IsNotNull(response); Assert.IsTrue(disconnected); diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs index 920f0acc..1706686b 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientOperationTests.cs @@ -10,6 +10,7 @@ using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; @@ -63,7 +64,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations session.Settings = settings; sessionContext.Current = session; sessionContext.Next = session; - proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny())).Returns(proxy.Object); + proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny(), It.IsAny())).Returns(proxy.Object); sut = new ClientOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0); } diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs index 56d0664d..3a55fd0a 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ClientTerminationOperationTests.cs @@ -9,6 +9,7 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; @@ -60,7 +61,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations sessionContext.ClientProxy = proxy.Object; sessionContext.Current = session; sessionContext.Next = session; - proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny())).Returns(proxy.Object); + proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny(), It.IsAny())).Returns(proxy.Object); sut = new ClientTerminationOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0); } diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ServiceOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ServiceOperationTests.cs index 4e7641dc..d5ecdeba 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ServiceOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ServiceOperationTests.cs @@ -7,6 +7,7 @@ */ using System; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Communication.Hosts; @@ -24,9 +25,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestClass] public class ServiceOperationTests { + private AppConfig appConfig; private Mock logger; private Mock runtimeHost; private Mock service; + private EventWaitHandle serviceEvent; private SessionConfiguration session; private SessionContext sessionContext; private Settings settings; @@ -35,15 +38,22 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestInitialize] public void Initialize() { + var serviceEventName = $"{nameof(SafeExamBrowser)}-{nameof(ServiceOperationTests)}"; + + appConfig = new AppConfig(); logger = new Mock(); runtimeHost = new Mock(); service = new Mock(); + serviceEvent = new EventWaitHandle(false, EventResetMode.AutoReset, serviceEventName); session = new SessionConfiguration(); sessionContext = new SessionContext(); settings = new Settings(); + appConfig.ServiceEventName = serviceEventName; sessionContext.Current = session; + sessionContext.Current.AppConfig = appConfig; sessionContext.Next = session; + sessionContext.Next.AppConfig = appConfig; session.Settings = settings; settings.ServicePolicy = ServicePolicy.Mandatory; @@ -71,10 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { service.SetupGet(s => s.IsConnected).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true); - service - .Setup(s => s.StartSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null)); + service.Setup(s => s.StartSession(It.IsAny())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set()); var result = sut.Perform(); @@ -88,10 +95,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { service.SetupGet(s => s.IsConnected).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true); - service - .Setup(s => s.StartSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null)); + service.Setup(s => s.StartSession(It.IsAny())).Returns(new CommunicationResult(true)); var result = sut.Perform(); @@ -197,10 +201,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void Repeat_MustStopCurrentAndStartNewSession() { - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null)); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set()); PerformNormally(); @@ -222,10 +223,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations service.Reset(); service.SetupGet(s => s.IsConnected).Returns(false); service.Setup(s => s.Connect(null, true)).Returns(true); - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null)); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)); var result = sut.Repeat(); @@ -251,58 +249,49 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void Revert_MustDisconnect() - { - service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null)); - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null)); - - PerformNormally(); - - var result = sut.Revert(); - - service.Verify(s => s.Disconnect(), Times.Once); - Assert.AreEqual(OperationResult.Success, result); - } - - [TestMethod] - public void Revert_MustFailIfServiceNotDisconnectedWithinTimeout() + public void Repeat_MustFailIfSessionNotStoppedWithinTimeout() { const int TIMEOUT = 50; var after = default(DateTime); var before = default(DateTime); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)); sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT); - service.Setup(s => s.Disconnect()).Returns(true); - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null)); - PerformNormally(); before = DateTime.Now; - var result = sut.Revert(); + var result = sut.Repeat(); after = DateTime.Now; - service.Verify(s => s.Disconnect(), Times.Once); + service.Verify(s => s.StopSession(It.IsAny()), Times.Once); + service.Verify(s => s.Disconnect(), Times.Never); Assert.AreEqual(OperationResult.Failed, result); Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT)); } + [TestMethod] + public void Revert_MustDisconnect() + { + service.Setup(s => s.Disconnect()).Returns(true); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set()); + + PerformNormally(); + + var result = sut.Revert(); + + service.Verify(s => s.Disconnect(), Times.Once); + + Assert.AreEqual(OperationResult.Success, result); + } + [TestMethod] public void Revert_MustStopSessionIfConnected() { - service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null)); - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null)); + service.Setup(s => s.Disconnect()).Returns(true); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set()); PerformNormally(); @@ -332,10 +321,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations [TestMethod] public void Revert_MustFailIfSessionStopUnsuccessful() { - service - .Setup(s => s.StopSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null)); + service.Setup(s => s.StopSession(It.IsAny())).Returns(new CommunicationResult(true)); PerformNormally(); @@ -377,6 +363,20 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations Assert.AreEqual(OperationResult.Success, result); } + [TestMethod] + public void Revert_MustNotStopSessionIfNoSessionRunning() + { + sessionContext.Current = null; + service.SetupGet(s => s.IsConnected).Returns(true); + service.Setup(s => s.Disconnect()).Returns(true); + + var result = sut.Revert(); + + service.Verify(s => s.StopSession(It.IsAny()), Times.Never); + + Assert.AreEqual(OperationResult.Success, result); + } + [TestMethod] public void Revert_MustNotDisconnnectIfNotConnected() { @@ -390,10 +390,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations { service.SetupGet(s => s.IsConnected).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true); - service - .Setup(s => s.StartSession(It.IsAny())) - .Returns(new CommunicationResult(true)) - .Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null)); + service.Setup(s => s.StartSession(It.IsAny())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set()); sut.Perform(); } diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/SessionInitializationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/SessionInitializationOperationTests.cs index e74c469a..8b7c08e8 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/SessionInitializationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/SessionInitializationOperationTests.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Runtime.Operations; @@ -52,33 +53,41 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations session.ClientAuthenticationToken = token; - sut.Perform(); + var result = sut.Perform(); configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once); - runtimeHost.VerifySet(r => r.AuthenticationToken = token, Times.Once); + + Assert.AreEqual(OperationResult.Success, result); + Assert.IsNull(sessionContext.Current); } [TestMethod] public void MustInitializeConfigurationOnRepeat() { + var currentSession = new SessionConfiguration(); var token = Guid.NewGuid(); session.ClientAuthenticationToken = token; + sessionContext.Current = currentSession; - sut.Repeat(); + var result = sut.Repeat(); configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once); - runtimeHost.VerifySet(r => r.AuthenticationToken = token, Times.Once); + + Assert.AreEqual(OperationResult.Success, result); + Assert.AreSame(currentSession,sessionContext.Current); } [TestMethod] public void MustDoNothingOnRevert() { - sut.Revert(); + var result = sut.Revert(); configuration.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls(); runtimeHost.VerifyNoOtherCalls(); + + Assert.AreEqual(OperationResult.Success, result); } } } diff --git a/SafeExamBrowser.Runtime/Communication/ProxyFactory.cs b/SafeExamBrowser.Runtime/Communication/ProxyFactory.cs deleted file mode 100644 index b862eae9..00000000 --- a/SafeExamBrowser.Runtime/Communication/ProxyFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -using SafeExamBrowser.Contracts.Communication.Proxies; -using SafeExamBrowser.Contracts.Logging; -using SafeExamBrowser.Communication.Proxies; -using SafeExamBrowser.Logging; - -namespace SafeExamBrowser.Runtime.Communication -{ - internal class ProxyFactory : IProxyFactory - { - private IProxyObjectFactory factory; - private ILogger logger; - - public ProxyFactory(IProxyObjectFactory factory, ILogger logger) - { - this.factory = factory; - this.logger = logger; - } - - public IClientProxy CreateClientProxy(string address) - { - return new ClientProxy(address, factory, new ModuleLogger(logger, nameof(ClientProxy))); - } - } -} diff --git a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs index 60c71a13..57aeb911 100644 --- a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs +++ b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs @@ -8,6 +8,7 @@ using System; using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; @@ -18,7 +19,7 @@ namespace SafeExamBrowser.Runtime.Communication internal class RuntimeHost : BaseHost, IRuntimeHost { public bool AllowConnection { get; set; } - public Guid AuthenticationToken { private get; set; } + public Guid? AuthenticationToken { private get; set; } public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientReady; @@ -26,10 +27,6 @@ namespace SafeExamBrowser.Runtime.Communication public event CommunicationEventHandler MessageBoxReplyReceived; public event CommunicationEventHandler PasswordReceived; public event CommunicationEventHandler ReconfigurationRequested; - public event CommunicationEventHandler ServiceDisconnected; - public event CommunicationEventHandler ServiceFailed; - public event CommunicationEventHandler ServiceSessionStarted; - public event CommunicationEventHandler ServiceSessionStopped; public event CommunicationEventHandler ShutdownRequested; public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) @@ -38,7 +35,7 @@ namespace SafeExamBrowser.Runtime.Communication protected override bool OnConnect(Guid? token = null) { - var authenticated = AuthenticationToken == token; + var authenticated = AuthenticationToken.HasValue && AuthenticationToken == token; var accepted = AllowConnection && authenticated; if (accepted) @@ -49,9 +46,12 @@ namespace SafeExamBrowser.Runtime.Communication return accepted; } - protected override void OnDisconnect() + protected override void OnDisconnect(Interlocutor interlocutor) { - ClientDisconnected?.Invoke(); + if (interlocutor == Interlocutor.Client) + { + ClientDisconnected?.Invoke(); + } } protected override Response OnReceive(Message message) diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index dffd8162..92524e4c 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -17,6 +17,7 @@ using SafeExamBrowser.Configuration.Cryptography; using SafeExamBrowser.Configuration.DataCompression; using SafeExamBrowser.Configuration.DataFormats; using SafeExamBrowser.Configuration.DataResources; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.I18n; @@ -65,9 +66,9 @@ namespace SafeExamBrowser.Runtime var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory))); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); - var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); + var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory))); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS); - var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy))); + var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime); var sessionContext = new SessionContext(); var uiFactory = new UserInterfaceFactory(text); diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs index 3159576d..626affcf 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs @@ -6,7 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.Threading; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; @@ -111,6 +113,7 @@ namespace SafeExamBrowser.Runtime.Operations logger.Info("Starting new client process..."); runtimeHost.AllowConnection = true; + runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken; runtimeHost.ClientReady += clientReadyEventHandler; ClientProcess = processFactory.StartNew(executablePath, logFilePath, logLevel, runtimeHostUri, authenticationToken, uiMode); ClientProcess.Terminated += clientTerminatedEventHandler; @@ -119,6 +122,7 @@ namespace SafeExamBrowser.Runtime.Operations clientReady = clientReadyEvent.WaitOne(timeout_ms); runtimeHost.AllowConnection = false; + runtimeHost.AuthenticationToken = default(Guid?); runtimeHost.ClientReady -= clientReadyEventHandler; ClientProcess.Terminated -= clientTerminatedEventHandler; @@ -145,7 +149,7 @@ namespace SafeExamBrowser.Runtime.Operations var success = false; logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host..."); - ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress); + ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress, Interlocutor.Runtime); if (ClientProxy.Connect(Context.Next.ClientAuthenticationToken)) { diff --git a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs index f3874ff2..825b9cb6 100644 --- a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs @@ -6,8 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.Security.AccessControl; using System.Threading; -using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; @@ -46,7 +47,7 @@ namespace SafeExamBrowser.Runtime.Operations public override OperationResult Perform() { - logger.Info($"Initializing service session..."); + logger.Info($"Initializing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = TryEstablishConnection(); @@ -61,7 +62,7 @@ namespace SafeExamBrowser.Runtime.Operations public override OperationResult Repeat() { - logger.Info($"Initializing new service session..."); + logger.Info($"Initializing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = false; @@ -85,14 +86,18 @@ namespace SafeExamBrowser.Runtime.Operations public override OperationResult Revert() { - logger.Info("Finalizing service session..."); + logger.Info("Finalizing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); var success = true; if (service.IsConnected) { - success &= TryStopSession(); + if (Context.Current != null) + { + success &= TryStopSession(); + } + success &= TryTerminateConnection(); } @@ -137,36 +142,18 @@ namespace SafeExamBrowser.Runtime.Operations private bool TryTerminateConnection() { - var serviceEvent = new AutoResetEvent(false); - var serviceEventHandler = new CommunicationEventHandler(() => serviceEvent.Set()); + var disconnected = service.Disconnect(); - runtimeHost.ServiceDisconnected += serviceEventHandler; - - var success = service.Disconnect(); - - if (success) + if (disconnected) { - logger.Info("Successfully disconnected from service. Waiting for service to disconnect..."); - - success = serviceEvent.WaitOne(timeout_ms); - - if (success) - { - logger.Info("Service disconnected successfully."); - } - else - { - logger.Error($"Service failed to disconnect within {timeout_ms / 1000} seconds!"); - } + logger.Info("Successfully disconnected from service."); } else { logger.Error("Failed to disconnect from service!"); } - runtimeHost.ServiceDisconnected -= serviceEventHandler; - - return success; + return disconnected; } private bool TryStartSession() @@ -174,18 +161,10 @@ namespace SafeExamBrowser.Runtime.Operations var configuration = new ServiceConfiguration { AppConfig = Context.Next.AppConfig, - AuthenticationToken = Context.Next.ServiceAuthenticationToken, SessionId = Context.Next.SessionId, Settings = Context.Next.Settings }; - var failure = false; - var success = false; - var serviceEvent = new AutoResetEvent(false); - var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); }); - var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); }); - - runtimeHost.ServiceFailed += failureEventHandler; - runtimeHost.ServiceSessionStarted += successEventHandler; + var started = false; logger.Info("Starting new service session..."); @@ -193,16 +172,12 @@ namespace SafeExamBrowser.Runtime.Operations if (communication.Success) { - serviceEvent.WaitOne(timeout_ms); + started = TryWaitForServiceEvent(Context.Next.AppConfig.ServiceEventName); - if (success) + if (started) { logger.Info("Successfully started new service session."); } - else if (failure) - { - logger.Error("An error occurred while attempting to start a new service session! Please check the service log for further information."); - } else { logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!"); @@ -213,22 +188,12 @@ namespace SafeExamBrowser.Runtime.Operations logger.Error("Failed to communicate session start to service!"); } - runtimeHost.ServiceFailed -= failureEventHandler; - runtimeHost.ServiceSessionStarted -= successEventHandler; - - return success; + return started; } private bool TryStopSession() { - var failure = false; - var success = false; - var serviceEvent = new AutoResetEvent(false); - var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); }); - var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); }); - - runtimeHost.ServiceFailed += failureEventHandler; - runtimeHost.ServiceSessionStopped += successEventHandler; + var stopped = false; logger.Info("Stopping current service session..."); @@ -236,16 +201,12 @@ namespace SafeExamBrowser.Runtime.Operations if (communication.Success) { - serviceEvent.WaitOne(timeout_ms); + stopped = TryWaitForServiceEvent(Context.Current.AppConfig.ServiceEventName); - if (success) + if (stopped) { logger.Info("Successfully stopped service session."); } - else if (failure) - { - logger.Error("An error occurred while attempting to stop the current service session! Please check the service log for further information."); - } else { logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!"); @@ -256,10 +217,31 @@ namespace SafeExamBrowser.Runtime.Operations logger.Error("Failed to communicate session stop to service!"); } - runtimeHost.ServiceFailed -= failureEventHandler; - runtimeHost.ServiceSessionStopped -= successEventHandler; + return stopped; + } - return success; + private bool TryWaitForServiceEvent(string eventName) + { + var serviceEvent = default(EventWaitHandle); + var startTime = DateTime.Now; + + do + { + if (EventWaitHandle.TryOpenExisting(eventName, EventWaitHandleRights.Synchronize, out serviceEvent)) + { + break; + } + } while (startTime.AddMilliseconds(timeout_ms) > DateTime.Now); + + if (serviceEvent != default(EventWaitHandle)) + { + using (serviceEvent) + { + return serviceEvent.WaitOne(timeout_ms); + } + } + + return false; } } } diff --git a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs index 790284ec..ae435dab 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; @@ -63,7 +62,6 @@ namespace SafeExamBrowser.Runtime.Operations StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession); Context.Next = configuration.InitializeSessionConfiguration(); - runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken; logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}"); logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}"); diff --git a/SafeExamBrowser.Runtime/Operations/SessionOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionOperation.cs index 3bdba83c..7196aab5 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionOperation.cs @@ -1,4 +1,12 @@ -using SafeExamBrowser.Contracts.Core.OperationModel; +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel.Events; namespace SafeExamBrowser.Runtime.Operations diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index dfd20194..0951b584 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -103,7 +103,6 @@ - diff --git a/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs index 2ea721ad..23b9b1e1 100644 --- a/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs +++ b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs @@ -11,12 +11,14 @@ using Moq; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Service.UnitTests { [TestClass] public class ServiceControllerTests { + private Mock logger; private Mock bootstrapSequence; private SessionContext sessionContext; private Mock sessionSequence; @@ -26,12 +28,13 @@ namespace SafeExamBrowser.Service.UnitTests [TestInitialize] public void Initialize() { + logger = new Mock(); bootstrapSequence = new Mock(); sessionContext = new SessionContext(); sessionSequence = new Mock(); serviceHost = new Mock(); - sut = new ServiceController(bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext); + sut = new ServiceController(logger.Object, bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext); } [TestMethod] @@ -39,7 +42,7 @@ namespace SafeExamBrowser.Service.UnitTests { bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); sessionSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); - sessionContext.Current = null; + sessionContext.Configuration = null; var success = sut.TryStart(); @@ -66,7 +69,7 @@ namespace SafeExamBrowser.Service.UnitTests bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); - sessionContext.Current = new ServiceConfiguration(); + sessionContext.Configuration = new ServiceConfiguration(); sut.Terminate(); @@ -94,7 +97,7 @@ namespace SafeExamBrowser.Service.UnitTests bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); - sessionContext.Current = null; + sessionContext.Configuration = null; sut.Terminate(); diff --git a/SafeExamBrowser.Service/Communication/ServiceHost.cs b/SafeExamBrowser.Service/Communication/ServiceHost.cs index 624ddcbd..452202ad 100644 --- a/SafeExamBrowser.Service/Communication/ServiceHost.cs +++ b/SafeExamBrowser.Service/Communication/ServiceHost.cs @@ -8,7 +8,9 @@ using System; using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication.Data; +using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Logging; @@ -16,28 +18,68 @@ namespace SafeExamBrowser.Service.Communication { internal class ServiceHost : BaseHost, IServiceHost { + private readonly object @lock = new object(); + + private bool allowConnection; + + public bool AllowConnection + { + get { lock (@lock) { return allowConnection; } } + set { lock (@lock) { allowConnection = value; } } + } + + public event CommunicationEventHandler SessionStartRequested; + public event CommunicationEventHandler SessionStopRequested; + internal ServiceHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) { + AllowConnection = true; } protected override bool OnConnect(Guid? token) { - throw new NotImplementedException(); + lock (@lock) + { + var allow = AllowConnection; + + if (allow) + { + AllowConnection = false; + } + + return allow; + } } - protected override void OnDisconnect() + protected override void OnDisconnect(Interlocutor interlocutor) { - throw new NotImplementedException(); + if (interlocutor == Interlocutor.Runtime) + { + lock (@lock) + { + AllowConnection = true; + } + } } protected override Response OnReceive(Message message) { - throw new NotImplementedException(); + switch (message) + { + case SessionStartMessage m: + SessionStartRequested?.InvokeAsync(new SessionStartEventArgs { Configuration = m.Configuration }); + return new SimpleResponse(SimpleResponsePurport.Acknowledged); + case SessionStopMessage m: + SessionStopRequested?.InvokeAsync(new SessionStopEventArgs { SessionId = m.SessionId }); + return new SimpleResponse(SimpleResponsePurport.Acknowledged); + } + + return new SimpleResponse(SimpleResponsePurport.UnknownMessage); } protected override Response OnReceive(SimpleMessagePurport message) { - throw new NotImplementedException(); + return new SimpleResponse(SimpleResponsePurport.UnknownMessage); } } } diff --git a/SafeExamBrowser.Service/CompositionRoot.cs b/SafeExamBrowser.Service/CompositionRoot.cs index c3880af2..c76bed16 100644 --- a/SafeExamBrowser.Service/CompositionRoot.cs +++ b/SafeExamBrowser.Service/CompositionRoot.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using SafeExamBrowser.Communication.Hosts; +using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Service; @@ -17,6 +18,7 @@ using SafeExamBrowser.Core.OperationModel; using SafeExamBrowser.Core.Operations; using SafeExamBrowser.Logging; using SafeExamBrowser.Service.Communication; +using SafeExamBrowser.Service.Operations; namespace SafeExamBrowser.Service { @@ -33,25 +35,26 @@ namespace SafeExamBrowser.Service InitializeLogging(); + var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ProxyFactory))); var serviceHost = new ServiceHost(SERVICE_ADDRESS, new HostObjectFactory(), new ModuleLogger(logger, nameof(ServiceHost)), FIVE_SECONDS); var sessionContext = new SessionContext(); var bootstrapOperations = new Queue(); - var sessionOperations = new Queue(); + var sessionOperations = new Queue(); // TODO: bootstrapOperations.Enqueue(new RestoreOperation()); bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger)); + bootstrapOperations.Enqueue(new ServiceEventCleanupOperation(logger, sessionContext)); - // sessionOperations.Enqueue(new RuntimeConnectionOperation()); - // sessionOperations.Enqueue(new LogOperation()); - // sessionOperations.Enqueue(new RegistryOperation()); - // sessionOperations.Enqueue(new WindowsUpdateOperation()); - // sessionOperations.Enqueue(new SessionActivationOperation()); + sessionOperations.Enqueue(new SessionInitializationOperation(logger, serviceHost, sessionContext)); + // TODO: sessionOperations.Enqueue(new RegistryOperation()); + // sessionOperations.Enqueue(new WindowsUpdateOperation()); + sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext)); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); - var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations); + var sessionSequence = new OperationSequence(logger, sessionOperations); - ServiceController = new ServiceController(bootstrapSequence, sessionSequence, serviceHost, sessionContext); + ServiceController = new ServiceController(logger, bootstrapSequence, sessionSequence, serviceHost, sessionContext); } internal void LogStartupInformation() @@ -62,7 +65,6 @@ namespace SafeExamBrowser.Service internal void LogShutdownInformation() { - logger?.Log(string.Empty); logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } diff --git a/SafeExamBrowser.Service/Operations/ServiceEventCleanupOperation.cs b/SafeExamBrowser.Service/Operations/ServiceEventCleanupOperation.cs new file mode 100644 index 00000000..c89fcd89 --- /dev/null +++ b/SafeExamBrowser.Service/Operations/ServiceEventCleanupOperation.cs @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Service.Operations +{ + internal class ServiceEventCleanupOperation : IOperation + { + private ILogger logger; + private SessionContext sessionContext; + + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged { add { } remove { } } + + public ServiceEventCleanupOperation(ILogger logger, SessionContext sessionContext) + { + this.logger = logger; + this.sessionContext = sessionContext; + } + + public OperationResult Perform() + { + return OperationResult.Success; + } + + public OperationResult Revert() + { + if (sessionContext.EventWaitHandle != null) + { + logger.Info("Closing service event..."); + sessionContext.EventWaitHandle.Close(); + logger.Info("Service event successfully closed."); + } + + return OperationResult.Success; + } + } +} diff --git a/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs b/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs new file mode 100644 index 00000000..411b559f --- /dev/null +++ b/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Service.Operations +{ + internal class SessionActivationOperation : SessionOperation + { + private ILogger logger; + + public SessionActivationOperation(ILogger logger, SessionContext sessionContext) : base(sessionContext) + { + this.logger = logger; + } + + public override OperationResult Perform() + { + var success = Context.EventWaitHandle.Set(); + + if (success) + { + logger.Info("Successfully informed runtime about new session activation."); + } + else + { + logger.Error("Failed to inform runtime about new session activation!"); + } + + return success ? OperationResult.Success : OperationResult.Failed; + } + + public override OperationResult Revert() + { + return OperationResult.Success; + } + } +} diff --git a/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs new file mode 100644 index 00000000..5b663c98 --- /dev/null +++ b/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System.Security.AccessControl; +using System.Security.Principal; +using System.Threading; +using SafeExamBrowser.Contracts.Communication.Hosts; +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Service.Operations +{ + internal class SessionInitializationOperation : SessionOperation + { + private ILogger logger; + private IServiceHost serviceHost; + + public SessionInitializationOperation(ILogger logger, IServiceHost serviceHost, SessionContext sessionContext) : base(sessionContext) + { + this.logger = logger; + this.serviceHost = serviceHost; + } + + public override OperationResult Perform() + { + logger.Info("Initializing new session..."); + + serviceHost.AllowConnection = false; + logger.Info($" -> Client-ID: {Context.Configuration.AppConfig.ClientId}"); + logger.Info($" -> Runtime-ID: {Context.Configuration.AppConfig.RuntimeId}"); + logger.Info($" -> Session-ID: {Context.Configuration.SessionId}"); + + InitializeEventWaitHandle(); + + return OperationResult.Success; + } + + public override OperationResult Revert() + { + logger.Info("Finalizing current session..."); + + var success = Context.EventWaitHandle?.Set() == true; + + if (success) + { + logger.Info("Successfully informed runtime about session termination."); + } + else + { + logger.Error("Failed to inform runtime about session termination!"); + } + + Context.Configuration = null; + serviceHost.AllowConnection = true; + + return success ? OperationResult.Success : OperationResult.Failed; + } + + private void InitializeEventWaitHandle() + { + var securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); + var accessRule = new EventWaitHandleAccessRule(securityIdentifier, EventWaitHandleRights.Synchronize, AccessControlType.Allow); + var security = new EventWaitHandleSecurity(); + + security.AddAccessRule(accessRule); + + if (Context.EventWaitHandle != null) + { + logger.Info("Closing service event from previous session..."); + Context.EventWaitHandle.Close(); + logger.Info("Service event successfully closed."); + } + + logger.Info("Attempting to create new service event..."); + Context.EventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Context.Configuration.AppConfig.ServiceEventName, out _, security); + logger.Info("Service event successfully created."); + } + } +} diff --git a/SafeExamBrowser.Service/Operations/SessionOperation.cs b/SafeExamBrowser.Service/Operations/SessionOperation.cs new file mode 100644 index 00000000..23480d03 --- /dev/null +++ b/SafeExamBrowser.Service/Operations/SessionOperation.cs @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; + +namespace SafeExamBrowser.Service.Operations +{ + /// + /// The base implementation to be used for all operations in the session operation sequence. + /// + internal abstract class SessionOperation : IOperation + { + protected SessionContext Context { get; private set; } + + public event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public event StatusChangedEventHandler StatusChanged { add { } remove { } } + + public SessionOperation(SessionContext sessionContext) + { + Context = sessionContext; + } + + public abstract OperationResult Perform(); + public abstract OperationResult Revert(); + } +} diff --git a/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj b/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj index 60c8368f..3de337fe 100644 --- a/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj +++ b/SafeExamBrowser.Service/SafeExamBrowser.Service.csproj @@ -64,6 +64,10 @@ Component + + + + Component @@ -92,8 +96,6 @@ SafeExamBrowser.Logging - - - + \ No newline at end of file diff --git a/SafeExamBrowser.Service/ServiceController.cs b/SafeExamBrowser.Service/ServiceController.cs index baac85f4..aa1f24ab 100644 --- a/SafeExamBrowser.Service/ServiceController.cs +++ b/SafeExamBrowser.Service/ServiceController.cs @@ -6,23 +6,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Service; namespace SafeExamBrowser.Service { internal class ServiceController : IServiceController { + private readonly ILogger logger; private IOperationSequence bootstrapSequence; - private IRepeatableOperationSequence sessionSequence; + private IOperationSequence sessionSequence; private IServiceHost serviceHost; private SessionContext sessionContext; private ServiceConfiguration Session { - get { return sessionContext.Current; } + get { return sessionContext.Configuration; } } private bool SessionIsRunning @@ -31,11 +35,13 @@ namespace SafeExamBrowser.Service } public ServiceController( + ILogger logger, IOperationSequence bootstrapSequence, - IRepeatableOperationSequence sessionSequence, + IOperationSequence sessionSequence, IServiceHost serviceHost, SessionContext sessionContext) { + this.logger = logger; this.bootstrapSequence = bootstrapSequence; this.sessionSequence = sessionSequence; this.serviceHost = serviceHost; @@ -44,22 +50,130 @@ namespace SafeExamBrowser.Service public bool TryStart() { + logger.Info("Initiating startup procedure..."); + var result = bootstrapSequence.TryPerform(); var success = result == OperationResult.Success; + if (success) + { + RegisterEvents(); + + logger.Info("Service successfully initialized."); + logger.Log(string.Empty); + } + else + { + logger.Info("Service startup aborted!"); + logger.Log(string.Empty); + } + return success; } public void Terminate() { - var result = default(OperationResult); - + DeregisterEvents(); + if (SessionIsRunning) { - result = sessionSequence.TryRevert(); + StopSession(); } - result = bootstrapSequence.TryRevert(); + logger.Log(string.Empty); + logger.Info("Initiating termination procedure..."); + + var result = bootstrapSequence.TryRevert(); + var success = result == OperationResult.Success; + + if (success) + { + logger.Info("Service successfully terminated."); + logger.Log(string.Empty); + } + else + { + logger.Info("Service termination failed!"); + logger.Log(string.Empty); + } + } + + private void StartSession() + { + logger.Info(AppendDivider("Session Start Procedure")); + + var result = sessionSequence.TryPerform(); + + if (result == OperationResult.Success) + { + logger.Info(AppendDivider("Session Running")); + } + else + { + logger.Info(AppendDivider("Session Start Failed")); + } + } + + private void StopSession() + { + logger.Info(AppendDivider("Session Stop Procedure")); + + var result = sessionSequence.TryRevert(); + + if (result == OperationResult.Success) + { + logger.Info(AppendDivider("Session Terminated")); + } + else + { + logger.Info(AppendDivider("Session Stop Failed")); + } + } + + private void RegisterEvents() + { + serviceHost.SessionStartRequested += ServiceHost_SessionStartRequested; + serviceHost.SessionStopRequested += ServiceHost_SessionStopRequested; + } + + private void DeregisterEvents() + { + serviceHost.SessionStartRequested -= ServiceHost_SessionStartRequested; + serviceHost.SessionStopRequested -= ServiceHost_SessionStopRequested; + } + + private void ServiceHost_SessionStartRequested(SessionStartEventArgs args) + { + if (!SessionIsRunning) + { + sessionContext.Configuration = args.Configuration; + + StartSession(); + } + else + { + logger.Warn("Received session start request, even though a session is already running!"); + } + } + + private void ServiceHost_SessionStopRequested(SessionStopEventArgs args) + { + if (SessionIsRunning) + { + StopSession(); + } + else + { + logger.Warn("Received session stop request, even though no session is currently running!"); + } + } + + private string AppendDivider(string message) + { + var dashesLeft = new String('-', 48 - message.Length / 2 - message.Length % 2); + var dashesRight = new String('-', 48 - message.Length / 2); + + return $"### {dashesLeft} {message} {dashesRight} ###"; } } } diff --git a/SafeExamBrowser.Service/SessionContext.cs b/SafeExamBrowser.Service/SessionContext.cs index ef126012..aa4e0222 100644 --- a/SafeExamBrowser.Service/SessionContext.cs +++ b/SafeExamBrowser.Service/SessionContext.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System.Threading; using SafeExamBrowser.Contracts.Configuration; namespace SafeExamBrowser.Service @@ -18,11 +19,11 @@ namespace SafeExamBrowser.Service /// /// The configuration of the currently active session. /// - internal ServiceConfiguration Current { get; set; } + internal ServiceConfiguration Configuration { get; set; } /// - /// The configuration of the next session to be activated. + /// The global inter-process event used for status synchronization with the runtime component. /// - internal ServiceConfiguration Next { get; set; } + internal EventWaitHandle EventWaitHandle { get; set; } } } diff --git a/appveyor.yml b/appveyor.yml index b0873eaa..725b08e4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,14 +20,15 @@ test_script: - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.I18n.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.I18n.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Logging.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Logging.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Runtime.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Runtime.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" + - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Service.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Service.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" after_test: - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" - pip install codecov - codecov -f "coverage.xml" artifacts: - path: SafeExamBrowser.Runtime\bin\$(platform)\$(configuration) - name: '$(appveyor_build_version)_Base_Application' + name: '$(appveyor_build_version)_Application' type: zip - path: SafeExamBrowser.Service\bin\$(platform)\$(configuration) - name: '$(appveyor_build_version)_Base_Service' + name: '$(appveyor_build_version)_Service' type: zip