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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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="prependType">If True, prepend the DataType at the beginning of the output.</param>
|
||||
/// <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);
|
||||
return tdu.Composed;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<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;
|
||||
|
||||
@@ -934,7 +908,7 @@ public static class DataSerializer
|
||||
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++)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<byte> flags);
|
||||
|
||||
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 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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
<PackageId>Esiur</PackageId>
|
||||
<Product>Esiur</Product>
|
||||
<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>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -67,7 +71,6 @@
|
||||
<Compile Remove="Net\Packets\EpAuthPacketAction.cs" />
|
||||
<Compile Remove="Net\Packets\EpAuthPacketAuthMode.cs" />
|
||||
<Compile Remove="Net\Packets\EpAuthPacketEvent.cs" />
|
||||
<Compile Remove="Net\Sockets\TcpSocket.cs" />
|
||||
<Compile Remove="Protocol\Authentication\HashAnonymousAuthenticator.cs" />
|
||||
<Compile Remove="Security\Authority\AuthenticationMethod.cs" />
|
||||
<Compile Remove="Security\Membership\IMembership.cs" />
|
||||
|
||||
@@ -47,7 +47,10 @@ public static class Global
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -34,7 +34,7 @@ using Esiur.Protocol;
|
||||
|
||||
namespace Esiur.Net.Http;
|
||||
|
||||
public class EpOvwerWebsocket : HttpFilter
|
||||
public class EpOverWebsocket : HttpFilter
|
||||
{
|
||||
//[Attribute]
|
||||
public EpServer Server
|
||||
|
||||
@@ -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<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;
|
||||
|
||||
//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<NetworkConnection> 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)
|
||||
//{
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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<bool> SendAsync(byte[] message, int offset, int length)
|
||||
{
|
||||
var socket = sock;
|
||||
if (socket == null)
|
||||
return new AsyncReply<bool>(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<bool>(false);
|
||||
}
|
||||
}
|
||||
@@ -257,9 +137,9 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
|
||||
sock?.Send(msg);
|
||||
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
|
||||
{
|
||||
sock.Send(msg, offset, length);
|
||||
sock?.Send(msg, offset, length);
|
||||
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);
|
||||
}
|
||||
|
||||
//{
|
||||
//ConnectionClosed();
|
||||
//OnClose?.Invoke(this);
|
||||
|
||||
//Receiver?.NetworkClose(this);
|
||||
//}
|
||||
|
||||
//public void NetworkConenct(ISocket sender)
|
||||
//{
|
||||
// OnConnect?.Invoke(this);
|
||||
//}
|
||||
|
||||
protected abstract void DataReceived(NetworkBuffer buffer);
|
||||
protected abstract void Connected();
|
||||
protected abstract void Disconnected();
|
||||
@@ -313,53 +181,30 @@ public abstract class NetworkConnection : IDestructible, INetworkReceiver<ISocke
|
||||
{
|
||||
try
|
||||
{
|
||||
// Unassigned ?
|
||||
if (sock == null)
|
||||
return;
|
||||
|
||||
// Closed ?
|
||||
if (sock.State == SocketState.Closed)// || sock.State == SocketState.Terminated) // || !connected)
|
||||
// Ignore callbacks once the socket is unassigned or closed.
|
||||
if (sock == null || sock.State == SocketState.Closed)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
//lock(buffer.SyncLock)
|
||||
while (buffer.Available > 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();
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -99,7 +99,7 @@ public partial class EpConnection : NetworkConnection, IStore
|
||||
|
||||
AsyncReply<bool> _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();
|
||||
|
||||
@@ -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<EpResource>(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<EpResource>(resource);
|
||||
}
|
||||
else if (resource != null && requestInfo.RequestSequence.Contains(id))
|
||||
{
|
||||
Global.Counters["EpResourceDeadLockCrossChain"]++;
|
||||
// dead lock avoidance for dependent reference.
|
||||
return new AsyncReply<EpResource>(resource);
|
||||
}
|
||||
else
|
||||
{
|
||||
Global.Counters["EpResourcePendingCacheHit"]++;
|
||||
return requestInfo.Reply;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ public class Warehouse
|
||||
ConcurrentDictionary<uint, WeakReference<IResource>> _resources = new ConcurrentDictionary<uint, 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 _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)
|
||||
|
||||
@@ -9,10 +9,20 @@ using Esiur.Data.Types;
|
||||
|
||||
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 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<object>();
|
||||
|
||||
@@ -97,7 +134,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
var remoteChallenge = (byte[])remoteAuthData[2];
|
||||
|
||||
// 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;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -112,7 +149,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.ToArray());
|
||||
|
||||
// compare remote challenge
|
||||
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
|
||||
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -160,7 +197,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
_responderIdentity = (string)remoteAuthData[1];
|
||||
|
||||
// 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;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -203,7 +240,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.Concat(_localNonce)
|
||||
.ToArray());
|
||||
|
||||
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
|
||||
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -262,7 +299,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
_remoteSalt = (byte[])remoteAuthData[2];
|
||||
|
||||
// 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;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -313,7 +350,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.Concat(_localNonce)
|
||||
.ToArray());
|
||||
|
||||
if (!remoteChallenge.SequenceEqual(expectedRemoteChallenge))
|
||||
if (!FixedTimeEquals(remoteChallenge, expectedRemoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
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.
|
||||
// @TODO: We can change our localNonce then send it
|
||||
if (_remoteNonce.SequenceEqual(_localNonce))
|
||||
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -403,7 +440,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.Concat(_localNonce)
|
||||
.ToArray());
|
||||
// compare remote challenge
|
||||
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
|
||||
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
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.
|
||||
// @TODO: We can change our localNonce then send it
|
||||
if (_remoteNonce.SequenceEqual(_localNonce))
|
||||
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -490,7 +527,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.ToArray());
|
||||
|
||||
// compare remote challenge
|
||||
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
|
||||
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
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.
|
||||
// @TODO: We can change our localNonce then send it
|
||||
if (_remoteNonce.SequenceEqual(_localNonce))
|
||||
if (_remoteNonce == null || _remoteNonce.Length != NonceLength || FixedTimeEquals(_remoteNonce, _localNonce))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -597,7 +634,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
.ToArray());
|
||||
|
||||
// compare remote challenge
|
||||
if (!expectedRemoteChallenge.SequenceEqual(remoteChallenge))
|
||||
if (!FixedTimeEquals(expectedRemoteChallenge, remoteChallenge))
|
||||
{
|
||||
_step = -1;
|
||||
return new AuthenticationResult(AuthenticationRuling.Failed, null);
|
||||
@@ -646,7 +683,7 @@ namespace Esiur.Security.Authority.Providers
|
||||
string domain,
|
||||
PasswordAuthenticationProvider provider)
|
||||
{
|
||||
_localNonce = Global.GenerateBytes(20);
|
||||
_localNonce = Global.GenerateBytes(NonceLength);
|
||||
|
||||
this._provider = provider;
|
||||
this._initiatorIdentity = initiatorIdentity;
|
||||
|
||||
Reference in New Issue
Block a user