SEBWIN-219: Basic startup sequence is now working.

This commit is contained in:
dbuechel 2018-02-16 13:15:16 +01:00
parent d935407ecb
commit d3dea29ecd
23 changed files with 329 additions and 114 deletions

View file

@ -7,7 +7,6 @@
*/ */
using System; using System;
using System.ComponentModel;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
@ -56,14 +55,16 @@ namespace SafeExamBrowser.Client
{ {
base.OnStartup(e); base.OnStartup(e);
ShutdownMode = ShutdownMode.OnMainWindowClose;
instances.BuildObjectGraph(); instances.BuildObjectGraph();
instances.LogStartupInformation();
var success = instances.ClientController.TryStart(); var success = instances.ClientController.TryStart();
if (success) if (success)
{ {
MainWindow = instances.Taskbar; MainWindow = instances.Taskbar;
MainWindow.Closing += MainWindow_Closing;
MainWindow.Show(); MainWindow.Show();
} }
else else
@ -72,10 +73,12 @@ namespace SafeExamBrowser.Client
} }
} }
private void MainWindow_Closing(object sender, CancelEventArgs e) protected override void OnExit(ExitEventArgs e)
{ {
MainWindow?.Hide();
instances.ClientController.Terminate(); instances.ClientController.Terminate();
instances.LogShutdownInformation();
base.OnExit(e);
} }
} }
} }

View file

@ -17,10 +17,13 @@ namespace SafeExamBrowser.Client.Communication
{ {
internal class ClientHost : BaseHost, IClientHost internal class ClientHost : BaseHost, IClientHost
{ {
private int processId;
public Guid StartupToken { private get; set; } public Guid StartupToken { private get; set; }
public ClientHost(string address, ILogger logger) : base(address, logger) public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
{ {
this.processId = processId;
} }
protected override bool OnConnect(Guid? token) protected override bool OnConnect(Guid? token)
@ -36,13 +39,19 @@ namespace SafeExamBrowser.Client.Communication
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)
{ {
// TODO // TODO
return null;
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
protected override Response OnReceive(MessagePurport message) protected override Response OnReceive(SimpleMessagePurport message)
{ {
// TODO switch (message)
return null; {
case SimpleMessagePurport.Authenticate:
return new AuthenticationResponse { ProcessId = processId };
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
} }
} }

View file

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using SafeExamBrowser.Browser; using SafeExamBrowser.Browser;
using SafeExamBrowser.Client.Behaviour; using SafeExamBrowser.Client.Behaviour;
using SafeExamBrowser.Client.Behaviour.Operations; using SafeExamBrowser.Client.Behaviour.Operations;
@ -37,6 +38,10 @@ namespace SafeExamBrowser.Client
{ {
internal class CompositionRoot internal class CompositionRoot
{ {
private string logFilePath;
private string runtimeHostUri;
private Guid startupToken;
private ClientConfiguration configuration; private ClientConfiguration configuration;
private ILogger logger; private ILogger logger;
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
@ -49,21 +54,19 @@ namespace SafeExamBrowser.Client
internal void BuildObjectGraph() internal void BuildObjectGraph()
{ {
var args = Environment.GetCommandLineArgs(); ValidateCommandLineArguments();
Validate(args);
configuration = new ClientConfiguration(); configuration = new ClientConfiguration();
logger = new Logger(); logger = new Logger();
nativeMethods = new NativeMethods(); nativeMethods = new NativeMethods();
systemInfo = new SystemInfo(); systemInfo = new SystemInfo();
InitializeLogging(args[1]); InitializeLogging();
text = new Text(logger); text = new Text(logger);
uiFactory = new UserInterfaceFactory(text); uiFactory = new UserInterfaceFactory(text);
var runtimeProxy = new RuntimeProxy(args[2], new ModuleLogger(logger, typeof(RuntimeProxy))); var runtimeProxy = new RuntimeProxy(runtimeHostUri, new ModuleLogger(logger, typeof(RuntimeProxy)));
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods); var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods); var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods); var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
@ -73,25 +76,38 @@ namespace SafeExamBrowser.Client
var operations = new Queue<IOperation>(); var operations = new Queue<IOperation>();
operations.Enqueue(new I18nOperation(logger, text)); operations.Enqueue(new I18nOperation(logger, text));
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, Guid.Parse(args[3]))); operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy)); operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation)); operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation)); // TODO
operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor)); //operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
operations.Enqueue(new ProcessMonitorOperation(logger, processMonitor)); //operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
//operations.Enqueue(new ProcessMonitorOperation(logger, processMonitor));
operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar)); operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
operations.Enqueue(new DelayedInitializationOperation(BuildTaskbarOperation)); operations.Enqueue(new DelayedInitializationOperation(BuildTaskbarOperation));
operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation)); operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new ClipboardOperation(logger, nativeMethods)); operations.Enqueue(new ClipboardOperation(logger, nativeMethods));
operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation)); //operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation));
var sequence = new OperationSequence(logger, operations); var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, Taskbar, windowMonitor); ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, Taskbar, windowMonitor);
} }
private void Validate(string[] args) internal void LogStartupInformation()
{ {
logger.Log($"# New client instance started at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
logger.Log(string.Empty);
}
internal void LogShutdownInformation()
{
logger?.Log($"# Client instance terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
private void ValidateCommandLineArguments()
{
var args = Environment.GetCommandLineArgs();
var hasFour = args?.Length == 4; var hasFour = args?.Length == 4;
if (hasFour) if (hasFour)
@ -102,16 +118,20 @@ namespace SafeExamBrowser.Client
if (hasLogfilePath && hasHostUri && hasToken) if (hasLogfilePath && hasHostUri && hasToken)
{ {
logFilePath = args[1];
runtimeHostUri = args[2];
startupToken = Guid.Parse(args[3]);
return; return;
} }
} }
throw new ArgumentException("Invalid parameters! Required: SafeExamBrowser.Client.exe <logfile path> <host URI> <token>"); throw new ArgumentException("Invalid arguments! Required: SafeExamBrowser.Client.exe <logfile path> <host URI> <token>");
} }
private void InitializeLogging(string filePath) private void InitializeLogging()
{ {
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), filePath); var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath);
logFileWriter.Initialize(); logFileWriter.Initialize();
logger.Subscribe(logFileWriter); logger.Subscribe(logFileWriter);
@ -128,9 +148,12 @@ namespace SafeExamBrowser.Client
private IOperation BuildCommunicationHostOperation() private IOperation BuildCommunicationHostOperation()
{ {
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost))); var processId = Process.GetCurrentProcess().Id;
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost)), processId);
var operation = new CommunicationOperation(host, logger); var operation = new CommunicationOperation(host, logger);
host.StartupToken = startupToken;
return operation; return operation;
} }

