Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Externals/CommonControls.dll
Binary file not shown.
7 changes: 4 additions & 3 deletions GeneratorForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,10 @@ private void SimulateToggle_CheckedChanged(object sender, EventArgs e)
_ = sender;
_ = e;

// Update the simulation status and button
m_Data.Simulated = !(m_Data.Simulated);
m_SimulateToggle.Checked = m_Data.Simulated;
// Take the simulation status from the toggle rather than by inverting what is recorded. The
// toggle is also set from code, such as when a session is loaded, and inverting the recorded
// value there disagrees with the control and sets the two of them toggling each other.
m_Data.Simulated = m_SimulateToggle.Checked;

// Record the device interface requires initialization
m_Timer.Initialized = false;
Expand Down
152 changes: 152 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Random Number Generator

A Windows desktop application for recording and analysing the output of a
[TruRNGpro](https://ubld.it/truerngpro) hardware random number generator.

The application samples the device, records every reading to a session file, charts the results as they
arrive, and compares recorded sessions against one another. A built-in simulator stands in for the device,
so the application can be run and developed without one attached.

## What it does

Each sample reads a block of bits from the device and records the **average of those bits**, which for an
unbiased generator sits around 0.5. Readings are taken ten times a second. The application shows, live:

- the running average, the number of data points, the deviation from the statistical mean, and the standard
deviation
- a chart of the individual readings against the running average, with the axis scaling itself to the data
- the elapsed session time

Every reading is written to the session file as it is taken, so a session survives the application being
closed or stopped unexpectedly.

### Analysis

A recorded session can be loaded as a **baseline** and another as a **result**, and the two are compared
directly: mean, standard deviation, skewness and kurtosis for each, the difference between their means, and
a histogram of the two distributions overlaid. Statistics are calculated with
[Math.NET Numerics](https://numerics.mathdotnet.com/).

### Targets

A session can record a target value of `0` or `1` — the outcome the operator is attempting to influence —
or `None`. The target is stored in the session file alongside the data.

## Requirements

- Windows
- Visual Studio 2022 with the **.NET desktop** and **Desktop development with C++** workloads
- .NET Framework 4.8
- A TruRNGpro on a USB serial port — optional, as the simulator can be used instead

## Building

Build the solution rather than the individual projects: the C# application depends on the native DLL, and
the post-build steps that place the DLL where the application and the tests can find it rely on the
solution directory.

```
msbuild RandomNumberGenerator.sln /p:Configuration=Debug /p:Platform="Any CPU"
```

MSBuild is not usually on the path; it is under the Visual Studio installation, for example
`C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\MSBuild.exe`.

NuGet packages are not committed, so restore them on a fresh clone. The projects use `packages.config`, so
`dotnet restore` will not work:

```
nuget restore RandomNumberGenerator.sln
```

The application is built to `bin\Debug\Random Number Generator.exe` and copied to `bin\`.

## Running

Start the application, then:

1. **Choose a source.** Leave *Simulate* unchecked and pick the COM port the device is on, or check it and
enter a seed for the simulator. The port list updates by itself as devices are connected and removed.
2. **Choose a file.** *Browse* selects the session file. An existing file is loaded and displayed, and the
session continues in it. A new file is created.
3. **Choose a target**, if the session has one.
4. **Start.** *Pause* suspends recording without ending the session; *Stop* ends it.

To analyse previous sessions, browse for a baseline file and a result file in the comparison section.

### Recovering a session

A file left unfinished — by the application being stopped while recording, or by the machine losing power —
is recovered when it is next opened. The readings it holds are loaded, the file is reported as having been
recovered, and recording continues in it. Only complete readings are recovered; a reading that was still
being written when the application stopped is discarded rather than being loaded as a smaller number.

## Session files

Sessions are XML, written as the data is recorded:

```xml
<?xml version="1.0" encoding="utf-8"?>
<Session Simulated="false" Target="-1">
<Data Time="00:00:00">0.499969482421875</Data>
<Data Time="00:00:00">0.500091552734375</Data>
</Session>
```

`Simulated` records whether the readings came from the simulator. `Target` is `0`, `1`, or `-1` for a
session with no target. `Time` is the elapsed session time when the reading was taken. Values are written
in the invariant culture, so a file written on one machine reads back as the same numbers on another.

The file is shared for reading while a session is in progress, so it can be inspected or backed up without
stopping the recording.

## Layout

| Project | |
|---|---|
| `RandomNumberGenerator.csproj` | The application. Windows Forms, .NET Framework 4.8, AnyCPU |
| `TruRNGpro/TruRNGpro.vcxproj` | Native DLL holding the device interface and the simulator. x64 |
| `RandomNumberGenerator.Test/` | Unit tests. MSTest and Moq |

The application talks to the native DLL through two exported functions, `Initialize` and
`GetRandomBitAverage`, which sit behind an interface implemented by both the real device and the simulator.

## Tests

```
vstest.console.exe "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll"
```

`vstest.console.exe` is under the Visual Studio installation, in
`Common7\IDE\CommonExtensions\Microsoft\TestWindow`. A single test or class can be selected:

```
vstest.console.exe "...\Random Number Generator.Test.dll" /Tests:WriteDataPoint_ValidWriter_Success
vstest.console.exe "...\Random Number Generator.Test.dll" /TestCaseFilter:"FullyQualifiedName~RNGXMLWriterTests"
```

Build the whole solution before running them, as the tests need the native DLL that the solution build puts
in place.

## Contributing

The coding standards are written down and are followed throughout:

- `CODING_GUIDELINES.md` for the application
- `RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md` for the tests
- `TruRNGpro/CODING_GUIDELINES_CPP.md` for the native code

`CLAUDE.md` describes the architecture and the build for anyone, or anything, new to the codebase.

## Third-party code

- [Math.NET Numerics](https://numerics.mathdotnet.com/) (MIT), for the statistics
- [Moq](https://github.com/devlooped/moq) and MSTest, for the tests
- `Externals/CommonControls.dll` and `Externals/DeviceInterfaces.dll`, committed binaries with no source
in this repository
- `TruRNGpro/rng.h`, a third-party header that wraps the serial port setup and reads for the device. Its
licensing has not been established, which is worth settling before the code is distributed.

## Copyright

Copyright (C) Mike Pullen. All rights reserved. The source files are marked confidential and proprietary.
55 changes: 55 additions & 0 deletions RandomNumberGenerator.Test/GeneratorForm.Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,61 @@ public void FileLoadingErrors_Integration_ProperErrorHandling()
StringAssert.Contains(generatorForm.StatusBoxText, "Result file not found", "Should show result file not found error");
}

/// <summary>
/// Tests setting the simulate toggle from code settles rather than toggling back and forth.
/// NOTE: Loading a session sets this toggle to match the file. Taking the new state by inverting
/// what is recorded rather than by reading the toggle left the two of them setting each other
/// without end, which overflowed the stack and took the application down as a file was opened.
/// </summary>
[TestMethod]
[TestCategory("Component")]
[Timeout(15000)]
public void SimulateToggle_SetFromCode_MatchesTheToggle()
{
//**************************************************************//
// Arrange
//**************************************************************//

// Mock the session timer and setup the properties
Mock<IRNGSessionTimer> mockSessionTimer = new Mock<IRNGSessionTimer>();
mockSessionTimer.SetupAllProperties();

// Mock the session data, recording it as simulated as a loaded session would
Mock<IRNGSessionData> mockSessionData = new Mock<IRNGSessionData>();
mockSessionData.Setup(mock => mock.Timer).Returns(mockSessionTimer.Object);
mockSessionData.SetupProperty(mock => mock.Simulated, true);
Mock<IRNGDeviceTimer> mockDeviceTimer = new Mock<IRNGDeviceTimer>();

// Create the object under test
GeneratorForm generatorForm = new GeneratorForm(mockSessionData.Object, mockDeviceTimer.Object);

// Reach the toggle, which is not exposed outside of the form
System.Reflection.FieldInfo toggleField = typeof(GeneratorForm).GetField("m_SimulateToggle",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(toggleField, "The simulate toggle should be present on the form");
System.Windows.Forms.CheckBox simulateToggle = (System.Windows.Forms.CheckBox)toggleField.GetValue(generatorForm);

//**************************************************************//
// Act
//**************************************************************//

// Set the toggle the way loading a simulated session does, which disagrees with the toggle
simulateToggle.Checked = true;

//**************************************************************//
// Assert
//**************************************************************//

// Verify the toggle and what is recorded agree, rather than having driven each other
Assert.IsTrue(simulateToggle.Checked, "The toggle should stay set");
Assert.IsTrue(mockSessionData.Object.Simulated, "The recorded state should match the toggle");

// And that setting it back settles the same way
simulateToggle.Checked = false;
Assert.IsFalse(simulateToggle.Checked, "The toggle should stay clear");
Assert.IsFalse(mockSessionData.Object.Simulated, "The recorded state should match the toggle");
}

#endregion
}
}
4 changes: 4 additions & 0 deletions RandomNumberGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@
if errorlevel 1 goto end
echo Copied to "$(SolutionDir)bin\"

xcopy /y "$(SolutionDir)bin\TruRNGpro.dll" "$(TargetDir)"
if errorlevel 1 goto end
echo Copied to "$(TargetDir)"

:end</PostBuildEvent>
</PropertyGroup>
</Project>