File: BackEnd\NodePacketTypeExtensions.cs
Web Access
Project: ..\..\..\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// 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;
using Microsoft.Build.Internal;
 
namespace Microsoft.Build.BackEnd;
 
/// <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.
    /// 
    /// null: CLR2 (NET35) task host. Version-dependent fields skipped (not compiled in NET35).
    /// 0: The constant value for Framework-to-Framework (CLR4) task host. Supports HostServices, TargetName, ProjectFile.
    /// 1: .NET task host support.
    /// 2: Added support for translating/reading HostServices, ProjectFile, TargetName in TaskHostConfiguration.
    /// 3: Added App Host support.
    /// 4: Added IsRunningMultipleNodes, Request/ReleaseCores, BuildProjectFile callbacks support for OOP TaskHost.
    /// 
    /// When incrementing this version, ensure compatibility with existing
    /// task hosts and update the corresponding deserialization logic.
    /// </summary>
    public const byte PacketVersion = 4;
 
    // 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, false otherwise</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>
    /// Create a packet type byte with extended header flag for net task host packets.
    /// </summary>
    /// <param name="handshakeOptions">Handshake options to check.</param>
    /// <param name="type">Base packet type.</param>
    /// <param name="extendedheader">Output byte with flag set if applicable.</param>
    /// <returns>True if extended header flag was set, false otherwise.</returns>
    public static bool TryCreateExtendedHeaderType(HandshakeOptions handshakeOptions, NodePacketType type, out byte extendedheader)
    {
        if (Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.TaskHost) && Handshake.IsHandshakeOptionEnabled(handshakeOptions, HandshakeOptions.NET))
        {
            extendedheader = (byte)((byte)type | ExtendedHeaderFlag);
            return true;
        }
 
        extendedheader = (byte)type;
        return false;
    }
 
    /// <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>
    /// Writes the protocol version byte to the extended header in the stream.
    /// This is typically called after writing a packet type with the extended header flag.
    /// </summary>
    /// <param name="stream">The stream to write the version byte to.</param>
    /// <param name="version">The protocol version to write to the stream.</param>
    public static void WriteVersion(Stream stream, byte version) => stream.WriteByte(version);
 
    /// <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);
}