View file

@ -73,6 +73,9 @@ namespace SafeExamBrowser.Configuration
}; };
settings.Browser.StartUrl = "https://www.duckduckgo.com"; settings.Browser.StartUrl = "https://www.duckduckgo.com";
settings.Taskbar.AllowApplicationLog = true;
settings.Taskbar.AllowKeyboardLayout = true;
settings.Taskbar.AllowWirelessNetwork = true;
CurrentSettings = settings; CurrentSettings = settings;

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Contracts.Communication
[ServiceKnownType(typeof(AuthenticationResponse))] [ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ConfigurationResponse))] [ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))] [ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(SimpleResponse))]
public interface ICommunication public interface ICommunication
{ {
/// <summary> /// <summary>

View file

@ -20,11 +20,13 @@ namespace SafeExamBrowser.Contracts.Communication
/// <summary> /// <summary>
/// Starts the host and opens it for communication. /// Starts the host and opens it for communication.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the host fails to start.</exception>
void Start(); void Start();
/// <summary> /// <summary>
/// Closes and terminates the host. /// Closes and terminates the host.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.CommunicationException">If the host fails to terminate.</exception>
void Stop(); void Stop();
} }
} }

View file

@ -17,5 +17,10 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
/// The communication token needed for authentication. /// The communication token needed for authentication.
/// </summary> /// </summary>
public Guid CommunicationToken { get; set; } public Guid CommunicationToken { get; set; }
public override string ToString()
{
return GetType().Name;
}
} }
} }

View file

@ -16,6 +16,16 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
/// <summary> /// <summary>
/// The purport of the message. /// The purport of the message.
/// </summary> /// </summary>
public MessagePurport Purport { get; set; } public SimpleMessagePurport Purport { get; set; }
public SimpleMessage(SimpleMessagePurport purport)
{
Purport = purport;
}
public override string ToString()
{
return $"{base.ToString()} -> {Purport}";
}
} }
} }

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Messages namespace SafeExamBrowser.Contracts.Communication.Messages
{ {
[Serializable] [Serializable]
public enum MessagePurport public enum SimpleMessagePurport
{ {
/// <summary> /// <summary>
/// Requests an interlocutor to submit data for authentication. /// Requests an interlocutor to submit data for authentication.

View file

@ -16,6 +16,6 @@ namespace SafeExamBrowser.Contracts.Communication.Responses
/// <summary> /// <summary>
/// The process identifier used for authentication. /// The process identifier used for authentication.
/// </summary> /// </summary>
public int ProcessId { get; } public int ProcessId { get; set; }
} }
} }

