diff --git a/CtfPlayback/EventStreams/CtfEvent.cs b/CtfPlayback/EventStreams/CtfEvent.cs
index 73aa195..0be1fbb 100644
--- a/CtfPlayback/EventStreams/CtfEvent.cs
+++ b/CtfPlayback/EventStreams/CtfEvent.cs
@@ -5,6 +5,7 @@
using System.Diagnostics;
using CtfPlayback.EventStreams.Interfaces;
using CtfPlayback.FieldValues;
+using CtfPlayback.Metadata;
using CtfPlayback.Metadata.Interfaces;
namespace CtfPlayback.EventStreams
@@ -25,6 +26,8 @@ public CtfEvent(PacketReader packetReader, ICtfMetadata metadata, ICtfPacket own
public ulong ByteOffsetWithinPacket { get; private set; }
+ public uint StreamId => this.owningPacket.StreamId;
+
public CtfTimestamp Timestamp { get; private set; }
public CtfFieldValue StreamDefinedEventHeader { get; private set; }
@@ -102,8 +105,7 @@ public void Read()
private void ReadStreamEventHeader()
{
- uint streamIndex = this.owningPacket.StreamId;
- var eventHeaderDescriptor = this.packetReader.Metadata.Streams[(int)streamIndex].EventHeader;
+ var eventHeaderDescriptor = this.packetReader.Metadata.GetStream(this.owningPacket.StreamId).EventHeader;
Debug.Assert(eventHeaderDescriptor != null);
StreamDefinedEventHeader = eventHeaderDescriptor.Read(this.packetReader);
@@ -111,8 +113,7 @@ private void ReadStreamEventHeader()
private void ReadStreamEventContext()
{
- uint streamIndex = this.owningPacket.StreamId;
- var eventContextDescriptor = this.packetReader.Metadata.Streams[(int)streamIndex].EventContext;
+ var eventContextDescriptor = this.packetReader.Metadata.GetStream(this.owningPacket.StreamId).EventContext;
if (eventContextDescriptor == null)
{
return;
diff --git a/CtfPlayback/EventStreams/CtfPacket.cs b/CtfPlayback/EventStreams/CtfPacket.cs
index a7c9d00..39a59f5 100644
--- a/CtfPlayback/EventStreams/CtfPacket.cs
+++ b/CtfPlayback/EventStreams/CtfPacket.cs
@@ -6,6 +6,7 @@
using System.IO;
using CtfPlayback.EventStreams.Interfaces;
using CtfPlayback.FieldValues;
+using CtfPlayback.Metadata;
using CtfPlayback.Metadata.Interfaces;
namespace CtfPlayback.EventStreams
@@ -61,10 +62,12 @@ public bool ReadPacketMetadata()
this.PacketByteOffset = this.packetReader.CountOfBytesProcessed;
ReadPacketHeader();
- ReadPacketContext();
+ // The packet context layout depends on the stream, which is identified in the packet header.
this.DetermineStreamIndex();
+ ReadPacketContext();
+
this.packetReader.SetPacketSize(this.packetReader.PlaybackCustomization.GetBitsInPacket(this));
this.BitsInPacketContent =
@@ -116,7 +119,7 @@ private void ReadPacketHeader()
private void ReadPacketContext()
{
- this.StreamPacketContext = this.Metadata.Streams[(int)this.StreamId].PacketContext.Read(this.packetReader) as CtfStructValue;
+ this.StreamPacketContext = this.Metadata.GetStream(this.StreamId).PacketContext.Read(this.packetReader) as CtfStructValue;
}
private ulong GetRemainingBits()
@@ -135,6 +138,7 @@ private void DetermineStreamIndex()
if (streamIdDescriptor == null)
{
this.StreamId = 0;
+ return;
}
bool streamIdFound = this.TracePacketHeader.FieldsByName.TryGetValue("stream_id", out var streamIdFieldValue);
diff --git a/CtfPlayback/EventStreams/Interfaces/ICtfEvent.cs b/CtfPlayback/EventStreams/Interfaces/ICtfEvent.cs
index c80a429..3775772 100644
--- a/CtfPlayback/EventStreams/Interfaces/ICtfEvent.cs
+++ b/CtfPlayback/EventStreams/Interfaces/ICtfEvent.cs
@@ -16,6 +16,11 @@ public interface ICtfEvent
///
ulong ByteOffsetWithinPacket { get; }
+ ///
+ /// Id of the stream (stream class) that contains this event.
+ ///
+ uint StreamId { get; }
+
///
/// Timestamp associated with the event
///
diff --git a/CtfPlayback/Metadata/Ctf2/Ctf2Json.cs b/CtfPlayback/Metadata/Ctf2/Ctf2Json.cs
new file mode 100644
index 0000000..9e0cf89
--- /dev/null
+++ b/CtfPlayback/Metadata/Ctf2/Ctf2Json.cs
@@ -0,0 +1,273 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace CtfPlayback.Metadata.Ctf2
+{
+ ///
+ /// A JSON number, kept in its textual form so that 64-bit unsigned values are not truncated.
+ ///
+ internal sealed class Ctf2JsonNumber
+ {
+ internal Ctf2JsonNumber(string text)
+ {
+ this.Text = text;
+ }
+
+ internal string Text { get; }
+
+ internal bool IsNegative => this.Text.StartsWith("-", StringComparison.Ordinal);
+
+ internal long AsLong() => long.Parse(this.Text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);
+
+ internal ulong AsULong() => ulong.Parse(this.Text, NumberStyles.None, CultureInfo.InvariantCulture);
+
+ public override string ToString() => this.Text;
+ }
+
+ ///
+ /// Minimal JSON reader used for CTF 2 metadata. Objects are returned as
+ /// of string to object, arrays as of object,
+ /// numbers as , and strings, booleans and null as their .NET equivalents.
+ ///
+ internal sealed class Ctf2Json
+ {
+ private readonly string text;
+ private int position;
+
+ private Ctf2Json(string text)
+ {
+ this.text = text;
+ }
+
+ internal static object Parse(string text)
+ {
+ var reader = new Ctf2Json(text);
+ reader.SkipWhitespace();
+ var value = reader.ReadValue();
+ reader.SkipWhitespace();
+ if (reader.position != text.Length)
+ {
+ throw reader.Error("Unexpected trailing characters");
+ }
+
+ return value;
+ }
+
+ private object ReadValue()
+ {
+ if (this.position >= this.text.Length)
+ {
+ throw this.Error("Unexpected end of JSON");
+ }
+
+ char c = this.text[this.position];
+ switch (c)
+ {
+ case '{':
+ return this.ReadObject();
+ case '[':
+ return this.ReadArray();
+ case '"':
+ return this.ReadString();
+ case 't':
+ this.Expect("true");
+ return true;
+ case 'f':
+ this.Expect("false");
+ return false;
+ case 'n':
+ this.Expect("null");
+ return null;
+ default:
+ if (c == '-' || (c >= '0' && c <= '9'))
+ {
+ return this.ReadNumber();
+ }
+
+ throw this.Error($"Unexpected character '{c}'");
+ }
+ }
+
+ private Dictionary ReadObject()
+ {
+ var result = new Dictionary(StringComparer.Ordinal);
+ this.position++;
+ this.SkipWhitespace();
+ if (this.TryConsume('}'))
+ {
+ return result;
+ }
+
+ while (true)
+ {
+ this.SkipWhitespace();
+ if (this.position >= this.text.Length || this.text[this.position] != '"')
+ {
+ throw this.Error("Expected property name");
+ }
+
+ string name = this.ReadString();
+ this.SkipWhitespace();
+ if (!this.TryConsume(':'))
+ {
+ throw this.Error("Expected ':'");
+ }
+
+ this.SkipWhitespace();
+ result[name] = this.ReadValue();
+ this.SkipWhitespace();
+
+ if (this.TryConsume(','))
+ {
+ continue;
+ }
+
+ if (this.TryConsume('}'))
+ {
+ return result;
+ }
+
+ throw this.Error("Expected ',' or '}'");
+ }
+ }
+
+ private List