< Summary

Information
Class: AsiBackbone.Core.Signing.CanonicalPayload
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/CanonicalPayload.cs
Line coverage
100%
Covered lines: 41
Uncovered lines: 0
Coverable lines: 41
Total lines: 197
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_ArtifactType()100%11100%
get_ArtifactId()100%11100%
get_PayloadSchemaVersion()100%11100%
get_CanonicalizationVersion()100%11100%
get_CanonicalJson()100%11100%
Create(...)100%11100%
ToUtf8Bytes()100%11100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/CanonicalPayload.cs

#LineLine coverage
 1using System.Buffers;
 2using System.Text;
 3using System.Text.Json;
 4
 5namespace AsiBackbone.Core.Signing;
 6
 7/// <summary>
 8/// Represents a deterministic, provider-neutral payload that can be hashed and later signed by a host or provider packa
 9/// </summary>
 10public sealed class CanonicalPayload
 11{
 14112    private CanonicalPayload(
 14113        string artifactType,
 14114        string artifactId,
 14115        string payloadSchemaVersion,
 14116        string canonicalizationVersion,
 14117        string canonicalJson)
 18    {
 14119        ArtifactType = artifactType;
 14120        ArtifactId = artifactId;
 14121        PayloadSchemaVersion = payloadSchemaVersion;
 14122        CanonicalizationVersion = canonicalizationVersion;
 14123        CanonicalJson = canonicalJson;
 14124    }
 25
 26    /// <summary>
 27    /// Gets the stable artifact type bound into the canonical payload.
 28    /// </summary>
 20129    public string ArtifactType { get; }
 30
 31    /// <summary>
 32    /// Gets the stable artifact identifier bound into the canonical payload.
 33    /// </summary>
 20134    public string ArtifactId { get; }
 35
 36    /// <summary>
 37    /// Gets the payload schema version bound into the canonical payload.
 38    /// </summary>
 19139    public string PayloadSchemaVersion { get; }
 40
 41    /// <summary>
 42    /// Gets the canonicalization version used to create the payload bytes.
 43    /// </summary>
 19144    public string CanonicalizationVersion { get; }
 45
 46    /// <summary>
 47    /// Gets the deterministic JSON payload to hash or sign.
 48    /// </summary>
 17049    public string CanonicalJson { get; }
 50
 51    /// <summary>
 52    /// Creates a canonical payload envelope around deterministic artifact content.
 53    /// </summary>
 54    public static CanonicalPayload Create(
 55        string artifactType,
 56        string artifactId,
 57        string payloadSchemaVersion,
 58        string canonicalizationVersion,
 59        IReadOnlyDictionary<string, object?> content)
 60    {
 14561        ArgumentException.ThrowIfNullOrWhiteSpace(artifactType);
 14562        ArgumentException.ThrowIfNullOrWhiteSpace(artifactId);
 14563        ArgumentException.ThrowIfNullOrWhiteSpace(payloadSchemaVersion);
 14564        ArgumentException.ThrowIfNullOrWhiteSpace(canonicalizationVersion);
 14565        ArgumentNullException.ThrowIfNull(content);
 66
 14567        string normalizedArtifactType = artifactType.Trim();
 14568        string normalizedArtifactId = artifactId.Trim();
 14569        string normalizedPayloadSchemaVersion = payloadSchemaVersion.Trim();
 14570        string normalizedCanonicalizationVersion = canonicalizationVersion.Trim();
 71
 14572        SortedDictionary<string, object?> envelope = new(StringComparer.Ordinal)
 14573        {
 14574            ["artifactId"] = normalizedArtifactId,
 14575            ["artifactType"] = normalizedArtifactType,
 14576            ["canonicalizationVersion"] = normalizedCanonicalizationVersion,
 14577            ["content"] = content,
 14578            ["payloadSchemaVersion"] = normalizedPayloadSchemaVersion
 14579        };
 80
 14581        return new CanonicalPayload(
 14582            normalizedArtifactType,
 14583            normalizedArtifactId,
 14584            normalizedPayloadSchemaVersion,
 14585            normalizedCanonicalizationVersion,
 14586            CanonicalPayloadJson.Serialize(envelope));
 87    }
 88
 89    /// <summary>
 90    /// Gets the canonical JSON payload as UTF-8 bytes.
 91    /// </summary>
 92    public byte[] ToUtf8Bytes()
 93    {
 12994        return Encoding.UTF8.GetBytes(CanonicalJson);
 95    }
 96}
 97
 98internal static class CanonicalPayloadJson
 99{
 100    private static readonly JsonWriterOptions WriterOptions = new()
 101    {
 102        Indented = false,
 103        SkipValidation = false
 104    };
 105
 106    public static string Serialize(IReadOnlyDictionary<string, object?> value)
 107    {
 108        ArrayBufferWriter<byte> buffer = new();
 109        using (Utf8JsonWriter writer = new(buffer, WriterOptions))
 110        {
 111            WriteDictionary(writer, value);
 112        }
 113
 114        return Encoding.UTF8.GetString(buffer.WrittenSpan);
 115    }
 116
 117    private static void WriteValue(Utf8JsonWriter writer, object? value)
 118    {
 119        switch (value)
 120        {
 121            case null:
 122                writer.WriteNullValue();
 123                break;
 124            case string stringValue:
 125                writer.WriteStringValue(stringValue);
 126                break;
 127            case bool boolValue:
 128                writer.WriteBooleanValue(boolValue);
 129                break;
 130            case int intValue:
 131                writer.WriteNumberValue(intValue);
 132                break;
 133            case long longValue:
 134                writer.WriteNumberValue(longValue);
 135                break;
 136            case double doubleValue:
 137                if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue))
 138                {
 139                    throw new ArgumentOutOfRangeException(nameof(value), value, "Canonical payload numbers must be finit
 140                }
 141
 142                writer.WriteNumberValue(doubleValue);
 143                break;
 144            case IReadOnlyDictionary<string, object?> dictionaryValue:
 145                WriteDictionary(writer, dictionaryValue);
 146                break;
 147            case IReadOnlyDictionary<string, string> stringDictionaryValue:
 148                WriteStringDictionary(writer, stringDictionaryValue);
 149                break;
 150            case IEnumerable<string> stringValues:
 151                writer.WriteStartArray();
 152                foreach (string item in stringValues)
 153                {
 154                    writer.WriteStringValue(item);
 155                }
 156
 157                writer.WriteEndArray();
 158                break;
 159            case IEnumerable<object?> objectValues:
 160                writer.WriteStartArray();
 161                foreach (object? item in objectValues)
 162                {
 163                    WriteValue(writer, item);
 164                }
 165
 166                writer.WriteEndArray();
 167                break;
 168            default:
 169                throw new NotSupportedException($"Canonical payload value type '{value.GetType().FullName}' is not suppo
 170        }
 171    }
 172
 173    private static void WriteDictionary(Utf8JsonWriter writer, IReadOnlyDictionary<string, object?> dictionary)
 174    {
 175        writer.WriteStartObject();
 176
 177        foreach (KeyValuePair<string, object?> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 178        {
 179            writer.WritePropertyName(item.Key);
 180            WriteValue(writer, item.Value);
 181        }
 182
 183        writer.WriteEndObject();
 184    }
 185
 186    private static void WriteStringDictionary(Utf8JsonWriter writer, IReadOnlyDictionary<string, string> dictionary)
 187    {
 188        writer.WriteStartObject();
 189
 190        foreach (KeyValuePair<string, string> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 191        {
 192            writer.WriteString(item.Key, item.Value);
 193        }
 194
 195        writer.WriteEndObject();
 196    }
 197}