2018-02-06 15:12:11 +01:00
|
|
|
|
/*
|
2024-03-05 18:37:42 +01:00
|
|
|
|
* Copyright (c) 2024 ETH Zürich, IT Services
|
2018-02-06 15:12:11 +01:00
|
|
|
|
*
|
|
|
|
|
* 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;
|
2019-06-18 10:18:56 +02:00
|
|
|
|
using System.Collections.Generic;
|
2018-02-06 15:12:11 +01:00
|
|
|
|
using System.ServiceModel;
|
2018-02-16 13:15:16 +01:00
|
|
|
|
using System.Threading;
|
2019-08-30 09:55:26 +02:00
|
|
|
|
using SafeExamBrowser.Communication.Contracts;
|
|
|
|
|
using SafeExamBrowser.Communication.Contracts.Data;
|
|
|
|
|
using SafeExamBrowser.Communication.Contracts.Hosts;
|
|
|
|
|
using SafeExamBrowser.Logging.Contracts;
|
2018-02-06 15:12:11 +01:00
|
|
|
|
|
2018-08-31 10:06:27 +02:00
|
|
|
|
namespace SafeExamBrowser.Communication.Hosts
|
2018-02-06 15:12:11 +01:00
|
|
|
|
{
|
2018-03-06 14:35:10 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The base implementation of an <see cref="ICommunicationHost"/>. Runs the host on a new, separate thread.
|
|
|
|
|
/// </summary>
|
2018-02-06 15:12:11 +01:00
|
|
|
|
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
|
|
|
|
|
public abstract class BaseHost : ICommunication, ICommunicationHost
|
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
private readonly object @lock = new object();
|
|
|
|
|
|
2018-02-06 15:12:11 +01:00
|
|
|
|
private string address;
|
2018-03-16 15:46:53 +01:00
|
|
|
|
private IHostObject host;
|
|
|
|
|
private IHostObjectFactory factory;
|
2018-02-16 13:15:16 +01:00
|
|
|
|
private Thread hostThread;
|
2018-10-02 08:02:48 +02:00
|
|
|
|
private int timeout_ms;
|
2018-02-06 15:12:11 +01:00
|
|
|
|
|
2019-06-18 10:18:56 +02:00
|
|
|
|
protected IList<Guid> CommunicationToken { get; private set; }
|
2018-02-14 15:26:05 +01:00
|
|
|
|
protected ILogger Logger { get; private set; }
|
|
|
|
|
|
2018-02-06 15:12:11 +01:00
|
|
|
|
public bool IsRunning
|
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
|
|
|
|
return host?.State == CommunicationState.Opened;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-02 08:02:48 +02:00
|
|
|
|
public BaseHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms)
|
2018-02-06 15:12:11 +01:00
|
|
|
|
{
|
|
|
|
|
this.address = address;
|
2019-06-18 10:18:56 +02:00
|
|
|
|
this.CommunicationToken = new List<Guid>();
|
2018-03-16 15:46:53 +01:00
|
|
|
|
this.factory = factory;
|
2018-03-08 15:27:12 +01:00
|
|
|
|
this.Logger = logger;
|
2018-10-02 08:02:48 +02:00
|
|
|
|
this.timeout_ms = timeout_ms;
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-14 15:26:05 +01:00
|
|
|
|
protected abstract bool OnConnect(Guid? token);
|
2019-06-18 10:18:56 +02:00
|
|
|
|
protected abstract void OnDisconnect(Interlocutor interlocutor);
|
2018-02-15 15:42:54 +01:00
|
|
|
|
protected abstract Response OnReceive(Message message);
|
2018-02-16 13:15:16 +01:00
|
|
|
|
protected abstract Response OnReceive(SimpleMessagePurport message);
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-15 15:42:54 +01:00
|
|
|
|
public ConnectionResponse Connect(Guid? token = null)
|
2018-02-08 13:32:48 +01:00
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
2019-06-18 10:18:56 +02:00
|
|
|
|
Logger.Debug($"Received connection request {(token.HasValue ? $"with authentication token '{token}'" : "without authentication token")}.");
|
2018-02-14 15:26:05 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
var response = new ConnectionResponse();
|
|
|
|
|
var connected = OnConnect(token);
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
if (connected)
|
|
|
|
|
{
|
2019-06-18 10:18:56 +02:00
|
|
|
|
var communicationToken = Guid.NewGuid();
|
|
|
|
|
|
|
|
|
|
response.CommunicationToken = communicationToken;
|
2018-02-16 13:15:16 +01:00
|
|
|
|
response.ConnectionEstablished = true;
|
2019-06-18 10:18:56 +02:00
|
|
|
|
|
|
|
|
|
CommunicationToken.Add(communicationToken);
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
return response;
|
|
|
|
|
}
|
2018-02-08 13:32:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-15 15:42:54 +01:00
|
|
|
|
public DisconnectionResponse Disconnect(DisconnectionMessage message)
|
2018-02-08 13:32:48 +01:00
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
|
|
|
|
var response = new DisconnectionResponse();
|
2018-02-14 15:26:05 +01:00
|
|
|
|
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug($"Received disconnection request with message '{ToString(message)}'.");
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
if (IsAuthorized(message?.CommunicationToken))
|
|
|
|
|
{
|
2019-06-18 10:18:56 +02:00
|
|
|
|
OnDisconnect(message.Interlocutor);
|
2018-02-14 15:26:05 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
response.ConnectionTerminated = true;
|
2019-06-18 10:18:56 +02:00
|
|
|
|
CommunicationToken.Remove(message.CommunicationToken);
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
2018-02-14 15:26:05 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
return response;
|
|
|
|
|
}
|
2018-02-08 13:32:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-15 15:42:54 +01:00
|
|
|
|
public Response Send(Message message)
|
2018-02-08 13:32:48 +01:00
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
2018-02-22 16:15:06 +01:00
|
|
|
|
var response = new SimpleResponse(SimpleResponsePurport.Unauthorized) as Response;
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
if (IsAuthorized(message?.CommunicationToken))
|
2018-02-14 15:26:05 +01:00
|
|
|
|
{
|
2018-02-27 15:28:54 +01:00
|
|
|
|
switch (message)
|
2018-02-16 13:15:16 +01:00
|
|
|
|
{
|
2018-02-27 15:28:54 +01:00
|
|
|
|
case SimpleMessage simpleMessage when simpleMessage.Purport == SimpleMessagePurport.Ping:
|
|
|
|
|
response = new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
|
|
|
|
break;
|
|
|
|
|
case SimpleMessage simpleMessage:
|
|
|
|
|
response = OnReceive(simpleMessage.Purport);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
response = OnReceive(message);
|
|
|
|
|
break;
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
2018-02-14 15:26:05 +01:00
|
|
|
|
}
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug($"Received message '{ToString(message)}', sending response '{ToString(response)}'.");
|
2018-02-08 13:32:48 +01:00
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
return response;
|
|
|
|
|
}
|
2018-02-08 13:32:48 +01:00
|
|
|
|
}
|
2018-02-06 15:12:11 +01:00
|
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
|
|
|
|
var exception = default(Exception);
|
|
|
|
|
var startedEvent = new AutoResetEvent(false);
|
|
|
|
|
|
|
|
|
|
hostThread = new Thread(() => TryStartHost(startedEvent, out exception));
|
|
|
|
|
hostThread.SetApartmentState(ApartmentState.STA);
|
|
|
|
|
hostThread.IsBackground = true;
|
|
|
|
|
hostThread.Start();
|
2018-02-06 15:12:11 +01:00
|
|
|
|
|
2018-10-02 08:02:48 +02:00
|
|
|
|
var success = startedEvent.WaitOne(timeout_ms);
|
2018-02-16 13:15:16 +01:00
|
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
|
{
|
2018-10-02 08:02:48 +02:00
|
|
|
|
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {timeout_ms / 1000} seconds!", exception);
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
|
{
|
2018-02-16 13:15:16 +01:00
|
|
|
|
lock (@lock)
|
|
|
|
|
{
|
|
|
|
|
var success = TryStopHost(out Exception exception);
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug($"Terminated communication host for endpoint '{address}'.");
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
2018-03-16 15:46:53 +01:00
|
|
|
|
else if (exception != null)
|
2018-02-16 13:15:16 +01:00
|
|
|
|
{
|
|
|
|
|
throw new CommunicationException($"Failed to terminate communication host for endpoint '{address}'!", exception);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-14 15:26:05 +01:00
|
|
|
|
private bool IsAuthorized(Guid? token)
|
|
|
|
|
{
|
2019-06-18 10:18:56 +02:00
|
|
|
|
return token.HasValue && CommunicationToken.Contains(token.Value);
|
2018-02-14 15:26:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
private void TryStartHost(AutoResetEvent startedEvent, out Exception exception)
|
|
|
|
|
{
|
|
|
|
|
exception = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2018-03-16 15:46:53 +01:00
|
|
|
|
host = factory.CreateObject(address, this);
|
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
host.Closed += Host_Closed;
|
|
|
|
|
host.Closing += Host_Closing;
|
|
|
|
|
host.Faulted += Host_Faulted;
|
|
|
|
|
host.Opened += Host_Opened;
|
|
|
|
|
host.Opening += Host_Opening;
|
|
|
|
|
|
2018-03-16 15:46:53 +01:00
|
|
|
|
host.Open();
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug($"Successfully started communication host for endpoint '{address}'.");
|
2018-02-16 13:15:16 +01:00
|
|
|
|
|
|
|
|
|
startedEvent.Set();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
exception = e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryStopHost(out Exception exception)
|
|
|
|
|
{
|
|
|
|
|
var success = false;
|
|
|
|
|
|
|
|
|
|
exception = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
host?.Close();
|
2018-10-02 08:02:48 +02:00
|
|
|
|
success = hostThread?.Join(timeout_ms) == true;
|
2018-02-16 13:15:16 +01:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
exception = e;
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-06 15:12:11 +01:00
|
|
|
|
private void Host_Closed(object sender, EventArgs e)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug("Communication host has been closed.");
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Host_Closing(object sender, EventArgs e)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug("Communication host is closing...");
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Host_Faulted(object sender, EventArgs e)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Error("Communication host has faulted!");
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Host_Opened(object sender, EventArgs e)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug("Communication host has been opened.");
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Host_Opening(object sender, EventArgs e)
|
|
|
|
|
{
|
2018-03-08 15:27:12 +01:00
|
|
|
|
Logger.Debug("Communication host is opening...");
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-16 13:15:16 +01:00
|
|
|
|
private string ToString(Message message)
|
|
|
|
|
{
|
|
|
|
|
return message != null ? message.ToString() : "<null>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ToString(Response response)
|
|
|
|
|
{
|
|
|
|
|
return response != null ? response.ToString() : "<null>";
|
|
|
|
|
}
|
2018-02-06 15:12:11 +01:00
|
|
|
|
}
|
|
|
|
|
}
|