using System; using System.Collections.Immutable; using System.IO; using System.Linq; using Collections.Special; using StellaOps.Scanner.Core.Contracts; using StellaOps.Scanner.Emit.Index; namespace StellaOps.Scanner.Emit.Tests.Index; public sealed class BomIndexBuilderTests { [Fact] public void Build_GeneratesDeterministicBinaryIndex_WithUsageBitmaps() { var graph = ComponentGraphBuilder.Build(new[] { LayerComponentFragment.Create("sha256:layer1", new[] { CreateComponent("pkg:npm/a", "1.0.0", "sha256:layer1", usageEntrypoints: new[] { "/app/start.sh" }), CreateComponent("pkg:npm/b", "2.0.0", "sha256:layer1"), }), LayerComponentFragment.Create("sha256:layer2", new[] { CreateComponent("pkg:npm/b", "2.0.0", "sha256:layer2"), CreateComponent("pkg:npm/c", "3.1.0", "sha256:layer2", usageEntrypoints: new[] { "/app/init.sh" }), }), }); var request = new BomIndexBuildRequest { ImageDigest = "sha256:image", Graph = graph, GeneratedAt = new DateTimeOffset(2025, 10, 19, 9, 45, 0, TimeSpan.Zero), }; var builder = new BomIndexBuilder(); var artifact = builder.Build(request); var second = builder.Build(request); Assert.Equal(artifact.Sha256, second.Sha256); Assert.Equal(artifact.Bytes, second.Bytes); Assert.Equal(2, artifact.LayerCount); Assert.Equal(3, artifact.ComponentCount); Assert.Equal(2, artifact.EntrypointCount); using var reader = new BinaryReader(new MemoryStream(artifact.Bytes), System.Text.Encoding.UTF8, leaveOpen: false); ValidateHeader(reader, request); var layers = ReadTable(reader, artifact.LayerCount); Assert.Equal(new[] { "sha256:layer1", "sha256:layer2" }, layers); var purls = ReadTable(reader, artifact.ComponentCount); Assert.Equal(new[] { "pkg:npm/a", "pkg:npm/b", "pkg:npm/c" }, purls); var componentBitmaps = ReadBitmaps(reader, artifact.ComponentCount); Assert.Equal(new[] { new[] { 0 }, new[] { 0, 1 }, new[] { 1 } }, componentBitmaps); var entrypoints = ReadTable(reader, artifact.EntrypointCount); Assert.Equal(new[] { "/app/init.sh", "/app/start.sh" }, entrypoints); var usageBitmaps = ReadBitmaps(reader, artifact.ComponentCount); Assert.Equal(new[] { new[] { 1 }, Array.Empty(), new[] { 0 } }, usageBitmaps); } private static void ValidateHeader(BinaryReader reader, BomIndexBuildRequest request) { var magic = reader.ReadBytes(7); Assert.Equal("BOMIDX1", System.Text.Encoding.ASCII.GetString(magic)); var version = reader.ReadUInt16(); Assert.Equal(1u, version); var flags = reader.ReadUInt16(); Assert.Equal(0x1, flags); var digestLength = reader.ReadUInt16(); var digestBytes = reader.ReadBytes(digestLength); Assert.Equal(request.ImageDigest, System.Text.Encoding.UTF8.GetString(digestBytes)); var unixMicroseconds = reader.ReadInt64(); var expectedMicroseconds = request.GeneratedAt.ToUniversalTime().ToUnixTimeMilliseconds() * 1000L; expectedMicroseconds += request.GeneratedAt.ToUniversalTime().Ticks % TimeSpan.TicksPerMillisecond / 10; Assert.Equal(expectedMicroseconds, unixMicroseconds); var layers = reader.ReadUInt32(); var components = reader.ReadUInt32(); var entrypoints = reader.ReadUInt32(); Assert.Equal(2u, layers); Assert.Equal(3u, components); Assert.Equal(2u, entrypoints); } private static string[] ReadTable(BinaryReader reader, int count) { var values = new string[count]; for (var i = 0; i < count; i++) { var length = reader.ReadUInt16(); var bytes = reader.ReadBytes(length); values[i] = System.Text.Encoding.UTF8.GetString(bytes); } return values; } private static int[][] ReadBitmaps(BinaryReader reader, int count) { var result = new int[count][]; for (var i = 0; i < count; i++) { var length = reader.ReadUInt32(); if (length == 0) { result[i] = Array.Empty(); continue; } var bytes = reader.ReadBytes((int)length); using var ms = new MemoryStream(bytes, writable: false); var bitmap = RoaringBitmap.Deserialize(ms); result[i] = bitmap.ToArray(); } return result; } private static ComponentRecord CreateComponent(string key, string version, string layerDigest, string[]? usageEntrypoints = null) { var usage = usageEntrypoints is null ? ComponentUsage.Unused : ComponentUsage.Create(true, usageEntrypoints); return new ComponentRecord { Identity = ComponentIdentity.Create(key, key.Split('/', 2)[^1], version, key, "library"), LayerDigest = layerDigest, Usage = usage, }; } }