< 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: 48
Uncovered lines: 0
Coverable lines: 48
Total lines: 227
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%
Create(...)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{
 49312    private CanonicalPayload(
 49313        string artifactType,
 49314        string artifactId,
 49315        string payloadSchemaVersion,
 49316        string canonicalizationVersion,
 49317        string hashAlgorithm,
 49318        string canonicalJson)
 19    {
 49320        ArtifactType = artifactType;
 49321        ArtifactId = artifactId;
 49322        PayloadSchemaVersion = payloadSchemaVersion;
 49323        CanonicalizationVersion = canonicalizationVersion;
 49324        HashAlgorithm = hashAlgorithm;
 49325        CanonicalJson = canonicalJson;
 49326    }
 27
 28    /// <summary>
 29    /// Gets the stable artifact type bound into the canonical payload.
 30    /// </summary>
 31    public string ArtifactType { get; }
 32
 33    /// <summary>
 34    /// Gets the stable artifact identifier bound into the canonical payload.
 35    /// </summary>
 36    public string ArtifactId { get; }
 37
 38    /// <summary>
 39    /// Gets the payload schema version bound into the canonical payload.
 40    /// </summary>
 41    public string PayloadSchemaVersion { get; }
 42
 43    /// <summary>
 44    /// Gets the canonicalization version used to create the payload bytes.
 45    /// </summary>
 46    public string CanonicalizationVersion { get; }
 47
 48    /// <summary>
 49    /// Gets the hash algorithm selected for the canonical payload bytes.
 50    /// </summary>
 51    public string HashAlgorithm { get; }
 52
 53    /// <summary>
 54    /// Gets the deterministic JSON payload to hash or sign.
 55    /// </summary>
 56    public string CanonicalJson { get; }
 57
 58    /// <summary>
 59    /// Creates a canonical payload envelope around deterministic artifact content.
 60    /// </summary>
 61    public static CanonicalPayload Create(
 62        string artifactType,
 63        string artifactId,
 64        string payloadSchemaVersion,
 65        string canonicalizationVersion,
 66        IReadOnlyDictionary<string, object?> content)
 67    {
 18868        return Create(
 18869            artifactType,
 18870            artifactId,
 18871            payloadSchemaVersion,
 18872            canonicalizationVersion,
 18873            CanonicalPayloadOptions.DefaultHashAlgorithm,
 18874            content);
 75    }
 76
 77    /// <summary>
 78    /// Creates a canonical payload envelope around deterministic artifact content with a selected hash algorithm.
 79    /// </summary>
 80    public static CanonicalPayload Create(
 81        string artifactType,
 82        string artifactId,
 83        string payloadSchemaVersion,
 84        string canonicalizationVersion,
 85        string hashAlgorithm,
 86        IReadOnlyDictionary<string, object?> content)
 87    {
 49788        ArgumentException.ThrowIfNullOrWhiteSpace(artifactType);
 49789        ArgumentException.ThrowIfNullOrWhiteSpace(artifactId);
 49790        ArgumentException.ThrowIfNullOrWhiteSpace(payloadSchemaVersion);
 49791        ArgumentException.ThrowIfNullOrWhiteSpace(canonicalizationVersion);
 49792        ArgumentException.ThrowIfNullOrWhiteSpace(hashAlgorithm);
 49793        ArgumentNullException.ThrowIfNull(content);
 94
 49795        string normalizedArtifactType = artifactType.Trim();
 49796        string normalizedArtifactId = artifactId.Trim();
 49797        string normalizedPayloadSchemaVersion = payloadSchemaVersion.Trim();
 49798        string normalizedCanonicalizationVersion = canonicalizationVersion.Trim();
 49799        string normalizedHashAlgorithm = CanonicalPayloadHash.NormalizeHashAlgorithm(hashAlgorithm);
 100
 497101        SortedDictionary<string, object?> envelope = new(StringComparer.Ordinal)
 497102        {
 497103            ["artifactId"] = normalizedArtifactId,
 497104            ["artifactType"] = normalizedArtifactType,
 497105            ["canonicalizationVersion"] = normalizedCanonicalizationVersion,
 497106            ["content"] = content,
 497107            ["payloadSchemaVersion"] = normalizedPayloadSchemaVersion
 497108        };
 109
 497110        return new CanonicalPayload(
 497111            normalizedArtifactType,
 497112            normalizedArtifactId,
 497113            normalizedPayloadSchemaVersion,
 497114            normalizedCanonicalizationVersion,
 497115            normalizedHashAlgorithm,
 497116            CanonicalPayloadJson.Serialize(envelope));
 117    }
 118
 119    /// <summary>
 120    /// Gets the canonical JSON payload as UTF-8 bytes.
 121    /// </summary>
 122    public byte[] ToUtf8Bytes()
 123    {
 406124        return Encoding.UTF8.GetBytes(CanonicalJson);
 125    }
 126}
 127
 128internal static class CanonicalPayloadJson
 129{
 130    private static readonly JsonWriterOptions WriterOptions = new()
 131    {
 132        Indented = false,
 133        SkipValidation = false
 134    };
 135
 136    public static string Serialize(IReadOnlyDictionary<string, object?> value)
 137    {
 138        ArrayBufferWriter<byte> buffer = new();
 139        using (Utf8JsonWriter writer = new(buffer, WriterOptions))
 140        {
 141            WriteDictionary(writer, value);
 142        }
 143
 144        return Encoding.UTF8.GetString(buffer.WrittenSpan);
 145    }
 146
 147    private static void WriteValue(Utf8JsonWriter writer, object? value)
 148    {
 149        switch (value)
 150        {
 151            case null:
 152                writer.WriteNullValue();
 153                break;
 154            case string stringValue:
 155                writer.WriteStringValue(stringValue);
 156                break;
 157            case bool boolValue:
 158                writer.WriteBooleanValue(boolValue);
 159                break;
 160            case int intValue:
 161                writer.WriteNumberValue(intValue);
 162                break;
 163            case long longValue:
 164                writer.WriteNumberValue(longValue);
 165                break;
 166            case double doubleValue:
 167                if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue))
 168                {
 169                    throw new ArgumentOutOfRangeException(nameof(value), value, "Canonical payload numbers must be finit
 170                }
 171
 172                writer.WriteNumberValue(doubleValue);
 173                break;
 174            case IReadOnlyDictionary<string, object?> dictionaryValue:
 175                WriteDictionary(writer, dictionaryValue);
 176                break;
 177            case IReadOnlyDictionary<string, string> stringDictionaryValue:
 178                WriteStringDictionary(writer, stringDictionaryValue);
 179                break;
 180            case IEnumerable<string> stringValues:
 181                writer.WriteStartArray();
 182                foreach (string item in stringValues)
 183                {
 184                    writer.WriteStringValue(item);
 185                }
 186
 187                writer.WriteEndArray();
 188                break;
 189            case IEnumerable<object?> objectValues:
 190                writer.WriteStartArray();
 191                foreach (object? item in objectValues)
 192                {
 193                    WriteValue(writer, item);
 194                }
 195
 196                writer.WriteEndArray();
 197                break;
 198            default:
 199                throw new NotSupportedException($"Canonical payload value type '{value.GetType().FullName}' is not suppo
 200        }
 201    }
 202
 203    private static void WriteDictionary(Utf8JsonWriter writer, IReadOnlyDictionary<string, object?> dictionary)
 204    {
 205        writer.WriteStartObject();
 206
 207        foreach (KeyValuePair<string, object?> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 208        {
 209            writer.WritePropertyName(item.Key);
 210            WriteValue(writer, item.Value);
 211        }
 212
 213        writer.WriteEndObject();
 214    }
 215
 216    private static void WriteStringDictionary(Utf8JsonWriter writer, IReadOnlyDictionary<string, string> dictionary)
 217    {
 218        writer.WriteStartObject();
 219
 220        foreach (KeyValuePair<string, string> item in dictionary.OrderBy(item => item.Key, StringComparer.Ordinal))
 221        {
 222            writer.WriteString(item.Key, item.Value);
 223        }
 224
 225        writer.WriteEndObject();
 226    }
 227}