File: DiagnosticsIpc\ProcessInfo.cs
Web Access
Project: src\src\diagnostics\src\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj (Microsoft.Diagnostics.NETCore.Client)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;

namespace Microsoft.Diagnostics.NETCore.Client
{
    /**
     * ==ProcessInfo==
     * The response payload to issuing the GetProcessInfo command.
     *
     * 8 bytes  - PID (little-endian)
     * 16 bytes - CLR Runtime Instance Cookie (little-endian)
     * # bytes  - Command line string length and data
     * # bytes  - Operating system string length and data
     * # bytes  - Process architecture string length and data
     *
     * ==ProcessInfo2==
     * The response payload to issuing the GetProcessInfo2 command.
     *
     * 8 bytes  - PID (little-endian)
     * 16 bytes - CLR Runtime Instance Cookie (little-endian)
     * # bytes  - Command line string length and data
     * # bytes  - Operating system string length and data
     * # bytes  - Process architecture string length and data
     * # bytes  - Managed entrypoint assembly name
     * # bytes  - CLR product version string (may include prerelease labels)
     *
     *
     * The "string length and data" fields are variable length:
     * 4 bytes            - Length of string data in UTF-16 characters
     * (2 * length) bytes - The data of the string encoded using Unicode
     *                      (includes null terminating character)
     */

    internal class ProcessInfo
    {
        private const int GuidSizeInBytes = 16;

        /// <summary>
        /// Parses a ProcessInfo payload.
        /// </summary>
        internal static ProcessInfo ParseV1(byte[] payload)
        {
            int index = 0;
            return ParseCommon(payload, ref index);
        }

        /// <summary>
        /// Parses a ProcessInfo2 payload.
        /// </summary>
        internal static ProcessInfo ParseV2(byte[] payload)
        {
            int index = 0;
            return ParseCommon2(payload, ref index);
        }

        /// <summary>
        /// Parses a ProcessInfo3 payload.
        /// </summary>
        internal static ProcessInfo ParseV3(byte[] payload)
        {
            int index = 0;

            // The ProcessInfo3 command is intended to allow the addition of new fields in future versions so
            // long as the version field is incremented; prior fields shall not be changed or removed.
            // Read the version field, parse the common payload, and dynamically parse the remainder depending on the version.
            uint version = BinaryPrimitives.ReadUInt32LittleEndian(new ReadOnlySpan<byte>(payload, index, 4));
            index += sizeof(uint);

            ProcessInfo processInfo = ParseCommon2(payload, ref index);

            if (version >= 1)
            {
                processInfo.PortableRuntimeIdentifier = IpcHelpers.ReadString(payload, ref index);
            }

            return processInfo;
        }

        private static ProcessInfo ParseCommon(byte[] payload, ref int index)
        {
            ProcessInfo processInfo = new();

            processInfo.ProcessId = BinaryPrimitives.ReadUInt64LittleEndian(new ReadOnlySpan<byte>(payload, index, 8));
            index += sizeof(ulong);

            byte[] cookieBuffer = new byte[GuidSizeInBytes];
            Array.Copy(payload, index, cookieBuffer, 0, GuidSizeInBytes);
            processInfo.RuntimeInstanceCookie = new Guid(cookieBuffer);
            index += GuidSizeInBytes;

            processInfo.CommandLine = IpcHelpers.ReadString(payload, ref index);
            processInfo.OperatingSystem = IpcHelpers.ReadString(payload, ref index);
            processInfo.ProcessArchitecture = IpcHelpers.ReadString(payload, ref index);

            return processInfo;
        }

        internal bool TryGetProcessClrVersion(out Version version, out bool isPrerelease)
        {
            version = null;
            isPrerelease = true;
            if (string.IsNullOrEmpty(ClrProductVersionString))
            {
                return false;
            }

            // The version is of the SemVer2 form: <major>.<minor>.<patch>[-<prerelease>][+<metadata>]
            // Remove the prerelease and metadata version information before parsing.

            ReadOnlySpan<char> versionSpan = ClrProductVersionString.AsSpan();
            int metadataIndex = versionSpan.IndexOf('+');
            if (-1 == metadataIndex)
            {
                metadataIndex = versionSpan.Length;
            }

            ReadOnlySpan<char> noMetadataVersion = versionSpan.Slice(0, metadataIndex);
            int prereleaseIndex = noMetadataVersion.IndexOf('-');
            if (-1 == prereleaseIndex)
            {
                isPrerelease = false;
                prereleaseIndex = metadataIndex;
            }

            return Version.TryParse(noMetadataVersion.Slice(0, prereleaseIndex).ToString(), out version);
        }

        private static ProcessInfo ParseCommon2(byte[] payload, ref int index)
        {
            ProcessInfo processInfo = ParseCommon(payload, ref index);

            processInfo.ManagedEntrypointAssemblyName = IpcHelpers.ReadString(payload, ref index);
            processInfo.ClrProductVersionString = IpcHelpers.ReadString(payload, ref index);

            return processInfo;
        }

        public ulong ProcessId { get; private set; }
        public Guid RuntimeInstanceCookie { get; private set; }
        public string CommandLine { get; private set; }
        public string OperatingSystem { get; private set; }
        public string ProcessArchitecture { get; private set; }
        public string ManagedEntrypointAssemblyName { get; private set; }
        public string ClrProductVersionString { get; private set; }
        public string PortableRuntimeIdentifier { get; private set; }
    }
}