| File: BackEnd\ServerNodeHandshake.cs | Web Access |
| Project: src\msbuild\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.Globalization; using System.Security.Cryptography; using System.Text; using Microsoft.Build.Framework; namespace Microsoft.Build.Internal; internal sealed class ServerNodeHandshake : Handshake { /// <summary> /// Caching computed hash. /// </summary> private string? _computedHash = null; /// <summary> /// Distinguishes one transient server from every other server, or <see langword="null"/> for the /// resident server. /// </summary> /// <remarks> /// This participates in <see cref="ComputeHash"/>, and therefore in the pipe and mutex names, but /// deliberately not in <see cref="RetrieveHandshakeComponents"/>: the handshake itself answers /// "are we compatible", while the hash answers "which server am I talking to". A transient server /// exists to serve exactly one build and is then torn down, so it must not be reachable by any /// other client - otherwise that client can order it to shut down mid-build, or find itself /// connected to one that is already exiting. /// </remarks> private readonly string? _instanceId; public override byte? ExpectedVersionInFirstByte => null; internal ServerNodeHandshake(HandshakeOptions nodeType, string? instanceId = null) : base(nodeType, includeSessionId: false, toolsDirectory: null) { _instanceId = instanceId; } public override HandshakeComponents RetrieveHandshakeComponents() => new( CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Options), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.Salt), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMajor), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionMinor), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionBuild), CommunicationsUtilities.AvoidEndOfHandshakeSignal(_handshakeComponents.FileVersionPrivate)); public override string GetKey() => $"{_handshakeComponents.Options} {_handshakeComponents.Salt} {_handshakeComponents.FileVersionMajor} {_handshakeComponents.FileVersionMinor} {_handshakeComponents.FileVersionBuild} {_handshakeComponents.FileVersionPrivate}" .ToString(CultureInfo.InvariantCulture); /// <summary> /// The handshake key plus the current user, and the instance id when this is a transient server. /// The pipe and mutex names derived from <see cref="ComputeHash"/> are machine-wide while the /// pipes are current-user-only, so without the user one account's server locks every other /// account on the machine out. /// </summary> public string GetKeyWithEndpointIdentity() => _instanceId is null ? $"{GetKey()} {EnvironmentUtilities.CurrentUserName}" : $"{GetKey()} {EnvironmentUtilities.CurrentUserName} {_instanceId}"; /// <summary> /// Computes Handshake stable hash string representing whole state of handshake. /// </summary> public string ComputeHash() => _computedHash ??= HashKey(GetKeyWithEndpointIdentity()); public static string HashKey(string key) { byte[] utf8 = Encoding.UTF8.GetBytes(key); #if NET Span<byte> bytes = stackalloc byte[SHA256.HashSizeInBytes]; SHA256.HashData(utf8, bytes); #else using var sha = SHA256.Create(); var bytes = sha.ComputeHash(utf8); #endif return Convert.ToBase64String(bytes) .Replace("/", "_") .Replace("=", string.Empty); } }