View file

@ -13,5 +13,9 @@ namespace SafeExamBrowser.Contracts.Communication.Responses
[Serializable] [Serializable]
public class Response public class Response
{ {
public override string ToString()
{
return GetType().Name;
}
} }
} }

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018 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.Responses
{
[Serializable]
public class SimpleResponse : Response
{
/// <summary>
/// The purport of the response.
/// </summary>
public SimpleResponsePurport Purport { get; set; }
public SimpleResponse(SimpleResponsePurport purport)
{
Purport = purport;
}
public override string ToString()
{
return $"{base.ToString()} -> {Purport}";
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 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.Responses
{
[Serializable]
public enum SimpleResponsePurport
{
/// <summary>
/// Signals an interlocutor that a message has been understood.
/// </summary>
Acknowledged = 1,
/// <summary>
/// Signals an interlocutor that a message has not been understood.
/// </summary>
UnknownMessage
}
}

View file

@ -66,13 +66,15 @@
<Compile Include="Communication\IServiceProxy.cs" /> <Compile Include="Communication\IServiceProxy.cs" />
<Compile Include="Communication\Messages\Message.cs" /> <Compile Include="Communication\Messages\Message.cs" />
<Compile Include="Communication\Messages\DisconnectionMessage.cs" /> <Compile Include="Communication\Messages\DisconnectionMessage.cs" />
<Compile Include="Communication\Messages\MessagePurport.cs" /> <Compile Include="Communication\Messages\SimpleMessagePurport.cs" />
<Compile Include="Communication\Messages\SimpleMessage.cs" /> <Compile Include="Communication\Messages\SimpleMessage.cs" />
<Compile Include="Communication\Responses\AuthenticationResponse.cs" /> <Compile Include="Communication\Responses\AuthenticationResponse.cs" />
<Compile Include="Communication\Responses\ConfigurationResponse.cs" /> <Compile Include="Communication\Responses\ConfigurationResponse.cs" />
<Compile Include="Communication\Responses\ConnectionResponse.cs" /> <Compile Include="Communication\Responses\ConnectionResponse.cs" />
<Compile Include="Communication\Responses\DisconnectionResponse.cs" /> <Compile Include="Communication\Responses\DisconnectionResponse.cs" />
<Compile Include="Communication\Responses\Response.cs" /> <Compile Include="Communication\Responses\Response.cs" />
<Compile Include="Communication\Responses\SimpleResponsePurport.cs" />
<Compile Include="Communication\Responses\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" /> <Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\RuntimeInfo.cs" /> <Compile Include="Configuration\RuntimeInfo.cs" />
<Compile Include="Configuration\ISession.cs" /> <Compile Include="Configuration\ISession.cs" />

View file

@ -8,6 +8,7 @@
using System; using System;
using System.ServiceModel; using System.ServiceModel;
using System.Threading;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages; using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
@ -18,16 +19,26 @@ namespace SafeExamBrowser.Core.Communication
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)] [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public abstract class BaseHost : ICommunication, ICommunicationHost public abstract class BaseHost : ICommunication, ICommunicationHost
{ {
private const int TWO_SECONDS = 2000;
private readonly object @lock = new object();
private string address; private string address;
private ILogger logger; private ILogger logger;
private ServiceHost host; private ServiceHost host;
private Thread hostThread;
protected Guid? CommunicationToken { get; private set; } protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; } protected ILogger Logger { get; private set; }
public bool IsRunning public bool IsRunning
{ {
get { return host?.State == CommunicationState.Opened; } get
{
lock (@lock)
{
return host?.State == CommunicationState.Opened;
}
}
} }
public BaseHost(string address, ILogger logger) public BaseHost(string address, ILogger logger)
@ -39,86 +50,111 @@ namespace SafeExamBrowser.Core.Communication
protected abstract bool OnConnect(Guid? token); protected abstract bool OnConnect(Guid? token);
protected abstract void OnDisconnect(); protected abstract void OnDisconnect();
protected abstract Response OnReceive(Message message); protected abstract Response OnReceive(Message message);
protected abstract Response OnReceive(MessagePurport message); protected abstract Response OnReceive(SimpleMessagePurport message);
public ConnectionResponse Connect(Guid? token = null) public ConnectionResponse Connect(Guid? token = null)
{ {
logger.Debug($"Received connection request with authentication token '{token}'."); lock (@lock)
var response = new ConnectionResponse();
var connected = OnConnect(token);
if (connected)
{ {
response.CommunicationToken = CommunicationToken = Guid.NewGuid(); logger.Debug($"Received connection request with authentication token '{token}'.");
response.ConnectionEstablished = true;
var response = new ConnectionResponse();
var connected = OnConnect(token);
if (connected)
{
response.CommunicationToken = CommunicationToken = Guid.NewGuid();
response.ConnectionEstablished = true;
}
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response;
} }
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response;
} }
public DisconnectionResponse Disconnect(DisconnectionMessage message) public DisconnectionResponse Disconnect(DisconnectionMessage message)
{ {
var response = new DisconnectionResponse(); lock (@lock)
// TODO: Compare with ToString in BaseProxy - needed?
logger.Debug($"Received disconnection request with message '{message}'.");
if (IsAuthorized(message?.CommunicationToken))
{ {
OnDisconnect(); var response = new DisconnectionResponse();
CommunicationToken = null; logger.Debug($"Received disconnection request with message '{ToString(message)}'.");
response.ConnectionTerminated = true;
if (IsAuthorized(message?.CommunicationToken))
{
OnDisconnect();
CommunicationToken = null;
response.ConnectionTerminated = true;
}
return response;
} }
return response;
} }
public Response Send(Message message) public Response Send(Message message)
{ {
Response response = null; lock (@lock)
logger.Debug($"Received message '{message}'.");
if (IsAuthorized(message?.CommunicationToken))
{ {
if (message is SimpleMessage) Response response = null;
logger.Debug($"Received message '{ToString(message)}'.");
if (IsAuthorized(message?.CommunicationToken))
{ {
response = OnReceive((message as SimpleMessage).Purport); if (message is SimpleMessage)
} {
else response = OnReceive((message as SimpleMessage).Purport);
{ }
response = OnReceive(message); else
{
response = OnReceive(message);
}
} }
logger.Debug($"Sending response '{ToString(response)}'.");
return response;
} }
logger.Debug($"Sending response '{response}'.");
return response;
} }
public void Start() public void Start()
{ {
host = new ServiceHost(this); lock (@lock)
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address); {
host.Closed += Host_Closed; var exception = default(Exception);
host.Closing += Host_Closing; var startedEvent = new AutoResetEvent(false);
host.Faulted += Host_Faulted;
host.Opened += Host_Opened;
host.Opening += Host_Opening;
host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open();
logger.Debug($"Successfully started communication host for endpoint '{address}'."); hostThread = new Thread(() => TryStartHost(startedEvent, out exception));
hostThread.SetApartmentState(ApartmentState.STA);
hostThread.IsBackground = true;
hostThread.Start();
var success = startedEvent.WaitOne(TWO_SECONDS);
if (!success)
{
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {TWO_SECONDS / 1000} seconds!", exception);
}
}
} }
public void Stop() public void Stop()
{ {
host?.Close(); lock (@lock)
logger.Debug($"Terminated communication host for endpoint '{address}'."); {
var success = TryStopHost(out Exception exception);
if (success)
{
logger.Debug($"Terminated communication host for endpoint '{address}'.");
}
else
{
throw new CommunicationException($"Failed to terminate communication host for endpoint '{address}'!", exception);
}
}
} }
private bool IsAuthorized(Guid? token) private bool IsAuthorized(Guid? token)
@ -126,6 +162,52 @@ namespace SafeExamBrowser.Core.Communication
return CommunicationToken == token; return CommunicationToken == token;
} }
private void TryStartHost(AutoResetEvent startedEvent, out Exception exception)
{
exception = null;
try
{
host = new ServiceHost(this);
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address);
host.Closed += Host_Closed;
host.Closing += Host_Closing;
host.Faulted += Host_Faulted;
host.Opened += Host_Opened;
host.Opening += Host_Opening;
host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open();
logger.Debug($"Successfully started communication host for endpoint '{address}'.");
startedEvent.Set();
}
catch (Exception e)
{
exception = e;
}
}
private bool TryStopHost(out Exception exception)
{
var success = false;
exception = null;
try
{
host?.Close();
success = hostThread.Join(TWO_SECONDS);
}
catch (Exception e)
{
exception = e;
success = false;
}
return success;
}
private void Host_Closed(object sender, EventArgs e) private void Host_Closed(object sender, EventArgs e)
{ {
logger.Debug("Communication host has been closed."); logger.Debug("Communication host has been closed.");
@ -155,5 +237,15 @@ namespace SafeExamBrowser.Core.Communication
{ {
logger.Debug($"Communication host has received an unknown message: {e?.Message}."); logger.Debug($"Communication host has received an unknown message: {e?.Message}.");
} }
private string ToString(Message message)
{
return message != null ? message.ToString() : "<null>";
}
private string ToString(Response response)
{
return response != null ? response.ToString() : "<null>";
}
} }
} }

