Skip to content

Commit

Permalink
SEBSP-23: Implemented scaffolding for network redundancy.
Browse files Browse the repository at this point in the history
  • Loading branch information
dbuechel committed Feb 16, 2024
1 parent 731a748 commit a213ec0
Show file tree
Hide file tree
Showing 13 changed files with 738 additions and 195 deletions.
4 changes: 2 additions & 2 deletions SafeExamBrowser.Proctoring/ProctoringImplementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void INotification.Terminate()
internal abstract void Stop();
internal abstract void Terminate();

protected abstract void ActivateNotification();
protected abstract void TerminateNotification();
protected virtual void ActivateNotification() { }
protected virtual void TerminateNotification() { }
}
}
4 changes: 4 additions & 0 deletions SafeExamBrowser.Proctoring/SafeExamBrowser.Proctoring.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,27 @@
<Compile Include="ScreenProctoring\Data\IntervalTrigger.cs" />
<Compile Include="ScreenProctoring\Data\KeyboardTrigger.cs" />
<Compile Include="ScreenProctoring\Data\MouseTrigger.cs" />
<Compile Include="ScreenProctoring\Events\DataCollectedEventHandler.cs" />
<Compile Include="ScreenProctoring\Imaging\Extensions.cs" />
<Compile Include="ScreenProctoring\Data\Metadata.cs" />
<Compile Include="ScreenProctoring\Imaging\ProcessingOrder.cs" />
<Compile Include="ScreenProctoring\Imaging\ScreenShot.cs" />
<Compile Include="ScreenProctoring\ScreenProctoringImplementation.cs" />
<Compile Include="ScreenProctoring\Service\Api.cs" />
<Compile Include="ScreenProctoring\DataCollector.cs" />
<Compile Include="ScreenProctoring\Service\Parser.cs" />
<Compile Include="ScreenProctoring\Service\Requests\ContentType.cs" />
<Compile Include="ScreenProctoring\Service\Requests\CreateSessionRequest.cs" />
<Compile Include="ScreenProctoring\Service\Requests\Header.cs" />
<Compile Include="ScreenProctoring\Service\Requests\Extensions.cs" />
<Compile Include="ScreenProctoring\Service\Requests\HealthRequest.cs" />
<Compile Include="ScreenProctoring\Service\Requests\TerminateSessionRequest.cs" />
<Compile Include="ScreenProctoring\Service\Requests\OAuth2TokenRequest.cs" />
<Compile Include="ScreenProctoring\Service\Requests\Request.cs" />
<Compile Include="ScreenProctoring\Service\Requests\ScreenShotRequest.cs" />
<Compile Include="ScreenProctoring\Service\ServiceProxy.cs" />
<Compile Include="ScreenProctoring\Service\ServiceResponse.cs" />
<Compile Include="ScreenProctoring\TransmissionSpooler.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
Expand Down
65 changes: 26 additions & 39 deletions SafeExamBrowser.Proctoring/ScreenProctoring/Data/Metadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
Expand All @@ -28,14 +27,16 @@ internal class Metadata

internal string ApplicationInfo { get; private set; }
internal string BrowserInfo { get; private set; }
internal TimeSpan Elapsed { get; private set; }
internal string TriggerInfo { get; private set; }
internal string Urls { get; private set; }
internal string WindowTitle { get; private set; }

internal Metadata(IApplicationMonitor applicationMonitor, IBrowserApplication browser, ILogger logger)
internal Metadata(IApplicationMonitor applicationMonitor, IBrowserApplication browser, TimeSpan elapsed, ILogger logger)
{
this.applicationMonitor = applicationMonitor;
this.browser = browser;
this.Elapsed = elapsed;
this.logger = logger;
}

Expand All @@ -57,6 +58,7 @@ internal void Capture(IntervalTrigger interval = default, KeyboardTrigger keyboa
CaptureMouseTrigger(mouse);
}

// TODO: Can only log URLs when allowed by policy in browser configuration!
logger.Debug($"Captured metadata: {ApplicationInfo} / {BrowserInfo} / {TriggerInfo} / {Urls} / {WindowTitle}.");
}

Expand All @@ -79,7 +81,7 @@ private void CaptureApplicationData()
if (applicationMonitor.TryGetActiveApplication(out var application))
{
ApplicationInfo = BuildApplicationInfo(application);
WindowTitle = BuildWindowTitle(application);
WindowTitle = string.IsNullOrEmpty(application.Window.Title) ? "-" : application.Window.Title;
}
else
{
Expand All @@ -88,46 +90,12 @@ private void CaptureApplicationData()
}
}

private string BuildApplicationInfo(ActiveApplication application)
{
var info = new StringBuilder();

info.Append(application.Process.Name);

if (application.Process.OriginalName != default)
{
info.Append($" ({application.Process.OriginalName}{(application.Process.Signature == default ? ")" : "")}");
}

if (application.Process.Signature != default)
{
info.Append($"{(application.Process.OriginalName == default ? "(" : ", ")}{application.Process.Signature})");
}

return info.ToString();
}

private string BuildWindowTitle(ActiveApplication application)
{
return string.IsNullOrEmpty(application.Window.Title) ? "-" : application.Window.Title;
}

private void CaptureBrowserData()
{
var windows = browser.GetWindows();

BrowserInfo = BuildBrowserInfo(windows);
Urls = BuildUrls(windows);
}

