\Drupal\strata\Cas Framer

Splits a byte stream into fixed-size frames.

Frames are a fixed size rather than content-defined. A pure-PHP FastCDC runs at 4.29 MB/s per-byte and 5.63 MB/s over unpacked 64 KiB windows, against 683 MB/s for this splitter plus a BLAKE2b per frame; unpack('C*') on an 8 MB buffer also exhausts a 128 MB memory limit, since an int array costs roughly 16x the string. Content-defined chunking exists to survive insertions, which shift every following boundary, and an append-only op log has none.

DeltaCodec covers the case content-defined chunking would have: it compresses a rewritten value against its previous version, measured at 63.7x on that class of change. Files, where an insertion can happen, are handled by BlockSplitter, which detects the shift.

Frame size trades compression against granularity: with a trained zstd dictionary the measured ratio is 6.20x at 8 KiB, 6.39x at 16 KiB, 6.54x at 32 KiB and 6.72x at 64 KiB, while the dictionary's own contribution falls from 40.3% to 15.7% across that range. 16 KiB is the shipped default; calibration against a real site decides whether to move it.

Summary

Methods
Properties
Constants
__construct
size
count
split
splitStream
frames
map
reassemble
No public properties found
DEFAULT_SIZE
MIN_SIZE
MAX_SIZE
No protected methods found
No protected properties found
No protected constants found
No private methods found
size
No private constants found

Constant

DEFAULT_SIZE

DEFAULT_SIZE = 16384

Default frame size in bytes.

MIN_SIZE

MIN_SIZE = 1024

Smallest frame size that still amortises a 32-byte digest and a frame header.

MAX_SIZE

MAX_SIZE = 4194304

Largest frame size.

Beyond this a single frame stops being independently addressable, which is the property the whole store is built on.

Properties

$size

$size : int

Frame size in bytes.

Type

int

Methods

__construct()

__construct(int  $size = \self::DEFAULT_SIZE) : mixed

Constructs a splitter.

Parameters

int $size

Frame size in bytes, between Framer::MIN_SIZE and Framer::MAX_SIZE. Powers of two are not required but are what the defaults use.

Throws

\InvalidArgumentException

When $size is outside the supported range.

Returns

mixed —

size()

size() : int

The configured frame size.

Returns

int —

Frame size in bytes.

count()

count(int  $length) : int

How many frames a payload of a given length produces.

The last frame is short rather than padded, so that appending to a payload never rewrites a frame that was already stored under its own digest.

Parameters

int $length

Payload length in bytes.

Throws

\InvalidArgumentException

When $length is negative.

Returns

int —

Frame count; zero for an empty payload.

split()

split(string  $payload) : \Generator<int,string>

Splits a string into frames.

An empty payload yields nothing, which is what lets a caller distinguish "no content" from "one empty frame" without a sentinel.

Parameters

string $payload

The bytes to split.

Returns

\Generator

Frame index keyed to frame bytes, in order.

splitStream()

splitStream(resource  $stream) : \Generator<int,string>

Splits a stream into frames without holding it in memory.

Reads from the current position to EOF. A short read that is not EOF is retried rather than silently producing an under-length frame in the middle of a payload, because a short frame anywhere but the end changes every following boundary and would fork the whole frame chain.

Parameters

resource $stream

An open, readable stream.

Throws

\InvalidArgumentException

When $stream is not a stream resource.

\RuntimeException

When a read fails.

Returns

\Generator

Frame index keyed to frame bytes, in order.

frames()

frames(string  $payload) : \Generator<int,array{hash: string, bytes: string, offset: int}>

Splits a payload and digests each frame.

The common case: a caller wants the frames and their content addresses together, and computing the digest separately would walk the payload twice.

Parameters

string $payload

The bytes to split.

Returns

\Generator

Frame index keyed to the frame's digest, bytes and byte offset in the payload.

map()

map(string  $payload) : list<string>

The ordered digests of a payload's frames.

This is the frame map a manifest stores. Reassembly needs the order, so a set will not do.

Parameters

string $payload

The bytes to split.

Returns

list

Frame digests in order.

reassemble()

reassemble(list  $map, callable  $fetch) : string

Rebuilds a payload from frames supplied in order.

Verifies each frame against the digest it was stored under, so a corrupted or substituted frame raises here rather than reaching Drupal as plausible-looking content. The map is the authority on order and length; a missing frame is named rather than skipped.

Parameters

list $map

Frame digests in order, as returned by Framer::map().

callable $fetch

Given a frame digest, returns its bytes, or NULL when the frame cannot be found.

Throws

\RuntimeException

When a frame is missing, or its bytes do not digest to the hash it was stored under.

Returns

string —

The reassembled payload.