// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using EpicGames.Tracing.UnrealInsights.Events;
[assembly: InternalsVisibleTo("EpicGames.Tracing.Tests")]
namespace EpicGames.Tracing.UnrealInsights
{
public static class BinaryReaderExtensions
{
public static byte[] ReadBytesStrict(this BinaryReader reader, int count)
{
byte[] result = reader.ReadBytes(count);
if (result.Length != count)
{
throw new EndOfStreamException();
}
return result;
}
///
/// Check if bit is set for a given byte
///
/// Value to check
/// Pos 0 is least significant bit, pos 7 is most
/// True if set
internal static bool IsBitSet(byte value, int pos)
{
return (value & (1 << pos)) != 0;
}
internal static bool IsTwoByteUid(byte uidLow)
{
return IsBitSet(uidLow, 0);
}
internal static byte GetHighByte(ushort a)
{
return (byte)(a >> 8);
}
internal static byte GetLowByte(ushort a)
{
return (byte)(a & 0xff);
}
internal static ushort GetHighWord(uint a)
{
return (ushort)(a >> 16);
}
internal static ushort GetLowWord(uint a)
{
return (ushort)(a & 0xffff);
}
internal static ushort MakeShort(byte lowByte, byte highByte)
{
return (ushort)((byte)(lowByte & 0xff) | (ushort)(highByte & 0xff) << 8);
}
internal static ushort GetPackedUid(byte uidLow, byte uidHigh, out bool isTwoByteUidArg)
{
// struct packed_uid
// {
// uint8 is_two_byte_uid : 1
// uid_low : 7
// (uint8 uid_high) // if is_two_byte_uid == 1
// }
isTwoByteUidArg = IsTwoByteUid(uidLow);
byte uidLowNoBit = (byte)(uidLow >> 1); // Strip the is_two_byte_uid bit
return MakeShort(uidLowNoBit, isTwoByteUidArg ? uidHigh : (byte) 0x00);
}
public static ushort ReadPackedUid(this BinaryReader reader, out bool isTwoByteUid)
{
byte uidLow = reader.ReadByte();
byte uidHigh = reader.ReadByte();
reader.BaseStream.Position -= 1; // UidLow is always consumed, but UidHigh maybe not.
ushort uid = GetPackedUid(uidLow, uidHigh, out isTwoByteUid);
if (isTwoByteUid)
{
reader.ReadByte(); // Consume UidHigh
}
return uid;
}
public static void EnsureEntireStreamIsConsumed(this BinaryReader reader)
{
bool isEntireStreamConsumed = reader.BaseStream.Position == reader.BaseStream.Length;
if (!isEntireStreamConsumed)
{
throw new Exception($"Entire stream/buffer was not consumed. Pos={reader.BaseStream.Position} Len={reader.BaseStream.Length}");
}
}
public static bool IsEntireStreamConsumed(this BinaryReader reader)
{
return reader.BaseStream.Position == reader.BaseStream.Length;
}
}
public static class BinaryWriterExtensions
{
public static void WritePackedUid(this BinaryWriter writer, ushort uid)
{
if (uid < PredefinedEventUid.WellKnownNum)
{
byte uidLow = (byte)uid;
uidLow = (byte) (uidLow << 1);
// LSB is 0 after shifting, indicating a one-byte UID
writer.Write(uidLow);
}
else if (uid < 127)
{
byte uidLow = BinaryReaderExtensions.GetLowByte(uid);
byte uidHigh = BinaryReaderExtensions.GetHighByte(uid);
uidLow = (byte) (uidLow << 1);
uidLow = (byte) (uidLow | 1);
writer.Write(uidLow);
writer.Write(uidHigh);
}
else
{
throw new NotImplementedException("Handling of UIDs >= 127 not implemented");
}
}
}
public interface ITraceEvent
{
ushort Size { get; }
EventType Type { get; }
public void Serialize(ushort uid, BinaryWriter writer);
}
public class EnterScopeEvent : ITraceEvent
{
public ushort Size => 0;
public EventType Type => EventType.WellKnown(PredefinedEventUid.EnterScope, "EnterScope");
public void Serialize(ushort uid, BinaryWriter writer) { throw new NotImplementedException(); }
}
public class LeaveScopeEvent : ITraceEvent
{
public ushort Size => 0;
public EventType Type => EventType.WellKnown(PredefinedEventUid.LeaveScope, "LeaveScope");
public void Serialize(ushort uid, BinaryWriter writer) { throw new NotImplementedException(); }
}
public class EnterScopeEventTimestamp : ITraceEvent
{
public ushort Size => 7;
public EventType Type => EventType.WellKnown(PredefinedEventUid.EnterScope_T, "EnterScopeTimestamp");
public ulong Timestamp { get; }
public EnterScopeEventTimestamp(ulong timestamp)
{
Timestamp = timestamp;
}
public void Serialize(ushort uid, BinaryWriter writer) { throw new NotImplementedException(); }
public static EnterScopeEventTimestamp Deserialize(BinaryReader reader)
{
ulong value = reader.ReadUInt64();
ushort uidFound = BinaryReaderExtensions.GetPackedUid((byte)(value & 0xFF), 0x00, out bool _);
if (uidFound != PredefinedEventUid.EnterScope_T)
{
throw new ArgumentException($"Bad UID found when deserializing 0x{uidFound:X4}/{uidFound}");
}
ulong timestamp = value >> 8;
return new EnterScopeEventTimestamp(timestamp);
}
}
public class LeaveScopeEventTimestamp : ITraceEvent
{
public ushort Size => 7;
public EventType Type => EventType.WellKnown(PredefinedEventUid.EnterScope_T, "LeaveScopeTimestamp");
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Serialize() unimplemented")]
readonly ulong _timestamp;
public LeaveScopeEventTimestamp(ulong timestamp)
{
_timestamp = timestamp;
}
public void Serialize(ushort uid, BinaryWriter writer) { throw new NotImplementedException(); }
public static LeaveScopeEventTimestamp Deserialize(BinaryReader reader)
{
ulong value = reader.ReadUInt64();
ushort uidFound = BinaryReaderExtensions.GetPackedUid((byte)(value & 0xFF), 0x00, out bool _);
if (uidFound != PredefinedEventUid.LeaveScope_T)
{
throw new ArgumentException($"Bad UID found when deserializing 0x{uidFound:X4}/{uidFound}");
}
ulong timestamp = value >> 8;
return new LeaveScopeEventTimestamp(timestamp);
}
}
public class TraceImportantEventHeader
{
public ushort Uid { get; }
public ushort EventSize { get; }
public const ushort HeaderSize = sizeof(ushort) + sizeof(ushort);
public TraceImportantEventHeader(ushort uid, ushort eventSize)
{
#pragma warning disable CA1508 // Avoid dead conditional code
Debug.Assert(HeaderSize == 4);
#pragma warning restore CA1508 // Avoid dead conditional code
Uid = uid;
EventSize = eventSize;
}
public void Serialize(BinaryWriter writer)
{
writer.Write(Uid);
writer.Write(EventSize);
}
public static TraceImportantEventHeader Deserialize(BinaryReader reader)
{
ushort uid = reader.ReadUInt16();
return new TraceImportantEventHeader(uid, reader.ReadUInt16());
}
}
}