Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support decoding array in dynamic event #4370

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,57 @@ public void TestReportingUnknownEvent()
};
test.Should().Throw<Exception>();
}

[TestMethod]
public void TestArray()
{
List<DynamicEventSchema> arraySchema = new List<DynamicEventSchema>
{
new DynamicEventSchema
{
DynamicEventName = "ArrayEventName",
Fields = new List<KeyValuePair<string, Type>>
{
new KeyValuePair<string, Type>("version", typeof(ushort)),
new KeyValuePair<string, Type>("Array1", typeof(ushort[])),
new KeyValuePair<string, Type>("Number1", typeof(ulong)),
new KeyValuePair<string, Type>("Array2", typeof(byte[])),
new KeyValuePair<string, Type>("Number2", typeof(ulong)),
}
},
};
DynamicEventSchema.Set(arraySchema, false);
List<GCDynamicEvent> dynamicEvents = new List<GCDynamicEvent>
{
new GCDynamicEvent(
"ArrayEventName",
DateTime.Now,
// ver [ Array 1 ] [ Number 1 ] [ Array 2 ] [ Number 2 ]
new byte[] { 1, 0, 5, 1, 0, 0, 0, 0, 0, 8, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 8, 2, 8, 9, 6, 3, 0, 3, 5, 2, 0, 0, 0, 0, 0, 0, 0 }
)
};
dynamic index = new DynamicIndex(dynamicEvents);
ushort[] array1 = (ushort[])index.ArrayEventName.Array1;
ulong number1 = (ulong)index.ArrayEventName.Number1;
byte[] array2 = (byte[])index.ArrayEventName.Array2;
ulong number2 = (ulong)index.ArrayEventName.Number2;
array1.Length.Should().Be(5);
array2.Length.Should().Be(8);
array1[0].Should().Be(1);
array1[1].Should().Be(0);
array1[2].Should().Be(0);
array1[3].Should().Be(8);
array1[4].Should().Be(6);
number1.Should().Be(1);
array2[0].Should().Be(2);
array2[1].Should().Be(8);
array2[2].Should().Be(9);
array2[3].Should().Be(6);
array2[4].Should().Be(3);
array2[5].Should().Be(0);
array2[6].Should().Be(3);
array2[7].Should().Be(5);
number2.Should().Be(2);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,94 @@ public sealed class DynamicEventSchema

public int MaxOccurrence { get; init; } = 1;

internal static bool TryComputeOffset(byte[] payload, List<int> unadjustedArrayLengthOffsets, List<int> arrayElementSizes, int unadjustedOffset, out int adjustedOffset)
{
//
// Any offsets on or before the first array content will have their
// actual offset equals to the unadjusted offsets. In particular,
// the offset to the first array's length is never adjusted. So we can
// find the length of the first array.
//
int adjustment = 0;
for (int i = 0; i < unadjustedArrayLengthOffsets.Count; i++)
{
int unadjustedArrayLengthOffset = unadjustedArrayLengthOffsets[i];
if (unadjustedOffset > unadjustedArrayLengthOffset)
{
if (payload.Length <= unadjustedArrayLengthOffset)
{
adjustedOffset = 0;
return false;
}

//
// If we had a second array, the second arrays unadjusted offsets will
// be earlier than its actual offset, but we know how to adjust it. So
// we can get the actual offset to the second array's length.
//
byte arrayLength = payload[unadjustedArrayLengthOffset + adjustment];
adjustment += arrayLength * arrayElementSizes[i];
}
else
{
//
// If the offset are are looking for is not after the kth array length
// Then we should stop computing the adjustment
//
break;
}
}

adjustedOffset = unadjustedOffset + adjustment;
return true;
}

internal static int ComputeOffset(byte[] payload, List<int> unadjustedArrayLengthOffsets, List<int> arrayElementSizes, int unadjustedOffset)
{
if (TryComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, unadjustedOffset, out int adjustedOffset))
{
return adjustedOffset;
}
else
{
throw new Exception("Fail to compute offset, this should not happen");
}
}

internal static bool IsSupportedPrimitiveType(Type type)
{
return
(type == typeof(ushort)) ||
(type == typeof(uint)) ||
(type == typeof(float)) ||
(type == typeof(ulong)) ||
(type == typeof(byte)) ||
(type == typeof(bool)) ||
false;
}

internal static int Size(Type type)
{
if (type == typeof(ushort)) { return 2; }
else if (type == typeof(uint)) { return 4; }
else if (type == typeof(float)) { return 8; }
else if (type == typeof(ulong)) { return 8; }
else if (type == typeof(byte)) { return 1; }
else if (type == typeof(bool)) { return 1; }
else { throw new Exception("Wrong type"); }
}

internal static object Decode(Type type, byte[] payload, int offset)
{
if (type == typeof(ushort)) { return BitConverter.ToUInt16(payload, offset); }
else if (type == typeof(uint)) { return BitConverter.ToUInt32(payload, offset); }
else if (type == typeof(float)) { return BitConverter.ToSingle(payload, offset); }
else if (type == typeof(ulong)) { return BitConverter.ToUInt64(payload, offset); }
else if (type == typeof(byte)) { return payload[offset]; }
else if (type == typeof(bool)) { return BitConverter.ToBoolean(payload, offset); }
else { throw new Exception("Wrong type"); }
}

internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bool allowPartialSchema = true)
{
if (DynamicEventSchemas != null && DynamicEventSchemas.ContainsKey(dynamicEventSchema.DynamicEventName))
Expand All @@ -61,6 +149,17 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo
}
schema.MinOccurrence = dynamicEventSchema.MinOccurrence;
schema.MaxOccurrence = dynamicEventSchema.MaxOccurrence;

//
// With array, a field in an event no longer have a fixed offset.
// Unadjusted offsets are offsets as if all the arrays are empty
// This list will store the unadjusted offsets to array lengths
//
// This is sufficient to get to the actual offsets, once we have
// the payload, see TryComputeOffset for more details.
//
List<int> unadjustedArrayLengthOffsets = new List<int>();
List<int> arrayElementSizes = new List<int>();
int offset = 0;
foreach (KeyValuePair<string, Type> field in dynamicEventSchema.Fields)
{
Expand All @@ -69,39 +168,42 @@ internal static CompiledSchema Compile(DynamicEventSchema dynamicEventSchema, bo
DynamicEventSchemas?.Clear();
throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a duplicated field named {field.Key}");
}
schema.Add(field.Key, new DynamicEventField { FieldOffset = offset, FieldType = field.Value });
Func<byte[], object>? fieldFetcher = null;

if (field.Value == typeof(ushort))
{
offset += 2;
}
else if (field.Value == typeof(uint))
{
offset += 4;
}
else if (field.Value == typeof(float))
{
offset += 4;
}
else if (field.Value == typeof(ulong))
{
offset += 8;
}
else if (field.Value == typeof(byte))
// The local variable makes sure we capture the value of the offset variable in the lambdas
int currentOffset = offset;
if (IsSupportedPrimitiveType(field.Value))
{
offset += 1;
fieldFetcher = (payload) => Decode(field.Value, payload, ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset));
offset += Size(field.Value);
}
else if (field.Value == typeof(bool))
else if (field.Value.IsArray && IsSupportedPrimitiveType(field.Value.GetElementType()))
{
Type elementType = field.Value.GetElementType();
int elementSize = Size(elementType);
fieldFetcher = (payload) =>
{
int unadjustedArrayLengthOffset = ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, currentOffset);
int length = (int)payload[unadjustedArrayLengthOffset];
Array result = Array.CreateInstance(elementType, length);
for (int i = 0; i < length; i++)
{
result.SetValue(Decode(elementType, payload, unadjustedArrayLengthOffset + 1 + elementSize * i), i);
}
return result;
};
unadjustedArrayLengthOffsets.Add(offset);
arrayElementSizes.Add(elementSize);
offset += 1;
}
else
{
DynamicEventSchemas?.Clear();
throw new Exception($"Provided event named {dynamicEventSchema.DynamicEventName} has a field named {field.Key} using an unsupported type {field.Value}");
}
schema.Add(field.Key, new DynamicEventField { FieldFetcher = fieldFetcher });
}
schema.Size = offset;
schema.SizeValidator = (payload) => payload.Length == ComputeOffset(payload, unadjustedArrayLengthOffsets, arrayElementSizes, offset);
return schema;
}

Expand All @@ -124,15 +226,14 @@ public static void Set(List<DynamicEventSchema> dynamicEventSchemas, bool allowP

internal sealed class DynamicEventField
{
public required int FieldOffset { get; init; }
public required Type FieldType { get; init; }
public required Func<byte[], object> FieldFetcher;
}

internal sealed class CompiledSchema : Dictionary<string, DynamicEventField>
{
public int MinOccurrence { get; set; }
public int MaxOccurrence { get; set; }
public int Size { get; set; }
public Func<byte[], bool> SizeValidator { get; set; }
}

internal sealed class DynamicIndex : DynamicObject
Expand Down Expand Up @@ -212,44 +313,13 @@ public DynamicEventObject(GCDynamicEvent dynamicEvent, CompiledSchema schema)
{
this.name = dynamicEvent.Name;
this.fieldValues = new Dictionary<string, object>();
if (dynamicEvent.Payload.Length != schema.Size)
if (!schema.SizeValidator(dynamicEvent.Payload))
{
throw new Exception($"Event {dynamicEvent.Name} does not have matching size");
}
foreach (KeyValuePair<string, DynamicEventField> field in schema)
{
object? value = null;
int fieldOffset = field.Value.FieldOffset;
Type fieldType = field.Value.FieldType;

if (fieldType == typeof(ushort))
{
value = BitConverter.ToUInt16(dynamicEvent.Payload, fieldOffset);
}
else if (fieldType == typeof(uint))
{
value = BitConverter.ToUInt32(dynamicEvent.Payload, fieldOffset);
}
else if (fieldType == typeof(float))
{
value = BitConverter.ToSingle(dynamicEvent.Payload, fieldOffset);
}
else if (fieldType == typeof(ulong))
{
value = BitConverter.ToUInt64(dynamicEvent.Payload, fieldOffset);
}
else if (fieldType == typeof(byte))
{
value = dynamicEvent.Payload[fieldOffset];
}
else if (fieldType == typeof(bool))
{
value = BitConverter.ToBoolean(dynamicEvent.Payload, fieldOffset);
}
else
{
throw new Exception($"Provided schema has a field named {field.Key} using an unsupported type {fieldType}");
}
object value = field.Value.FieldFetcher(dynamicEvent.Payload);
this.fieldValues.Add(field.Key, value);
}
this.fieldValues.Add("TimeStamp", dynamicEvent.TimeStamp);
Expand Down