private string BuildUrls(IEnumerable<IBrowserWindow> windows)
{
return string.Join(", ", windows.Select(w => w.Url));
}

private string BuildBrowserInfo(IEnumerable<IBrowserWindow> windows)
{
return string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title} ({w.Url})"));
BrowserInfo = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title} ({w.Url})"));
Urls = string.Join(", ", windows.Select(w => w.Url));
}

private void CaptureIntervalTrigger(IntervalTrigger interval)
Expand All @@ -154,5 +122,24 @@ private void CaptureMouseTrigger(MouseTrigger mouse)
TriggerInfo = $"{mouse.Button} mouse button has been {mouse.State.ToString().ToLower()} at ({mouse.Info.X}/{mouse.Info.Y}).";
}
}

private string BuildApplicationInfo(ActiveApplication application)
{
var info = new StringBuilder();

info.Append(application.Process.Name);

if (application.Process.OriginalName != default)
{
info.Append($" ({application.Process.OriginalName}{(application.Process.Signature == default ? ")" : "")}");
}

if (application.Process.Signature != default)
{
info.Append($"{(application.Process.OriginalName == default ? "(" : ", ")}{application.Process.Signature})");
}

return info.ToString();
}
}
}
176 changes: 176 additions & 0 deletions SafeExamBrowser.Proctoring/ScreenProctoring/DataCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright (c) 2023 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 System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Input;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
using SafeExamBrowser.Proctoring.ScreenProctoring.Events;
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Contracts.Events;
using MouseButton = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButton;
using MouseButtonState = SafeExamBrowser.WindowsApi.Contracts.Events.MouseButtonState;
using Timer = System.Timers.Timer;

namespace SafeExamBrowser.Proctoring.ScreenProctoring
{
internal class DataCollector
{
private readonly object @lock = new object();

private readonly IApplicationMonitor applicationMonitor;
private readonly IBrowserApplication browser;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly ScreenProctoringSettings settings;
private readonly Timer timer;

private DateTime last;
private Guid? keyboardHookId;
private Guid? mouseHookId;

internal event DataCollectedEventHandler DataCollected;

internal DataCollector(
IApplicationMonitor applicationMonitor,
IBrowserApplication browser,
IModuleLogger logger,
INativeMethods nativeMethods,
ScreenProctoringSettings settings)
{
this.applicationMonitor = applicationMonitor;
this.browser = browser;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.settings = settings;
this.timer = new Timer();
}

internal void Start()
{
last = DateTime.Now;

keyboardHookId = nativeMethods.RegisterKeyboardHook(KeyboardHookCallback);
mouseHookId = nativeMethods.RegisterMouseHook(MouseHookCallback);

timer.AutoReset = false;
timer.Elapsed += MaxIntervalElapsed;
timer.Interval = settings.MaxInterval;
timer.Start();

logger.Debug("Started.");
}

internal void Stop()
{
last = DateTime.Now;

if (keyboardHookId.HasValue)
{
nativeMethods.DeregisterKeyboardHook(keyboardHookId.Value);
}

if (mouseHookId.HasValue)
{
nativeMethods.DeregisterMouseHook(mouseHookId.Value);
}

keyboardHookId = default;
mouseHookId = default;

timer.Elapsed -= MaxIntervalElapsed;
timer.Stop();

logger.Debug("Stopped.");
}

private bool KeyboardHookCallback(int keyCode, KeyModifier modifier, KeyState state)
{
var trigger = new KeyboardTrigger
{
Key = KeyInterop.KeyFromVirtualKey(keyCode),
Modifier = modifier,
State = state
};

TryCollect(keyboard: trigger);

return false;
}

private void MaxIntervalElapsed(object sender, ElapsedEventArgs args)
{
var trigger = new IntervalTrigger
{
ConfigurationValue = settings.MaxInterval,
TimeElapsed = Convert.ToInt32(DateTime.Now.Subtract(last).TotalMilliseconds)
};

TryCollect(interval: trigger);
}

private bool MouseHookCallback(MouseButton button, MouseButtonState state, MouseInformation info)
{
var trigger = new MouseTrigger
{
Button = button,
Info = info,
State = state
};

TryCollect(mouse: trigger);

return false;
}

private void TryCollect(IntervalTrigger interval = default, KeyboardTrigger keyboard = default, MouseTrigger mouse = default)
{
if (MinIntervalElapsed() && Monitor.TryEnter(@lock))
{
var elapsed = DateTime.Now.Subtract(last);

last = DateTime.Now;
timer.Stop();

Task.Run(() =>
{
try
{
var metadata = new Metadata(applicationMonitor, browser, elapsed, logger.CloneFor(nameof(Metadata)));
var screenShot = new ScreenShot(logger.CloneFor(nameof(ScreenShot)), settings);
metadata.Capture(interval, keyboard, mouse);
screenShot.Take();
screenShot.Compress();
DataCollected?.Invoke(metadata, screenShot);
}
catch (Exception e)
{
logger.Error("Failed to execute data collection!", e);
}
});

timer.Start();
Monitor.Exit(@lock);
}
}

private bool MinIntervalElapsed()
{
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.MinInterval);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2023 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.Proctoring.ScreenProctoring.Data;
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;

namespace SafeExamBrowser.Proctoring.ScreenProctoring.Events
{
internal delegate void DataCollectedEventHandler(Metadata metadata, ScreenShot screenShot);
}
Loading

0 comments on commit a213ec0

Please sign in to comment.