View file

@ -40,7 +40,7 @@ namespace SafeExamBrowser.Core.Communication
(channel as ICommunicationObject).Opened += BaseProxy_Opened; (channel as ICommunicationObject).Opened += BaseProxy_Opened;
(channel as ICommunicationObject).Opening += BaseProxy_Opening; (channel as ICommunicationObject).Opening += BaseProxy_Opening;
Logger.Debug($"Trying to connect to endpoint {address} with authentication token '{token}'..."); Logger.Debug($"Trying to connect to endpoint {address}{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
var response = channel.Connect(token); var response = channel.Connect(token);
@ -70,21 +70,14 @@ namespace SafeExamBrowser.Core.Communication
var response = channel.Send(message); var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}."); Logger.Debug($"Sent message '{ToString(message)}', got response '{ToString(response)}'.");
return response; return response;
} }
protected Response Send(MessagePurport purport) protected Response Send(SimpleMessagePurport purport)
{ {
FailIfNotConnected(nameof(Send)); return Send(new SimpleMessage(purport));
var message = new SimpleMessage { Purport = purport };
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
return response;
} }
private void BaseProxy_Closed(object sender, EventArgs e) private void BaseProxy_Closed(object sender, EventArgs e)
@ -132,12 +125,12 @@ namespace SafeExamBrowser.Core.Communication
private string ToString(Message message) private string ToString(Message message)
{ {
return message != null ? $"message of type '{message.GetType()}'" : "no message"; return message != null ? message.ToString() : "<null>";
} }
private string ToString(Response response) private string ToString(Response response)
{ {
return response != null ? $"response of type '{response.GetType()}'" : "no response"; return response != null ? response.ToString() : "<null>";
} }
} }
} }

