File: BackEnd\INodePacket.cs
Web Access
Project: ..\..\..\src\MSBuildTaskHost\MSBuildTaskHost.csproj (MSBuildTaskHost)
// 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.IO;
 
namespace Microsoft.Build.TaskHost.BackEnd;
 
/// <summary>
/// Enumeration of all of the packet types used for communication.
/// Uses lower 6 bits for packet type (0-63), upper 2 bits reserved for flags.
/// </summary>
/// <remarks>
/// This is a reduced set of packet types used by MSBuildTaskHost.
/// It is derived from the full set of packet types used for MSBuild's internal node communication.
/// </remarks>
internal enum NodePacketType : byte
{
    // Mask for extracting packet type (lower 6 bits)
    TypeMask = 0x3F, // 00111111
 
    /// <summary>
    /// A logging message.
    ///
    /// Contents:
    /// Build Event Type
    /// Build Event Args
    /// </summary>
    LogMessage = 0x08,
 
    /// <summary>
    /// Informs the node that the build is complete.
    ///
    /// Contents:
    /// Prepare For Reuse
    /// </summary>
    NodeBuildComplete = 0x09,
 
    /// <summary>
    /// Reported by the node (or node provider) when a node has terminated.  This is the final packet that will be received
    /// from a node.
    ///
    /// Contents:
    /// Reason
    /// </summary>
    NodeShutdown = 0x0A,
 
    /// <summary>
    /// Notifies the task host to set the task-specific configuration for a particular task execution.
    /// This is sent in place of NodeConfiguration and gives the task host all the information it needs
    /// to set itself up and execute the task that matches this particular configuration.
    ///
    /// Contains:
    /// Node ID (of parent MSBuild node, to make the logging work out)
    /// Startup directory
    /// Environment variables
    /// UI Culture information
    /// App Domain Configuration XML
    /// Task name
    /// Task assembly location
    /// Parameter names and values to set to the task prior to execution
    /// </summary>
    TaskHostConfiguration = 0x0B,
 
    /// <summary>
    /// Informs the parent node that the task host has finished executing a
    /// particular task.  Does not need to contain identifying information
    /// about the task, because the task host will only ever be connected to
    /// one parent node at a a time, and will only ever be executing one task
    /// for that node at any one time.
    ///
    /// Contents:
    /// Task result (success / failure)
    /// Resultant parameter values (for output gathering)
    /// </summary>
    TaskHostTaskComplete = 0x0C,
 
    /// <summary>
    /// Message sent from the node to its paired task host when a task that
    /// supports ICancellableTask is cancelled.
    ///
    /// Contents:
    /// (nothing)
    /// </summary>
    TaskHostTaskCancelled = 0x0D,
}
 
/// <summary>
/// This interface represents a packet which may be transmitted using an INodeEndpoint.
/// Implementations define the serialized form of the data.
/// </summary>
internal interface INodePacket : ITranslatable
{
    /// <summary>
    /// Gets the type of the packet.  Used to reconstitute the packet using the correct factory.
    /// </summary>
    NodePacketType Type { get; }
}
 
/// <summary>
/// Provides utilities for handling node packet types and extended headers in MSBuild's distributed build system.
/// 
/// This class manages the communication protocol between build nodes, including:
/// - Packet versioning for protocol compatibility
/// - Extended header flags for enhanced packet metadata
/// - Type extraction and manipulation for network communication
/// 
/// The packet format uses the upper 2 bits (6-7) for flags while preserving
/// the lower 6 bits for the actual packet type enumeration.
/// </summary>
internal static class NodePacketTypeExtensions
{
    /// <summary>
    /// Defines the communication protocol version for node communication.
    /// 
    /// Version 1: Introduced for the .NET Task Host protocol. This version
    /// excludes the translation of appDomainConfig within TaskHostConfiguration
    /// to maintain backward compatibility and reduce serialization overhead.
    ///
    /// Version 2: Adds support of HostServices and target name translation in TaskHostConfiguration.
    /// 
    /// When incrementing this version, ensure compatibility with existing
    /// task hosts and update the corresponding deserialization logic.
    /// </summary>
    public const byte PacketVersion = 2;
 
    // Flag bits in upper 2 bits
    private const byte ExtendedHeaderFlag = 0x40;  // Bit 6: 01000000
 
    /// <summary>
    /// Determines if a packet has an extended header by checking if the extended header flag is set.
    /// Uses bit 6 which is now safely separated from packet type values.
    /// </summary>
    /// <param name="rawType">The raw packet type byte.</param>
    /// <returns>True if the packet has an extended header; otherwise, false.</returns>
    public static bool HasExtendedHeader(byte rawType)
        => (rawType & ExtendedHeaderFlag) != 0;
 
    /// <summary>
    /// Get base packet type, stripping all flag bits (bits 6 and 7).
    /// </summary>
    /// <param name="rawType">The raw packet type byte with potential flags.</param>
    /// <returns>The clean packet type without flag bits.</returns>
    public static NodePacketType GetNodePacketType(byte rawType)
        => (NodePacketType)(rawType & (byte)NodePacketType.TypeMask);
 
    /// <summary>
    /// Reads the protocol version from an extended header in the stream.
    /// This method expects the stream to be positioned at the version byte.
    /// </summary>
    /// <param name="stream">The stream to read the version byte from.</param>
    /// <returns>The protocol version byte read from the stream.</returns>
    /// <exception cref="EndOfStreamException">Thrown when the stream ends unexpectedly while reading the version.</exception>
    public static byte ReadVersion(Stream stream)
    {
        int value = stream.ReadByte();
        if (value == -1)
        {
            throw new EndOfStreamException("Unexpected end of stream while reading version");
        }
 
        return (byte)value;
    }
 
    /// <summary>
    /// Negotiates the packet version to use for communication between nodes.
    /// Returns the lower of the two versions to ensure compatibility between
    /// nodes that may be running different versions of MSBuild.
    /// 
    /// This allows forward and backward compatibility when nodes with different
    /// packet versions communicate - they will use the lowest common version
    /// that both understand.
    /// </summary>
    /// <param name="otherPacketVersion">The packet version supported by the other node.</param>
    /// <returns>The negotiated protocol version that both nodes can use (the minimum of the two versions).</returns>
    public static byte GetNegotiatedPacketVersion(byte otherPacketVersion)
        => Math.Min(PacketVersion, otherPacketVersion);
}