RateControl

This commit is contained in:
2026-07-14 02:27:33 +03:00
parent 4f6d2d0801
commit 1ca9fc477b
19 changed files with 879 additions and 146 deletions
+9 -4
View File
@@ -89,8 +89,8 @@ public class AutoList<T, ST> : IEnumerable<T>, ICollection, ICollection<T>
/// <returns>Array</returns>
public T[] ToArray()
{
// list.OrderBy()
return list.ToArray();
lock (syncRoot)
return list.ToArray();
}
/// <summary>
@@ -242,7 +242,11 @@ public class AutoList<T, ST> : IEnumerable<T>, ICollection, ICollection<T>
/// </summary>
public int Count
{
get { return list.Count; }
get
{
lock (syncRoot)
return list.Count;
}
}
public bool IsSynchronized => (list as ICollection).IsSynchronized;
@@ -256,7 +260,8 @@ public class AutoList<T, ST> : IEnumerable<T>, ICollection, ICollection<T>
/// <param name="value">Item to check if exists</param>
public bool Contains(T value)
{
return list.Contains(value);
lock (syncRoot)
return list.Contains(value);
}
/// <summary>
+108 -24
View File
@@ -23,6 +23,18 @@ public static class DataDeserializer
{
internal static readonly AsyncLocal<ulong[]> TypeDefRequestSequence = new();
private static void EnsureTypedArrayBudget(ParsedTdu tdu, Warehouse warehouse, int elementSize)
=> ParserGuard.EnsureAllocation(
warehouse,
ParserGuard.MultiplySaturated(tdu.PayloadLength, (ulong)elementSize),
"typed array");
private static T[] GuardDecodedArray<T>(Warehouse warehouse, T[] values, int elementSize)
{
ParserGuard.EnsureCollectionCount(warehouse, values.Length, elementSize);
return values;
}
public static async AsyncReply<object> TypeDefInfoParserAsync(
ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
@@ -324,6 +336,10 @@ public static class DataDeserializer
public static object ResourceLinkParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
ParserGuard.EnsureAllocation(
connection?.ParsingWarehouse,
ParserGuard.MultiplySaturated(tdu.PayloadLength, 2),
"resource link");
var link = tdu.Data.GetString(tdu.PayloadOffset, (uint)tdu.PayloadLength);
if (connection == null)
{
@@ -337,6 +353,10 @@ public static class DataDeserializer
public static object ResourceLinkParser(ParsedTdu tdu, Warehouse warehouse)
{
ParserGuard.EnsureAllocation(
warehouse,
ParserGuard.MultiplySaturated(tdu.PayloadLength, 2),
"resource link");
var link = tdu.Data.GetString(tdu.PayloadOffset, (uint)tdu.PayloadLength);
return new ResourceLink(link);
}
@@ -432,27 +452,41 @@ public static class DataDeserializer
public static unsafe object RawDataParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
ParserGuard.EnsureAllocation(connection?.ParsingWarehouse, tdu.PayloadLength, "raw data");
return tdu.Data.Clip(tdu.PayloadOffset, (uint)tdu.PayloadLength);
}
public static unsafe object RawDataParser(ParsedTdu tdu, Warehouse warehouse)
{
ParserGuard.EnsureAllocation(warehouse, tdu.PayloadLength, "raw data");
return tdu.Data.Clip(tdu.PayloadOffset, (uint)tdu.PayloadLength);
}
public static unsafe object StringParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
ParserGuard.EnsureAllocation(
connection?.ParsingWarehouse,
ParserGuard.MultiplySaturated(tdu.PayloadLength, 2),
"string");
return tdu.Data.GetString(tdu.PayloadOffset, (uint)tdu.PayloadLength);
}
public static unsafe object StringParser(ParsedTdu tdu, Warehouse warehouse)
{
ParserGuard.EnsureAllocation(
warehouse,
ParserGuard.MultiplySaturated(tdu.PayloadLength, 2),
"string");
return tdu.Data.GetString(tdu.PayloadOffset, (uint)tdu.PayloadLength);
}
public static async AsyncReply<IRecord> RecordParserAsync(ParsedTdu tdu, TypeDef recordTypeDef, EpConnection connection, uint[] requestSequence)
{
ParserGuard.EnsureCollectionCount(
connection?.ParsingWarehouse,
recordTypeDef.Properties.Length,
IntPtr.Size);
//if (tdu.Metadata.TypeDefId == null)
// throw new Exception("TypeDefId metadata is required for record parsing.");
@@ -618,6 +652,8 @@ public static class DataDeserializer
"TypeDef not found for record.");
}
ParserGuard.EnsureCollectionCount(warehouse, recordTypeDef.Properties.Length, IntPtr.Size);
var list = new List<object>();
ParsedTdu current;
@@ -789,6 +825,7 @@ public static class DataDeserializer
public static async AsyncReply<object> RecordListParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
var rt = new AsyncBag<IRecord>();
var count = 0;
var length = tdu.PayloadLength;
var offset = tdu.PayloadOffset;
@@ -798,6 +835,7 @@ public static class DataDeserializer
//var (cs, reply)
var pr = await Codec.ParseAsync(tdu.Data, offset, connection, requestSequence);
ParserGuard.EnsureCollectionCount(connection?.ParsingWarehouse, ++count, IntPtr.Size);
rt.Add(pr.Value);
if (pr.Size > 0)
@@ -827,6 +865,7 @@ public static class DataDeserializer
{
var (cs, reply) = Codec.ParseSync(tdu.Data, offset, warehouse);
ParserGuard.EnsureCollectionCount(warehouse, rt.Count + 1, IntPtr.Size);
rt.Add(reply as IRecord);
if (cs > 0)
@@ -845,6 +884,7 @@ public static class DataDeserializer
public static async AsyncReply<object> ResourceListParserAsync(ParsedTdu tdu, EpConnection connection, uint[] requestSequence)
{
var rt = new AsyncBag<IResource>();
var count = 0;
var length = tdu.PayloadLength;
var offset = tdu.PayloadOffset;
@@ -854,6 +894,7 @@ public static class DataDeserializer
//var (cs, reply)
var pr = await Codec.ParseAsync(tdu.Data, offset, connection, requestSequence);
ParserGuard.EnsureCollectionCount(connection?.ParsingWarehouse, ++count, IntPtr.Size);
rt.Add(pr.Value);// reply);
if (pr.Size > 0)
@@ -884,6 +925,7 @@ public static class DataDeserializer
{
var (cs, reply) = Codec.ParseSync(tdu.Data, offset, warehouse);
ParserGuard.EnsureCollectionCount(warehouse, rt.Count + 1, IntPtr.Size);
rt.Add(reply as IResource);
if (cs > 0)
@@ -931,6 +973,7 @@ public static class DataDeserializer
var rt = new AsyncBag<object>();
var count = 0;
//var list = new List<object>();
@@ -962,6 +1005,7 @@ public static class DataDeserializer
//var (cs, reply)
var value = Codec.ParseAsync(current, connection, requestSequence);
ParserGuard.EnsureCollectionCount(connection?.ParsingWarehouse, ++count, IntPtr.Size);
rt.Add(value);
if (current.TotalLength > 0)
@@ -1012,6 +1056,7 @@ public static class DataDeserializer
var reply = Codec.ParseSync(current, warehouse);
ParserGuard.EnsureCollectionCount(warehouse, list.Count + 1, IntPtr.Size);
list.Add(reply);
if (current.TotalLength > 0)
@@ -1040,6 +1085,7 @@ public static class DataDeserializer
{
var (cs, reply) = Codec.ParseSync(data, offset, warehouse);
ParserGuard.EnsureCollectionCount(warehouse, rt.Count + 1, IntPtr.Size);
rt.Add(reply);
if (cs > 0)
@@ -1151,26 +1197,37 @@ public static class DataDeserializer
public static Array TypedArrayParser(ParsedTdu tdu, Tru elementTru, Warehouse warehouse)
{
var primitiveElementSize = elementTru.Identifier switch
{
TruIdentifier.Int16 or TruIdentifier.UInt16 => 2,
TruIdentifier.Int32 or TruIdentifier.UInt32 => 4,
TruIdentifier.Int64 or TruIdentifier.UInt64 => 8,
_ => 0
};
if (primitiveElementSize > 0)
EnsureTypedArrayBudget(tdu, warehouse, primitiveElementSize);
switch (elementTru.Identifier)
{
case TruIdentifier.Int32:
return GroupInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 4);
case TruIdentifier.Int64:
return GroupInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 8);
case TruIdentifier.Int16:
return GroupInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 2);
case TruIdentifier.UInt32:
return GroupUInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupUInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 4);
case TruIdentifier.UInt64:
return GroupUInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupUInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 8);
case TruIdentifier.UInt16:
return GroupUInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength));
return GuardDecodedArray(warehouse, GroupUInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 2);
//case TruIdentifier.Enum:
// var enumType = tru.GetRuntimeType(warehouse);
@@ -1224,6 +1281,7 @@ public static class DataDeserializer
var value = Codec.ParseSync(current, warehouse);
ParserGuard.EnsureCollectionCount(warehouse, list.Count + 1, IntPtr.Size);
list.Add(value);
if (current.TotalLength > 0)
@@ -1249,26 +1307,38 @@ public static class DataDeserializer
// Non-async/await version of TypedArrayParserAsync using continuations
public static AsyncReply TypedArrayParserAsync2(ParsedTdu tdu, Tru elementTru, EpConnection connection, uint[] requestSequence)
{
var warehouse = connection?.ParsingWarehouse;
var primitiveElementSize = elementTru.Identifier switch
{
TruIdentifier.Int16 or TruIdentifier.UInt16 => 2,
TruIdentifier.Int32 or TruIdentifier.UInt32 => 4,
TruIdentifier.Int64 or TruIdentifier.UInt64 => 8,
_ => 0
};
if (primitiveElementSize > 0)
EnsureTypedArrayBudget(tdu, warehouse, primitiveElementSize);
switch (elementTru.Identifier)
{
case TruIdentifier.Int32:
return new AsyncReply(GroupInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 4));
case TruIdentifier.Int64:
return new AsyncReply(GroupInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 8));
case TruIdentifier.Int16:
return new AsyncReply(GroupInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 2));
case TruIdentifier.UInt32:
return new AsyncReply(GroupUInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupUInt32Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 4));
case TruIdentifier.UInt64:
return new AsyncReply(GroupUInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupUInt64Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 8));
case TruIdentifier.UInt16:
return new AsyncReply(GroupUInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)));
return new AsyncReply(GuardDecodedArray(warehouse, GroupUInt16Codec.Decode(tdu.Data.AsSpan(
(int)tdu.PayloadOffset, (int)tdu.PayloadLength)), 2));
default:
var rt = new AsyncReply();
@@ -1285,6 +1355,7 @@ public static class DataDeserializer
// Recursive processor using continuations
Action<uint, uint, ParsedTdu?> processNext = null;
var itemCount = 0;
processNext = (curOffset, curLength, previous) =>
{
@@ -1333,6 +1404,19 @@ public static class DataDeserializer
var reply = Codec.ParseAsync(current, connection, requestSequence);
try
{
ParserGuard.EnsureCollectionCount(
warehouse,
++itemCount,
IntPtr.Size);
}
catch (Exception ex)
{
rt.TriggerError(ex);
return;
}
list.Add(reply);
if (current.TotalLength > 0)
+44 -31
View File
@@ -62,14 +62,16 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
public T Take(KT key)
{
if (dic.ContainsKey(key))
lock (syncRoot)
{
var v = dic[key];
Remove(key);
return v;
}
else
if (dic.TryGetValue(key, out var value))
{
Remove(key);
return value;
}
return default(T);
}
}
public void Sort(Func<KeyValuePair<KT, T>, object> keySelector)
@@ -79,9 +81,12 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
public T[] ToArray()
{
var a = new T[Count];
dic.Values.CopyTo(a, 0);
return a;
lock (syncRoot)
{
var a = new T[dic.Count];
dic.Values.CopyTo(a, 0);
return a;
}
}
public void Add(KT key, T value)
@@ -132,10 +137,8 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
{
get
{
if (dic.ContainsKey(key))
return dic[key];
else
return default(T);
lock (syncRoot)
return dic.TryGetValue(key, out var value) ? value : default(T);
}
set
{
@@ -157,13 +160,15 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
public void Clear()
{
if (removableList)
foreach (IDestructible v in dic.Values)
if (v != null)
v.OnDestroy -= ItemDestroyed;
lock (syncRoot)
{
if (removableList)
foreach (IDestructible v in dic.Values)
if (v != null)
v.OnDestroy -= ItemDestroyed;
dic.Clear();
}
if (OnCleared != null)
OnCleared(this);
@@ -184,17 +189,18 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
public void Remove(KT key)
{
if (!dic.ContainsKey(key))
return;
var value = dic[key];
if (removableList)
if (value != null)
((IDestructible)value).OnDestroy -= ItemDestroyed;
T value;
lock (syncRoot)
{
if (!dic.TryGetValue(key, out value))
return;
if (removableList)
if (value != null)
((IDestructible)value).OnDestroy -= ItemDestroyed;
dic.Remove(key);
}
if (OnRemoved != null)
OnRemoved(key, value, this);
@@ -208,19 +214,26 @@ public class KeyList<KT, T> : IEnumerable<KeyValuePair<KT, T>>
public int Count
{
get { return dic.Count; }
get
{
lock (syncRoot)
return dic.Count;
}
}
public bool Contains(KT Key)
{
return dic.ContainsKey(Key);
lock (syncRoot)
return dic.ContainsKey(Key);
}
public bool ContainsKey(KT Key)
{
return dic.ContainsKey(Key);
lock (syncRoot)
return dic.ContainsKey(Key);
}
public bool ContainsValue(T Value)
{
return dic.ContainsValue(Value);
lock (syncRoot)
return dic.ContainsValue(Value);
}
+27 -6
View File
@@ -27,8 +27,6 @@ namespace Esiur.Data
public static async AsyncReply<ParsedTdu> ParseAsync(byte[] data, uint offset, uint ends, EpConnection connection)
{
// @TODO: add protection against memory allocation attacks by checking the length of the data before parsing it.
var h = data[offset++];
var cls = (TduClass)(h >> 6);
@@ -91,6 +89,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(ParserGuard.GetWarehouse(connection), cl);
if (ends - offset < cl)
return new ParsedTdu()
{
@@ -102,6 +102,10 @@ namespace Esiur.Data
//offset += data[offset] + (uint)1;
var metaDataTru = await Tru.ParseAsync(data, offset, connection, null);
if (metaDataTru.Size > cl)
throw new ParserLimitException("Typed TDU metadata exceeds its declared payload length.");
offset += metaDataTru.Size;
return new ParsedTdu()
@@ -133,6 +137,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(ParserGuard.GetWarehouse(connection), cl);
if (ends - offset < cl)
return new ParsedTdu()
{
@@ -158,8 +164,6 @@ namespace Esiur.Data
public static object Parse(byte[] data, uint offset, uint ends, EpConnection connection)
{
// @TODO: add protection against memory allocation attacks by checking the length of the data before parsing it.
var h = data[offset++];
var cls = (TduClass)(h >> 6);
@@ -222,6 +226,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(ParserGuard.GetWarehouse(connection), cl);
if (ends - offset < cl)
return new ParsedTdu()
{
@@ -235,6 +241,13 @@ namespace Esiur.Data
Tru.ParseAsync(data, offset, connection, null).Then(metaDataTru =>
{
if (metaDataTru.Size > cl)
{
rt.TriggerError(new ParserLimitException(
"Typed TDU metadata exceeds its declared payload length."));
return;
}
offset += metaDataTru.Size;
rt.Trigger(new ParsedTdu()
@@ -269,6 +282,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(ParserGuard.GetWarehouse(connection), cl);
if (ends - offset < cl)
return new ParsedTdu()
{
@@ -347,8 +362,6 @@ namespace Esiur.Data
public static ParsedTdu ParseSync(byte[] data, uint offset, uint ends, Warehouse warehouse)
{
// @TODO: add protection against memory allocation attacks by checking the length of the data before parsing it.
var h = data[offset++];
var cls = (TduClass)(h >> 6);
@@ -411,6 +424,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(warehouse, cl);
if (ends - offset < cl)
return new ParsedTdu()
{
@@ -422,6 +437,10 @@ namespace Esiur.Data
//offset += data[offset] + (uint)1;
var metaDataTru = Tru.Parse(data, offset, warehouse);
if (metaDataTru.Size > cl)
throw new ParserLimitException("Typed TDU metadata exceeds its declared payload length.");
offset += metaDataTru.Size;
return new ParsedTdu()
@@ -453,6 +472,8 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
ParserGuard.EnsurePacketSize(warehouse, cl);
if (ends - offset < cl)
return new ParsedTdu()
{
+61
View File
@@ -0,0 +1,61 @@
using Esiur.Protocol;
using Esiur.Resource;
using System;
using System.IO;
namespace Esiur.Data;
/// <summary>
/// Raised when untrusted input exceeds a configured parser budget.
/// </summary>
public sealed class ParserLimitException : Exception
{
public ParserLimitException(string message) : base(message)
{
}
}
internal static class ParserGuard
{
internal static Warehouse? GetWarehouse(EpConnection? connection)
=> connection?.ParsingWarehouse;
internal static void EnsurePacketSize(Warehouse? warehouse, ulong size)
{
var limit = warehouse?.Configuration.Parser.MaximumPacketSize ?? 0;
if (limit > 0 && size > limit)
throw new ParserLimitException(
$"Declared packet payload of {size} bytes exceeds the {limit}-byte limit.");
}
internal static void EnsureAllocation(Warehouse? warehouse, ulong size, string kind)
{
var limit = warehouse?.Configuration.Parser.MaximumAllocationSize ?? 0;
if (limit > 0 && size > limit)
throw new ParserLimitException(
$"Decoded {kind} allocation of {size} bytes exceeds the {limit}-byte limit.");
}
internal static void EnsureCollectionCount(
Warehouse? warehouse,
int count,
int estimatedBytesPerItem = 0)
{
var configuration = warehouse?.Configuration.Parser;
if (configuration == null)
return;
if (configuration.MaximumCollectionItems > 0 && count > configuration.MaximumCollectionItems)
throw new ParserLimitException(
$"Decoded collection count of {count} exceeds the {configuration.MaximumCollectionItems}-item limit.");
if (estimatedBytesPerItem > 0)
EnsureAllocation(
warehouse,
(ulong)count * (ulong)estimatedBytesPerItem,
"collection");
}
internal static ulong MultiplySaturated(ulong value, ulong multiplier)
=> value > ulong.MaxValue / multiplier ? ulong.MaxValue : value * multiplier;
}
+13 -1
View File
@@ -21,7 +21,11 @@ namespace Esiur.Data
public static PlainTdu Parse(byte[] data, uint offset, uint ends)
public static PlainTdu Parse(
byte[] data,
uint offset,
uint ends,
ulong maximumPayloadLength = ulong.MaxValue)
{
var oOffset = offset;
@@ -91,6 +95,10 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
if (cl > maximumPayloadLength)
throw new ParserLimitException(
$"Declared packet payload of {cl} bytes exceeds the {maximumPayloadLength}-byte limit.");
if (ends - offset < cl)
return new PlainTdu()
{
@@ -128,6 +136,10 @@ namespace Esiur.Data
for (uint i = 0; i < cll; i++)
cl = cl << 8 | data[offset++];
if (cl > maximumPayloadLength)
throw new ParserLimitException(
$"Declared packet payload of {cl} bytes exceeds the {maximumPayloadLength}-byte limit.");
if (ends - offset < cl)
return new PlainTdu()
{