mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-09-08 10:10:49 +00:00
Limits
This commit is contained in:
@@ -649,6 +649,7 @@ public static class Codec
|
||||
public static byte[] Compose(object valueOrSource, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
var tdu = ComposeInternal(valueOrSource, warehouse, connection);
|
||||
ParserGuard.EnsureRemotePacketSize(connection, (ulong)tdu.Composed.LongLength);
|
||||
return tdu.Composed;
|
||||
}
|
||||
|
||||
|
||||
@@ -363,6 +363,10 @@ public static class DataSerializer
|
||||
public static Tdu StringComposer(object value, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
var b = Encoding.UTF8.GetBytes((string)value);
|
||||
ParserGuard.EnsureRemoteAllocation(
|
||||
connection,
|
||||
ParserGuard.MultiplySaturated((ulong)b.LongLength, 2),
|
||||
"string");
|
||||
|
||||
return new Tdu(TduIdentifier.String, b, (uint)b.Length, null, null);
|
||||
}
|
||||
@@ -370,6 +374,10 @@ public static class DataSerializer
|
||||
public static Tdu ResourceLinkComposer(object value, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
var b = Encoding.UTF8.GetBytes((ResourceLink)value);
|
||||
ParserGuard.EnsureRemoteAllocation(
|
||||
connection,
|
||||
ParserGuard.MultiplySaturated((ulong)b.LongLength, 2),
|
||||
"resource link");
|
||||
|
||||
return new Tdu(TduIdentifier.ResourceLink, b, (uint)b.Length, null, null);
|
||||
}
|
||||
@@ -446,12 +454,14 @@ public static class DataSerializer
|
||||
public static Tdu RawDataComposerFromArray(object value, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
var b = (byte[])value;
|
||||
ParserGuard.EnsureRemoteAllocation(connection, (ulong)b.LongLength, "raw data");
|
||||
return new Tdu(TduIdentifier.RawData, b, (uint)b.Length, null, null);
|
||||
}
|
||||
|
||||
public static Tdu RawDataComposerFromList(dynamic value, Warehouse warehouse, EpConnection connection)
|
||||
{
|
||||
var b = value as List<byte>;
|
||||
ParserGuard.EnsureRemoteAllocation(connection, (ulong)b.Count, "raw data");
|
||||
return new Tdu(TduIdentifier.RawData, b.ToArray(), (uint)b.Count, null, null);
|
||||
}
|
||||
|
||||
@@ -516,6 +526,9 @@ public static class DataSerializer
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
if (value is ICollection collection)
|
||||
ParserGuard.EnsureRemoteCollectionCount(connection, collection.Count, GetTypedArrayElementSize(tru));
|
||||
|
||||
if (tru.Identifier == TruIdentifier.Int32)
|
||||
{
|
||||
composed = GroupInt32Codec.Encode((IList<int>)value);
|
||||
@@ -748,12 +761,18 @@ public static class DataSerializer
|
||||
|
||||
// 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);
|
||||
var knownCount = value is ICollection collection ? collection.Count : -1;
|
||||
if (knownCount >= 0)
|
||||
ParserGuard.EnsureRemoteCollectionCount(connection, knownCount, IntPtr.Size);
|
||||
var rt = new List<byte>(knownCount >= 0 ? knownCount * 4 : 16);
|
||||
|
||||
Tdu? previous = null;
|
||||
var count = 0;
|
||||
|
||||
foreach (var i in value)
|
||||
{
|
||||
if (knownCount < 0)
|
||||
ParserGuard.EnsureRemoteCollectionCount(connection, ++count, IntPtr.Size);
|
||||
var tdu = Codec.ComposeInternal(i, warehouse, connection);
|
||||
if (previous != null && tdu.MatchType(previous.Value))
|
||||
{
|
||||
@@ -862,13 +881,24 @@ public static class DataSerializer
|
||||
|
||||
var rt = new List<byte>();
|
||||
var map = (IMap)value;
|
||||
var serialized = map.Serialize();
|
||||
ParserGuard.EnsureRemoteCollectionCount(connection, serialized.Length, IntPtr.Size);
|
||||
|
||||
foreach (var el in map.Serialize())
|
||||
foreach (var el in serialized)
|
||||
rt.AddRange(Codec.Compose(el, warehouse, connection));
|
||||
|
||||
return new Tdu(TduIdentifier.Map, rt.ToArray(), (uint)rt.Count, null, null);
|
||||
}
|
||||
|
||||
static int GetTypedArrayElementSize(Tru tru)
|
||||
=> tru.Identifier switch
|
||||
{
|
||||
TruIdentifier.Int16 or TruIdentifier.UInt16 => 2,
|
||||
TruIdentifier.Int32 or TruIdentifier.UInt32 => 4,
|
||||
TruIdentifier.Int64 or TruIdentifier.UInt64 => 8,
|
||||
_ => IntPtr.Size
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Composes an indexed CLR structure using the compatible Map<byte, object> wire shape.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,6 +15,16 @@ public sealed class ParserLimitException : Exception
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised before transmission when a composed value exceeds a budget advertised by the peer.
|
||||
/// </summary>
|
||||
public sealed class RemoteParserLimitException : Exception
|
||||
{
|
||||
public RemoteParserLimitException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ParserGuard
|
||||
{
|
||||
internal static Warehouse? GetWarehouse(EpConnection? connection)
|
||||
@@ -70,4 +80,64 @@ internal static class ParserGuard
|
||||
|
||||
internal static ulong MultiplySaturated(ulong value, ulong multiplier)
|
||||
=> value > ulong.MaxValue / multiplier ? ulong.MaxValue : value * multiplier;
|
||||
|
||||
internal static void EnsureRemotePacketSize(EpConnection? connection, ulong size)
|
||||
{
|
||||
var limit = connection?.Session?.RemoteMaximumPacketSize ?? 0;
|
||||
if (limit > 0 && size > limit)
|
||||
throw new RemoteParserLimitException(
|
||||
$"Composed packet payload of {size} bytes exceeds the peer's advertised {limit}-byte limit.");
|
||||
}
|
||||
|
||||
internal static void EnsureRemoteAllocation(
|
||||
EpConnection? connection,
|
||||
ulong size,
|
||||
string kind)
|
||||
{
|
||||
var limit = connection?.Session?.RemoteMaximumAllocationSize ?? 0;
|
||||
if (limit > 0 && size > limit)
|
||||
throw new RemoteParserLimitException(
|
||||
$"Composed {kind} would allocate {size} bytes at the peer, exceeding its advertised {limit}-byte limit.");
|
||||
}
|
||||
|
||||
internal static void EnsureRemoteCollectionCount(
|
||||
EpConnection? connection,
|
||||
int count,
|
||||
int estimatedBytesPerItem = 0)
|
||||
{
|
||||
var limit = connection?.Session?.RemoteMaximumCollectionItems ?? 0;
|
||||
if (limit > 0 && count > limit)
|
||||
throw new RemoteParserLimitException(
|
||||
$"Composed collection has {count} items, exceeding the peer's advertised {limit}-item limit.");
|
||||
|
||||
if (estimatedBytesPerItem > 0)
|
||||
EnsureRemoteAllocation(
|
||||
connection,
|
||||
MultiplySaturated((ulong)count, (ulong)estimatedBytesPerItem),
|
||||
"collection");
|
||||
}
|
||||
|
||||
internal static void EnsureRemoteTypeMetadataDepth(EpConnection? connection, Tru? tru)
|
||||
{
|
||||
var limit = connection?.Session?.RemoteMaximumTypeMetadataDepth ?? 0;
|
||||
if (limit <= 0 || tru == null)
|
||||
return;
|
||||
|
||||
var depth = GetTypeMetadataDepth(tru);
|
||||
if (depth > limit)
|
||||
throw new RemoteParserLimitException(
|
||||
$"Composed TRU type metadata depth of {depth} exceeds the peer's advertised limit of {limit}.");
|
||||
}
|
||||
|
||||
static int GetTypeMetadataDepth(Tru tru)
|
||||
{
|
||||
if (tru is not TruComposite composite || composite.SubTypes == null || composite.SubTypes.Length == 0)
|
||||
return 1;
|
||||
|
||||
var maximumChildDepth = 0;
|
||||
foreach (var child in composite.SubTypes)
|
||||
maximumChildDepth = Math.Max(maximumChildDepth, GetTypeMetadataDepth(child));
|
||||
|
||||
return 1 + maximumChildDepth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ public struct Tdu
|
||||
if (metadata == null)
|
||||
throw new Exception("Metadata must be provided for types.");
|
||||
|
||||
ParserGuard.EnsureRemoteTypeMetadataDepth(connection, metadata);
|
||||
var metadataData = metadata.Compose(connection);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user