From 7326d16cf37a2fc32e533281575c0ab233fcbfa4 Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:06:48 -0500 Subject: [PATCH 1/4] Add README Describes what the application records and analyses, how to build and run it, the session file format and the recovery behaviour, the layout of the three projects, and how to run the tests. Points at the coding guidelines already in the repository. Notes that the licensing of the third-party header in TruRNGpro has not been established, which is worth settling before the code is distributed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- README.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e2b6f1 --- /dev/null +++ b/README.md @@ -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 + + + 0.499969482421875 + 0.500091552734375 + +``` + +`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. From ac7350d2bc2322753665a7178b829034dc592502 Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Wed, 2 Sep 2026 07:12:42 -0500 Subject: [PATCH 2/4] Deploy TruRNGpro.dll alongside the application Starting a session threw DllNotFoundException for TruRNGpro.dll. The native DLL was copied to the solution bin directory and into the test output, but nothing put it next to the application in its own output directory, which is where the application runs from and so where it looks for it. The test project already copies the DLL into its output, which is why the tests exercised the device interface without trouble while the application could not load it at all. The application project now does the same. Verified by loading the built assembly with neither the PATH nor the working directory pointing at the DLL, calling InitializeDevice in simulate mode and reading from the simulator. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- RandomNumberGenerator.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RandomNumberGenerator.csproj b/RandomNumberGenerator.csproj index e5e0a49..f327200 100644 --- a/RandomNumberGenerator.csproj +++ b/RandomNumberGenerator.csproj @@ -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 \ No newline at end of file From 4d01cf60a3d9011882ed25a1e1ba98ec0cc03ef6 Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:33:18 -0500 Subject: [PATCH 3/4] Fix the crash when a session file is opened Opening a session file took the application down with a stack overflow. Loading a session sets the simulate toggle to match the file it came from. The handler for that toggle worked out the new state by inverting what was recorded rather than by reading the toggle, and then set the toggle from that result. Setting it from code with a value the recording disagreed with left the two of them setting each other without end. The handler now takes the state from the toggle and no longer writes to it, so setting it from code settles. This is reached by opening any session recorded in a different mode to the one currently selected, which includes the ordinary case of opening a simulated session in a freshly started application. The file itself has nothing to do with it; a properly closed file and one left unfinished both brought the application down in the same way. Found by driving the interface rather than the classes behind it. The unit tests do not build the form's event wiring, and a session had to be started before opening a file for the toggle to already agree and the fault to be missed. A test covers the invariant now, reaching the toggle by reflection as it is not exposed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- GeneratorForm.cs | 7 ++- .../GeneratorForm.Test.cs | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/GeneratorForm.cs b/GeneratorForm.cs index 59e99ed..d1004fa 100644 --- a/GeneratorForm.cs +++ b/GeneratorForm.cs @@ -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; diff --git a/RandomNumberGenerator.Test/GeneratorForm.Test.cs b/RandomNumberGenerator.Test/GeneratorForm.Test.cs index 1a88284..b686ca2 100644 --- a/RandomNumberGenerator.Test/GeneratorForm.Test.cs +++ b/RandomNumberGenerator.Test/GeneratorForm.Test.cs @@ -1667,6 +1667,61 @@ public void FileLoadingErrors_Integration_ProperErrorHandling() StringAssert.Contains(generatorForm.StatusBoxText, "Result file not found", "Should show result file not found error"); } + /// + /// 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. + /// + [TestMethod] + [TestCategory("Component")] + [Timeout(15000)] + public void SimulateToggle_SetFromCode_MatchesTheToggle() + { + //**************************************************************// + // Arrange + //**************************************************************// + + // Mock the session timer and setup the properties + Mock mockSessionTimer = new Mock(); + mockSessionTimer.SetupAllProperties(); + + // Mock the session data, recording it as simulated as a loaded session would + Mock mockSessionData = new Mock(); + mockSessionData.Setup(mock => mock.Timer).Returns(mockSessionTimer.Object); + mockSessionData.SetupProperty(mock => mock.Simulated, true); + Mock mockDeviceTimer = new Mock(); + + // 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 } } \ No newline at end of file From ecf16e0d7eb7e6ad63557c7cbf4224d156f54114 Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:11:29 -0500 Subject: [PATCH 4/4] Update CommonControls.dll Replaces the committed binary with the build supplied for this work. There is no source for it in this repository, so it is carried as a binary the same way DeviceInterfaces.dll is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- Externals/CommonControls.dll | Bin 7168 -> 8704 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Externals/CommonControls.dll b/Externals/CommonControls.dll index b0c509587f428ae302098c21b625db180221544f..67018a0af5627a0ce99bdd15d344828277589686 100644 GIT binary patch literal 8704 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 UKNZnS4bR?R-p;<$vy~b6Ut}yaMgRZ+ delta 2548 zcmai0eQcH09e&PvKkn_l?QP%pmiG3xwr_j+cxg**i*tZMsVxlF8R)Q`Hur+<`eOIB0jrg<3@A1y z>*K`(B-HOSdr?p?n}f8N7kSH}<*~TI1EMxoY?3Zxv02K4XT>~`4Bin9RfpM~`c|?? zSnyK7bO|7WPZK{3)<`pVgN`%mmjRJ60hrQf?~yU7sdkM^E=OVl=N%cdG>t3Dap{(m z%DVQts_ZPXOa-Aa-fG|tj=_!{=~vR4Yh)6vl9?)EE-$2?q{vh&s3DX`%A?Z1jBG(t zj;evOwG+|WoP;;j}&^d8TwvN9cUMMZ@_ zn{ij#wy(CtH9}#3jw0NxxvcG6I+4yNh2xfMJEmjlgzP*<=VW|^MI7+ZwS+4=GxL?< zsEOU*`8_AoNS}ohG0@GL%X3^*B=q=&eM-1IWi@L49MY9H_)Tj1O<@-azv;U`bvo+G zP)N_%4rk{CC(Mdmvl^gQj!JG+;}U5UuE&hc@$$AJ(x0)_S?FvH!(NW6izHqvX61Vc~VwFLD8Et`((0yzW z#?v@XyvI6@HJ%!1M`D7L5;&yQG;Xs_Qa!@=1Z>A^#wqTw$6Kt_fX1{l1_+U_%psDyT2m&cxpe>Rz_9FkMi)+0MeN1+|s! zY`j@e53-$&j|!?$(MA<9g84aCWoXm5SYwCART_IVrZo-{XJM1(w`&~H_zz+dyHIA< z5pAjP*OtOZiQ5^q@d9x+PGiz&B>vc1ib?D-J8%xUkXS*h*ZK)@-0Y!sm)S>t9s2QG zHtS;-9pvY5))>H_>1-tb+Q<;ki#za~?)qus!)#@nOR6n;t0|9kdm@vshbXnsswV0g^>H-amp zKTp^3h}GYkneUH0FZKPmmw$oZ_YUC*uf_l7b?X#^^TE``XpIQ!OQwP|C8?k!Rv9dg zHBa3gyGl$QFMU!3zjCr-F!;o2E?f2F`B@(y{7=WuzpiR*Sa;L*pxI64Oizd!_U0_N zJSoCqi`p414q5JX{MY03x@p5h*!19T)3FgnZzrf0%w5i~46&^{=JwdSF4i*b_T+7+ zw=@(AJL{c6r#FwvS1jb2hH!d=b8eUD4O-#{Dk=rs&de`Hwrsj(<5utJ)1KGf+P*Nj z7~d64S6;y%0Q|mr{>t}M&BbFQOAU9jCLYu#8h&uHd20B=nMEu2eu$H2S8SV9r`5Hj zfA#QC*OpDc9P01N+`Mh`(D2r5e`jXHQ2*MEgIk6+1nU!*yU*m0gZaQQ#l6Y;3VFCK zcs4N*#FAH+9Ms34<2V?klTV1U;9@c*|0SnttM3xVO8(e-v}SRUhjz5GES&nhrq~FU Wi9`{9n-#O+skSM{J1-_%L*D`vc93uY