< Summary

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

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Serialize(...)100%11100%
WriteValue(...)100%2828100%
WriteDictionary(...)100%22100%
WriteStringDictionary(...)100%22100%

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{
 12    private CanonicalPayload(
 13        string artifactType,
 14        string artifactId,
 15        string payloadSchemaVersion,
 16        string canonicalizationVersion,
 17        string canonicalJson)
 18    {
 19        ArtifactType = artifactType;
 20        ArtifactId = artifactId;
 21        PayloadSchemaVersion = payloadSchemaVersion;
 22        CanonicalizationVersion = canonicalizationVersion;
 23        CanonicalJson = canonicalJson;
 24    }
 25
 26    /// <summary>
 27    /// Gets the stable artifact type bound into the canonical payload.
 28    /// </summary>
 29    public string ArtifactType { get; }
 30
 31    /// <summary>
 32    /// Gets the stable artifact identifier bound into the canonical payload.
 33    /// </summary>
 34    public string ArtifactId { get; }
 35
 36    /// <summary>
 37    /// Gets the payload schema version bound into the canonical payload.
 38    /// </summary>
 39    public string PayloadSchemaVersion { get; }
 40
 41    /// <summary>
 42    /// Gets the canonicalization version used to create the payload bytes.
 43    /// </summary>
 44    public string CanonicalizationVersion { get; }
 45
 46    /// <summary>
 47    /// Gets the deterministic JSON payload to hash or sign.
 48    /// </summary>
 49    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    {
 61        ArgumentException.ThrowIfNullOrWhiteSpace(artifactType);
 62        ArgumentException.ThrowIfNullOrWhiteSpace(artifactId);
 63        ArgumentException.ThrowIfNullOrWhiteSpace(payloadSchemaVersion);
 64        ArgumentException.ThrowIfNullOrWhiteSpace(canonicalizationVersion);
 65        ArgumentNullException.ThrowIfNull(content);
 66
 67        string normalizedArtifactType = artifactType.Trim();
 68        string normalizedArtifactId = artifactId.Trim();
 69        string normalizedPayloadSchemaVersion = payloadSchemaVersion.Trim();
 70        string normalizedCanonicalizationVersion = canonicalizationVersion.Trim();
 71
 72        SortedDictionary<string, object?> envelope = new(StringComparer.Ordinal)
 73        {
 74            ["artifactId"] = normalizedArtifactId,
 75            ["artifactType"] = normalizedArtifactType,
 76            ["canonicalizationVersion"] = normalizedCanonicalizationVersion,
 77            ["content"] = content,
 78            ["payloadSchemaVersion"] = normalizedPayloadSchemaVersion
 79        };
 80
 81        return new CanonicalPayload(
 82            normalizedArtifactType,
 83            normalizedArtifactId,
 84            normalizedPayloadSchemaVersion,
 85            normalizedCanonicalizationVersion,
 86            CanonicalPayloadJson.Serialize(envelope));
 87    }
 88
 89    /// <summary>
 90    /// Gets the canonical JSON payload as UTF-8 bytes.
 91    /// </summary>
 92    public byte[] ToUtf8Bytes()
 93    {
 94        return Encoding.UTF8.GetBytes(CanonicalJson);
 95    }
 96}
 97
 98internal static class CanonicalPayloadJson
 99{
 2100    private static readonly JsonWriterOptions WriterOptions = new()
 2101    {
 2102        Indented = false,
 2103        SkipValidation = false
 2104    };
 105
 106    public static string Serialize(IReadOnlyDictionary<string, object?> value)
 107    {
 145108        ArrayBufferWriter<byte> buffer = new();
 145109        using (Utf8JsonWriter writer = new(buffer, WriterOptions))
 110        {
 145111            WriteDictionary(writer, value);
 141112        }
 113
 141114        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:
 245122                writer.WriteNullValue();
 245123                break;
 124            case string stringValue:
 1580125                writer.WriteStringValue(stringValue);
 1580126                break;
 127            case bool boolValue:
 4128                writer.WriteBooleanValue(boolValue);
 4129                break;
 130            case int intValue:
 32131                writer.WriteNumberValue(intValue);
 32132                break;
 133            case long longValue:
 58134                writer.WriteNumberValue(longValue);
 58135                break;
 136            case double doubleValue:
 13137                if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue))
 138                {
 3139                    throw new ArgumentOutOfRangeException(nameof(value), value, "Canonical payload numbers must be finit
 140                }
 141
 10142                writer.WriteNumberValue(doubleValue);
 10143                break;
 144            case IReadOnlyDictionary<string, object?> dictionaryValue:
 211145                WriteDictionary(writer, dictionaryValue);
 207146                break;
 147            case IReadOnlyDictionary<string, string> stringDictionaryValue:
 1148                WriteStringDictionary(writer, stringDictionaryValue);
 1149                break;
 150            case IEnumerable<string> stringValues:
 47151                writer.WriteStartArray();
 206152                foreach (string item in stringValues)
 153                {
 56154                    writer.WriteStringValue(item);
 155                }
 156
 47157                writer.WriteEndArray();
 47158                break;
 159            case IEnumerable<object?> objectValues:
 1160                writer.WriteStartArray();
 10161                foreach (object? item in objectValues)
 162                {
 4163                    WriteValue(writer, item);
 164                }
 165
 1166                writer.WriteEndArray();
 1167                break;
 168            default:
 1169                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    {
 356175        writer.WriteStartObject();
 176
 7275177        foreach (KeyValuePair<string, object?> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 178        {
 2189179            writer.WritePropertyName(item.Key);
 2189180            WriteValue(writer, item.Value);
 181        }
 182
 348183        writer.WriteEndObject();
 348184    }
 185
 186    private static void WriteStringDictionary(Utf8JsonWriter writer, IReadOnlyDictionary<string, string> dictionary)
 187    {
 1188        writer.WriteStartObject();
 189
 8190        foreach (KeyValuePair<string, string> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 191        {
 2192            writer.WriteString(item.Key, item.Value);
 193        }
 194
 1195        writer.WriteEndObject();
 1196    }
 197}