// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// A Managed wrapper for Event Tracing for Windows
// Based on TraceEvent.cs found in nt\base\wmi\trace.net
// Provides an internal Avalon API to replace Microsoft.Windows.EventTracing.dll

#if !SILVERLIGHTXAML
using System;
using MS.Win32;
using MS.Internal;
using System.Runtime.InteropServices;
using System.Security;
using System.Globalization; //for CultureInfo
using System.Diagnostics;
using MS.Internal.WindowsBase;

#pragma warning disable 1634, 1691  //disable warnings about unknown pragma

#if SYSTEM_XAML
using System.Xaml;
namespace MS.Internal.Xaml
#else
namespace MS.Utility
#endif
{
    [StructLayout(LayoutKind.Explicit, Size = 16)]
    internal struct EventData
    {
        [FieldOffset(0)]
        internal unsafe ulong Ptr;
        [FieldOffset(8)]
        internal uint Size;
        [FieldOffset(12)]
        internal uint Reserved;
    }

    internal abstract class TraceProvider
    {
        protected bool _enabled = false;
        protected EventTrace.Level _level = EventTrace.Level.LogAlways;
        protected EventTrace.Keyword _keywords = (EventTrace.Keyword)0; /* aka Flags */
        protected EventTrace.Keyword _matchAllKeyword = (EventTrace.Keyword)0; /*Vista only*/
        protected SecurityCriticalDataForSet<ulong> _registrationHandle;

        private const int s_basicTypeAllocationBufferSize = sizeof(decimal);
        private const int s_traceEventMaximumSize = 65482; // maximum buffer size is 64k - header size
        private const int s_etwMaxNumberArguments = 32;
        private const int s_etwAPIMaxStringCount = 8; // Arbitrary limit on the number of strings you can emit. This is just to limit allocations so raise it if necessary.
        private const int ErrorEventTooBig = 2;


        [SecurityCritical]
        internal TraceProvider()
        {
            _registrationHandle = new SecurityCriticalDataForSet<ulong>(0);
        }

        [SecurityCritical]
        internal abstract void Register(Guid providerGuid);
        [SecurityCritical]
        internal unsafe abstract uint EventWrite(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level, int argc, EventData* argv);

        internal uint TraceEvent(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level)
        {
            // Optimization for 0-1 arguments
            return TraceEvent(eventID, keywords, level, (object)null);
        }

        #region Properties and Structs

        //
        // Properties
        //
        internal EventTrace.Keyword Keywords
        {
            get
            {
                return _keywords;
            }
        }

        internal EventTrace.Keyword MatchAllKeywords
        {
            get
            {
                return _matchAllKeyword;
            }
        }

        internal EventTrace.Level Level
        {
            get
            {
                return _level;
            }
        }

        #endregion

        internal bool IsEnabled(EventTrace.Keyword keyword, EventTrace.Level level)
        {
           return _enabled &&
                  (level <= _level) &&
                  (keyword & _keywords) != 0 &&
                  (keyword & _matchAllKeyword) == _matchAllKeyword;
        }

        // Optimization for 0-1 arguments
        [SecurityCritical, SecurityTreatAsSafe]
        internal unsafe uint TraceEvent(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level, object eventData)
        {
            // It is the responsibility of the caller to check that flags/keywords are enabled before calling this method
            Debug.Assert(IsEnabled(keywords, level));

            uint status = 0;
            int argCount = 0;

            EventData userData;
            userData.Size = 0;
            string dataString = null;
            byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize];

            if (eventData != null)
            {
                dataString = EncodeObject(ref eventData, &userData, dataBuffer);
                argCount = 1;
            }

            if (userData.Size > s_traceEventMaximumSize)
            {
                return ErrorEventTooBig;
            }

            if (dataString != null)
            {
                fixed(char* pdata = dataString)
                {
                    userData.Ptr = (ulong)pdata;
                    status = EventWrite(eventID, keywords, level, argCount, &userData);
                }
            }
            else
            {
                status = EventWrite(eventID, keywords, level, argCount, &userData);
            }

            return status;
        }

        [SecurityCritical, SecurityTreatAsSafe]
        internal unsafe uint TraceEvent(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level, params object[] eventPayload)
        {
            // It is the responsibility of the caller to check that flags/keywords are enabled before calling this method
            Debug.Assert(IsEnabled(keywords, level));

            int argCount = eventPayload.Length;

            Debug.Assert(argCount <= s_etwMaxNumberArguments);

            uint totalEventSize = 0;
            int stringIndex = 0;
            int[] stringPosition =  new int[s_etwAPIMaxStringCount];
            string [] dataString = new string[s_etwAPIMaxStringCount];
            EventData* userData = stackalloc EventData[argCount];
            EventData* userDataPtr = userData;
            byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * argCount];
            byte* currentBuffer = dataBuffer;


            for (int index = 0; index < argCount; index++)
            {
                if (eventPayload[index] != null)
                {
                    string isString = EncodeObject(ref eventPayload[index], userDataPtr, currentBuffer);
                    currentBuffer += s_basicTypeAllocationBufferSize;
                    totalEventSize = userDataPtr->Size;
                    userDataPtr++;
                    if (isString != null)
                    {
                        Debug.Assert(stringIndex < s_etwAPIMaxStringCount); // need to increase string count or emit fewer strings
                        dataString[stringIndex] = isString;
                        stringPosition[stringIndex] = index;
                        stringIndex++;
                    }
                }
            }

            if (totalEventSize > s_traceEventMaximumSize)
            {
                return ErrorEventTooBig;
            }

            fixed(char* s0 = dataString[0], s1 = dataString[1], s2 = dataString[2], s3 = dataString[3],
                    s4 = dataString[4], s5 = dataString[5], s6 = dataString[6], s7 = dataString[7])
            {
                userDataPtr = userData;
                if (dataString[0] != null)
                {
                    userDataPtr[stringPosition[0]].Ptr = (ulong)s0;
                }
                if (dataString[1] != null)
                {
                    userDataPtr[stringPosition[1]].Ptr = (ulong)s1;
                }
                if (dataString[2] != null)
                {
                    userDataPtr[stringPosition[2]].Ptr = (ulong)s2;
                }
                if (dataString[3] != null)
                {
                    userDataPtr[stringPosition[3]].Ptr = (ulong)s3;
                }
                if (dataString[4] != null)
                {
                    userDataPtr[stringPosition[4]].Ptr = (ulong)s4;
                }
                if (dataString[5] != null)
                {
                    userDataPtr[stringPosition[5]].Ptr = (ulong)s5;
                }
                if (dataString[6] != null)
                {
                    userDataPtr[stringPosition[6]].Ptr = (ulong)s6;
                }
                if (dataString[7] != null)
                {
                    userDataPtr[stringPosition[7]].Ptr = (ulong)s7;
                }

                return EventWrite(eventID, keywords, level, argCount, userData);
            }
        }


        // <SecurityKernel Critical="True" Ring="0">
        // <UsesUnsafeCode Name="Local intptrPtr of type: IntPtr*" />
        // <UsesUnsafeCode Name="Local intptrPtr of type: Int32*" />
        // <UsesUnsafeCode Name="Local longptr of type: Int64*" />
        // <UsesUnsafeCode Name="Local uintptr of type: UInt32*" />
        // <UsesUnsafeCode Name="Local ulongptr of type: UInt64*" />
        // <UsesUnsafeCode Name="Local charptr of type: Char*" />
        // <UsesUnsafeCode Name="Local byteptr of type: Byte*" />
        // <UsesUnsafeCode Name="Local shortptr of type: Int16*" />
        // <UsesUnsafeCode Name="Local sbyteptr of type: SByte*" />
        // <UsesUnsafeCode Name="Local ushortptr of type: UInt16*" />
        // <UsesUnsafeCode Name="Local floatptr of type: Single*" />
        // <UsesUnsafeCode Name="Local doubleptr of type: Double*" />
        // <UsesUnsafeCode Name="Local boolptr of type: Boolean*" />
        // <UsesUnsafeCode Name="Local guidptr of type: Guid*" />
        // <UsesUnsafeCode Name="Local decimalptr of type: Decimal*" />
        // <UsesUnsafeCode Name="Local booleanptr of type: Boolean*" />
        // <UsesUnsafeCode Name="Parameter dataDescriptor of type: EventData*" />
        // <UsesUnsafeCode Name="Parameter dataBuffer of type: Byte*" />
        // </SecurityKernel>
        [SecurityCritical]
        private static unsafe string EncodeObject(ref object data, EventData* dataDescriptor, byte* dataBuffer)
        /*++

        Routine Description:

           This routine is used by WriteEvent to unbox the object type and
           to fill the passed in ETW data descriptor.

        Arguments:

           data - argument to be decoded

           dataDescriptor - pointer to the descriptor to be filled

           dataBuffer - storage buffer for storing user data, needed because cant get the address of the object

        Return Value:

           null if the object is a basic type other than string. String otherwise

        --*/
        {
            dataDescriptor->Reserved = 0;

            string sRet = data as string;
            if (sRet != null)
            {
                dataDescriptor->Size = (uint)((sRet.Length + 1) * 2);
                return sRet;
            }

            // If the data is an enum we'll convert it to it's underlying type
            Type dataType = data.GetType();
            if (dataType.IsEnum)
            {
                data = Convert.ChangeType(data, Enum.GetUnderlyingType(dataType), CultureInfo.InvariantCulture);
            }

            if (data is IntPtr)
            {
                dataDescriptor->Size = (uint)sizeof(IntPtr);
                IntPtr* intptrPtr = (IntPtr*)dataBuffer;
                *intptrPtr = (IntPtr)data;
                dataDescriptor->Ptr = (ulong)intptrPtr;
            }
            else if (data is int)
            {
                dataDescriptor->Size = (uint)sizeof(int);
                int* intptrPtr = (int*)dataBuffer;
                *intptrPtr = (int)data;
                dataDescriptor->Ptr = (ulong)intptrPtr;
            }
            else if (data is long)
            {
                dataDescriptor->Size = (uint)sizeof(long);
                long* longptr = (long*)dataBuffer;
                *longptr = (long)data;
                dataDescriptor->Ptr = (ulong)longptr;
            }
            else if (data is uint)
            {
                dataDescriptor->Size = (uint)sizeof(uint);
                uint* uintptr = (uint*)dataBuffer;
                *uintptr = (uint)data;
                dataDescriptor->Ptr = (ulong)uintptr;
            }
            else if (data is UInt64)
            {
                dataDescriptor->Size = (uint)sizeof(ulong);
                ulong* ulongptr = (ulong*)dataBuffer;
                *ulongptr = (ulong)data;
                dataDescriptor->Ptr = (ulong)ulongptr;
            }
            else if (data is char)
            {
                dataDescriptor->Size = (uint)sizeof(char);
                char* charptr = (char*)dataBuffer;
                *charptr = (char)data;
                dataDescriptor->Ptr = (ulong)charptr;
            }
            else if (data is byte)
            {
                dataDescriptor->Size = (uint)sizeof(byte);
                byte* byteptr = (byte*)dataBuffer;
                *byteptr = (byte)data;
                dataDescriptor->Ptr = (ulong)byteptr;
            }
            else if (data is short)
            {
                dataDescriptor->Size = (uint)sizeof(short);
                short* shortptr = (short*)dataBuffer;
                *shortptr = (short)data;
                dataDescriptor->Ptr = (ulong)shortptr;
            }
            else if (data is sbyte)
            {
                dataDescriptor->Size = (uint)sizeof(sbyte);
                sbyte* sbyteptr = (sbyte*)dataBuffer;
                *sbyteptr = (sbyte)data;
                dataDescriptor->Ptr = (ulong)sbyteptr;
            }
            else if (data is ushort)
            {
                dataDescriptor->Size = (uint)sizeof(ushort);
                ushort* ushortptr = (ushort*)dataBuffer;
                *ushortptr = (ushort)data;
                dataDescriptor->Ptr = (ulong)ushortptr;
            }
            else if (data is float)
            {
                dataDescriptor->Size = (uint)sizeof(float);
                float* floatptr = (float*)dataBuffer;
                *floatptr = (float)data;
                dataDescriptor->Ptr = (ulong)floatptr;
            }
            else if (data is double)
            {
                dataDescriptor->Size = (uint)sizeof(double);
                double* doubleptr = (double*)dataBuffer;
                *doubleptr = (double)data;
                dataDescriptor->Ptr = (ulong)doubleptr;
            }
            else if (data is bool)
            {
                dataDescriptor->Size = (uint)sizeof(bool);
                bool* boolptr = (bool*)dataBuffer;
                *boolptr = (bool)data;
                dataDescriptor->Ptr = (ulong)boolptr;
            }
            else if (data is Guid)
            {
                dataDescriptor->Size = (uint)sizeof(Guid);
                Guid* guidptr = (Guid*)dataBuffer;
                *guidptr = (Guid)data;
                dataDescriptor->Ptr = (ulong)guidptr;
            }
            else if (data is decimal)
            {
                dataDescriptor->Size = (uint)sizeof(decimal);
                decimal* decimalptr = (decimal*)dataBuffer;
                *decimalptr = (decimal)data;
                dataDescriptor->Ptr = (ulong)decimalptr;
            }
            else if (data is Boolean)
            {
                dataDescriptor->Size = (uint)sizeof(Boolean);
                Boolean* booleanptr = (Boolean*)dataBuffer;
                *booleanptr = (Boolean)data;
                dataDescriptor->Ptr = (ulong)booleanptr;
            }
            else
            {
                //To our eyes, everything else is a just a string
                sRet = data.ToString();
                dataDescriptor->Size = (uint)((sRet.Length + 1) * 2);
                return sRet;
            }

            return null;
        }
    }

    // XP
    internal sealed class ClassicTraceProvider : TraceProvider
    {
        private ulong _traceHandle = 0;
        
        /// <SecurityNote>
        ///     Critical - Field for critical type ClassicEtw.ControlCallback.
        /// </SecurityNote>
        [SecurityCritical]
        private static ClassicEtw.ControlCallback _etwProc;   // Trace Callback function

        [SecurityCritical]
        internal ClassicTraceProvider()
        {
        }

        //
        // Registers the providerGuid with an inbuilt callback
        //
        ///<SecurityNote>
        /// Critical:  This calls critical code in UnsafeNativeMethods.EtwTrace
        /// and sets critical for set field _registrationHandle and _etwProc
        ///</SecurityNote>
        [SecurityCritical]
        internal override unsafe void Register(Guid providerGuid)
        {
            ulong registrationHandle;
            ClassicEtw.TRACE_GUID_REGISTRATION guidReg;

            Guid dummyGuid = new Guid(0xb4955bf0,
                                      0x3af1,
                                      0x4740,
                                      0xb4,0x75,
                                      0x99,0x05,0x5d,0x3f,0xe9,0xaa);

            _etwProc = new ClassicEtw.ControlCallback(EtwEnableCallback);

            // This dummyGuid is there for ETW backward compat issues and is the same for all downlevel trace providers
            guidReg.Guid = &dummyGuid;
            guidReg.RegHandle = null;

            ClassicEtw.RegisterTraceGuidsW(_etwProc, IntPtr.Zero, ref providerGuid, 1, ref guidReg, null, null, out registrationHandle);
            _registrationHandle.Value = registrationHandle;
        }

        //
        // This callback function is called by ETW to enable or disable this provider
        //
        ///<SecurityNote>
        /// Critical:  This calls critical code in ClassicEtw
        ///</SecurityNote>
        [SecurityCritical]
        private unsafe uint EtwEnableCallback(ClassicEtw.WMIDPREQUESTCODE requestCode, IntPtr context, IntPtr bufferSize, ClassicEtw.WNODE_HEADER* buffer)
        {
            try
            {
                switch (requestCode)
                {
                    case ClassicEtw.WMIDPREQUESTCODE.EnableEvents:
                        _traceHandle = buffer->HistoricalContext;
                        _keywords = (EventTrace.Keyword)ClassicEtw.GetTraceEnableFlags((ulong)buffer->HistoricalContext);
                        _level = (EventTrace.Level)ClassicEtw.GetTraceEnableLevel((ulong)buffer->HistoricalContext);
                        _enabled = true;
                        break;
                    case ClassicEtw.WMIDPREQUESTCODE.DisableEvents:
                        _enabled = false;
                        _traceHandle = 0;
                        _level = EventTrace.Level.LogAlways;
                        _keywords = 0;
                        break;
                    default:
                        _enabled = false;
                        _traceHandle = 0;
                        break;
                }
                return 0;
            }
            catch(Exception e)
            {
                if (CriticalExceptions.IsCriticalException(e))
                {
                   throw;
                }
                else
                {
                    return 0;
                }
            }
        }

        ///<SecurityNote>
        /// Critical:  This calls critical code in EtwTrace
        /// TreatAsSafe: the registration handle this passes in to UnregisterTraceGuids
        /// was generated by the ETW unmanaged API and can't be tampered with from our side
        ///</SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        ~ClassicTraceProvider()
        {
            #pragma warning suppress 6031  //presharp suppression
            ClassicEtw.UnregisterTraceGuids(_registrationHandle.Value);
        }

        // pack the argv data and emit the event using TraceEvent
        [SecurityCritical]
        internal unsafe override uint EventWrite(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level, int argc, EventData* argv)
        {
            ClassicEtw.EVENT_HEADER header;
            header.Header.ClientContext = 0;
            header.Header.Flags = ClassicEtw.WNODE_FLAG_TRACED_GUID | ClassicEtw.WNODE_FLAG_USE_MOF_PTR;
            header.Header.Guid = EventTrace.GetGuidForEvent(eventID);
            header.Header.Level = (byte)level;
            header.Header.Type = (byte)EventTrace.GetOpcodeForEvent(eventID);
            header.Header.Version = (ushort)EventTrace.GetVersionForEvent(eventID);
            // Extra copy on XP to move argv to the end of the EVENT_HEADER
            EventData* eventData = &header.Data;

            if (argc > ClassicEtw.MAX_MOF_FIELDS)
            {
                // Data will be lost on XP
                argc = ClassicEtw.MAX_MOF_FIELDS;
            }

            header.Header.Size = (ushort) (argc * sizeof(EventData) + 48);
            for (int x = 0; x < argc; x++)
            {
                eventData[x].Ptr = argv[x].Ptr;
                eventData[x].Size = argv[x].Size;
            }

            return ClassicEtw.TraceEvent(_traceHandle, &header);
        }
    }

    // Vista and above
    internal class ManifestTraceProvider : TraceProvider
    {
        /// <SecurityNote>
        ///     Critical - Field for critical type ManifestEtw.EtwEnableCallback.
        /// </SecurityNote>
        [SecurityCritical]
        private static ManifestEtw.EtwEnableCallback _etwEnabledCallback;

        [SecurityCritical]
        internal ManifestTraceProvider()
        {
        }

        /// <SecurityNote>
        ///     Critical - Sets critical _etwEnabledCallback field
        ///              - Calls critical ManifestEtw.EventRegister
        /// </SecurityNote>
        [SecurityCritical]
        internal unsafe override void Register(Guid providerGuid)
        {
            _etwEnabledCallback =new ManifestEtw.EtwEnableCallback(EtwEnableCallback);
            ulong registrationHandle = 0;
            ManifestEtw.EventRegister(ref providerGuid, _etwEnabledCallback, null, ref registrationHandle);
            _registrationHandle.Value = registrationHandle;
        }

        /// <SecurityNote>
        ///     Critical - Accepts untrusted pointer argument
        /// </SecurityNote>
        [SecurityCritical]
        private unsafe void EtwEnableCallback(ref Guid sourceId, int isEnabled, byte level, long matchAnyKeywords, long matchAllKeywords, ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, void* callbackContext)
        {
            _enabled = isEnabled > 0;
            _level = (EventTrace.Level)level;
            _keywords = (EventTrace.Keyword) matchAnyKeywords;
            _matchAllKeyword = (EventTrace.Keyword) matchAllKeywords;

            // todo: parse data from EVENT_FILTER_DESCRIPTOR - see CLR EventProvider::GetDataFromController
        }

        /// <SecurityNote>
        ///     Critical - Calls critical ManifestEtw.EventUnregister
        ///     TreatAsSafe: Only critical code can create this resource, 
        ///     and no input parameters are accepted to this method.
        ///     In fact, this method is not directly callable, but only as
        ///     part of the GC, and the GC ensures that no other rooted
        ///     objects are holding a reference.  This method clears the
        ///     handle and skips future calls to unregister the event, which
        ///     protects against resurrection.
        /// </SecurityNote>
        [SecurityCritical, SecurityTreatAsSafe]
        ~ManifestTraceProvider()
        {
            if(_registrationHandle.Value != 0)
            {
                try
                {
                    ManifestEtw.EventUnregister(_registrationHandle.Value);
                }
                finally
                {
                    _registrationHandle.Value = 0;
                }
            }
        }

        /// <SecurityNote>
        ///     Critical - Accepts untrusted pointer argument
        /// </SecurityNote>
        [SecurityCritical]
        internal unsafe override uint EventWrite(EventTrace.Event eventID, EventTrace.Keyword keywords, EventTrace.Level level, int argc, EventData* argv)
        {
            ManifestEtw.EventDescriptor eventDescriptor;
            eventDescriptor.Id = (ushort) eventID;
            eventDescriptor.Version = EventTrace.GetVersionForEvent(eventID);
            eventDescriptor.Channel = 0x10; // Since Channel isn't supported on XP we only use a single default channel.
            eventDescriptor.Level = (byte)level;
            eventDescriptor.Opcode = EventTrace.GetOpcodeForEvent(eventID);
            eventDescriptor.Task = EventTrace.GetTaskForEvent(eventID);
            eventDescriptor.Keywords = (long)keywords;
            if (argc == 0)
            {
                argv = null;
            }

            return ManifestEtw.EventWrite(_registrationHandle.Value, ref eventDescriptor, (uint)argc, argv);
        }

    }
}

#endif
