Help us shape the future of Go! Take the 2026 Go Developer Survey

transfer

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package transfer gives the acoustic link file semantics.

The link layer delivers ordered, verified chunks. This package decides what those chunks mean: a named file, of a known length, with a digest that is checked after the last chunk lands. That final check is the whole basis of - a transfer is complete if and only if the whole-file digest matches - and it is the reason a per-chunk CRC is never treated as proof of anything.

Index

Constants

View Source
const (
	// DigestSize is SHA-256. BLAKE3 would be faster and is a drop-in upgrade
	// once a dependency is justified; SHA-256 costs nothing at 50
	// bytes per second and is already in the standard library, which for a tool
	// that may be run on an air-gapped machine is worth more than throughput.
	DigestSize = 32

	// ManifestVersion is the manifest wire format version.
	ManifestVersion uint8 = 1

	// MinChunkSize and MaxChunkSize bound the payload per burst.
	//
	// The lower bound keeps framing overhead from dominating: a 16-byte chunk
	// would spend 32 bytes on a header and CRC. The upper bound is a sanity cap;
	// the real limit is always what the modulation can carry.
	MinChunkSize = 32
	MaxChunkSize = 4096

	// PartSuffix marks an incomplete file. A partial transfer is never written
	// under its real name, so an interrupted transfer cannot be mistaken for a
	// complete one by anything - including the user's editor.
	PartSuffix = ".murmur.part"
)
View Source
const (
	// MessageVersion is the message header version.
	MessageVersion uint8 = 1
	// DigestPrefixSize is how many bytes of the payload's SHA-256 travel with it.
	// Twelve rather than thirty-two because this digest guards reassembly, not
	// authenticity: authenticity is the AEAD's job, frame by frame. Ninety-six bits
	// still makes a coincidence impossible, and the other twenty bytes would have
	// cost six tenths of a second of sound.
	DigestPrefixSize = 12
	// MessagePrefixSize is the length prefix in front of an encoded header.
	MessagePrefixSize = 2
)

Messages: the payload unit the link carries. A message is a small header followed by the bytes it describes:

[2 bytes header length][header][payload]

The header travels inside the message rather than as a separate exchange because a second exchange to announce a payload would be another burst of airtime, and the receiver can unpack the header and the payload together. It is deliberately not a Manifest. A Manifest describes a resumable file: name, mode, modification time, chunk size, and a chunk-indexed layout. A message is one thing that either arrives whole or does not arrive at all, and every field it does not need is airtime: on the one profile murmur ships, a byte costs about 32 milliseconds.

A message carries no name. The receiver's output is the payload itself - the whole point is that `murmur decode > go.mod` works - so a file name on the wire would be metadata nothing downstream can act on, and its 64-byte ceiling would have cost two seconds of sound for it.

View Source
const DefaultChunkSize = 256

DefaultChunkSize is the fallback when the modulation's capacity is unknown.

Variables

View Source
var (
	// ErrBadManifest means the manifest is structurally invalid.
	ErrBadManifest = errors.New("transfer: invalid manifest")
	// ErrShortManifest means the manifest payload was truncated.
	ErrShortManifest = errors.New("transfer: manifest too short")
	// ErrChunkRange means a chunk index is outside the manifest.
	ErrChunkRange = errors.New("transfer: chunk index out of range")
	// ErrChunkLength means a chunk's length contradicts the manifest.
	ErrChunkLength = errors.New("transfer: chunk length contradicts the manifest")
	// ErrIncomplete means a finalize was attempted with chunks missing.
	ErrIncomplete = errors.New("transfer: transfer incomplete")
	// ErrDigestMismatch means the assembled file does not match the manifest.
	// This is the one error that must never be downgraded to a warning.
	ErrDigestMismatch = errors.New("transfer: whole-file digest mismatch")
)
View Source
var ErrBadMessage = errors.New("transfer: malformed message")

ErrBadMessage means a packed message contradicts its own header.

Functions

func ChooseChunkSize

func ChooseChunkSize(scheme modulation.Scheme, burstSamples int) int

ChooseChunkSize returns the largest chunk that fits in one burst. A chunk size larger than a burst's payload capacity would mean every chunk spans multiple bursts, which makes the interleaver and the retransmit unit disagree about what a unit of loss is. Deriving the chunk size from the PHY keeps one chunk equal to one burst, so a lost burst loses exactly one retransmittable unit and nothing else.

func HashFile

func HashFile(path string) ([DigestSize]byte, error)

HashFile returns the SHA-256 digest of a file.

func PackMessage

func PackMessage(m Message, data []byte) []byte

PackMessage renders a payload and its header as one wire message.

Types

type Manifest

type Manifest struct {
	Name      string
	Size      int64
	Mode      uint32
	ModTime   time.Time
	Digest    [DigestSize]byte
	ChunkSize int
}

