Table of Contents

Struct CanonicalHashBuilder

Namespace
Typhon.Protocol
Assembly
Typhon.Protocol.dll

The FNV-1a 64 construction this repository uses for content fingerprints: UTF-8 bytes, fixed-width little-endian integers, and an explicit end-of-entry separator, folded over entries the caller has already sorted.

public struct CanonicalHashBuilder
Inherited Members

Remarks

Why this exists. The same construction was hand-rolled at two sites with identical constants and no shared helper — ProfilerSessionMetadataBuilder.ComputeSchemaFingerprint and DatabaseRepair.Fingerprint — the second of which re-implemented it deliberately and said so in a comment, after a string.GetHashCode non-determinism bug. The catalog needs the same digest, and a third copy is the point at which it becomes a convention nobody owns.

What each part is for, because each one is load-bearing. Hashing UTF-8 bytes rather than string.GetHashCode is what makes the value stable across processes — GetHashCode is randomized per process, and a fingerprint that changes between runs silently defeats every comparison it exists for. Fixed-width little-endian integers make it stable across architectures. The 0xFF separator at the end of each entry is what stops ("Ab", 1) colliding with ("A", …) by concatenation. Ordinal sorting is what makes it independent of declaration order — but it belongs to the caller, because only the caller knows what an entry is.

What this helper does NOT do. It does not sort. Each caller folds its own already-sorted entries, because the tuple shape differs at every site (name + revision; code + page + occurrences; archetype + field + codec). Sorting here would mean a collection type and an allocation for the privilege.

Not every FNV-like hash in this repository is this one. Three further sites — ArchetypeClusterState.HashUnitGeometry, IndexContentChecks and the Workbench's StorageMapService — start from 1469598103934665603, which is the FNV-1a 64 offset basis with its final digit missing, and two of them say "FNV-1a offset basis" in a comment. They are self-consistent cache keys, so nothing is broken today, but they are not this construction and must not be migrated onto this helper expecting the same digests.

Properties

Value

The digest of everything folded in so far.

public readonly ulong Value { get; }

Property Value

ulong

Methods

AddBytes(ReadOnlySpan<byte>)

Folds raw bytes in, one FNV-1a round each.

public void AddBytes(ReadOnlySpan<byte> value)

Parameters

value ReadOnlySpan<byte>

The bytes to fold.

AddInt32(int)

Folds a 32-bit integer in as four little-endian bytes.

public void AddInt32(int value)

Parameters

value int

The value to fold.

AddInt64(long)

Folds a 64-bit integer in as eight little-endian bytes.

public void AddInt64(long value)

Parameters

value long

The value to fold.

AddUtf8(ReadOnlySpan<char>)

Folds character data in as its UTF-8 bytes.

public void AddUtf8(ReadOnlySpan<char> value)

Parameters

value ReadOnlySpan<char>

The characters to fold.

Remarks

Encodes into a stack or pooled buffer rather than calling Encoding.UTF8.GetBytes(string), which allocates a fresh array per entry as the two original sites did. The bytes are identical; only the garbage is gone, which matters because the catalog folds one entry per field of every replicated archetype.

AddUtf8(string)

Folds a string in as its UTF-8 bytes. A null string folds as empty, exactly as the call sites it replaces did.

public void AddUtf8(string value)

Parameters

value string

The string to fold; may be null.

Create()

Creates a builder seeded with the FNV-1a 64 offset basis.

public static CanonicalHashBuilder Create()

Returns

CanonicalHashBuilder

A builder with no entries folded in yet.

EndEntry()

Closes the current entry. Call once after the last field of every entry, including the last one.

public void EndEntry()

Remarks

Skipping it is not a cosmetic difference: without a separator the fields of adjacent entries run together, so ("Ab", 1) and ("A", …) can produce the same digest. Both original sites emit it unconditionally at the end of each loop iteration, and this reproduces that.

ToHex()

The digest as 16 lower-case hexadecimal digits, invariant-formatted.

public readonly string ToHex()

Returns

string

A 16-character hexadecimal string.