//
// Copyright (c) 2022-present, Trail of Bits, Inc.
// All rights reserved.
//
// This source code is licensed in accordance with the terms specified in
// the LICENSE file found in the root directory of this source tree.
//
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using RpcInvestigator.Util;
using System.IO;

namespace UnitTests
{
    using static NativeTraceConsumer;
    using static NativeTraceControl;

    [TestClass]
    public class EtwTests
    {
        private readonly Guid s_RpcEtwGuid =
            new Guid("6ad52b32-d609-4be9-ae07-ce8dae937e39");
        private readonly int s_NumEvents = 1000;

        [DataTestMethod]
        [DataRow(true)]
        [DataRow(false)]
        public async Task RealTimeEtwTraceTest(bool EnableDebugEvents)
        {
            var env = new Environment();
            env.Initialize();
            int eventsConsumed = 0;

            await Task.Run(() =>
            {
                //
                // This trace will automatically terminate after a set number
                // of ETW events have been successfully consumed/parsed.
                //
                using (var trace = new EtwRealTimeTrace(
                    "RPC Investigator Unit Test Real-Time Tracing",
                    s_RpcEtwGuid,
                    EnableDebugEvents))
                using (var parserBuffers = new EtwEventParserBuffers())
                {
                    try
                    {
                        trace.Start();

                        //
                        // Begin consuming events. This is a blocking call.
                        //
                        trace.Consume(new EventRecordCallback((Event) =>
                        {
                            var evt = (EVENT_RECORD)Marshal.PtrToStructure(
                                    Event, typeof(EVENT_RECORD));

                            var parser = new EtwEventParser(
                                evt,
                                parserBuffers,
                                trace.GetPerfFreq());
                            try
                            {
                                var result = parser.Parse();
                                Assert.IsNotNull(result);
                            }
                            catch (Exception ex)
                            {
                                Assert.Fail("Unable to parse event: " + ex.Message);
                            }
                            eventsConsumed++;
                        }),
                        new BufferCallback((LogFile) =>
                        {
                            var logfile = new EVENT_TRACE_LOGFILE();
                            try
                            {
                                logfile = (EVENT_TRACE_LOGFILE)
                                    Marshal.PtrToStructure(LogFile, typeof(EVENT_TRACE_LOGFILE));
                            }
                            catch (Exception ex)
                            {
                                Assert.Fail("Unable to cast EVENT_TRACE_LOGFILE: " + ex.Message);
                            }
                            if (eventsConsumed >= s_NumEvents)
                            {
                                return 0;
                            }
                            return 1;
                        }));
                    }
                    catch (Exception ex)
                    {
                        Assert.Fail("An exception occurred when consuming events: " + ex.Message);
                    }
                }
            });
        }

        [TestMethod]
        [DeploymentItem(@"..\..\data\trace_files\ms-rpc-capture-arrays.etl")]
        public void FileEtwTraceTest()
        {
            var env = new Environment();
            env.Initialize();
            int eventsConsumed = 0;

            //
            // This trace will automatically terminate after a set number
            // of ETW events have been successfully consumed/parsed.
            //
            var current = Directory.GetCurrentDirectory();
            var target = Path.Combine(current, "ms-rpc-capture-arrays.etl");

            using (var trace = new EtwFileTrace(target))
            using (var parserBuffers = new EtwEventParserBuffers())
            {
                try
                {
                    //
                    // Begin consuming events. This is a blocking call.
                    //
                    trace.Consume(new EventRecordCallback((Event) =>
                    {
                        var evt = (EVENT_RECORD)Marshal.PtrToStructure(
                                    Event, typeof(EVENT_RECORD));

                        if (!evt.EventHeader.ProviderId.Equals(s_RpcEtwGuid))
                        {
                            //
                            // Skip events from other providers, because it might not
                            // be a builtin provider, in which case we'd need to go
                            // find the right manifest and that is overly complex for
                            // this unit test.
                            //
                            return;
                        }
                        var parser = new EtwEventParser(
                            evt,
                            parserBuffers,
                            trace.GetPerfFreq());
                        try
                        {
                            var result = parser.Parse();
                            Assert.IsNotNull(result, "Failed to parse the event");
                        }
                        catch (Exception ex)
                        {
                            Assert.Fail("Unable to parse event: " + ex.Message);
                        }
                        eventsConsumed++;
                    }),
                    new BufferCallback((LogFile) =>
                    {
                        var logfile = new EVENT_TRACE_LOGFILE();
                        try
                        {
                            logfile = (EVENT_TRACE_LOGFILE)
                                Marshal.PtrToStructure(LogFile, typeof(EVENT_TRACE_LOGFILE));
                        }
                        catch (Exception ex)
                        {
                            Assert.Fail("Unable to cast EVENT_TRACE_LOGFILE: " + ex.Message);
                        }
                        if (eventsConsumed >= s_NumEvents)
                        {
                            return 0;
                        }
                        return 1;
                    }));
                }
                catch (Exception ex)
                {
                    Assert.Fail("An exception occurred when consuming events: " + ex.Message);
                }
            }
        }
    }
}
