Skip to main content

Transport

Trait Transport 

Source
pub trait Transport: Send + Sync {
Show 13 methods // Required methods fn upload_pack(&self, bytes: &[u8], key: &PackKey) -> TransportResult<()>; fn download_pack(&self, key: &PackKey) -> TransportResult<Vec<u8>>; fn pack_exists(&self, key: &PackKey) -> TransportResult<bool>; fn update_ref( &self, name: &str, condition: RefWriteCondition, hash: &Hash, ) -> TransportResult<()>; fn read_ref(&self, name: &str) -> TransportResult<Option<Hash>>; fn list_refs(&self, prefix: &str) -> TransportResult<Vec<Ref>>; // Provided methods fn upload_pack_streaming( &self, key: &PackKey, total_bytes: u64, chunks: &mut dyn Iterator<Item = TransportResult<PackChunk>>, ) -> TransportResult<()> { ... } fn download_pack_streaming( &self, key: &PackKey, ) -> TransportResult<Box<dyn Iterator<Item = TransportResult<PackChunk>> + '_>> { ... } fn upload_blob(&self, bytes: &[u8], key: &PackKey) -> TransportResult<()> { ... } fn download_blob(&self, key: &PackKey) -> TransportResult<Vec<u8>> { ... } fn write_ref(&self, name: &str, hash: &Hash) -> TransportResult<()> { ... } fn advance_refs( &self, head_ref: &str, head_condition: RefWriteCondition, head_value: &Hash, packmap_ref: &str, packmap_condition: RefWriteCondition, packmap_value: &Hash, ) -> TransportResult<AdvanceOutcome> { ... } fn supports_atomic_advance(&self) -> bool { ... }
}
Expand description

The mkit transport vtable.

Every transport (memory, file, HTTP, S3, SSH) implements this trait. Methods are synchronous and take &self; transports that need interior mutability (e.g. connection pools) MUST use a Mutex / RwLock internally. This keeps the trait object-safe.

All implementations MUST honour the retry policy in SPEC-TRANSPORT §7 internally OR document that the caller is responsible — the abstract trait takes no position. The is_retryable and BackoffIterator helpers are provided for implementations that embed the policy.

Required Methods§

Source

fn upload_pack(&self, bytes: &[u8], key: &PackKey) -> TransportResult<()>

Upload a pack. The digest is computed by the caller (BLAKE3 of the full pack bytes) and used as the object key — servers MAY dedupe on this key.

Source

fn download_pack(&self, key: &PackKey) -> TransportResult<Vec<u8>>

Download a pack by its digest.

Returns TransportError::PackNotFound if the remote does not hold this digest.

Source

fn pack_exists(&self, key: &PackKey) -> TransportResult<bool>

HEAD-check a pack. Cheaper than Self::download_pack on network transports.

Source

fn update_ref( &self, name: &str, condition: RefWriteCondition, hash: &Hash, ) -> TransportResult<()>

CAS ref write. See RefWriteCondition.

On .missing / .match CAS failure, returns TransportError::RefConflict. Callers retrying after a timeout MUST follow up with Self::read_ref to confirm whether the first attempt actually landed (SPEC-TRANSPORT §7).

Source

fn read_ref(&self, name: &str) -> TransportResult<Option<Hash>>

Read the current value of a ref, or None if it does not exist.

Source

fn list_refs(&self, prefix: &str) -> TransportResult<Vec<Ref>>

List refs whose full name starts with prefix. Returned names have prefix stripped per SPEC-REFS §4. An empty prefix lists every ref.

Provided Methods§

Source

fn upload_pack_streaming( &self, key: &PackKey, total_bytes: u64, chunks: &mut dyn Iterator<Item = TransportResult<PackChunk>>, ) -> TransportResult<()>

Upload a pack by streaming bounded-size PackChunks instead of requiring the whole pack materialized as one &[u8] up front.

total_bytes is the caller-declared pack length — the wire header most streaming transports send before the first chunk. chunks MUST yield its segments in ascending contiguous offset order and end with exactly one item whose last field is true (an empty pack still yields one last = true chunk with empty data); the accumulated data length across every yielded chunk MUST equal total_bytes. The digest (key) is still computed by the caller up front, exactly as for Self::upload_pack — this method does not hash the stream itself.

This is an additive, opt-in entry point: no existing transport is forced to implement real streaming. The default impl buffers chunks into one Vec (bounded by PACK_BODY_LIMIT_USIZE) and delegates to Self::upload_pack, so every transport gets a working implementation with zero code — callers may always use this entry point, even against a transport that has not opted into streaming. Transports that can forward chunks directly to their own wire (SSH, enc — see mkit-transport-ssh’s existing PackChunk frame loop) SHOULD override this to avoid the buffer and stay in bounded memory regardless of pack size.

