Restore SEBPatch
This commit is contained in:
258
SafeExamBrowser.Communication/Hosts/BaseHost.cs
Normal file
258
SafeExamBrowser.Communication/Hosts/BaseHost.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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);
|
||||
protected abstract Response OnReceive(Message message);
|
||||
protected abstract Response OnReceive(SimpleMessagePurport message);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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>";
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user