seb-win-refactoring/SafeExamBrowser.Communication/Hosts/BaseHost.cs

259 lines
6.3 KiB
C#
Raw Normal View History

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