§Errors

Returns TransportError::ProtocolError if chunks never yields a last = true item, or if the accumulated byte count does not equal total_bytes. Returns TransportError::PayloadTooLarge if total_bytes (or the accumulated count) would exceed PACK_BODY_LIMIT. Propagates any error yielded by chunks itself (e.g. the caller’s own I/O error while reading a pack off disk).

Source

fn download_pack_streaming( &self, key: &PackKey, ) -> TransportResult<Box<dyn Iterator<Item = TransportResult<PackChunk>> + '_>>

Download a pack as a lazy stream of bounded-size PackChunks instead of one big Vec<u8>.

This is an additive, opt-in entry point mirroring Self::upload_pack_streaming. The default impl calls Self::download_pack eagerly (so it does not save memory by itself) and wraps the whole result as a single last = true chunk — every transport gets a working implementation with zero code. Transports that can read their own wire incrementally (SSH, enc) SHOULD override this to yield each wire chunk as it arrives, keeping memory bounded to roughly one chunk at a time regardless of total pack size.

§Errors

Returns TransportError::PackNotFound immediately if the remote does not hold key — a conforming implementation never returns a stream that then fails its first item with PackNotFound. Errors surfacing mid-stream (a malformed frame, a connection drop) are yielded as Err items from the returned iterator rather than failing this call itself, since an overridden implementation may not know the transfer will fail until partway through.

Source

fn upload_blob(&self, bytes: &[u8], key: &PackKey) -> TransportResult<()>

Upload a content-addressed auxiliary blob — transfer metadata that is NOT a packfile (e.g. a packlist chain node, SPEC-PACKFILE is silent on these). The key is BLAKE3 of bytes, exactly like a pack.

Auxiliary blobs share the digest-keyed content-addressed store with packs (the store is a general blob store; “pack” is just the primary content kind), so the default impl delegates to Self::upload_pack. The distinct verb keeps the kind explicit at the call site so a caller never has to infer “is this blob a packfile or metadata?”.

Source

fn download_blob(&self, key: &PackKey) -> TransportResult<Vec<u8>>

Download an auxiliary blob by digest. Counterpart to Self::upload_blob; default impl delegates to Self::download_pack. Returns TransportError::PackNotFound if the remote does not hold this digest.

Source

fn write_ref(&self, name: &str, hash: &Hash) -> TransportResult<()>

Unconditional ref write — equivalent to update_ref(name, RefWriteCondition::Any, hash).

Default impl delegates to Self::update_ref so transports only implement one entry point.

Source

fn advance_refs( &self, head_ref: &str, head_condition: RefWriteCondition, head_value: &Hash, packmap_ref: &str, packmap_condition: RefWriteCondition, packmap_value: &Hash, ) -> TransportResult<AdvanceOutcome>

Advance a branch by updating its head ref and its packmap ref together, each under its own CAS precondition.

This exists so the delta-transfer invariant — “if head_ref resolves to T, the packmap reconstructs closure(T)” — can be upheld without a window where the two refs disagree. A transport backed by a transactional ref store SHOULD override this to apply both writes in ONE transaction: then a failed advance changes nothing, and the head is never observed past a packmap that can’t yet reconstruct it.

The default impl is the safe non-transactional approximation used by stores without multi-ref transactions: it writes the packmap first (durable before the head moves) then the head. A crash in between leaves the head at its prior value and the packmap a superset — still consistent for fetch. The AdvanceOutcome distinguishes a packmap precondition failure (caller re-reads and retries the chain) from a head precondition failure (caller treats it as non-fast-forward), which a single RefConflict could not.

Source

fn supports_atomic_advance(&self) -> bool

Whether Self::advance_refs commits the head + packmap advance as one indivisible transaction, rather than the default’s ordered packmap-then-head writes.

The default (non-transactional) advance_refs is safe for an appending packmap write: per its doc comment, a crash or lost head-CAS race between the two writes leaves the packmap a strict superset of what the (unmoved) head needs — still reconstructable. That safety argument does NOT extend to a packmap reset (a fresh node with prev = None, produced by the pack-chain re-baseline, mkit #406): a reset is not a superset of the prior chain, so a packmap write that commits while the paired head write loses its CAS would strand the (still-unmoved) head pointing at a commit whose closure the reset packmap can no longer reconstruct (mkit #521).

Callers MUST treat false (the default) as “never request a packmap reset against this transport” — see remote_dispatch::push_branch’s re-baseline gate. Override to true ONLY when Self::advance_refs is overridden with a genuinely transactional implementation (e.g. the HTTP transport’s single-request /refs/advance endpoint, mkit #408).

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§