Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling. - Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options. - Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation. - Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios. - Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling. - Included tests for UdpTransportOptions to verify default values and modification capabilities. - Enhanced service registration tests for Udp transport services in the dependency injection container.
287 lines
12 KiB
C#
287 lines
12 KiB
C#
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace StellaOps.Scanner.Reachability;
|
|
|
|
/// <summary>
|
|
/// Builds canonical SymbolIDs per the reachability union schema (v0.1).
|
|
/// SymbolIDs are stable, path-independent identifiers that enable CAS lookups
|
|
/// to remain reproducible and cacheable across hosts.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Format: <c>sym:{lang}:{stable-fragment}</c>
|
|
/// where stable-fragment is SHA-256(base64url-no-pad) of the canonical tuple per language.
|
|
/// </para>
|
|
/// <para>
|
|
/// <strong>INTEROP NOTE:</strong> This static class uses SHA-256 for maximum external tool
|
|
/// compatibility. For compliance-profile-aware symbol IDs that respect GOST/SM3/FIPS profiles,
|
|
/// use <see cref="SymbolIdBuilder"/> with an injected <see cref="StellaOps.Cryptography.ICryptoHash"/>.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class SymbolId
|
|
{
|
|
/// <summary>
|
|
/// Supported languages for symbol IDs.
|
|
/// </summary>
|
|
public static class Lang
|
|
{
|
|
public const string Java = "java";
|
|
public const string DotNet = "dotnet";
|
|
public const string Go = "go";
|
|
public const string Node = "node";
|
|
public const string Deno = "deno";
|
|
public const string Rust = "rust";
|
|
public const string Swift = "swift";
|
|
public const string Shell = "shell";
|
|
public const string Binary = "binary";
|
|
public const string Python = "python";
|
|
public const string Ruby = "ruby";
|
|
public const string Php = "php";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Java symbol ID from method signature components.
|
|
/// </summary>
|
|
/// <param name="package">Package name (e.g., "com.example").</param>
|
|
/// <param name="className">Class name (e.g., "MyClass").</param>
|
|
/// <param name="method">Method name (e.g., "doSomething").</param>
|
|
/// <param name="descriptor">JVM method descriptor (e.g., "(Ljava/lang/String;)V").</param>
|
|
public static string ForJava(string package, string className, string method, string descriptor)
|
|
{
|
|
var tuple = $"{Lower(package)}\0{Lower(className)}\0{Lower(method)}\0{Lower(descriptor)}";
|
|
return Build(Lang.Java, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a .NET symbol ID from member signature components.
|
|
/// </summary>
|
|
/// <param name="assemblyName">Assembly name (without version/key).</param>
|
|
/// <param name="ns">Namespace.</param>
|
|
/// <param name="typeName">Type name.</param>
|
|
/// <param name="memberSignature">Member signature using ECMA-335 format.</param>
|
|
public static string ForDotNet(string assemblyName, string ns, string typeName, string memberSignature)
|
|
{
|
|
var tuple = $"{Norm(assemblyName)}\0{Norm(ns)}\0{Norm(typeName)}\0{Norm(memberSignature)}";
|
|
return Build(Lang.DotNet, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Node/Deno symbol ID from module export components.
|
|
/// </summary>
|
|
/// <param name="pkgNameOrPath">npm package name or normalized absolute path (drive stripped).</param>
|
|
/// <param name="exportPath">ESM/CJS export path (slash-joined).</param>
|
|
/// <param name="kind">Export kind (e.g., "function", "class", "default").</param>
|
|
public static string ForNode(string pkgNameOrPath, string exportPath, string kind)
|
|
{
|
|
var tuple = $"{Norm(pkgNameOrPath)}\0{Norm(exportPath)}\0{Norm(kind)}";
|
|
return Build(Lang.Node, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Deno symbol ID from module export components.
|
|
/// </summary>
|
|
public static string ForDeno(string pkgNameOrPath, string exportPath, string kind)
|
|
{
|
|
var tuple = $"{Norm(pkgNameOrPath)}\0{Norm(exportPath)}\0{Norm(kind)}";
|
|
return Build(Lang.Deno, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Go symbol ID from function/method components.
|
|
/// </summary>
|
|
/// <param name="modulePath">Module path (e.g., "github.com/example/repo").</param>
|
|
/// <param name="packagePath">Package path within module.</param>
|
|
/// <param name="receiver">Receiver type (empty for functions).</param>
|
|
/// <param name="func">Function name.</param>
|
|
public static string ForGo(string modulePath, string packagePath, string receiver, string func)
|
|
{
|
|
var tuple = $"{Norm(modulePath)}\0{Norm(packagePath)}\0{Norm(receiver)}\0{Norm(func)}";
|
|
return Build(Lang.Go, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Rust symbol ID from item components.
|
|
/// </summary>
|
|
/// <param name="crateName">Crate name.</param>
|
|
/// <param name="modulePath">Module path within crate (e.g., "foo::bar").</param>
|
|
/// <param name="itemName">Item name (function, struct, trait, etc.).</param>
|
|
/// <param name="mangled">Optional Rust-mangled name.</param>
|
|
public static string ForRust(string crateName, string modulePath, string itemName, string? mangled = null)
|
|
{
|
|
var tuple = $"{Norm(crateName)}\0{Norm(modulePath)}\0{Norm(itemName)}\0{Norm(mangled)}";
|
|
return Build(Lang.Rust, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Swift symbol ID from member components.
|
|
/// </summary>
|
|
/// <param name="module">Swift module name.</param>
|
|
/// <param name="typeName">Type name (class, struct, enum, protocol).</param>
|
|
/// <param name="member">Member name.</param>
|
|
/// <param name="mangled">Optional Swift-mangled name.</param>
|
|
public static string ForSwift(string module, string typeName, string member, string? mangled = null)
|
|
{
|
|
var tuple = $"{Norm(module)}\0{Norm(typeName)}\0{Norm(member)}\0{Norm(mangled)}";
|
|
return Build(Lang.Swift, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a shell symbol ID from script/function components.
|
|
/// </summary>
|
|
/// <param name="scriptRelPath">Relative path to script file.</param>
|
|
/// <param name="functionOrCmd">Function name or command identifier.</param>
|
|
public static string ForShell(string scriptRelPath, string functionOrCmd)
|
|
{
|
|
var tuple = $"{Norm(scriptRelPath)}\0{Norm(functionOrCmd)}";
|
|
return Build(Lang.Shell, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a binary symbol ID from ELF/PE/Mach-O components (legacy overload).
|
|
/// </summary>
|
|
/// <param name="buildId">Binary build-id (GNU build-id, PE GUID, Mach-O UUID).</param>
|
|
/// <param name="section">Section name (e.g., ".text", ".dynsym").</param>
|
|
/// <param name="symbolName">Symbol name from symbol table.</param>
|
|
public static string ForBinary(string buildId, string section, string symbolName)
|
|
=> ForBinaryAddressed(buildId, section, string.Empty, symbolName, "static", null);
|
|
|
|
/// <summary>
|
|
/// Creates a binary symbol ID that includes file hash, section, address, and linkage.
|
|
/// Aligns with {file:hash, section, addr, name, linkage} tuple used by richgraph-v1.
|
|
/// </summary>
|
|
public static string ForBinaryAddressed(string fileHash, string section, string address, string symbolName, string linkage, string? codeBlockHash = null)
|
|
{
|
|
var tuple = $"{Norm(fileHash)}\0{Norm(section)}\0{NormalizeAddress(address)}\0{Norm(symbolName)}\0{Norm(linkage)}\0{Norm(codeBlockHash)}";
|
|
return Build(Lang.Binary, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Python symbol ID from module/function components.
|
|
/// </summary>
|
|
/// <param name="packageOrPath">Package name or module file path.</param>
|
|
/// <param name="modulePath">Module path within package (dot-separated).</param>
|
|
/// <param name="qualifiedName">Qualified name (class.method or function).</param>
|
|
public static string ForPython(string packageOrPath, string modulePath, string qualifiedName)
|
|
{
|
|
var tuple = $"{Norm(packageOrPath)}\0{Norm(modulePath)}\0{Norm(qualifiedName)}";
|
|
return Build(Lang.Python, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Ruby symbol ID from module/method components.
|
|
/// </summary>
|
|
/// <param name="gemOrPath">Gem name or file path.</param>
|
|
/// <param name="modulePath">Module/class path (e.g., "Foo::Bar").</param>
|
|
/// <param name="methodName">Method name (with prefix # for instance, . for class).</param>
|
|
public static string ForRuby(string gemOrPath, string modulePath, string methodName)
|
|
{
|
|
var tuple = $"{Norm(gemOrPath)}\0{Norm(modulePath)}\0{Norm(methodName)}";
|
|
return Build(Lang.Ruby, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a PHP symbol ID from namespace/function components.
|
|
/// </summary>
|
|
/// <param name="composerPackage">Composer package name or file path.</param>
|
|
/// <param name="ns">Namespace (e.g., "App\\Services").</param>
|
|
/// <param name="qualifiedName">Fully qualified class::method or function name.</param>
|
|
public static string ForPhp(string composerPackage, string ns, string qualifiedName)
|
|
{
|
|
var tuple = $"{Norm(composerPackage)}\0{Norm(ns)}\0{Norm(qualifiedName)}";
|
|
return Build(Lang.Php, tuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a symbol ID from a pre-computed canonical tuple and language.
|
|
/// </summary>
|
|
/// <param name="lang">Language identifier (use <see cref="Lang"/> constants).</param>
|
|
/// <param name="canonicalTuple">Pre-formatted canonical tuple (NUL-separated components).</param>
|
|
public static string FromTuple(string lang, string canonicalTuple)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(lang);
|
|
return Build(lang, canonicalTuple);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a symbol ID into its language and fragment components.
|
|
/// </summary>
|
|
/// <returns>Tuple of (language, fragment) or null if invalid format.</returns>
|
|
public static (string Lang, string Fragment)? Parse(string symbolId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(symbolId) || !symbolId.StartsWith("sym:", StringComparison.Ordinal))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var rest = symbolId.AsSpan(4); // Skip "sym:"
|
|
var colonIndex = rest.IndexOf(':');
|
|
if (colonIndex < 1)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var lang = rest[..colonIndex].ToString();
|
|
var fragment = rest[(colonIndex + 1)..].ToString();
|
|
return (lang, fragment);
|
|
}
|
|
|
|
private static string Build(string lang, string tuple)
|
|
{
|
|
var hash = ComputeFragment(tuple);
|
|
return $"sym:{lang}:{hash}";
|
|
}
|
|
|
|
private static string NormalizeAddress(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return "0x0";
|
|
}
|
|
|
|
var addrText = value.Trim();
|
|
var isHex = addrText.StartsWith("0x", StringComparison.OrdinalIgnoreCase);
|
|
if (isHex)
|
|
{
|
|
addrText = addrText[2..];
|
|
}
|
|
|
|
if (long.TryParse(addrText, isHex ? System.Globalization.NumberStyles.HexNumber : System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var addrValue))
|
|
{
|
|
if (addrValue < 0)
|
|
{
|
|
addrValue = 0;
|
|
}
|
|
|
|
return $"0x{addrValue:x}";
|
|
}
|
|
|
|
// Fallback to normalized string representation
|
|
addrText = addrText.TrimStart('0');
|
|
if (addrText.Length == 0)
|
|
{
|
|
addrText = "0";
|
|
}
|
|
|
|
return $"0x{addrText.ToLowerInvariant()}";
|
|
}
|
|
|
|
private static string ComputeFragment(string tuple)
|
|
{
|
|
var bytes = Encoding.UTF8.GetBytes(tuple);
|
|
var hash = SHA256.HashData(bytes);
|
|
// Base64url without padding per spec
|
|
return Convert.ToBase64String(hash)
|
|
.Replace('+', '-')
|
|
.Replace('/', '_')
|
|
.TrimEnd('=');
|
|
}
|
|
|
|
private static string Lower(string? value)
|
|
=> string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim().ToLowerInvariant();
|
|
|
|
private static string Norm(string? value)
|
|
=> string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
|
|
}
|