pull

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package pull fetches a signed JSON document, checks its signature against keys it was handed, and keeps the last good copy on disk so that the next run has something to work with when the fetch does not go through.

THE SHAPE IT EXISTS FOR. A document this package serves changes rarely and is needed on every run; the source that publishes it is not always reachable. The whole point is that a caller can ask for the document on any run and get an answer: either a fresh one, or the last good one with an error saying why it is not fresh. The network is consulted only when the cache is older than the caller's patience, and every failure the source can throw — a dead host, a timeout, a bad signature, a rollback — falls back to the cache rather than leaving the caller empty.

WHAT MAKES A DOCUMENT GOOD is written down in [Puller.good] and nowhere else, because it is the one contract a reader can spot on its own: the document is JSON, an object, and carries an integer "version"; its ".sig" verifies under one of the keys the caller trusts; and its version is not lower than the one the cache already holds, because a copy of yesterday served today is the one attack a reader can catch without trusting the source. A good document is cached and returned; a bad one returns whatever the cache had, together with the error that says why the fresh one was refused.

THE CACHE IS NOT A TRUSTED STORE. Bytes in Puller.CacheDir that do not parse, or whose signature no longer verifies under Puller.Keys, are treated as if nothing were cached: no error of their own, no panic, and the next good fetch replaces them. The cache is a copy of a good fetch, never a source of truth in its own right.

Index

Constants

View Source
const (
	// MaxDoc is the largest document Pull will read. A document larger than
	// this is refused rather than read to the end, because a source that answers
	// with an unbounded body is a source that can exhaust the reader.
	MaxDoc = 4 << 20 // 4 MiB

	// DefaultBudget is what a Pull with a zero Budget spends. It is short on
	// purpose: this package serves a document that is needed on the path of a
	// keystroke, and a caller who can afford to wait longer sets its own.
	DefaultBudget = 2 * time.Second
)

Variables

View Source
var ErrBadSignature = errors.New("pull: signature does not verify")

ErrBadSignature is a fetched document whose signature does not verify under any of the keys in hand. It is its own error because a caller deciding whether to look elsewhere for the same document has to tell a document that failed its check from a source that did not answer: a copy somewhere else cannot vouch for bytes whose signature just failed.

Functions

func Verify

func Verify(doc, sig []byte, keys []ed25519.PublicKey) bool

Verify answers whether sig is a detached ed25519 signature over doc under ANY ONE of keys. sig is read as base64 text in the standard encoding; space around it is ignored. No keys, an unreadable base64, a signature of the wrong length or a signature nobody's key made is false. A caller who trusts nobody trusts nothing.

The key-length guard is here because ed25519.Verify panics on a wrong-length key, and a key the caller mis-hands is a misconfiguration, not a crash.

Types

type Puller

type Puller struct {
	URL      string
	Keys     []ed25519.PublicKey
	CacheDir string
	TTL      time.Duration
	Budget   time.Duration    // 0 means DefaultBudget
	Client   *http.Client     // nil means a client the package makes
	Now      func() time.Time // nil means time.Now
}

Puller fetches one document from one URL, verifies it against one set of keys, and keeps the last good copy in one directory. The zero value is not usable: URL, Keys and CacheDir must be set before Pull is called.

Client and Now have nil defaults the package fills: a plain http.Client with no timeout of its own (the budget governs) and time.Now. A caller that wants deterministic clocks or a test transport sets both.

func (*Puller) Pull

func (p *Puller) Pull(ctx context.Context) (Result, error)

Pull fetches the document and returns a Result and an error. It never panics. The whole call lives inside Budget (DefaultBudget when it is zero) and inside ctx, whichever ends first.

type Result

type Result struct {
	Doc       []byte
	Version   int64
	FromCache bool
	Changed   bool
}

Result is what a single Pull returns. Doc is the document bytes, nil when there is nothing to give. Version is its "version". FromCache is true when the bytes came off disk, not off the wire. Changed is true when this version is newer than the one the cache held.

Jump to

Keyboard shortcuts

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