View file

@ -21,7 +21,9 @@ namespace SafeExamBrowser.Core.Communication
public AuthenticationResponse RequestAuthentication() public AuthenticationResponse RequestAuthentication()
{ {
return (AuthenticationResponse) Send(MessagePurport.ClientIsReady); var response = Send(SimpleMessagePurport.Authenticate);
return response as AuthenticationResponse;
} }
} }
} }

View file

@ -22,12 +22,12 @@ namespace SafeExamBrowser.Core.Communication
public ClientConfiguration GetConfiguration() public ClientConfiguration GetConfiguration()
{ {
return ((ConfigurationResponse) Send(MessagePurport.ConfigurationNeeded)).Configuration; return ((ConfigurationResponse) Send(SimpleMessagePurport.ConfigurationNeeded)).Configuration;
} }
public void InformClientReady() public void InformClientReady()
{ {
Send(MessagePurport.ClientIsReady); Send(SimpleMessagePurport.ClientIsReady);
} }
} }
} }

View file

@ -81,6 +81,15 @@ namespace SafeExamBrowser.Core.Logging
details.AppendLine(); details.AppendLine();
details.AppendLine(exception.StackTrace); details.AppendLine(exception.StackTrace);
for (var inner = exception.InnerException; inner != null; inner = inner.InnerException)
{
details.AppendLine();
details.AppendLine($" Inner Exception Message: {inner.Message}");
details.AppendLine($" Inner Exception Type: {inner.GetType()}");
details.AppendLine();
details.AppendLine(inner.StackTrace);
}
Add(LogLevel.Error, message); Add(LogLevel.Error, message);
Add(new LogText(details.ToString())); Add(new LogText(details.ToString()));
} }

View file