Manifest describes a payload before any of it is sent. It is sent first, and the receiver's acceptance decision is based on it. That ordering is deliberate: everything the user needs in order to decide - name, size, mode - arrives in one small burst, before a single byte of content has been spent.

func BuildManifest

func BuildManifest(path string, chunkSize int) (Manifest, error)

BuildManifest hashes a file and returns its manifest. This is the only place the sender touches the whole payload before starting, and it is what makes the receiver's final verification meaningful: the digest the receiver checks against describes the file as it existed at the moment this function read it.

func DecodeManifest

func DecodeManifest(b []byte) (Manifest, error)

DecodeManifest parses a manifest.

func (Manifest) ChunkCount

func (m Manifest) ChunkCount() int

ChunkCount returns how many chunks the payload is divided into.

func (Manifest) ChunkLen

func (m Manifest) ChunkLen(index int) (int, error)

ChunkLen returns the expected length of a chunk. The final chunk is short unless the size divides evenly.

func (Manifest) Encode

func (m Manifest) Encode() []byte

Encode renders the manifest. ModTime is transmitted as Unix seconds and truncated, because a manifest that is one byte longer is 160 milliseconds of airtime at the v0 rate. Sub-second precision on a file's timestamp is not worth that.

func (Manifest) String

func (m Manifest) String() string

func (Manifest) Validate

func (m Manifest) Validate() error

Validate checks the manifest's internal consistency.

type Message

type Message struct {
	// Size is the payload length in bytes.
	Size int64
	// Digest is the first DigestPrefixSize bytes of the payload's SHA-256.
	Digest [DigestPrefixSize]byte
}

Message describes one payload.

func DecodeMessage

func DecodeMessage(b []byte) (Message, error)

DecodeMessage parses a message header.

func MessageOf

func MessageOf(data []byte) Message

MessageOf describes an in-memory payload.

func UnpackMessage

func UnpackMessage(b []byte) (Message, []byte, error)

UnpackMessage splits a received message and checks it against its header. The checks are deliberately paranoid. A receiver that trusted the length alone would hand the caller whatever arrived; the digest is what turns "the frames were delivered in order" into "these are the bytes that were sent".

func (Message) Encode

func (m Message) Encode() []byte

Encode renders the message header.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader reads a manifest's chunks from disk for transmission.

func NewReader

func NewReader(path string, m Manifest) (*Reader, error)

NewReader opens a file for chunked transmission.

func (*Reader) Chunk

func (r *Reader) Chunk(index int) ([]byte, error)

Chunk reads one chunk into a freshly allocated buffer.

func (*Reader) ChunkInto

func (r *Reader) ChunkInto(index int, buf []byte) ([]byte, error)

ChunkInto reads one chunk into buf, which must be large enough.

func (*Reader) Close

func (r *Reader) Close() error

Close releases the file handle.

func (*Reader) Manifest

func (r *Reader) Manifest() Manifest

Manifest returns the manifest being transmitted.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer assembles a file from chunks arriving in any order.

func NewWriter

func NewWriter(dst string, m Manifest) (*Writer, error)

NewWriter starts a fresh transfer to dst, refusing to overwrite silently if a partial file is already there.

func (*Writer) Abort

func (w *Writer) Abort() error

Abort removes the partial file. Calling it after Finalize is a no-op, so a deferred Abort cannot delete a completed transfer.

func (*Writer) Close

func (w *Writer) Close() error

Close releases the file handle without finalizing. The partial file remains, which is what makes an interrupted transfer distinguishable from a finished one - and what NewWriter refuses to clobber.

func (*Writer) Complete

func (w *Writer) Complete() bool

Complete reports whether every chunk has arrived.

func (*Writer) Finalize

func (w *Writer) Finalize() error

Finalize verifies the whole-file digest and atomically installs the file. The order is: verify, then fsync the file, then rename, then fsync the directory. Renaming before verifying would install a file that is known to be wrong; skipping the directory sync would leave a window in which a crash removes the file after the tool has already reported success.

func (*Writer) Manifest

func (w *Writer) Manifest() Manifest

Manifest returns the manifest being written.

func (*Writer) Missing

func (w *Writer) Missing() []int

Missing returns the indices not yet received, in order.

func (*Writer) PartPath

func (w *Writer) PartPath() string

PartPath returns the path of the partial file.

func (*Writer) Progress

func (w *Writer) Progress() (int, int64)

Progress returns how many chunks are present contiguously from the start, and how many bytes that represents. Contiguous progress rather than total chunk count is the honest measure: what the user wants to know is how much of the file can be trusted so far, and a file with a hole at byte 100 is worth nothing beyond byte 100.

func (*Writer) WriteChunk

func (w *Writer) WriteChunk(index int, data []byte) error

WriteChunk stores one chunk at its manifest-defined offset. Writing at an offset rather than appending is what makes the receive path independent of arrival order. That matters because selective repeat explicitly does not deliver in order - the link layer holds fragments until the holes fill, and the writer must not care either way.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL