\Drupal\strata\Cas Hash

The content address every stored object is named by.

BLAKE2b-256 through ext-sodium, which ships with PHP 8.3+ and needs no extra dependency. Measured at 718 MB/s on the reference host, against 229 MB/s for SHA-256 and 643 MB/s for MD5, so the digest is never the bottleneck in a flush; an xxHash pre-filter would add a collision class for no throughput that matters.

Digests are handled as lowercase hex everywhere they cross a boundary - a bucket key, a database column, a manifest - because a binary digest cannot survive JSON, a URL or a log line. The raw form stays available through Hash::raw() for callers doing their own framing.

Summary

Methods
Properties
Constants
of
raw
ofStream
ofFile
ofData
equals
shard
key
abbreviate
isValid
No public properties found
ALGORITHM
BYTES
HEX_LENGTH
SHARD_DEPTH
No protected methods found
No protected properties found
No protected constants found
normalize
assertValid
No private properties found
STREAM_CHUNK

Constant

ALGORITHM

ALGORITHM = 'blake2b-256'

Name recorded in frame headers so a future digest change stays decodable.

BYTES

BYTES = 32

Digest length in bytes.

HEX_LENGTH

HEX_LENGTH = 64

Digest length as lowercase hex.

SHARD_DEPTH

SHARD_DEPTH = 2

How many two-character directories a shard path carries.

Two levels gives 65,536 buckets, which keeps any single prefix listing small on providers that paginate a flat namespace.

STREAM_CHUNK

STREAM_CHUNK = 1048576

Bytes read per iteration when digesting a stream.

Methods

of()

of(string  $data, string  $key = '') : string

Digests a value and returns lowercase hex.

Parameters

string $data

The bytes to digest. A string is digested as-is; no encoding is applied.

string $key

Optional keyed-hashing key, at most 64 bytes. An empty string means unkeyed.

Throws

\InvalidArgumentException

When the key is longer than sodium allows.

Returns

string —

A 64-character lowercase hex digest.

raw()

raw(string  $data, string  $key = '') : string

Digests a value and returns the raw 32 bytes.

Prefer Hash::of() unless you are packing the digest into a binary header yourself.

Parameters

string $data

The bytes to digest.

string $key

Optional keyed-hashing key, at most 64 bytes.

Throws

\InvalidArgumentException

When the key is longer than sodium allows.

Returns

string —

Exactly Hash::BYTES bytes.

ofStream()

ofStream(resource  $stream, int|null  $limit = null, string  $key = '') : string

Digests a stream without holding it in memory.

Reads from the current position to EOF, or to $limit bytes if given, and leaves the pointer where it stopped so a caller can keep reading. Used by the file realm, where a 256 MiB object must not become a 256 MiB string.

Parameters

resource $stream

An open, readable stream.

int|null $limit

Stop after this many bytes, or NULL to read to EOF.

string $key

Optional keyed-hashing key, at most 64 bytes.

Throws

\InvalidArgumentException

When $stream is not a stream resource, or $limit is negative.

\RuntimeException

When a read fails before the limit or EOF is reached.

Returns

string —

A 64-character lowercase hex digest.

ofFile()

ofFile(string  $path, string  $key = '') : string

Digests a file by path.

Parameters

string $path

Absolute or stream-wrapper path to a readable file.

string $key

Optional keyed-hashing key, at most 64 bytes.

Throws

\RuntimeException

When the file cannot be opened.

Returns

string —

A 64-character lowercase hex digest.

ofData()

ofData(array  $value, string  $key = '') : string

Digests a structure, whatever bytes it holds.

For a digest that is compared and never read back: a watermark over sampled rows, a fingerprint over a file tree, a deploy digest over per-file hashes. Such a digest only has to be deterministic and binary-safe.

JSON is tried first so a digest a previous release stored stays byte-identical and an upgrade does not report a change that did not happen. serialize() is the fallback because json_encode() returns FALSE for anything that is not valid UTF-8, and the callers of this method all key on something the site controls - a database row, a filesystem path - which on POSIX is bytes rather than text. (string) false is the empty string, so every such structure used to digest to Hash::of(''), two different structures compared equal, and the tripwire built on the comparison was blind rather than wrong.

Parameters

array $value

The structure to digest. Order matters, so sort it first if the caller's identity does not depend on the order it was built in.

string $key

Optional keyed-hashing key, at most 64 bytes.

Returns

string —

A 64-character lowercase hex digest.

equals()

equals(string  $left, string  $right) : bool

Compares two digests without leaking where they differ.

Accepts hex or raw on either side, so a digest read out of a manifest can be compared with one just computed. Mismatched lengths return FALSE rather than throwing, because a truncated digest is a corruption symptom the caller wants to report, not an argument error.

Parameters

string $left

A digest, hex or raw.

string $right

A digest, hex or raw.

Returns

bool —

TRUE when the two digests are the same value.

shard()

shard(string  $hex) : string

Splits a digest into its bucket path.

Parameters

string $hex

A 64-character lowercase hex digest.

Throws

\InvalidArgumentException

When $hex is not a valid digest.

Returns

string —

A path fragment such as "bd/dd", with no leading or trailing slash.

key()

key(string  $hex, string  $prefix) : string

Builds the full object key a digest is stored under.

Parameters

string $hex

A 64-character lowercase hex digest.

string $prefix

Key prefix, such as "frames" or "media". Slashes are trimmed.

Throws

\InvalidArgumentException

When $hex is not a valid digest.

Returns

string —

A key such as "frames/bd/dd/bddd813c...".

abbreviate()

abbreviate(string  $hex, int  $length = 12) : string

Shortens a digest for display.

Never use the result as an identifier; it is for a table cell or a log line.

Parameters

string $hex

A 64-character lowercase hex digest.

int $length

How many characters to keep, between 4 and Hash::HEX_LENGTH.

Throws

\InvalidArgumentException

When $hex is not a valid digest, or $length is out of range.

Returns

string —

The first $length characters of the digest.

isValid()

isValid(string  $hex) : bool

Whether a string is a well-formed digest of this algorithm.

Parameters

string $hex

The candidate.

Returns

bool —

TRUE for exactly 64 lowercase hex characters.

normalize()

normalize(string  $digest) : string|null

Reduces a digest in either representation to its raw bytes.

Parameters

string $digest

A digest, hex or raw.

Returns

string|null —

The raw bytes, or NULL when $digest is neither representation.

assertValid()

assertValid(string  $hex) : void

Guards a digest argument.

Parameters

string $hex

The candidate.

Throws

\InvalidArgumentException

When $hex is not a valid digest.

Returns

void —