@ -104,19 +104,19 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
const int TEN_SECONDS = 10000; const int TEN_SECONDS = 10000;
var clientReady = new AutoResetEvent(false); var clientStarted = false;
var clientReadyHandler = new CommunicationEventHandler(() => clientReady.Set()); var clientReadyEvent = new AutoResetEvent(false);
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath; var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
var clientLogFile = $"{'"' + configuration.RuntimeInfo.ClientLogFile + '"'}"; var clientLogFile = $"{'"' + configuration.RuntimeInfo.ClientLogFile + '"'}";
var hostUri = configuration.RuntimeInfo.RuntimeAddress; var hostUri = configuration.RuntimeInfo.RuntimeAddress;
var token = session.StartupToken.ToString("D"); var token = session.StartupToken.ToString("D");
runtimeHost.ClientReady += clientReadyHandler; runtimeHost.ClientReady += clientReadyEventHandler;
session.ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token); session.ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
var clientStarted = clientReady.WaitOne(TEN_SECONDS); clientStarted = clientReadyEvent.WaitOne(TEN_SECONDS);
runtimeHost.ClientReady -= clientReadyEventHandler;
runtimeHost.ClientReady -= clientReadyHandler;
// TODO: Check if client process alive! // TODO: Check if client process alive!
if (clientStarted) if (clientStarted)
@ -126,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
var response = client.RequestAuthentication(); var response = client.RequestAuthentication();
// TODO: Further integrity checks necessary? // TODO: Further integrity checks necessary?
if (session.ClientProcess.Id == response.ProcessId) if (session.ClientProcess.Id == response?.ProcessId)
{ {
sessionRunning = true; sessionRunning = true;
} }

View file

@ -7,7 +7,6 @@
*/ */
using System; using System;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages; using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
@ -43,21 +42,21 @@ namespace SafeExamBrowser.Runtime.Communication
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)
{ {
// TODO // TODO
return null; return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
protected override Response OnReceive(MessagePurport message) protected override Response OnReceive(SimpleMessagePurport message)
{ {
switch (message) switch (message)
{ {
case MessagePurport.ClientIsReady: case SimpleMessagePurport.ClientIsReady:
ClientReady?.Invoke(); ClientReady?.Invoke();
break; return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case MessagePurport.ConfigurationNeeded: case SimpleMessagePurport.ConfigurationNeeded:
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() }; return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
} }
return null; return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
} }
} }

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.SystemComponents
public class WirelessNetwork : ISystemComponent<ISystemWirelessNetworkControl> public class WirelessNetwork : ISystemComponent<ISystemWirelessNetworkControl>
{ {
private const int TWO_SECONDS = 2000; private const int TWO_SECONDS = 2000;
private readonly object @lock = new object();
private ISystemWirelessNetworkControl control; private ISystemWirelessNetworkControl control;
private ILogger logger; private ILogger logger;
@ -144,7 +145,7 @@ namespace SafeExamBrowser.SystemComponents
private void UpdateControl() private void UpdateControl()
{ {
lock (networks) lock (@lock)
{ {
try try
{ {

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic" xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic"
xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="750" WindowStyle="None" WindowStartupLocation="CenterScreen" mc:Ignorable="d" Background="White" Foreground="White" Height="500" Width="850" WindowStyle="None" WindowStartupLocation="CenterScreen"
Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True"> Icon="./Images/SafeExamBrowser.ico" ResizeMode="NoResize" Title="Safe Exam Browser" Topmost="True">
<Grid> <Grid>
<Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5" Visibility="{Binding AnimatedBorderVisibility}"> <Border x:Name="AnimatedBorder" Panel.ZIndex="10" BorderBrush="DodgerBlue" BorderThickness="5" Visibility="{Binding AnimatedBorderVisibility}">
@ -36,10 +36,10 @@
<Grid Grid.Row="0"> <Grid Grid.Row="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="350" /> <ColumnDefinition Width="400" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" /> <Image Grid.Column="0" Grid.ColumnSpan="2" Margin="-25,0,0,0" Source="pack://application:,,,/SafeExamBrowser.UserInterface.Classic;component/Images/SplashScreen.png" />
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,175,10" TextWrapping="Wrap" /> <TextBlock x:Name="InfoTextBlock" Grid.Column="1" Foreground="Gray" Margin="10,75,225,10" TextWrapping="Wrap" />
</Grid> </Grid>
<ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue" <ProgressBar x:Name="ProgressBar" Grid.Row="1" Background="WhiteSmoke" BorderThickness="0" Foreground="DodgerBlue"
IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}" IsIndeterminate="{Binding IsIndeterminate}" Maximum="{Binding MaxProgress}" Value="{Binding CurrentProgress}"