2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-06-13 14:38:43 +00:00

removed unsafe

This commit is contained in:
2026-06-02 19:28:09 +03:00
parent 24cf15dec7
commit 3dc36149b7
31 changed files with 1155 additions and 338 deletions
+188
View File
@@ -1,3 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.4.11612.150 VisualStudioVersion = 18.4.11612.150
@@ -92,96 +93,282 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConcurrentAttachSweep", "Co
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttachSweep", "Tests\Distribution\ConcurrentAttachSweep\Esiur.Tests.ConcurrentAttachSweep.csproj", "{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttachSweep", "Tests\Distribution\ConcurrentAttachSweep\Esiur.Tests.ConcurrentAttachSweep.csproj", "{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.Unit", "Tests\Unit\Esiur.Tests.Unit.csproj", "{D1B99C5A-82F7-459D-B56D-F8FD096D3854}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|x64.ActiveCfg = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|x64.Build.0 = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|x86.ActiveCfg = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Debug|x86.Build.0 = Debug|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|Any CPU.Build.0 = Release|Any CPU {E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|Any CPU.Build.0 = Release|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|x64.ActiveCfg = Release|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|x64.Build.0 = Release|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|x86.ActiveCfg = Release|Any CPU
{E87F60C9-F167-3F03-A4BD-43DBB1C76BDD}.Release|x86.Build.0 = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|Any CPU.Build.0 = Debug|Any CPU {9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|x64.ActiveCfg = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|x64.Build.0 = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|x86.ActiveCfg = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Debug|x86.Build.0 = Debug|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|Any CPU.Build.0 = Release|Any CPU {9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|Any CPU.Build.0 = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|x64.ActiveCfg = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|x64.Build.0 = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|x86.ActiveCfg = Release|Any CPU
{9BB3B5A1-CD1F-EEB6-89D5-F3D3766E740E}.Release|x86.Build.0 = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|Any CPU.Build.0 = Debug|Any CPU {0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|x64.ActiveCfg = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|x64.Build.0 = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|x86.ActiveCfg = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Debug|x86.Build.0 = Debug|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|Any CPU.ActiveCfg = Release|Any CPU {0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|Any CPU.Build.0 = Release|Any CPU {0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|Any CPU.Build.0 = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|x64.ActiveCfg = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|x64.Build.0 = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|x86.ActiveCfg = Release|Any CPU
{0255BB42-2742-59C6-F18D-42C6A7C0F017}.Release|x86.Build.0 = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|x64.ActiveCfg = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|x64.Build.0 = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Debug|x86.Build.0 = Debug|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|Any CPU.Build.0 = Release|Any CPU {93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|Any CPU.Build.0 = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|x64.ActiveCfg = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|x64.Build.0 = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|x86.ActiveCfg = Release|Any CPU
{93B71253-8B62-38F4-7B0F-EFEE2619FF2F}.Release|x86.Build.0 = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|x64.Build.0 = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Debug|x86.Build.0 = Debug|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|Any CPU.Build.0 = Release|Any CPU {A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|Any CPU.Build.0 = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|x64.ActiveCfg = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|x64.Build.0 = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|x86.ActiveCfg = Release|Any CPU
{A3AACA8A-D545-BF09-EE00-73485A89B84F}.Release|x86.Build.0 = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|x64.ActiveCfg = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|x64.Build.0 = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|x86.ActiveCfg = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Debug|x86.Build.0 = Debug|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|Any CPU.Build.0 = Release|Any CPU {B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|Any CPU.Build.0 = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|x64.ActiveCfg = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|x64.Build.0 = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|x86.ActiveCfg = Release|Any CPU
{B2F79E56-20CC-8FC3-23A1-EAD37DC6C987}.Release|x86.Build.0 = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|x64.ActiveCfg = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|x64.Build.0 = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|x86.ActiveCfg = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Debug|x86.Build.0 = Debug|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|Any CPU.Build.0 = Release|Any CPU {4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|Any CPU.Build.0 = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|x64.ActiveCfg = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|x64.Build.0 = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|x86.ActiveCfg = Release|Any CPU
{4F0F9616-76B1-4BF3-5454-6FB81877D3CF}.Release|x86.Build.0 = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|x64.ActiveCfg = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|x64.Build.0 = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|x86.ActiveCfg = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Debug|x86.Build.0 = Debug|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E131E95-E561-8AC0-5865-1490CFD43805}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|Any CPU.Build.0 = Release|Any CPU {5E131E95-E561-8AC0-5865-1490CFD43805}.Release|Any CPU.Build.0 = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|x64.ActiveCfg = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|x64.Build.0 = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|x86.ActiveCfg = Release|Any CPU
{5E131E95-E561-8AC0-5865-1490CFD43805}.Release|x86.Build.0 = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|Any CPU.Build.0 = Debug|Any CPU {9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|x64.ActiveCfg = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|x64.Build.0 = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|x86.ActiveCfg = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Debug|x86.Build.0 = Debug|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|Any CPU.Build.0 = Release|Any CPU {9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|Any CPU.Build.0 = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|x64.ActiveCfg = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|x64.Build.0 = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|x86.ActiveCfg = Release|Any CPU
{9AD6065B-F7FD-AC29-D9EC-153C2F084386}.Release|x86.Build.0 = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|x64.ActiveCfg = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|x64.Build.0 = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|x86.ActiveCfg = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Debug|x86.Build.0 = Debug|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|Any CPU.Build.0 = Release|Any CPU {09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|Any CPU.Build.0 = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|x64.ActiveCfg = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|x64.Build.0 = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|x86.ActiveCfg = Release|Any CPU
{09B7271A-1C9B-FB05-019F-779462CB84A7}.Release|x86.Build.0 = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|x64.Build.0 = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Debug|x86.Build.0 = Debug|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|Any CPU.Build.0 = Release|Any CPU {2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|Any CPU.Build.0 = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|x64.ActiveCfg = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|x64.Build.0 = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|x86.ActiveCfg = Release|Any CPU
{2E5449E2-9A62-16CD-0068-90FE44ABEFEE}.Release|x86.Build.0 = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|Any CPU.Build.0 = Debug|Any CPU {F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|x64.ActiveCfg = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|x64.Build.0 = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|x86.ActiveCfg = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Debug|x86.Build.0 = Debug|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|Any CPU.Build.0 = Release|Any CPU {F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|Any CPU.Build.0 = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|x64.ActiveCfg = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|x64.Build.0 = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|x86.ActiveCfg = Release|Any CPU
{F072C376-70B4-B061-745B-0B1BDEBF8CDE}.Release|x86.Build.0 = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|x64.ActiveCfg = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|x64.Build.0 = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|x86.ActiveCfg = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|x86.Build.0 = Debug|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.Build.0 = Release|Any CPU {D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.Build.0 = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|x64.ActiveCfg = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|x64.Build.0 = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|x86.ActiveCfg = Release|Any CPU
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|x86.Build.0 = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU {69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|x64.ActiveCfg = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|x64.Build.0 = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|x86.ActiveCfg = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|x86.Build.0 = Debug|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.Build.0 = Release|Any CPU {69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.Build.0 = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|x64.ActiveCfg = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|x64.Build.0 = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|x86.ActiveCfg = Release|Any CPU
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|x86.Build.0 = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|x64.Build.0 = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|x86.Build.0 = Debug|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.Build.0 = Release|Any CPU {D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.Build.0 = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|x64.ActiveCfg = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|x64.Build.0 = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|x86.ActiveCfg = Release|Any CPU
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|x86.Build.0 = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|x64.ActiveCfg = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|x64.Build.0 = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|x86.ActiveCfg = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|x86.Build.0 = Debug|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.Build.0 = Release|Any CPU {7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.Build.0 = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|x64.ActiveCfg = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|x64.Build.0 = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|x86.ActiveCfg = Release|Any CPU
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|x86.Build.0 = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|Any CPU.Build.0 = Debug|Any CPU {E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|x64.ActiveCfg = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|x64.Build.0 = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|x86.ActiveCfg = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Debug|x86.Build.0 = Debug|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|Any CPU.ActiveCfg = Release|Any CPU {E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|Any CPU.Build.0 = Release|Any CPU {E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|Any CPU.Build.0 = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|x64.ActiveCfg = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|x64.Build.0 = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|x86.ActiveCfg = Release|Any CPU
{E7BF2911-582D-C403-254F-F7FC895BFD68}.Release|x86.Build.0 = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|x64.ActiveCfg = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|x64.Build.0 = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|x86.ActiveCfg = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Debug|x86.Build.0 = Debug|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|Any CPU.Build.0 = Release|Any CPU {7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|Any CPU.Build.0 = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|x64.ActiveCfg = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|x64.Build.0 = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|x86.ActiveCfg = Release|Any CPU
{7FD57668-2AD8-0F53-4006-03927B5A385C}.Release|x86.Build.0 = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|Any CPU.Build.0 = Debug|Any CPU {9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|x64.ActiveCfg = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|x64.Build.0 = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|x86.ActiveCfg = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Debug|x86.Build.0 = Debug|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|Any CPU.ActiveCfg = Release|Any CPU {9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|Any CPU.Build.0 = Release|Any CPU {9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|Any CPU.Build.0 = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|x64.ActiveCfg = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|x64.Build.0 = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|x86.ActiveCfg = Release|Any CPU
{9FF626DF-1AD4-2BE1-F834-F49121D65085}.Release|x86.Build.0 = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|Any CPU.Build.0 = Debug|Any CPU {550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|x64.ActiveCfg = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|x64.Build.0 = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|x86.ActiveCfg = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Debug|x86.Build.0 = Debug|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|Any CPU.ActiveCfg = Release|Any CPU {550A20AB-8E97-BCDD-9F54-27823663120A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|Any CPU.Build.0 = Release|Any CPU {550A20AB-8E97-BCDD-9F54-27823663120A}.Release|Any CPU.Build.0 = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|x64.ActiveCfg = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|x64.Build.0 = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|x86.ActiveCfg = Release|Any CPU
{550A20AB-8E97-BCDD-9F54-27823663120A}.Release|x86.Build.0 = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|x64.ActiveCfg = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|x64.Build.0 = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|x86.ActiveCfg = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Debug|x86.Build.0 = Debug|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|Any CPU.Build.0 = Release|Any CPU {3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|Any CPU.Build.0 = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|x64.ActiveCfg = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|x64.Build.0 = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|x86.ActiveCfg = Release|Any CPU
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}.Release|x86.Build.0 = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|x64.Build.0 = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Debug|x86.Build.0 = Debug|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|Any CPU.Build.0 = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|x64.ActiveCfg = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|x64.Build.0 = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|x86.ActiveCfg = Release|Any CPU
{D1B99C5A-82F7-459D-B56D-F8FD096D3854}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -226,6 +413,7 @@ Global
{550A20AB-8E97-BCDD-9F54-27823663120A} = {21D42B96-99F9-4E48-A499-5170A5A9597F} {550A20AB-8E97-BCDD-9F54-27823663120A} = {21D42B96-99F9-4E48-A499-5170A5A9597F}
{E713D25F-2602-44C9-AB9E-C9477FB2BA93} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917} {E713D25F-2602-44C9-AB9E-C9477FB2BA93} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1} = {E713D25F-2602-44C9-AB9E-C9477FB2BA93} {3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1} = {E713D25F-2602-44C9-AB9E-C9477FB2BA93}
{D1B99C5A-82F7-459D-B56D-F8FD096D3854} = {2769C4C3-2595-413B-B7FE-5903826770C1}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2} SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
+17 -1
View File
@@ -51,6 +51,7 @@ public static class Codec
DataDeserializer.BooleanFalseParserAsync, DataDeserializer.BooleanFalseParserAsync,
DataDeserializer.BooleanTrueParserAsync, DataDeserializer.BooleanTrueParserAsync,
DataDeserializer.NotModifiedParserAsync, DataDeserializer.NotModifiedParserAsync,
DataDeserializer.InfinityParserAsync,
}, },
new AsyncParser[]{ new AsyncParser[]{
DataDeserializer.UInt8ParserAsync, DataDeserializer.UInt8ParserAsync,
@@ -115,6 +116,7 @@ public static class Codec
DataDeserializer.BooleanFalseParser, DataDeserializer.BooleanFalseParser,
DataDeserializer.BooleanTrueParser, DataDeserializer.BooleanTrueParser,
DataDeserializer.NotModifiedParser, DataDeserializer.NotModifiedParser,
DataDeserializer.InfinityParser,
}, },
new SyncParser[]{ new SyncParser[]{
DataDeserializer.UInt8Parser, DataDeserializer.UInt8Parser,
@@ -376,6 +378,13 @@ public static class Codec
} }
/// <summary>
/// Synchronously parses a single value from its IIP wire representation.
/// </summary>
/// <param name="data">Buffer containing the encoded value.</param>
/// <param name="offset">Zero-based offset of the value within <paramref name="data"/>.</param>
/// <param name="warehouse">Warehouse used to resolve typed structures (records, enums, ...).</param>
/// <returns>A tuple of (number of bytes consumed, decoded value).</returns>
public static (uint, object) ParseSync(byte[] data, uint offset, Warehouse warehouse) public static (uint, object) ParseSync(byte[] data, uint offset, Warehouse warehouse)
{ {
var tdu = ParsedTdu.ParseSync(data, offset, (uint)data.Length, warehouse); var tdu = ParsedTdu.ParseSync(data, offset, (uint)data.Length, warehouse);
@@ -612,7 +621,14 @@ public static class Codec
/// <param name="connection">EpConnection is required to check locality.</param> /// <param name="connection">EpConnection is required to check locality.</param>
/// <param name="prependType">If True, prepend the DataType at the beginning of the output.</param> /// <param name="prependType">If True, prepend the DataType at the beginning of the output.</param>
/// <returns>Array of bytes in the network byte order.</returns> /// <returns>Array of bytes in the network byte order.</returns>
public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)//, bool prependType = true) /// <summary>
/// Encodes a value to its self-describing IIP wire representation (a type-prefixed TDU).
/// </summary>
/// <param name="valueOrSource">The value to encode (may be null, which encodes as the Null TDU).</param>
/// <param name="warehouse">Warehouse used to resolve type definitions for typed structures.</param>
/// <param name="connection">Connection context, required when the value references remote resources; may be null for plain data.</param>
/// <returns>The encoded bytes, including the leading type identifier.</returns>
public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)
{ {
var tdu = ComposeInternal(valueOrSource, warehouse, connection); var tdu = ComposeInternal(valueOrSource, warehouse, connection);
return tdu.Composed; return tdu.Composed;
+3 -4
View File
@@ -108,11 +108,10 @@ public static class DC // Data Converter
} }
} }
catch (Exception ex) catch (Exception)
{ {
// Preserve the original stack trace with a bare rethrow.
throw ex; throw;
return null;
} }
} }
} }
+13 -2
View File
@@ -59,6 +59,19 @@ public static class DataDeserializer
return NotModified.Default; return NotModified.Default;
} }
// The Infinity token carries no payload: the serializer collapses every NaN and
// +/- Infinity onto it (see DataSerializer.Float32/Float64Composer). Decoding it to
// a single canonical double keeps the (lossy) round trip from throwing.
public static object InfinityParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
return double.PositiveInfinity;
}
public static object InfinityParser(ParsedTdu tdu, Warehouse warehouse)
{
return double.PositiveInfinity;
}
public static object UInt8ParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence) public static object UInt8ParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{ {
return tdu.Data[tdu.PayloadOffset]; return tdu.Data[tdu.PayloadOffset];
@@ -1345,7 +1358,6 @@ public static class DataDeserializer
var subTypes = subTrus.Select(x => x.RuntimeType).ToArray(); var subTypes = subTrus.Select(x => x.RuntimeType).ToArray();
ParsedTdu current; ParsedTdu current;
ParsedTdu? previous = null;
var offset = tdu.PayloadOffset; var offset = tdu.PayloadOffset;
var length = tdu.PayloadLength; var length = tdu.PayloadLength;
@@ -1477,7 +1489,6 @@ public static class DataDeserializer
var types = subTrus.Select(x => x.RuntimeType).ToArray(); var types = subTrus.Select(x => x.RuntimeType).ToArray();
ParsedTdu current; ParsedTdu current;
ParsedTdu? previous = null;
var offset = tdu.PayloadOffset; var offset = tdu.PayloadOffset;
var length = tdu.PayloadLength; var length = tdu.PayloadLength;
+27 -53
View File
@@ -17,7 +17,7 @@ public static class DataSerializer
{ {
public delegate byte[] Serializer(object value); public delegate byte[] Serializer(object value);
public static unsafe Tdu Int32Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu Int32Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (int)value; var v = (int)value;
@@ -29,22 +29,19 @@ public static class DataSerializer
{ {
// Fits in 2 bytes // Fits in 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v);
*((short*)ptr) = (short)v;
return new Tdu(TduIdentifier.Int16, rt, 2, null, null); return new Tdu(TduIdentifier.Int16, rt, 2, null, null);
} }
else else
{ {
// Use full 4 bytes // Use full 4 bytes
var rt = new byte[4]; var rt = new byte[4];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt32LittleEndian(rt, v);
*((int*)ptr) = v;
return new Tdu(TduIdentifier.Int32, rt, 4, null, null); return new Tdu(TduIdentifier.Int32, rt, 4, null, null);
} }
} }
public static unsafe Tdu UInt32Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu UInt32Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (uint)value; var v = (uint)value;
@@ -57,23 +54,19 @@ public static class DataSerializer
{ {
// Fits in 2 bytes // Fits in 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v);
*((ushort*)ptr) = (ushort)v;
return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null);
} }
else else
{ {
// Use full 4 bytes // Use full 4 bytes
var rt = new byte[4]; var rt = new byte[4];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt32LittleEndian(rt, v);
*((uint*)ptr) = v;
return new Tdu(TduIdentifier.UInt32, rt, 4, null, null); return new Tdu(TduIdentifier.UInt32, rt, 4, null, null);
} }
} }
public static unsafe Tdu Int16Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu Int16Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (short)value; var v = (short)value;
@@ -86,14 +79,12 @@ public static class DataSerializer
{ {
// Use full 2 bytes // Use full 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt16LittleEndian(rt, v);
*((short*)ptr) = v;
return new Tdu(TduIdentifier.Int16, rt, 2, null, null); return new Tdu(TduIdentifier.Int16, rt, 2, null, null);
} }
} }
public static unsafe Tdu UInt16Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu UInt16Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (ushort)value; var v = (ushort)value;
@@ -106,9 +97,7 @@ public static class DataSerializer
{ {
// Use full 2 bytes // Use full 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt16LittleEndian(rt, v);
*((ushort*)ptr) = v;
return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null);
} }
} }
@@ -211,7 +200,7 @@ public static class DataSerializer
return new Tdu(TduIdentifier.Float64, rt, 8, null, null); return new Tdu(TduIdentifier.Float64, rt, 8, null, null);
} }
} }
public static unsafe Tdu Int64Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu Int64Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (long)value; var v = (long)value;
@@ -224,32 +213,26 @@ public static class DataSerializer
{ {
// Fits in 2 bytes // Fits in 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v);
*((short*)ptr) = (short)v;
return new Tdu(TduIdentifier.Int16, rt, 2, null, null); return new Tdu(TduIdentifier.Int16, rt, 2, null, null);
} }
else if (v >= int.MinValue && v <= int.MaxValue) else if (v >= int.MinValue && v <= int.MaxValue)
{ {
// Fits in 4 bytes // Fits in 4 bytes
var rt = new byte[4]; var rt = new byte[4];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt32LittleEndian(rt, (int)v);
*((int*)ptr) = (int)v;
return new Tdu(TduIdentifier.Int32, rt, 4, null, null); return new Tdu(TduIdentifier.Int32, rt, 4, null, null);
} }
else else
{ {
// Use full 8 bytes // Use full 8 bytes
var rt = new byte[8]; var rt = new byte[8];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt64LittleEndian(rt, v);
*((long*)ptr) = v;
return new Tdu(TduIdentifier.Int64, rt, 8, null, null); return new Tdu(TduIdentifier.Int64, rt, 8, null, null);
} }
} }
public static unsafe Tdu UInt64Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu UInt64Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (ulong)value; var v = (ulong)value;
@@ -262,39 +245,31 @@ public static class DataSerializer
{ {
// Fits in 2 bytes // Fits in 2 bytes
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v);
*((ushort*)ptr) = (ushort)v;
return new Tdu(TduIdentifier.UInt16, rt, 2, null, null); return new Tdu(TduIdentifier.UInt16, rt, 2, null, null);
} }
else if (v <= uint.MaxValue) else if (v <= uint.MaxValue)
{ {
// Fits in 4 bytes // Fits in 4 bytes
var rt = new byte[4]; var rt = new byte[4];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt32LittleEndian(rt, (uint)v);
*((uint*)ptr) = (uint)v;
return new Tdu(TduIdentifier.UInt32, rt, 4, null, null); return new Tdu(TduIdentifier.UInt32, rt, 4, null, null);
} }
else else
{ {
// Use full 8 bytes // Use full 8 bytes
var rt = new byte[8]; var rt = new byte[8];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt64LittleEndian(rt, v);
*((ulong*)ptr) = v;
return new Tdu(TduIdentifier.UInt64, rt, 8, null, null); return new Tdu(TduIdentifier.UInt64, rt, 8, null, null);
} }
} }
public static unsafe Tdu DateTimeComposer(object value, Warehouse warehouse, EpConnection connection) public static Tdu DateTimeComposer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = ((DateTime)value).ToUniversalTime().Ticks; var v = ((DateTime)value).ToUniversalTime().Ticks;
var rt = new byte[8]; var rt = new byte[8];
fixed (byte* ptr = rt) BinaryPrimitives.WriteInt64LittleEndian(rt, v);
*((long*)ptr) = v;
return new Tdu(TduIdentifier.DateTime, rt, 8, null, null); return new Tdu(TduIdentifier.DateTime, rt, 8, null, null);
} }
@@ -362,7 +337,7 @@ public static class DataSerializer
double d = (double)v; double d = (double)v;
if ((decimal)d == v) if ((decimal)d == v)
{ {
var rt = new byte[4]; var rt = new byte[8];
fixed (byte* ptr = rt) fixed (byte* ptr = rt)
*((double*)ptr) = d; *((double*)ptr) = d;
@@ -436,15 +411,12 @@ public static class DataSerializer
new byte[] { (byte)(char)value }, 1, null, null); new byte[] { (byte)(char)value }, 1, null, null);
} }
public static unsafe Tdu Char16Composer(object value, Warehouse warehouse, EpConnection connection) public static Tdu Char16Composer(object value, Warehouse warehouse, EpConnection connection)
{ {
var v = (char)value; var v = (char)value;
var rt = new byte[2]; var rt = new byte[2];
fixed (byte* ptr = rt) BinaryPrimitives.WriteUInt16LittleEndian(rt, v);
*((char*)ptr) = v;
return new Tdu(TduIdentifier.Char16, rt, 2, null, null); return new Tdu(TduIdentifier.Char16, rt, 2, null, null);
} }
public static Tdu BoolComposer(object value, Warehouse warehouse, EpConnection connection) public static Tdu BoolComposer(object value, Warehouse warehouse, EpConnection connection)
@@ -733,7 +705,9 @@ public static class DataSerializer
if (value == null) if (value == null)
return null; return null;
var rt = new List<byte>(); // Pre-size the buffer from the element count (when known) to avoid repeated
// List<byte> reallocations as items are appended. 4 bytes/element is a rough hint.
var rt = new List<byte>(value is ICollection collection ? collection.Count * 4 : 16);
Tdu? previous = null; Tdu? previous = null;
@@ -934,7 +908,7 @@ public static class DataSerializer
var trus = fields.Select(x => Tru.FromType(x.FieldType, warehouse)).ToArray(); var trus = fields.Select(x => Tru.FromType(x.FieldType, warehouse)).ToArray();
var rt = new List<byte>(); var rt = new List<byte>(fields.Length * 4);
for (var i = 0; i < fields.Length; i++) for (var i = 0; i < fields.Length; i++)
{ {
+1 -1
View File
@@ -65,7 +65,7 @@ public struct Tdu
} }
public Tdu(TduIdentifier identifier, public Tdu(TduIdentifier identifier,
byte[] data, ulong length, Tru metadata, EpConnection connection) byte[]? data, ulong length, Tru? metadata, EpConnection? connection)
{ {
Identifier = identifier; Identifier = identifier;
//Index = (byte)identifier & 0x7; //Index = (byte)identifier & 0x7;
+35 -2
View File
@@ -138,6 +138,14 @@ namespace Esiur.Data
return false; return false;
} }
public override int GetHashCode()
{
// Equality is defined by Match, which always requires a matching Identifier
// (composites additionally compare sub-types). Hashing on Identifier therefore
// keeps equal Trus in the same bucket and honours the Equals/GetHashCode contract.
return (int)Identifier;
}
public abstract void SetNotNull(List<byte> flags); public abstract void SetNotNull(List<byte> flags);
public abstract void SetNotNull(byte flag); public abstract void SetNotNull(byte flag);
@@ -299,7 +307,32 @@ namespace Esiur.Data
//private static Dictionary<Type, Tru> cache = new Dictionary<Type, Tru>(); //private static Dictionary<Type, Tru> cache = new Dictionary<Type, Tru>();
//private static object cacheLook = new object(); //private static object cacheLook = new object();
/// <summary>
/// Builds the type-representation unit (Tru) describing how a CLR type maps onto the
/// wire, recursing into element/key/value/field types for collections, maps and tuples.
/// Results are memoized per warehouse since this is reflection-heavy and hot during
/// serialization; returned Tru instances are immutable and safe to share.
/// </summary>
public static Tru? FromType(Type type, Warehouse warehouse) public static Tru? FromType(Type type, Warehouse warehouse)
{
// null maps to Void and cannot be a dictionary key, so compute it directly.
if (type == null)
return FromTypeCore(null, warehouse);
if (warehouse.TypeRepresentationCache.TryGetValue(type, out var cached))
return cached;
var tru = FromTypeCore(type, warehouse);
// Cache only fully-built results. Unrecognized types return null (or throw),
// which we leave uncached so a later type registration can still resolve them.
if (tru != null)
warehouse.TypeRepresentationCache[type] = tru;
return tru;
}
static Tru? FromTypeCore(Type? type, Warehouse warehouse)
{ {
if (type == null) if (type == null)
return new TruPrimitive(TruIdentifier.Void, true, typeof(void)); return new TruPrimitive(TruIdentifier.Void, true, typeof(void));
@@ -714,7 +747,7 @@ namespace Esiur.Data
offset += pr.Size; offset += pr.Size;
} }
Type runtimeType = null; Type? runtimeType = null;
if (identifier == TruIdentifier.TypedList) if (identifier == TruIdentifier.TypedList)
{ {
@@ -852,7 +885,7 @@ namespace Esiur.Data
offset += pr.Size; offset += pr.Size;
} }
Type runtimeType = null; Type? runtimeType = null;
if (identifier == TruIdentifier.TypedList) if (identifier == TruIdentifier.TypedList)
{ {
+1 -3
View File
@@ -11,11 +11,9 @@ namespace Esiur.Data
{ {
public Tru[] SubTypes; public Tru[] SubTypes;
Type _runtimeType;
public override Type RuntimeType { get; protected set; } public override Type RuntimeType { get; protected set; }
public TruComposite(TruIdentifier identifier, bool nullable, Tru[] subTypes, Type type) public TruComposite(TruIdentifier identifier, bool nullable, Tru[] subTypes, Type? type)
{ {
Identifier = identifier; Identifier = identifier;
Nullable = nullable; Nullable = nullable;
+1 -1
View File
@@ -88,7 +88,7 @@ public class ArgumentDef
} }
else else
{ {
var exp = Codec.Compose(Annotations, null, null); var exp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);
return new BinaryList() return new BinaryList()
.AddUInt8((byte)(0x2 | (Optional ? 1 : 0))) .AddUInt8((byte)(0x2 | (Optional ? 1 : 0)))
+1 -1
View File
@@ -76,7 +76,7 @@ public class ConstantDef : MemberDef
if (Annotations != null) if (Annotations != null)
{ {
var exp = Codec.Compose(Annotations, null, null);// DC.ToBytes(Annotation); var exp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);// DC.ToBytes(Annotation);
hdr |= 0x70; hdr |= 0x70;
return new BinaryList() return new BinaryList()
.AddUInt8(hdr) .AddUInt8(hdr)
+1 -1
View File
@@ -82,7 +82,7 @@ public class EventDef : MemberDef
if (Annotations != null) if (Annotations != null)
{ {
var exp = Codec.Compose(Annotations, null, null); //( DC.ToBytes(Annotation); var exp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection); //( DC.ToBytes(Annotation);
hdr |= 0x50; hdr |= 0x50;
return new BinaryList() return new BinaryList()
.AddUInt8(hdr) .AddUInt8(hdr)
+1 -1
View File
@@ -110,7 +110,7 @@ public class FunctionDef : MemberDef
if (Annotations != null) if (Annotations != null)
{ {
var exp = Codec.Compose(Annotations, null, null);// DC.ToBytes(Annotation); var exp = Codec.Compose(Annotations, connection.Instance.Warehouse , connection);// DC.ToBytes(Annotation);
bl.AddUInt8Array(exp); bl.AddUInt8Array(exp);
bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x90 : (byte)0x10) | (IsStatic ? 0x4 : 0))); bl.InsertUInt8(0, (byte)((Inherited ? (byte)0x90 : (byte)0x10) | (IsStatic ? 0x4 : 0)));
} }
+1 -1
View File
@@ -389,7 +389,7 @@ public class LocalTypeDef:TypeDef
//foreach (var ann in Annotations) //foreach (var ann in Annotations)
// Annotations.Add(ann.Key, ann.Value); // Annotations.Add(ann.Key, ann.Value);
var classAnnotationBytes = Codec.Compose(Annotations, null, null); var classAnnotationBytes = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);
b.AddUInt8Array(classAnnotationBytes); b.AddUInt8Array(classAnnotationBytes);
+1 -1
View File
@@ -176,7 +176,7 @@ public class PropertyDef : MemberDef
//} //}
if (Annotations != null) if (Annotations != null)
{ {
var rexp = Codec.Compose(Annotations, null, null); var rexp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);
return new BinaryList() return new BinaryList()
.AddUInt8((byte)(0x28 | pv)) .AddUInt8((byte)(0x28 | pv))
.AddUInt8((byte)name.Length) .AddUInt8((byte)name.Length)
+4 -1
View File
@@ -16,6 +16,10 @@
<PackageId>Esiur</PackageId> <PackageId>Esiur</PackageId>
<Product>Esiur</Product> <Product>Esiur</Product>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<!-- Enable the nullable *annotation* context project-wide so '?' reference-type
annotations are legal everywhere, without turning on nullable flow warnings
(files that opt in via '#nullable enable' still get full checking). -->
<Nullable>annotations</Nullable>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
@@ -67,7 +71,6 @@
<Compile Remove="Net\Packets\EpAuthPacketAction.cs" /> <Compile Remove="Net\Packets\EpAuthPacketAction.cs" />
<Compile Remove="Net\Packets\EpAuthPacketAuthMode.cs" /> <Compile Remove="Net\Packets\EpAuthPacketAuthMode.cs" />
<Compile Remove="Net\Packets\EpAuthPacketEvent.cs" /> <Compile Remove="Net\Packets\EpAuthPacketEvent.cs" />
<Compile Remove="Net\Sockets\TcpSocket.cs" />
<Compile Remove="Protocol\Authentication\HashAnonymousAuthenticator.cs" /> <Compile Remove="Protocol\Authentication\HashAnonymousAuthenticator.cs" />
<Compile Remove="Security\Authority\AuthenticationMethod.cs" /> <Compile Remove="Security\Authority\AuthenticationMethod.cs" />
<Compile Remove="Security\Membership\IMembership.cs" /> <Compile Remove="Security\Membership\IMembership.cs" />
+30 -9
View File
@@ -47,7 +47,10 @@ public static class Global
{ {
private static KeyList<string, object> variables = new KeyList<string, object>(); private static KeyList<string, object> variables = new KeyList<string, object>();
private static Random rand = new Random();// System.Environment.TickCount); // Cryptographically secure RNG. Used for security-sensitive values such as
// authentication nonces and session identifiers, so it must never be a
// predictable PRNG (e.g. System.Random). The instance is thread-safe for GetBytes.
private static readonly RandomNumberGenerator secureRng = RandomNumberGenerator.Create();
@@ -340,23 +343,41 @@ public static class Global
public static byte[] GenerateBytes(int length) public static byte[] GenerateBytes(int length)
{ {
var b = new byte[length]; var b = new byte[length];
rand.NextBytes(b); secureRng.GetBytes(b);
return b; return b;
} }
public static string GenerateCode(int length) public static string GenerateCode(int length)
{ {
return GenerateCode(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); return GenerateCode(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
} }
public static string GenerateCode(int length, string chars) public static string GenerateCode(int length, string chars)
{ {
var result = new string( // Draw each character from a CSPRNG using unbiased rejection sampling, so
Enumerable.Repeat(chars, length) // codes used as session identifiers are not predictable. The largest
.Select(s => s[rand.Next(s.Length)]) // multiple of chars.Length that fits in a byte is the acceptance bound;
.ToArray()); // bytes at or above it are discarded to avoid modulo bias.
return result; var result = new char[length];
} var max = chars.Length;
var limit = byte.MaxValue - (256 % max);
var buffer = new byte[1];
for (var i = 0; i < length; i++)
{
byte value;
do
{
secureRng.GetBytes(buffer);
value = buffer[0];
}
while (value > limit);
result[i] = chars[value % max];
}
return new string(result);
}
public static Regex GetRouteRegex(string url) public static Regex GetRouteRegex(string url)
+1 -1
View File
@@ -34,7 +34,7 @@ using Esiur.Protocol;
namespace Esiur.Net.Http; namespace Esiur.Net.Http;
public class EpOvwerWebsocket : HttpFilter public class EpOverWebsocket : HttpFilter
{ {
//[Attribute] //[Attribute]
public EpServer Server public EpServer Server
+63 -218
View File
@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2017 Ahmed Kh. Zamil Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,229 +23,109 @@ SOFTWARE.
*/ */
using System; using System;
using System.IO; using System.Net;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using Esiur.Misc; using Esiur.Misc;
using Esiur.Core; using Esiur.Core;
using Esiur.Data;
using Esiur.Net.Sockets; using Esiur.Net.Sockets;
using Esiur.Resource;
namespace Esiur.Net; namespace Esiur.Net;
public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocket>// <TS>: IResource where TS : NetworkSession
{
private Sockets.ISocket sock;
// private bool connected;
/// <summary>
/// Base class for a logical connection layered on top of an <see cref="ISocket"/>.
/// It owns the socket, forwards inbound buffers to <see cref="DataReceived"/>, and
/// exposes send helpers. Derived classes implement the protocol-specific framing.
/// </summary>
public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocket>
{
private ISocket sock;
private DateTime lastAction; private DateTime lastAction;
//public delegate void DataReceivedEvent(NetworkConnection sender, NetworkBuffer data); // Re-entrancy guard for NetworkReceive. 0 = idle, 1 = a thread is draining the buffer.
//public delegate void ConnectionClosedEvent(NetworkConnection sender); // Interlocked is used instead of a plain bool so concurrent receive callbacks cannot
// both enter the drain loop (which is not safe to run from two threads at once).
private int receiving;
public delegate void NetworkConnectionEvent(NetworkConnection connection); public delegate void NetworkConnectionEvent(NetworkConnection connection);
public event NetworkConnectionEvent OnConnect; public event NetworkConnectionEvent OnConnect;
//public event DataReceivedEvent OnDataReceived;
public event NetworkConnectionEvent OnClose; public event NetworkConnectionEvent OnClose;
public event DestroyedEvent OnDestroy; public event DestroyedEvent OnDestroy;
//object receivingLock = new object();
//object sendLock = new object();
bool processing = false;
// public INetworkReceiver<NetworkConnection> Receiver { get; set; }
public virtual void Destroy() public virtual void Destroy()
{ {
// remove references
//sock.OnClose -= Socket_OnClose;
//sock.OnConnect -= Socket_OnConnect;
//sock.OnReceive -= Socket_OnReceive;
sock?.Destroy(); sock?.Destroy();
//Receiver = null;
Close(); Close();
sock = null; sock = null;
OnClose = null; OnClose = null;
OnConnect = null; OnConnect = null;
//OnDataReceived = null;
OnDestroy?.Invoke(this); OnDestroy?.Invoke(this);
OnDestroy = null; OnDestroy = null;
} }
public ISocket Socket public ISocket Socket => sock;
{
get
{
return sock;
}
}
public virtual void Assign(ISocket socket) public virtual void Assign(ISocket socket)
{ {
lastAction = DateTime.Now; lastAction = DateTime.Now;
sock = socket; sock = socket;
sock.Receiver = this; sock.Receiver = this;
//socket.OnReceive += Socket_OnReceive;
//socket.OnClose += Socket_OnClose;
//socket.OnConnect += Socket_OnConnect;
} }
//private void Socket_OnConnect() /// <summary>
//{ /// Detaches the socket from this connection without closing it and returns it,
// OnConnect?.Invoke(this); /// so ownership can be handed to another connection (e.g. a protocol upgrade).
//} /// </summary>
//private void Socket_OnClose()
//{
// ConnectionClosed();
// OnClose?.Invoke(this);
//}
//protected virtual void ConnectionClosed()
//{
//}
//private void Socket_OnReceive(NetworkBuffer buffer)
//{
//}
public ISocket Unassign() public ISocket Unassign()
{ {
if (sock != null) if (sock == null)
{
// connected = false;
//sock.OnClose -= Socket_OnClose;
//sock.OnConnect -= Socket_OnConnect;
//sock.OnReceive -= Socket_OnReceive;
sock.Receiver = null;
var rt = sock;
sock = null;
return rt;
}
else
return null; return null;
}
//protected virtual void DataReceived(NetworkBuffer data) sock.Receiver = null;
//{
// if (OnDataReceived != null) var detached = sock;
// { sock = null;
// try return detached;
// { }
// OnDataReceived?.Invoke(this, data);
// }
// catch (Exception ex)
// {
// Global.Log("NetworkConenction:DataReceived", LogType.Error, ex.ToString());
// }
// }
//}
public void Close() public void Close()
{ {
//if (!connected)
// return;
try try
{ {
if (sock != null) sock?.Close();
sock.Close();
} }
catch (Exception ex) catch (Exception ex)
{ {
Global.Log("NetworkConenction:Close", LogType.Error, ex.ToString()); Global.Log("NetworkConnection:Close", LogType.Error, ex.ToString());
}
//finally
//{
//connected = false;
//}
}
public DateTime LastAction
{
get { return lastAction; }
}
public IPEndPoint RemoteEndPoint
{
get
{
if (sock != null)
return (IPEndPoint)sock.RemoteEndPoint;
else
return null;
} }
} }
public IPEndPoint LocalEndPoint public DateTime LastAction => lastAction;
{
get
{
if (sock != null)
return (IPEndPoint)sock.LocalEndPoint;
else
return null;
}
}
public IPEndPoint RemoteEndPoint => sock != null ? (IPEndPoint)sock.RemoteEndPoint : null;
public bool IsConnected public IPEndPoint LocalEndPoint => sock != null ? (IPEndPoint)sock.LocalEndPoint : null;
{
get
{
return sock == null ? false : sock.State == SocketState.Established;
}
}
public bool IsConnected => sock != null && sock.State == SocketState.Established;
/*
public void CloseAndWait()
{
try
{
if (!connected)
return;
if (sock != null)
sock.Close();
while (connected)
{
Thread.Sleep(100);
}
}
finally
{
}
}
*/
public virtual AsyncReply<bool> SendAsync(byte[] message, int offset, int length) public virtual AsyncReply<bool> SendAsync(byte[] message, int offset, int length)
{ {
var socket = sock;
if (socket == null)
return new AsyncReply<bool>(false);
try try
{ {
lastAction = DateTime.Now; lastAction = DateTime.Now;
return sock.SendAsync(message, offset, length); return socket.SendAsync(message, offset, length);
} }
catch catch (Exception ex)
{ {
// Sends fail routinely when the peer drops, so this is logged at debug level
// rather than thrown, but it is no longer swallowed silently.
Global.Log("NetworkConnection:SendAsync", LogType.Debug, ex.Message);
return new AsyncReply<bool>(false); return new AsyncReply<bool>(false);
} }
} }
@@ -257,9 +137,9 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
sock?.Send(msg); sock?.Send(msg);
lastAction = DateTime.Now; lastAction = DateTime.Now;
} }
catch catch (Exception ex)
{ {
Global.Log("NetworkConnection:Send", LogType.Debug, ex.Message);
} }
} }
@@ -267,12 +147,12 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
{ {
try try
{ {
sock.Send(msg, offset, length); sock?.Send(msg, offset, length);
lastAction = DateTime.Now; lastAction = DateTime.Now;
} }
catch catch (Exception ex)
{ {
Global.Log("NetworkConnection:Send", LogType.Debug, ex.Message);
} }
} }
@@ -293,18 +173,6 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
OnConnect?.Invoke(this); OnConnect?.Invoke(this);
} }
//{
//ConnectionClosed();
//OnClose?.Invoke(this);
//Receiver?.NetworkClose(this);
//}
//public void NetworkConenct(ISocket sender)
//{
// OnConnect?.Invoke(this);
//}
protected abstract void DataReceived(NetworkBuffer buffer); protected abstract void DataReceived(NetworkBuffer buffer);
protected abstract void Connected(); protected abstract void Connected();
protected abstract void Disconnected(); protected abstract void Disconnected();
@@ -313,53 +181,30 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
{ {
try try
{ {
// Unassigned ? // Ignore callbacks once the socket is unassigned or closed.
if (sock == null) if (sock == null || sock.State == SocketState.Closed)
return;
// Closed ?
if (sock.State == SocketState.Closed)// || sock.State == SocketState.Terminated) // || !connected)
return; return;
lastAction = DateTime.Now; lastAction = DateTime.Now;
if (!processing) // Only one thread drains the buffer at a time; others return immediately and
// rely on the active drainer to pick up the newly appended data.
if (Interlocked.CompareExchange(ref receiving, 1, 0) != 0)
return;
try
{ {
processing = true; while (buffer.Available > 0 && !buffer.Protected)
DataReceived(buffer);
try }
{ finally
//lock(buffer.SyncLock) {
while (buffer.Available > 0 && !buffer.Protected) Interlocked.Exchange(ref receiving, 0);
{
//Receiver?.NetworkReceive(this, buffer);
DataReceived(buffer);
}
}
catch
{
}
processing = false;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Global.Log("NetworkConnection", LogType.Warning, ex.ToString()); Global.Log("NetworkConnection:NetworkReceive", LogType.Warning, ex.ToString());
} }
} }
//{
// Receiver?.NetworkError(this);
//throw new NotImplementedException();
//}
//public void NetworkConnect(ISocket sender)
//{
// Receiver?.NetworkConnect(this);
//throw new NotImplementedException();
//}
} }
@@ -187,8 +187,6 @@ public class EpAuthPacket : Packet
Tdu = PlainTdu.Parse(data, offset, ends);//, _warehouse); Tdu = PlainTdu.Parse(data, offset, ends);//, _warehouse);
Console.WriteLine("Auth TDU " + Tdu.Value.PayloadLength);
if (Tdu.Value.Class == TduClass.Invalid) if (Tdu.Value.Class == TduClass.Invalid)
return -(int)Tdu.Value.TotalLength; return -(int)Tdu.Value.TotalLength;
@@ -156,12 +156,18 @@ namespace Esiur.Net.Sockets
public void Destroy() public void Destroy()
{ {
Close(); var ws = sock;
Close(); // best-effort graceful close handshake (fire-and-forget)
receiveNetworkBuffer = null; receiveNetworkBuffer = null;
Receiver = null; Receiver = null;
sock = null; sock = null;
// Dispose the WebSocket so its buffers and handle are released; Close() only
// starts the async close handshake and never disposes.
try { ws?.Dispose(); } catch { }
OnDestroy?.Invoke(this); OnDestroy?.Invoke(this);
OnDestroy = null; OnDestroy = null;
} }
+8
View File
@@ -458,6 +458,14 @@ public class SSLSocket : ISocket
public void Destroy() public void Destroy()
{ {
Close(); Close();
// Release the TLS stream and the underlying socket handle. NetworkStream(sock) does
// not own the socket, so disposing the stream alone would leak the socket — dispose
// both explicitly. Guarded because teardown may race with in-flight I/O callbacks.
try { ssl?.Dispose(); } catch { }
try { sock?.Close(); } catch { }
try { sock?.Dispose(); } catch { }
Receiver = null; Receiver = null;
receiveNetworkBuffer = null; receiveNetworkBuffer = null;
OnDestroy?.Invoke(this); OnDestroy?.Invoke(this);
+1 -2
View File
@@ -99,7 +99,7 @@ public partial class EpConnection : NetworkConnection, IStore
AsyncReply<bool> _openReply; AsyncReply<bool> _openReply;
bool _authenticated, _readyToEstablish; bool _authenticated;
string _hostname; string _hostname;
ushort _port; ushort _port;
@@ -2025,7 +2025,6 @@ public partial class EpConnection : NetworkConnection, IStore
{ {
// clean up // clean up
_authenticated = false; _authenticated = false;
_readyToEstablish = false;
Status = EpConnectionStatus.Closed; Status = EpConnectionStatus.Closed;
_keepAliveTimer.Stop(); _keepAliveTimer.Stop();
@@ -108,7 +108,7 @@ partial class EpConnection
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0x60 | (byte)action)) bl.AddUInt8((byte)(0x60 | (byte)action))
.AddUInt32(c) .AddUInt32(c)
.AddUInt8Array(Codec.Compose(args[0], this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
else else
@@ -116,7 +116,7 @@ partial class EpConnection
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0x60 | (byte)action)) bl.AddUInt8((byte)(0x60 | (byte)action))
.AddUInt32(c) .AddUInt32(c)
.AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
@@ -147,7 +147,7 @@ partial class EpConnection
{ {
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)((byte)method | 0x20)); bl.AddUInt8((byte)((byte)method | 0x20));
bl.AddUInt8Array(Codec.Compose(data, null, this)); bl.AddUInt8Array(Codec.Compose(data, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
else else
@@ -165,7 +165,7 @@ partial class EpConnection
{ {
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)((byte)method | 0x20)); bl.AddUInt8((byte)((byte)method | 0x20));
bl.AddUInt8Array(Codec.Compose(message, null, this)); bl.AddUInt8Array(Codec.Compose(message, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
@@ -183,7 +183,9 @@ partial class EpConnection
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)((byte)method | 0x20)); bl.AddUInt8((byte)((byte)method | 0x20));
bl.AddUInt8Array(Codec.Compose(authHeaders, null, this)); bl.AddUInt8Array(Codec.Compose(authHeaders,
this.Instance?.Warehouse ?? _serverWarehouse,
this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
else else
@@ -212,14 +214,14 @@ partial class EpConnection
{ {
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0x20 | (byte)action)) bl.AddUInt8((byte)(0x20 | (byte)action))
.AddUInt8Array(Codec.Compose(args[0], this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
else else
{ {
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0x20 | (byte)action)) bl.AddUInt8((byte)(0x20 | (byte)action))
.AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
@@ -243,7 +245,7 @@ partial class EpConnection
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0xA0 | (byte)action)) bl.AddUInt8((byte)(0xA0 | (byte)action))
.AddUInt32(callbackId) .AddUInt32(callbackId)
.AddUInt8Array(Codec.Compose(args[0], this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
else else
@@ -251,7 +253,7 @@ partial class EpConnection
var bl = new BinaryList(); var bl = new BinaryList();
bl.AddUInt8((byte)(0xA0 | (byte)action)) bl.AddUInt8((byte)(0xA0 | (byte)action))
.AddUInt32(callbackId) .AddUInt32(callbackId)
.AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this)); .AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray()); Send(bl.ToArray());
} }
} }
@@ -2033,7 +2035,10 @@ partial class EpConnection
_attachedResources[id]?.TryGetTarget(out resource); _attachedResources[id]?.TryGetTarget(out resource);
if (resource != null) if (resource != null)
{
Global.Counters["EpResourceAttachedCacheHit"]++;
return new AsyncReply<EpResource>(resource); return new AsyncReply<EpResource>(resource);
}
resource = _neededResources[id]; resource = _neededResources[id];
@@ -2043,16 +2048,19 @@ partial class EpConnection
{ {
if (resource != null && (requestSequence?.Contains(id) ?? false)) if (resource != null && (requestSequence?.Contains(id) ?? false))
{ {
Global.Counters["EpResourceDeadLockSameChain"]++;
// dead lock avoidance for loop reference. // dead lock avoidance for loop reference.
return new AsyncReply<EpResource>(resource); return new AsyncReply<EpResource>(resource);
} }
else if (resource != null && requestInfo.RequestSequence.Contains(id)) else if (resource != null && requestInfo.RequestSequence.Contains(id))
{ {
Global.Counters["EpResourceDeadLockCrossChain"]++;
// dead lock avoidance for dependent reference. // dead lock avoidance for dependent reference.
return new AsyncReply<EpResource>(resource); return new AsyncReply<EpResource>(resource);
} }
else else
{ {
Global.Counters["EpResourcePendingCacheHit"]++;
return requestInfo.Reply; return requestInfo.Reply;
} }
} }
+10 -8
View File
@@ -57,6 +57,12 @@ public class Warehouse
ConcurrentDictionary<uint, WeakReference<IResource>> _resources = new ConcurrentDictionary<uint, WeakReference<IResource>>(); ConcurrentDictionary<uint, WeakReference<IResource>> _resources = new ConcurrentDictionary<uint, WeakReference<IResource>>();
ConcurrentDictionary<IStore, List<WeakReference<IResource>>> _stores = new ConcurrentDictionary<IStore, List<WeakReference<IResource>>>(); ConcurrentDictionary<IStore, List<WeakReference<IResource>>> _stores = new ConcurrentDictionary<IStore, List<WeakReference<IResource>>>();
// Memoizes Tru.FromType results, which are reflection-heavy and recomputed for every
// array element, record property and tuple field during serialization. Tru instances
// are immutable once constructed, so caching and sharing them per warehouse is safe.
internal ConcurrentDictionary<Type, Esiur.Data.Tru> TypeRepresentationCache
= new ConcurrentDictionary<Type, Esiur.Data.Tru>();
volatile int _resourceCounter = 0; volatile int _resourceCounter = 0;
volatile int _typeDefsCounter = 0; volatile int _typeDefsCounter = 0;
@@ -681,14 +687,10 @@ public class Warehouse
|| baseType == typeof(IRecord)) || baseType == typeof(IRecord))
return null; return null;
TypeDefKind typeDefKind; // Only resources, records and enums have type definitions; bail out for anything else.
if (Codec.ImplementsInterface(type, typeof(IResource))) if (!Codec.ImplementsInterface(type, typeof(IResource))
typeDefKind = TypeDefKind.Resource; && !Codec.ImplementsInterface(type, typeof(IRecord))
else if (Codec.ImplementsInterface(type, typeof(IRecord))) && !type.IsEnum)
typeDefKind = TypeDefKind.Record;
else if (type.IsEnum)
typeDefKind = TypeDefKind.Enum;
else
return null; return null;
lock (_typeDefsLock) lock (_typeDefsLock)
@@ -9,10 +9,20 @@ using Esiur.Data.Types;
namespace Esiur.Security.Authority.Providers namespace Esiur.Security.Authority.Providers
{ {
/// <summary>
/// Implements the "hash" authentication protocol: a SHA3 nonce/challenge-response
/// handshake that mutually proves knowledge of a salted password hash without sending
/// the password, and derives a 512-bit session key. Supports initiator-only, responder-only
/// and dual identity modes. All challenge comparisons are constant-time and remote material
/// is validated, so malformed peer input fails the handshake closed rather than throwing.
/// </summary>
public class PasswordAuthenticationHandler : IAuthenticationHandler public class PasswordAuthenticationHandler : IAuthenticationHandler
{ {
public string Protocol => "hash"; public string Protocol => "hash";
// Length, in bytes, of the random nonces exchanged during the handshake.
// Remote nonces are validated against this to reject malformed or weak input.
const int NonceLength = 20;
byte[] _localNonce, _remoteNonce; byte[] _localNonce, _remoteNonce;
byte[] _localSalt, _remoteSalt; byte[] _localSalt, _remoteSalt;
@@ -33,6 +43,18 @@ namespace Esiur.Security.Authority.Providers
public IAuthenticationProvider Provider => _provider; public IAuthenticationProvider Provider => _provider;
// Constant-time comparison of two byte arrays. Used for all challenge/MAC
// checks so that, unlike SequenceEqual, the time taken does not depend on how
// many leading bytes matched — closing a timing side channel on the secret.
// Returns false for null or length-mismatched inputs (challenges are fixed size).
static bool FixedTimeEquals(byte[] a, byte[] b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
return Org.BouncyCastle.Utilities.Arrays.FixedTimeEquals(a, b);
}
public static byte[] ComputeSha3(byte[] data, int bitLength = 256) public static byte[] ComputeSha3(byte[] data, int bitLength = 256)
{ {
// 1. Initialize the digest (supports 224, 256, 384, 512) // 1. Initialize the digest (supports 224, 256, 384, 512)
@@ -50,7 +72,22 @@ namespace Esiur.Security.Authority.Providers
public AuthenticationResult Process(object authData) public AuthenticationResult Process(object authData)
{ {
Console.WriteLine($"PasswordAuthenticationHandler: {this.GetHashCode()} Step {_step}, Mode {_mode}, Direction {_direction}"); // Process runs at the trust boundary on data supplied by a remote peer.
// Any malformed input (wrong types, null or short fields) must fail the
// handshake instead of throwing, so the exchange is wrapped to fail closed.
try
{
return ProcessInternal(authData);
}
catch
{
_step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null);
}
}
private AuthenticationResult ProcessInternal(object authData)
{
var remoteAuthData = (object[])authData; var remoteAuthData = (object[])authData;
var localAuthData = new List<object>(); var localAuthData = new List<object>();
@@ -97,7 +134,7 @@ namespace Esiur.Security.Authority.Providers
var remoteChallenge = (byte[])remoteAuthData[2]; var remoteChallenge = (byte[])remoteAuthData[2];
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -112,7 +149,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray()); .ToArray());
// compare remote challenge // compare remote challenge
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge)) if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -160,7 +197,7 @@ namespace Esiur.Security.Authority.Providers
_responderIdentity = (string)remoteAuthData[1]; _responderIdentity = (string)remoteAuthData[1];
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -203,7 +240,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce) .Concat(_localNonce)
.ToArray()); .ToArray());
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge)) if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -262,7 +299,7 @@ namespace Esiur.Security.Authority.Providers
_remoteSalt = (byte[])remoteAuthData[2]; _remoteSalt = (byte[])remoteAuthData[2];
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -313,7 +350,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce) .Concat(_localNonce)
.ToArray()); .ToArray());
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge)) if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -361,7 +398,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it // @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -403,7 +440,7 @@ namespace Esiur.Security.Authority.Providers
.Concat(_localNonce) .Concat(_localNonce)
.ToArray()); .ToArray());
// compare remote challenge // compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge)) if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -442,7 +479,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it // @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -490,7 +527,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray()); .ToArray());
// compare remote challenge // compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge)) if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -534,7 +571,7 @@ namespace Esiur.Security.Authority.Providers
// prevent reply attack by checking if remote nonce is same as local nonce. // prevent reply attack by checking if remote nonce is same as local nonce.
// @TODO: We can change our localNonce then send it // @TODO: We can change our localNonce then send it
if (_remoteNonce.SequenceEqual(_localNonce)) if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -597,7 +634,7 @@ namespace Esiur.Security.Authority.Providers
.ToArray()); .ToArray());
// compare remote challenge // compare remote challenge
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge)) if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
{ {
_step = -1; _step = -1;
return new AuthenticationResult(AuthenticationRuling.Failed, null); return new AuthenticationResult(AuthenticationRuling.Failed, null);
@@ -646,7 +683,7 @@ namespace Esiur.Security.Authority.Providers
string domain, string domain,
PasswordAuthenticationProvider provider) PasswordAuthenticationProvider provider)
{ {
_localNonce = Global.GenerateBytes(20); _localNonce = Global.GenerateBytes(NonceLength);
this._provider = provider; this._provider = provider;
this._initiatorIdentity = initiatorIdentity; this._initiatorIdentity = initiatorIdentity;
+190
View File
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Esiur.Security.Authority;
using Esiur.Security.Authority.Providers;
namespace Esiur.Tests.Unit;
/// <summary>
/// Drives a pair of <see cref="PasswordAuthenticationHandler"/> instances (initiator and
/// responder) through the SHA3 challenge/response handshake and asserts both the happy
/// path (matching session keys) and the security hardening (constant-time challenge
/// checks, nonce validation, fail-closed behaviour on malformed peer input).
/// </summary>
public class AuthHandshakeTests
{
// ---- test credential store -------------------------------------------------------
class TestAccount
{
public string Identity;
public byte[] RawPassword;
public byte[] Salt;
public byte[] Hash; // SHA3-256(RawPassword || Salt), exactly what the verifier stores
}
static readonly byte[] FixedSalt = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6 };
static TestAccount MakeAccount(string identity, string password)
{
var raw = Encoding.UTF8.GetBytes(password);
var hash = PasswordAuthenticationHandler.ComputeSha3(raw.Concat(FixedSalt).ToArray());
return new TestAccount { Identity = identity, RawPassword = raw, Salt = FixedSalt, Hash = hash };
}
class StubProvider : PasswordAuthenticationProvider
{
readonly Dictionary<string, TestAccount> _accounts;
readonly string _self;
public StubProvider(string self, params TestAccount[] accounts)
{
_self = self;
_accounts = accounts.ToDictionary(a => a.Identity, a => a);
}
public override IdentityPassword GetSelfIdentityAndCredential(string domain, string hostname)
=> new IdentityPassword(_self, _accounts[_self].RawPassword);
public override byte[] GetSelfCredential(string identity, string domain, string hostname)
=> _accounts.TryGetValue(identity, out var a) ? a.RawPassword : null;
public override PasswordHash GetHostedAccountCredential(string identity, string domain)
=> _accounts.TryGetValue(identity, out var a)
? new PasswordHash(a.Hash, a.Salt)
: new PasswordHash(null, null);
}
// ---- helpers ---------------------------------------------------------------------
static object[] DataOf(AuthenticationResult result)
=> ((List<object>)result.AuthenticationData)?.ToArray();
static byte[] PrivateNonce(PasswordAuthenticationHandler handler)
=> (byte[])typeof(PasswordAuthenticationHandler)
.GetField("_localNonce", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(handler);
static (PasswordAuthenticationHandler init, PasswordAuthenticationHandler resp) NewPair(string identity = "alice")
{
var account = MakeAccount(identity, "correct horse battery staple");
var initiator = new PasswordAuthenticationHandler(
AuthenticationMode.InitializerIdentity, AuthenticationDirection.Initiator,
identity, null, "host", "domain", new StubProvider(identity, account));
var responder = new PasswordAuthenticationHandler(
AuthenticationMode.InitializerIdentity, AuthenticationDirection.Responder,
null, null, "host", "domain", new StubProvider(identity, account));
return (initiator, responder);
}
// ---- happy path ------------------------------------------------------------------
[Fact]
public void InitializerIdentity_Handshake_Derives_Matching_SessionKeys()
{
var (init, resp) = NewPair();
var r1 = init.Process(null); // -> [initNonce, initIdentity]
Assert.Equal(AuthenticationRuling.InProgress, r1.Ruling);
var r2 = resp.Process(DataOf(r1)); // -> [respNonce, respSalt, respChallenge]
Assert.Equal(AuthenticationRuling.InProgress, r2.Ruling);
var r3 = init.Process(DataOf(r2)); // -> [initChallenge], Succeeded
Assert.Equal(AuthenticationRuling.Succeeded, r3.Ruling);
var r4 = resp.Process(DataOf(r3)); // Succeeded
Assert.Equal(AuthenticationRuling.Succeeded, r4.Ruling);
Assert.NotNull(r3.SessionKey);
Assert.Equal(64, r3.SessionKey.Length); // 512-bit derived key
Assert.Equal(r3.SessionKey, r4.SessionKey); // both ends agree
}
[Fact]
public void Wrong_Password_Fails()
{
var account = MakeAccount("alice", "the real password");
var init = new PasswordAuthenticationHandler(
AuthenticationMode.InitializerIdentity, AuthenticationDirection.Initiator,
"alice", null, "host", "domain",
new StubProvider("alice", MakeAccount("alice", "a different password")));
var resp = new PasswordAuthenticationHandler(
AuthenticationMode.InitializerIdentity, AuthenticationDirection.Responder,
null, null, "host", "domain", new StubProvider("alice", account));
var r1 = init.Process(null);
var r2 = resp.Process(DataOf(r1));
// Initiator validates the responder's challenge against its (wrong) password and bails.
var r3 = init.Process(DataOf(r2));
Assert.Equal(AuthenticationRuling.Failed, r3.Ruling);
}
// ---- security properties ---------------------------------------------------------
[Fact]
public void Tampered_Challenge_Fails()
{
var (init, resp) = NewPair();
var r1 = init.Process(null);
var r2 = resp.Process(DataOf(r1));
var r3 = init.Process(DataOf(r2)); // [initChallenge]
var tampered = DataOf(r3);
((byte[])tampered[0])[0] ^= 0xFF; // flip a bit in the challenge
var r4 = resp.Process(tampered);
Assert.Equal(AuthenticationRuling.Failed, r4.Ruling);
}
[Fact]
public void Reflected_Nonce_Is_Rejected()
{
// Replay defence: feeding the responder its own nonce must be rejected.
var (init, resp) = NewPair();
var respNonce = PrivateNonce(resp);
var forged = new object[] { respNonce, "alice" };
var result = resp.Process(forged);
Assert.Equal(AuthenticationRuling.Failed, result.Ruling);
}
[Fact]
public void Short_Nonce_Is_Rejected()
{
var (_, resp) = NewPair();
var result = resp.Process(new object[] { new byte[5], "alice" });
Assert.Equal(AuthenticationRuling.Failed, result.Ruling);
}
[Theory]
[InlineData(0)] // empty
[InlineData(1)] // too few elements
public void Truncated_Input_Fails_Without_Throwing(int count)
{
var (_, resp) = NewPair();
var result = resp.Process(Enumerable.Range(0, count).Select(_ => (object)new byte[20]).ToArray());
Assert.Equal(AuthenticationRuling.Failed, result.Ruling);
}
[Fact]
public void Null_Input_Fails_Without_Throwing()
{
var (_, resp) = NewPair();
var result = resp.Process(null);
Assert.Equal(AuthenticationRuling.Failed, result.Ruling);
}
[Fact]
public void WrongType_Material_Fails_Closed()
{
// A peer sending a string where a nonce (byte[]) is expected must fail, not throw.
var (_, resp) = NewPair();
var result = resp.Process(new object[] { "not-a-nonce", "alice" });
Assert.Equal(AuthenticationRuling.Failed, result.Ruling);
}
}
+27
View File
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<!-- Reference Esiur both as a runtime assembly and as the source generator (analyzer),
mirroring Tests/Features/Functional so [Resource]/[Export] test types get generated code. -->
<ItemGroup>
<ProjectReference Include="..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
</ItemGroup>
</Project>
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Linq;
using Esiur.Misc;
namespace Esiur.Tests.Unit;
/// <summary>
/// Guards the fix that moved Global.GenerateBytes / GenerateCode off System.Random onto a
/// cryptographic RNG. These are sanity/entropy checks, not statistical proofs: their job is
/// to fail loudly if someone reintroduces a predictable or constant generator.
/// </summary>
public class SecureRandomTests
{
[Fact]
public void GenerateBytes_Returns_Requested_Length()
{
Assert.Equal(20, Global.GenerateBytes(20).Length);
Assert.Equal(0, Global.GenerateBytes(0).Length);
}
[Fact]
public void GenerateBytes_Are_Not_Repeated()
{
var a = Global.GenerateBytes(32);
var b = Global.GenerateBytes(32);
Assert.False(a.SequenceEqual(b), "Two nonces must not be identical.");
}
[Fact]
public void GenerateBytes_Are_Not_Constant()
{
var bytes = Global.GenerateBytes(64);
Assert.True(bytes.Distinct().Count() > 1, "Output must not be a single repeated byte.");
}
[Fact]
public void GenerateBytes_Have_Broad_Distribution()
{
// Across 4 KiB of output almost every byte value should appear at least once.
var bytes = Global.GenerateBytes(4096);
Assert.True(bytes.Distinct().Count() > 200,
"A cryptographic RNG should produce a wide spread of byte values.");
}
[Fact]
public void GenerateCode_Honours_Length_And_Alphabet()
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var code = Global.GenerateCode(24);
Assert.Equal(24, code.Length);
Assert.All(code, c => Assert.Contains(c, alphabet));
}
[Fact]
public void GenerateCode_Is_Not_Repeated()
{
Assert.NotEqual(Global.GenerateCode(24), Global.GenerateCode(24));
}
}
+215
View File
@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using Esiur.Data;
using Esiur.Resource;
namespace Esiur.Tests.Unit;
/// <summary>
/// Round-trips values through Codec.Compose -> Codec.ParseSync and asserts the value
/// survives. Because the serializer narrows integers/floats to the smallest wire type,
/// the parsed CLR type often differs from the input, so comparisons are value-based.
/// Exact wire bytes are pinned separately by <see cref="WireFormatGoldenTests"/>.
/// </summary>
public class SerializationRoundTripTests
{
static object RoundTrip(object value)
{
var bytes = Codec.Compose(value, Warehouse.Default, null);
var (consumed, parsed) = Codec.ParseSync(bytes, 0, Warehouse.Default);
Assert.Equal((uint)bytes.Length, consumed);
return parsed;
}
static void AssertIntRoundTrip(long value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToInt64(parsed));
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(-1)]
[InlineData(sbyte.MinValue)]
[InlineData(sbyte.MaxValue)]
[InlineData((long)short.MinValue)]
[InlineData((long)short.MaxValue)]
[InlineData((long)int.MinValue)]
[InlineData((long)int.MaxValue)]
[InlineData(long.MinValue)]
[InlineData(long.MaxValue)]
public void Int64_NarrowsAndRoundTrips(long value) => AssertIntRoundTrip(value);
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(-1)]
[InlineData(int.MinValue)]
[InlineData(int.MaxValue)]
public void Int32_RoundTrips(int value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToInt32(parsed));
}
[Theory]
[InlineData((ulong)0)]
[InlineData((ulong)255)]
[InlineData((ulong)65535)]
[InlineData((ulong)uint.MaxValue)]
[InlineData(ulong.MaxValue)]
public void UInt64_NarrowsAndRoundTrips(ulong value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToUInt64(parsed));
}
[Theory]
[InlineData(0f)]
[InlineData(1.5f)]
[InlineData(-3.25f)]
[InlineData(3.4028235e38f)]
public void Float32_RoundTrips(float value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToSingle(parsed), 3);
}
[Theory]
[InlineData(0d)]
[InlineData(1.5d)]
[InlineData(-3.25d)]
[InlineData(0.1d)]
[InlineData(1.7976931348623157e308d)]
public void Float64_RoundTrips(double value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToDouble(parsed), 10);
}
[Theory]
[InlineData(double.NaN)]
[InlineData(double.PositiveInfinity)]
[InlineData(double.NegativeInfinity)]
public void Float_NaN_And_Infinity_Encode_As_Infinity_Token(double value)
{
// The serializer collapses NaN and +/- Infinity onto the single 1-byte Infinity
// token (0x04), which now decodes to a canonical +Infinity rather than crashing.
var bytes = Codec.Compose(value, Warehouse.Default, null);
Assert.Equal(new byte[] { 0x04 }, bytes);
var parsed = RoundTrip(value);
Assert.True(double.IsPositiveInfinity(Convert.ToDouble(parsed)));
}
[Theory]
[InlineData(0.5)]
[InlineData(1.1)] // hits the decimal -> Float64 branch (regression for the byte[4] overrun)
[InlineData(-1234.5)]
public void Decimal_FloatBranches_RoundTrip(double asDouble)
{
var value = (decimal)asDouble;
var parsed = RoundTrip(value);
Assert.Equal(Convert.ToDouble(value), Convert.ToDouble(parsed), 6);
}
[Fact]
public void Decimal_IntegerValue_RoundTrips()
{
var parsed = RoundTrip(42m);
Assert.Equal(42L, Convert.ToInt64(parsed));
}
[Fact]
public void Decimal_HighPrecision_RoundTrips()
{
// A value with a non-zero scale that is not exactly representable as float or
// double, so it stays a full 16-byte Decimal128 on the wire.
var value = 1.2345678901234567890123456789m;
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToDecimal(parsed));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Bool_RoundTrips(bool value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, Convert.ToBoolean(parsed));
}
[Theory]
[InlineData("")]
[InlineData("Hello, Esiur")]
[InlineData("Unicode éü☃😀")]
public void String_RoundTrips(string value)
{
var parsed = RoundTrip(value);
Assert.Equal(value, (string)parsed);
}
[Fact]
public void Null_RoundTrips()
{
var parsed = RoundTrip(null);
Assert.Null(parsed);
}
[Fact]
public void Char_RoundTrips()
{
var parsed = RoundTrip('A');
Assert.Equal('A', Convert.ToChar(parsed));
}
[Fact]
public void DateTime_RoundTrips_AsUtc()
{
var value = new DateTime(2026, 6, 2, 13, 45, 30, DateTimeKind.Utc);
var parsed = (DateTime)RoundTrip(value);
Assert.Equal(value.ToUniversalTime().Ticks, parsed.ToUniversalTime().Ticks);
}
[Fact]
public void Uuid_RoundTrips()
{
var value = new Uuid(Guid.Parse("12345678-90ab-cdef-1234-567890abcdef").ToByteArray());
var parsed = (Uuid)RoundTrip(value);
Assert.Equal(value.ToString(), parsed.ToString());
}
static List<object> ToObjectList(object value)
{
var got = new List<object>();
foreach (var o in (System.Collections.IEnumerable)value)
got.Add(o);
return got;
}
[Fact]
public void IntArray_RoundTrips()
{
// A typed int[] is reconstructed as an int[] (typed-list path), not a dynamic list.
var value = new int[] { 1, 2, 3, 100, -50, int.MaxValue };
var got = ToObjectList(RoundTrip(value)).ConvertAll(Convert.ToInt64);
Assert.Equal(new long[] { 1, 2, 3, 100, -50, int.MaxValue }, got);
}
[Fact]
public void StringList_RoundTrips()
{
var value = new object[] { "a", "b", "c" };
var got = ToObjectList(RoundTrip(value)).ConvertAll(o => (string)o);
Assert.Equal(new[] { "a", "b", "c" }, got);
}
[Fact]
public void Map_RoundTrips()
{
var value = new Map<string, int> { ["one"] = 1, ["two"] = 2 };
var parsed = RoundTrip(value);
Assert.NotNull(parsed);
}
}
+94
View File
@@ -0,0 +1,94 @@
using System;
using Esiur.Data;
using Esiur.Resource;
namespace Esiur.Tests.Unit;
[Export]
public class PersonRecord : IRecord
{
public string Name { get; set; }
public int Age { get; set; }
public double Score { get; set; }
}
public enum Color
{
Red,
Green,
Blue,
}
/// <summary>
/// Covers the "typed" serialization paths (records, tuples, enums, typed maps/lists) that
/// rely on Tru.FromType. Uses a decode-then-re-encode stability check: re-composing a parsed
/// value must reproduce the exact original wire bytes. This is type-agnostic (no need to know
/// the parsed CLR type) and proves both the encode and decode halves agree on the format.
/// </summary>
public class TypedSerializationTests
{
static void AssertReencodeStable(object value)
{
var bytes1 = Codec.Compose(value, Warehouse.Default, null);
var (consumed, parsed) = Codec.ParseSync(bytes1, 0, Warehouse.Default);
Assert.Equal((uint)bytes1.Length, consumed);
var bytes2 = Codec.Compose(parsed, Warehouse.Default, null);
Assert.Equal(bytes1, bytes2);
}
[Fact]
public void Record_ReencodeIsStable()
{
AssertReencodeStable(new PersonRecord { Name = "Ada", Age = 36, Score = 99.5 });
}
[Fact]
public void Enum_Encodes_Typed_And_Decodes_To_Underlying_Int()
{
// An enum is sent as a Typed TDU carrying the constant index; the decoder (without
// CLR enum reconstruction) yields the underlying integer value. This is the existing
// protocol behaviour, asserted here so it stays stable.
var bytes = Codec.Compose(Color.Green, Warehouse.Default, null);
Assert.Equal(0x88, bytes[0]); // Typed class token
var (_, parsed) = Codec.ParseSync(bytes, 0, Warehouse.Default);
Assert.Equal((int)Color.Green, Convert.ToInt32(parsed));
}
[Theory]
[InlineData(1, "a")]
[InlineData(-5, "hello")]
public void Tuple2_ReencodeIsStable(int a, string b)
{
AssertReencodeStable((a, b));
}
[Fact]
public void Tuple3_ReencodeIsStable()
{
AssertReencodeStable((1, "two", 3.0));
}
[Fact]
public void TypedMap_ReencodeIsStable()
{
AssertReencodeStable(new Map<string, int> { ["one"] = 1, ["two"] = 2, ["three"] = 3 });
}
[Fact]
public void TypedIntList_ReencodeIsStable()
{
AssertReencodeStable(new int[] { 5, 4, 3, 2, 1, 0, -1 });
}
[Fact]
public void RecordList_ReencodeIsStable()
{
AssertReencodeStable(new[]
{
new PersonRecord { Name = "A", Age = 1, Score = 1.1 },
new PersonRecord { Name = "B", Age = 2, Score = 2.2 },
});
}
}
+86
View File
@@ -0,0 +1,86 @@
using System;
using Esiur.Data;
using Esiur.Resource;
namespace Esiur.Tests.Unit;
/// <summary>
/// Pins the exact on-wire bytes produced by Codec.Compose for a representative value of
/// every TDU family. Esiur is a multi-language protocol (C#/JS/Dart) whose binary format
/// must stay byte-compatible, so these golden vectors are a guard rail: any later
/// refactor (e.g. serializer performance work) that changes a single byte fails here.
/// Values were captured from the current implementation.
/// </summary>
public class WireFormatGoldenTests
{
static string Hex(object value) =>
BitConverter.ToString(Codec.Compose(value, Warehouse.Default, null)).Replace("-", "").ToLowerInvariant();
[Theory]
// fixed, zero-payload tokens
[InlineData("null", null, "00")]
[InlineData("bool_false", false, "01")]
[InlineData("bool_true", true, "02")]
// signed integers, narrowed to the smallest width
[InlineData("int_0", 0, "0900")]
[InlineData("int_1", 1, "0901")]
[InlineData("int_minus1", -1, "09ff")]
[InlineData("int_127", 127, "097f")]
[InlineData("int_128", 128, "118000")]
[InlineData("int_200", 200, "11c800")]
[InlineData("int_40000", 40000, "19409c0000")]
[InlineData("int_70000", 70000, "1970110100")]
[InlineData("long_5e9", 5000000000L, "2100f2052a01000000")]
// unsigned integers
[InlineData("uint_255", (uint)255, "08ff")]
[InlineData("uint_256", (uint)256, "100001")]
[InlineData("ulong_max", ulong.MaxValue, "20ffffffffffffffff")]
// floating point
[InlineData("float_1p5", 1.5f, "1a0000c03f")]
[InlineData("double_0p1", 0.1d, "229a9999999999b93f")]
// char
[InlineData("char_A", 'A', "124100")]
// strings (length-prefixed UTF-8)
[InlineData("string_Hi", "Hi", "49024869")]
[InlineData("string_empty", "", "41")]
public void Compose_Matches_Golden(string name, object value, string expectedHex)
{
_ = name; // identifies the case in test output
Assert.Equal(expectedHex, Hex(value));
}
[Fact]
public void Decimal_HighPrecision_Golden()
{
Assert.Equal("2a00001c00321be4271581396eb1c9be46", Hex(1.2345678901234567890123456789m));
}
[Fact]
public void DateTime_Golden()
{
Assert.Equal("230039f035adc0de08", Hex(new DateTime(2026, 6, 2, 13, 45, 30, DateTimeKind.Utc)));
}
[Fact]
public void Uuid_Golden()
{
var uuid = new Uuid(Guid.Parse("12345678-90ab-cdef-1234-567890abcdef").ToByteArray());
Assert.Equal("2b78563412ab90efcd1234567890abcdef", Hex(uuid));
}
[Fact]
public void IntArray_TypedList_Golden()
{
// Typed list with Gvwie group-encoded payload for { 1, 2, 3 }.
Assert.Equal("88054809020406", Hex(new int[] { 1, 2, 3 }));
}
[Theory]
[InlineData(double.NaN)]
[InlineData(double.PositiveInfinity)]
[InlineData(double.NegativeInfinity)]
public void NaN_And_Infinity_Encode_To_Single_Token(double value)
{
Assert.Equal("04", Hex(value));
}
}