diff --git a/Esiur.sln b/Esiur.sln
index 600224a..13bfa1c 100644
--- a/Esiur.sln
+++ b/Esiur.sln
@@ -1,3 +1,4 @@
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.4.11612.150
@@ -92,96 +93,282 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConcurrentAttachSweep", "Co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttachSweep", "Tests\Distribution\ConcurrentAttachSweep\Esiur.Tests.ConcurrentAttachSweep.csproj", "{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.Unit", "Tests\Unit\Esiur.Tests.Unit.csproj", "{D1B99C5A-82F7-459D-B56D-F8FD096D3854}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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|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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -226,6 +413,7 @@ Global
{550A20AB-8E97-BCDD-9F54-27823663120A} = {21D42B96-99F9-4E48-A499-5170A5A9597F}
{E713D25F-2602-44C9-AB9E-C9477FB2BA93} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
{3FFB2BF4-159E-3073-4BDF-08AE93C7A2C1} = {E713D25F-2602-44C9-AB9E-C9477FB2BA93}
+ {D1B99C5A-82F7-459D-B56D-F8FD096D3854} = {2769C4C3-2595-413B-B7FE-5903826770C1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
diff --git a/Libraries/Esiur/Data/Codec.cs b/Libraries/Esiur/Data/Codec.cs
index a9e0dba..0f3da05 100644
--- a/Libraries/Esiur/Data/Codec.cs
+++ b/Libraries/Esiur/Data/Codec.cs
@@ -51,6 +51,7 @@ public static class Codec
DataDeserializer.BooleanFalseParserAsync,
DataDeserializer.BooleanTrueParserAsync,
DataDeserializer.NotModifiedParserAsync,
+ DataDeserializer.InfinityParserAsync,
},
new AsyncParser[]{
DataDeserializer.UInt8ParserAsync,
@@ -115,6 +116,7 @@ public static class Codec
DataDeserializer.BooleanFalseParser,
DataDeserializer.BooleanTrueParser,
DataDeserializer.NotModifiedParser,
+ DataDeserializer.InfinityParser,
},
new SyncParser[]{
DataDeserializer.UInt8Parser,
@@ -376,6 +378,13 @@ public static class Codec
}
+ ///
+ /// Synchronously parses a single value from its IIP wire representation.
+ ///
+ /// Buffer containing the encoded value.
+ /// Zero-based offset of the value within .
+ /// Warehouse used to resolve typed structures (records, enums, ...).
+ /// A tuple of (number of bytes consumed, decoded value).
public static (uint, object) ParseSync(byte[] data, uint offset, Warehouse warehouse)
{
var tdu = ParsedTdu.ParseSync(data, offset, (uint)data.Length, warehouse);
@@ -612,7 +621,14 @@ public static class Codec
/// EpConnection is required to check locality.
/// If True, prepend the DataType at the beginning of the output.
/// Array of bytes in the network byte order.
- public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)//, bool prependType = true)
+ ///
+ /// Encodes a value to its self-describing IIP wire representation (a type-prefixed TDU).
+ ///
+ /// The value to encode (may be null, which encodes as the Null TDU).
+ /// Warehouse used to resolve type definitions for typed structures.
+ /// Connection context, required when the value references remote resources; may be null for plain data.
+ /// The encoded bytes, including the leading type identifier.
+ public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)
{
var tdu = ComposeInternal(valueOrSource, warehouse, connection);
return tdu.Composed;
diff --git a/Libraries/Esiur/Data/DataConverter.cs b/Libraries/Esiur/Data/DataConverter.cs
index 01600ef..0404912 100644
--- a/Libraries/Esiur/Data/DataConverter.cs
+++ b/Libraries/Esiur/Data/DataConverter.cs
@@ -108,11 +108,10 @@ public static class DC // Data Converter
}
}
- catch (Exception ex)
+ catch (Exception)
{
-
- throw ex;
- return null;
+ // Preserve the original stack trace with a bare rethrow.
+ throw;
}
}
}
diff --git a/Libraries/Esiur/Data/DataDeserializer.cs b/Libraries/Esiur/Data/DataDeserializer.cs
index e35bdfa..c8d14bf 100644
--- a/Libraries/Esiur/Data/DataDeserializer.cs
+++ b/Libraries/Esiur/Data/DataDeserializer.cs
@@ -59,6 +59,19 @@ public static class DataDeserializer
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)
{
return tdu.Data[tdu.PayloadOffset];
@@ -1345,7 +1358,6 @@ public static class DataDeserializer
var subTypes = subTrus.Select(x => x.RuntimeType).ToArray();
ParsedTdu current;
- ParsedTdu? previous = null;
var offset = tdu.PayloadOffset;
var length = tdu.PayloadLength;
@@ -1477,7 +1489,6 @@ public static class DataDeserializer
var types = subTrus.Select(x => x.RuntimeType).ToArray();
ParsedTdu current;
- ParsedTdu? previous = null;
var offset = tdu.PayloadOffset;
var length = tdu.PayloadLength;
diff --git a/Libraries/Esiur/Data/DataSerializer.cs b/Libraries/Esiur/Data/DataSerializer.cs
index 4779bad..2eaf46d 100644
--- a/Libraries/Esiur/Data/DataSerializer.cs
+++ b/Libraries/Esiur/Data/DataSerializer.cs
@@ -17,7 +17,7 @@ public static class DataSerializer
{
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;
@@ -29,22 +29,19 @@ public static class DataSerializer
{
// Fits in 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((short*)ptr) = (short)v;
-
+ BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v);
return new Tdu(TduIdentifier.Int16, rt, 2, null, null);
}
else
{
// Use full 4 bytes
var rt = new byte[4];
- fixed (byte* ptr = rt)
- *((int*)ptr) = v;
+ BinaryPrimitives.WriteInt32LittleEndian(rt, v);
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;
@@ -57,23 +54,19 @@ public static class DataSerializer
{
// Fits in 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((ushort*)ptr) = (ushort)v;
-
+ BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v);
return new Tdu(TduIdentifier.UInt16, rt, 2, null, null);
}
else
{
// Use full 4 bytes
var rt = new byte[4];
- fixed (byte* ptr = rt)
- *((uint*)ptr) = v;
-
+ BinaryPrimitives.WriteUInt32LittleEndian(rt, v);
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;
@@ -86,14 +79,12 @@ public static class DataSerializer
{
// Use full 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((short*)ptr) = v;
-
+ BinaryPrimitives.WriteInt16LittleEndian(rt, v);
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;
@@ -106,9 +97,7 @@ public static class DataSerializer
{
// Use full 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((ushort*)ptr) = v;
-
+ BinaryPrimitives.WriteUInt16LittleEndian(rt, v);
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);
}
}
- 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;
@@ -224,32 +213,26 @@ public static class DataSerializer
{
// Fits in 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((short*)ptr) = (short)v;
-
+ BinaryPrimitives.WriteInt16LittleEndian(rt, (short)v);
return new Tdu(TduIdentifier.Int16, rt, 2, null, null);
}
else if (v >= int.MinValue && v <= int.MaxValue)
{
// Fits in 4 bytes
var rt = new byte[4];
- fixed (byte* ptr = rt)
- *((int*)ptr) = (int)v;
-
+ BinaryPrimitives.WriteInt32LittleEndian(rt, (int)v);
return new Tdu(TduIdentifier.Int32, rt, 4, null, null);
}
else
{
// Use full 8 bytes
var rt = new byte[8];
- fixed (byte* ptr = rt)
- *((long*)ptr) = v;
-
+ BinaryPrimitives.WriteInt64LittleEndian(rt, v);
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;
@@ -262,39 +245,31 @@ public static class DataSerializer
{
// Fits in 2 bytes
var rt = new byte[2];
- fixed (byte* ptr = rt)
- *((ushort*)ptr) = (ushort)v;
-
+ BinaryPrimitives.WriteUInt16LittleEndian(rt, (ushort)v);
return new Tdu(TduIdentifier.UInt16, rt, 2, null, null);
}
else if (v <= uint.MaxValue)
{
// Fits in 4 bytes
var rt = new byte[4];
- fixed (byte* ptr = rt)
- *((uint*)ptr) = (uint)v;
-
+ BinaryPrimitives.WriteUInt32LittleEndian(rt, (uint)v);
return new Tdu(TduIdentifier.UInt32, rt, 4, null, null);
}
else
{
// Use full 8 bytes
var rt = new byte[8];
- fixed (byte* ptr = rt)
- *((ulong*)ptr) = v;
-
+ BinaryPrimitives.WriteUInt64LittleEndian(rt, v);
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 rt = new byte[8];
- fixed (byte* ptr = rt)
- *((long*)ptr) = v;
-
+ BinaryPrimitives.WriteInt64LittleEndian(rt, v);
return new Tdu(TduIdentifier.DateTime, rt, 8, null, null);
}
@@ -362,7 +337,7 @@ public static class DataSerializer
double d = (double)v;
if ((decimal)d == v)
{
- var rt = new byte[4];
+ var rt = new byte[8];
fixed (byte* ptr = rt)
*((double*)ptr) = d;
@@ -436,15 +411,12 @@ public static class DataSerializer
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 rt = new byte[2];
- fixed (byte* ptr = rt)
- *((char*)ptr) = v;
-
+ BinaryPrimitives.WriteUInt16LittleEndian(rt, v);
return new Tdu(TduIdentifier.Char16, rt, 2, null, null);
-
}
public static Tdu BoolComposer(object value, Warehouse warehouse, EpConnection connection)
@@ -733,7 +705,9 @@ public static class DataSerializer
if (value == null)
return null;
- var rt = new List();
+ // Pre-size the buffer from the element count (when known) to avoid repeated
+ // List reallocations as items are appended. 4 bytes/element is a rough hint.
+ var rt = new List(value is ICollection collection ? collection.Count * 4 : 16);
Tdu? previous = null;
@@ -934,7 +908,7 @@ public static class DataSerializer
var trus = fields.Select(x => Tru.FromType(x.FieldType, warehouse)).ToArray();
- var rt = new List();
+ var rt = new List(fields.Length * 4);
for (var i = 0; i < fields.Length; i++)
{
diff --git a/Libraries/Esiur/Data/Tdu.cs b/Libraries/Esiur/Data/Tdu.cs
index c3834f3..4418482 100644
--- a/Libraries/Esiur/Data/Tdu.cs
+++ b/Libraries/Esiur/Data/Tdu.cs
@@ -65,7 +65,7 @@ public struct Tdu
}
public Tdu(TduIdentifier identifier,
- byte[] data, ulong length, Tru metadata, EpConnection connection)
+ byte[]? data, ulong length, Tru? metadata, EpConnection? connection)
{
Identifier = identifier;
//Index = (byte)identifier & 0x7;
diff --git a/Libraries/Esiur/Data/Tru.cs b/Libraries/Esiur/Data/Tru.cs
index 3f4d679..e253705 100644
--- a/Libraries/Esiur/Data/Tru.cs
+++ b/Libraries/Esiur/Data/Tru.cs
@@ -138,6 +138,14 @@ namespace Esiur.Data
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 flags);
public abstract void SetNotNull(byte flag);
@@ -299,7 +307,32 @@ namespace Esiur.Data
//private static Dictionary cache = new Dictionary();
//private static object cacheLook = new object();
+ ///
+ /// 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.
+ ///
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)
return new TruPrimitive(TruIdentifier.Void, true, typeof(void));
@@ -714,7 +747,7 @@ namespace Esiur.Data
offset += pr.Size;
}
- Type runtimeType = null;
+ Type? runtimeType = null;
if (identifier == TruIdentifier.TypedList)
{
@@ -852,7 +885,7 @@ namespace Esiur.Data
offset += pr.Size;
}
- Type runtimeType = null;
+ Type? runtimeType = null;
if (identifier == TruIdentifier.TypedList)
{
diff --git a/Libraries/Esiur/Data/TruComposite.cs b/Libraries/Esiur/Data/TruComposite.cs
index aff54f0..e876e1e 100644
--- a/Libraries/Esiur/Data/TruComposite.cs
+++ b/Libraries/Esiur/Data/TruComposite.cs
@@ -11,11 +11,9 @@ namespace Esiur.Data
{
public Tru[] SubTypes;
- Type _runtimeType;
-
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;
Nullable = nullable;
diff --git a/Libraries/Esiur/Data/Types/ArgumentDef.cs b/Libraries/Esiur/Data/Types/ArgumentDef.cs
index 9db51f1..166acac 100644
--- a/Libraries/Esiur/Data/Types/ArgumentDef.cs
+++ b/Libraries/Esiur/Data/Types/ArgumentDef.cs
@@ -88,7 +88,7 @@ public class ArgumentDef
}
else
{
- var exp = Codec.Compose(Annotations, null, null);
+ var exp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);
return new BinaryList()
.AddUInt8((byte)(0x2 | (Optional ? 1 : 0)))
diff --git a/Libraries/Esiur/Data/Types/ConstantDef.cs b/Libraries/Esiur/Data/Types/ConstantDef.cs
index 3a2a323..af003b1 100644
--- a/Libraries/Esiur/Data/Types/ConstantDef.cs
+++ b/Libraries/Esiur/Data/Types/ConstantDef.cs
@@ -76,7 +76,7 @@ public class ConstantDef : MemberDef
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;
return new BinaryList()
.AddUInt8(hdr)
diff --git a/Libraries/Esiur/Data/Types/EventDef.cs b/Libraries/Esiur/Data/Types/EventDef.cs
index b85dd21..7799765 100644
--- a/Libraries/Esiur/Data/Types/EventDef.cs
+++ b/Libraries/Esiur/Data/Types/EventDef.cs
@@ -82,7 +82,7 @@ public class EventDef : MemberDef
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;
return new BinaryList()
.AddUInt8(hdr)
diff --git a/Libraries/Esiur/Data/Types/FunctionDef.cs b/Libraries/Esiur/Data/Types/FunctionDef.cs
index c24576b..ea61bfe 100644
--- a/Libraries/Esiur/Data/Types/FunctionDef.cs
+++ b/Libraries/Esiur/Data/Types/FunctionDef.cs
@@ -110,7 +110,7 @@ public class FunctionDef : MemberDef
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.InsertUInt8(0, (byte)((Inherited ? (byte)0x90 : (byte)0x10) | (IsStatic ? 0x4 : 0)));
}
diff --git a/Libraries/Esiur/Data/Types/LocalTypeDef.cs b/Libraries/Esiur/Data/Types/LocalTypeDef.cs
index e123a41..56a59e5 100644
--- a/Libraries/Esiur/Data/Types/LocalTypeDef.cs
+++ b/Libraries/Esiur/Data/Types/LocalTypeDef.cs
@@ -389,7 +389,7 @@ public class LocalTypeDef:TypeDef
//foreach (var ann in Annotations)
// 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);
diff --git a/Libraries/Esiur/Data/Types/PropertyDef.cs b/Libraries/Esiur/Data/Types/PropertyDef.cs
index cfd7cfa..9caba10 100644
--- a/Libraries/Esiur/Data/Types/PropertyDef.cs
+++ b/Libraries/Esiur/Data/Types/PropertyDef.cs
@@ -176,7 +176,7 @@ public class PropertyDef : MemberDef
//}
if (Annotations != null)
{
- var rexp = Codec.Compose(Annotations, null, null);
+ var rexp = Codec.Compose(Annotations, connection.Instance.Warehouse, connection);
return new BinaryList()
.AddUInt8((byte)(0x28 | pv))
.AddUInt8((byte)name.Length)
diff --git a/Libraries/Esiur/Esiur.csproj b/Libraries/Esiur/Esiur.csproj
index 377ad70..5642d37 100644
--- a/Libraries/Esiur/Esiur.csproj
+++ b/Libraries/Esiur/Esiur.csproj
@@ -16,6 +16,10 @@
Esiur
Esiur
latest
+
+ annotations
netstandard2.0
README.md
enable
@@ -67,7 +71,6 @@
-
diff --git a/Libraries/Esiur/Misc/Global.cs b/Libraries/Esiur/Misc/Global.cs
index e0758fe..2edd547 100644
--- a/Libraries/Esiur/Misc/Global.cs
+++ b/Libraries/Esiur/Misc/Global.cs
@@ -47,7 +47,10 @@ public static class Global
{
private static KeyList variables = new KeyList();
- 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)
{
var b = new byte[length];
- rand.NextBytes(b);
+ secureRng.GetBytes(b);
return b;
}
public static string GenerateCode(int length)
{
- return GenerateCode(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ return GenerateCode(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
}
public static string GenerateCode(int length, string chars)
{
- var result = new string(
- Enumerable.Repeat(chars, length)
- .Select(s => s[rand.Next(s.Length)])
- .ToArray());
- return result;
- }
+ // Draw each character from a CSPRNG using unbiased rejection sampling, so
+ // codes used as session identifiers are not predictable. The largest
+ // multiple of chars.Length that fits in a byte is the acceptance bound;
+ // bytes at or above it are discarded to avoid modulo bias.
+ 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)
diff --git a/Libraries/Esiur/Net/Http/EpOverWebsocket.cs b/Libraries/Esiur/Net/Http/EpOverWebsocket.cs
index 8c895ef..a130de5 100644
--- a/Libraries/Esiur/Net/Http/EpOverWebsocket.cs
+++ b/Libraries/Esiur/Net/Http/EpOverWebsocket.cs
@@ -34,7 +34,7 @@ using Esiur.Protocol;
namespace Esiur.Net.Http;
-public class EpOvwerWebsocket : HttpFilter
+public class EpOverWebsocket : HttpFilter
{
//[Attribute]
public EpServer Server
diff --git a/Libraries/Esiur/Net/NetworkConnection.cs b/Libraries/Esiur/Net/NetworkConnection.cs
index b617c09..f573874 100644
--- a/Libraries/Esiur/Net/NetworkConnection.cs
+++ b/Libraries/Esiur/Net/NetworkConnection.cs
@@ -1,5 +1,5 @@
-/*
-
+/*
+
Copyright (c) 2017 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,229 +23,109 @@ SOFTWARE.
*/
using System;
-using System.IO;
-using System.Net.Sockets;
+using System.Net;
using System.Text;
using System.Threading;
-using System.Net;
-using System.Collections;
-using System.Collections.Generic;
using Esiur.Misc;
using Esiur.Core;
-using Esiur.Data;
using Esiur.Net.Sockets;
-using Esiur.Resource;
namespace Esiur.Net;
-public abstract class NetworkConnection : IDestructible, INetworkReceiver// : IResource where TS : NetworkSession
-{
- private Sockets.ISocket sock;
- // private bool connected;
+///
+/// Base class for a logical connection layered on top of an .
+/// It owns the socket, forwards inbound buffers to , and
+/// exposes send helpers. Derived classes implement the protocol-specific framing.
+///
+public abstract class NetworkConnection : IDestructible, INetworkReceiver
+{
+ private ISocket sock;
private DateTime lastAction;
- //public delegate void DataReceivedEvent(NetworkConnection sender, NetworkBuffer data);
- //public delegate void ConnectionClosedEvent(NetworkConnection sender);
+ // Re-entrancy guard for NetworkReceive. 0 = idle, 1 = a thread is draining the buffer.
+ // 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 event NetworkConnectionEvent OnConnect;
- //public event DataReceivedEvent OnDataReceived;
public event NetworkConnectionEvent OnClose;
-
-
public event DestroyedEvent OnDestroy;
- //object receivingLock = new object();
-
- //object sendLock = new object();
-
- bool processing = false;
-
- // public INetworkReceiver Receiver { get; set; }
public virtual void Destroy()
{
- // remove references
- //sock.OnClose -= Socket_OnClose;
- //sock.OnConnect -= Socket_OnConnect;
- //sock.OnReceive -= Socket_OnReceive;
sock?.Destroy();
- //Receiver = null;
Close();
sock = null;
OnClose = null;
OnConnect = null;
- //OnDataReceived = null;
OnDestroy?.Invoke(this);
OnDestroy = null;
}
- public ISocket Socket
- {
- get
- {
- return sock;
- }
- }
+ public ISocket Socket => sock;
public virtual void Assign(ISocket socket)
{
lastAction = DateTime.Now;
sock = socket;
sock.Receiver = this;
-
- //socket.OnReceive += Socket_OnReceive;
- //socket.OnClose += Socket_OnClose;
- //socket.OnConnect += Socket_OnConnect;
}
- //private void Socket_OnConnect()
- //{
- // OnConnect?.Invoke(this);
- //}
-
- //private void Socket_OnClose()
- //{
- // ConnectionClosed();
- // OnClose?.Invoke(this);
- //}
-
- //protected virtual void ConnectionClosed()
- //{
-
- //}
-
- //private void Socket_OnReceive(NetworkBuffer buffer)
- //{
- //}
-
+ ///
+ /// Detaches the socket from this connection without closing it and returns it,
+ /// so ownership can be handed to another connection (e.g. a protocol upgrade).
+ ///
public ISocket Unassign()
{
- 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
+ if (sock == null)
return null;
- }
- //protected virtual void DataReceived(NetworkBuffer data)
- //{
- // if (OnDataReceived != null)
- // {
- // try
- // {
- // OnDataReceived?.Invoke(this, data);
- // }
- // catch (Exception ex)
- // {
- // Global.Log("NetworkConenction:DataReceived", LogType.Error, ex.ToString());
- // }
- // }
- //}
+ sock.Receiver = null;
+
+ var detached = sock;
+ sock = null;
+ return detached;
+ }
public void Close()
{
- //if (!connected)
- // return;
-
-
try
{
- if (sock != null)
- sock.Close();
+ sock?.Close();
}
catch (Exception ex)
{
- Global.Log("NetworkConenction: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;
+ Global.Log("NetworkConnection:Close", LogType.Error, ex.ToString());
}
}
- public IPEndPoint LocalEndPoint
- {
- get
- {
- if (sock != null)
- return (IPEndPoint)sock.LocalEndPoint;
- else
- return null;
- }
- }
+ public DateTime LastAction => lastAction;
+ public IPEndPoint RemoteEndPoint => sock != null ? (IPEndPoint)sock.RemoteEndPoint : null;
- public bool IsConnected
- {
- get
- {
- return sock == null ? false : sock.State == SocketState.Established;
- }
- }
+ public IPEndPoint LocalEndPoint => sock != null ? (IPEndPoint)sock.LocalEndPoint : null;
-
- /*
- public void CloseAndWait()
- {
- try
- {
- if (!connected)
- return;
-
- if (sock != null)
- sock.Close();
-
- while (connected)
- {
- Thread.Sleep(100);
- }
- }
- finally
- {
-
- }
- }
- */
+ public bool IsConnected => sock != null && sock.State == SocketState.Established;
public virtual AsyncReply SendAsync(byte[] message, int offset, int length)
{
+ var socket = sock;
+ if (socket == null)
+ return new AsyncReply(false);
+
try
{
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(false);
}
}
@@ -257,9 +137,9 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver 0 && !buffer.Protected)
- {
- //Receiver?.NetworkReceive(this, buffer);
- DataReceived(buffer);
- }
- }
- catch
- {
-
- }
-
- processing = false;
+ while (buffer.Available > 0 && !buffer.Protected)
+ DataReceived(buffer);
+ }
+ finally
+ {
+ Interlocked.Exchange(ref receiving, 0);
}
-
}
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();
- //}
}
diff --git a/Libraries/Esiur/Net/Packets/EpAuthPacket.cs b/Libraries/Esiur/Net/Packets/EpAuthPacket.cs
index 3274038..cec726f 100644
--- a/Libraries/Esiur/Net/Packets/EpAuthPacket.cs
+++ b/Libraries/Esiur/Net/Packets/EpAuthPacket.cs
@@ -187,8 +187,6 @@ public class EpAuthPacket : Packet
Tdu = PlainTdu.Parse(data, offset, ends);//, _warehouse);
- Console.WriteLine("Auth TDU " + Tdu.Value.PayloadLength);
-
if (Tdu.Value.Class == TduClass.Invalid)
return -(int)Tdu.Value.TotalLength;
diff --git a/Libraries/Esiur/Net/Sockets/FrameworkWebSocket.cs b/Libraries/Esiur/Net/Sockets/FrameworkWebSocket.cs
index a37bf91..a716ea8 100644
--- a/Libraries/Esiur/Net/Sockets/FrameworkWebSocket.cs
+++ b/Libraries/Esiur/Net/Sockets/FrameworkWebSocket.cs
@@ -156,12 +156,18 @@ namespace Esiur.Net.Sockets
public void Destroy()
{
- Close();
+ var ws = sock;
+
+ Close(); // best-effort graceful close handshake (fire-and-forget)
receiveNetworkBuffer = null;
Receiver = 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 = null;
}
diff --git a/Libraries/Esiur/Net/Sockets/SSLSocket.cs b/Libraries/Esiur/Net/Sockets/SSLSocket.cs
index 9e28148..5fc77ab 100644
--- a/Libraries/Esiur/Net/Sockets/SSLSocket.cs
+++ b/Libraries/Esiur/Net/Sockets/SSLSocket.cs
@@ -458,6 +458,14 @@ public class SSLSocket : ISocket
public void Destroy()
{
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;
receiveNetworkBuffer = null;
OnDestroy?.Invoke(this);
diff --git a/Libraries/Esiur/Protocol/EpConnection.cs b/Libraries/Esiur/Protocol/EpConnection.cs
index d29323e..5d17058 100644
--- a/Libraries/Esiur/Protocol/EpConnection.cs
+++ b/Libraries/Esiur/Protocol/EpConnection.cs
@@ -99,7 +99,7 @@ public partial class EpConnection : NetworkConnection, IStore
AsyncReply _openReply;
- bool _authenticated, _readyToEstablish;
+ bool _authenticated;
string _hostname;
ushort _port;
@@ -2025,7 +2025,6 @@ public partial class EpConnection : NetworkConnection, IStore
{
// clean up
_authenticated = false;
- _readyToEstablish = false;
Status = EpConnectionStatus.Closed;
_keepAliveTimer.Stop();
diff --git a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs
index 9c2db14..14d4512 100644
--- a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs
+++ b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs
@@ -108,7 +108,7 @@ partial class EpConnection
var bl = new BinaryList();
bl.AddUInt8((byte)(0x60 | (byte)action))
.AddUInt32(c)
- .AddUInt8Array(Codec.Compose(args[0], this.Instance.Warehouse, this));
+ .AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray());
}
else
@@ -116,7 +116,7 @@ partial class EpConnection
var bl = new BinaryList();
bl.AddUInt8((byte)(0x60 | (byte)action))
.AddUInt32(c)
- .AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this));
+ .AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray());
}
@@ -147,7 +147,7 @@ partial class EpConnection
{
var bl = new BinaryList();
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());
}
else
@@ -165,7 +165,7 @@ partial class EpConnection
{
var bl = new BinaryList();
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());
}
@@ -183,7 +183,9 @@ partial class EpConnection
var bl = new BinaryList();
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());
}
else
@@ -212,14 +214,14 @@ partial class EpConnection
{
var bl = new BinaryList();
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());
}
else
{
var bl = new BinaryList();
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());
}
@@ -243,7 +245,7 @@ partial class EpConnection
var bl = new BinaryList();
bl.AddUInt8((byte)(0xA0 | (byte)action))
.AddUInt32(callbackId)
- .AddUInt8Array(Codec.Compose(args[0], this.Instance.Warehouse, this));
+ .AddUInt8Array(Codec.Compose(args[0], this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray());
}
else
@@ -251,7 +253,7 @@ partial class EpConnection
var bl = new BinaryList();
bl.AddUInt8((byte)(0xA0 | (byte)action))
.AddUInt32(callbackId)
- .AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this));
+ .AddUInt8Array(Codec.Compose(args, this.Instance?.Warehouse ?? _serverWarehouse, this));
Send(bl.ToArray());
}
}
@@ -2033,7 +2035,10 @@ partial class EpConnection
_attachedResources[id]?.TryGetTarget(out resource);
if (resource != null)
+ {
+ Global.Counters["EpResourceAttachedCacheHit"]++;
return new AsyncReply(resource);
+ }
resource = _neededResources[id];
@@ -2043,16 +2048,19 @@ partial class EpConnection
{
if (resource != null && (requestSequence?.Contains(id) ?? false))
{
+ Global.Counters["EpResourceDeadLockSameChain"]++;
// dead lock avoidance for loop reference.
return new AsyncReply(resource);
}
else if (resource != null && requestInfo.RequestSequence.Contains(id))
{
+ Global.Counters["EpResourceDeadLockCrossChain"]++;
// dead lock avoidance for dependent reference.
return new AsyncReply(resource);
}
else
{
+ Global.Counters["EpResourcePendingCacheHit"]++;
return requestInfo.Reply;
}
}
diff --git a/Libraries/Esiur/Resource/Warehouse.cs b/Libraries/Esiur/Resource/Warehouse.cs
index c7bce57..2d2ff38 100644
--- a/Libraries/Esiur/Resource/Warehouse.cs
+++ b/Libraries/Esiur/Resource/Warehouse.cs
@@ -57,6 +57,12 @@ public class Warehouse
ConcurrentDictionary> _resources = new ConcurrentDictionary>();
ConcurrentDictionary>> _stores = new ConcurrentDictionary>>();
+ // 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 TypeRepresentationCache
+ = new ConcurrentDictionary();
+
volatile int _resourceCounter = 0;
volatile int _typeDefsCounter = 0;
@@ -681,14 +687,10 @@ public class Warehouse
|| baseType == typeof(IRecord))
return null;
- TypeDefKind typeDefKind;
- if (Codec.ImplementsInterface(type, typeof(IResource)))
- typeDefKind = TypeDefKind.Resource;
- else if (Codec.ImplementsInterface(type, typeof(IRecord)))
- typeDefKind = TypeDefKind.Record;
- else if (type.IsEnum)
- typeDefKind = TypeDefKind.Enum;
- else
+ // Only resources, records and enums have type definitions; bail out for anything else.
+ if (!Codec.ImplementsInterface(type, typeof(IResource))
+ && !Codec.ImplementsInterface(type, typeof(IRecord))
+ && !type.IsEnum)
return null;
lock (_typeDefsLock)
diff --git a/Libraries/Esiur/Security/Authority/Providers/PasswordAuthenticationHandler.cs b/Libraries/Esiur/Security/Authority/Providers/PasswordAuthenticationHandler.cs
index 561bb34..585b241 100644
--- a/Libraries/Esiur/Security/Authority/Providers/PasswordAuthenticationHandler.cs
+++ b/Libraries/Esiur/Security/Authority/Providers/PasswordAuthenticationHandler.cs
@@ -9,10 +9,20 @@ using Esiur.Data.Types;
namespace Esiur.Security.Authority.Providers
{
+ ///
+ /// 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.
+ ///
public class PasswordAuthenticationHandler : IAuthenticationHandler
{
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[] _localSalt, _remoteSalt;
@@ -33,6 +43,18 @@ namespace Esiur.Security.Authority.Providers
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)
{
// 1. Initialize the digest (supports 224, 256, 384, 512)
@@ -50,7 +72,22 @@ namespace Esiur.Security.Authority.Providers
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 localAuthData = new List