| | | 1 | | using System.Buffers.Binary; |
| | | 2 | | using System.Security.Cryptography; |
| | | 3 | | using System.Text; |
| | | 4 | | |
| | | 5 | | namespace Elsa.Bpmn.Interchange.Services; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// The one place a workflow definition's serialized activity graph |
| | | 9 | | /// (<see cref="Elsa.Workflows.Management.Entities.WorkflowDefinition.StringData"/>) is turned into a content hash, |
| | | 10 | | /// shared by <see cref="BpmnInterchangeDocumentService"/> — which records the hash at import time under |
| | | 11 | | /// <see cref="BpmnInterchangeDocumentService.SourceGraphHashCustomPropertyKey"/> to tell a designer-edited graph |
| | | 12 | | /// apart from the one BPMN source was imported against — and |
| | | 13 | | /// <see cref="Elsa.Bpmn.Interchange.Endpoints.Bpmn.Document.BpmnDocumentETag"/>, so the two never disagree about how |
| | | 14 | | /// the same field hashes. |
| | | 15 | | /// </summary> |
| | | 16 | | internal static class BpmnContentHash |
| | | 17 | | { |
| | | 18 | | /// <summary>The hex-encoded SHA-256 of a workflow definition's serialized activity graph.</summary> |
| | | 19 | | public static string OfGraph(string? stringData) |
| | | 20 | | { |
| | 216 | 21 | | using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); |
| | 216 | 22 | | AppendField(hash, stringData); |
| | 216 | 23 | | return Convert.ToHexString(hash.GetHashAndReset()); |
| | 216 | 24 | | } |
| | | 25 | | |
| | | 26 | | /// <summary> |
| | | 27 | | /// Appends a length-prefixed UTF-8 encoding of <paramref name="value"/> to <paramref name="hash"/>, marking a |
| | | 28 | | /// <c>null</c> value distinctly from an empty one so no two different inputs feed the hash the same bytes. |
| | | 29 | | /// </summary> |
| | | 30 | | public static void AppendField(IncrementalHash hash, string? value) |
| | | 31 | | { |
| | 856 | 32 | | Span<byte> header = stackalloc byte[5]; |
| | | 33 | | |
| | 856 | 34 | | if (value is null) |
| | | 35 | | { |
| | 1 | 36 | | header[0] = 0; |
| | 1 | 37 | | hash.AppendData(header[..1]); |
| | 1 | 38 | | return; |
| | | 39 | | } |
| | | 40 | | |
| | 855 | 41 | | var bytes = Encoding.UTF8.GetBytes(value); |
| | 855 | 42 | | header[0] = 1; |
| | 855 | 43 | | BinaryPrimitives.WriteInt32BigEndian(header[1..], bytes.Length); |
| | 855 | 44 | | hash.AppendData(header); |
| | 855 | 45 | | hash.AppendData(bytes); |
| | 855 | 46 | | } |
| | | 47 | | } |