package catalog import ( "context" "errors" "fmt" ) var ErrNoValidCatalog = errors.New("no valid catalog available") // Fetcher obtains a signed Catalog document from a remote or test source. type Fetcher interface { Fetch(context.Context) ([]byte, error) } // FetchFunc adapts a function to Fetcher. type FetchFunc func(context.Context) ([]byte, error) func (function FetchFunc) Fetch(ctx context.Context) ([]byte, error) { return function(ctx) } // Cache stores the last verified signed document. type Cache interface { Load() ([]byte, error) Store([]byte) error } // LoadSource describes where a verified result came from. type LoadSource string const ( SourceRemote LoadSource = "remote" SourceCache LoadSource = "cache" ) // LoadResult returns verified bytes and a non-fatal refresh/cache warning. type LoadResult struct { Document VerifiedDocument Source LoadSource Warning error } // LoadError preserves both the refresh and cache failure. type LoadError struct { Refresh error Cache error } func (err *LoadError) Error() string { return fmt.Sprintf("%s: refresh=%v; cache=%v", ErrNoValidCatalog, err.Refresh, err.Cache) } func (err *LoadError) Unwrap() error { return ErrNoValidCatalog } // Loader verifies remote data before storing it and re-verifies cache fallback. type Loader struct { verifier Verifier fetcher Fetcher cache Cache } func NewLoader(verifier Verifier, fetcher Fetcher, cache Cache) *Loader { return &Loader{ verifier: verifier, fetcher: fetcher, cache: cache, } } // Load prefers a verified remote document and falls back to verified cache. func (loader *Loader) Load(ctx context.Context) (LoadResult, error) { remoteBytes, refreshErr := loader.fetcher.Fetch(ctx) if refreshErr == nil { verified, verifyErr := loader.verifier.Verify(remoteBytes) if verifyErr == nil { storeErr := loader.cache.Store(verified.Bytes) return LoadResult{ Document: verified, Source: SourceRemote, Warning: storeErr, }, nil } refreshErr = verifyErr } cachedBytes, cacheErr := loader.cache.Load() if cacheErr == nil { var verified VerifiedDocument verified, cacheErr = loader.verifier.Verify(cachedBytes) if cacheErr == nil { return LoadResult{ Document: verified, Source: SourceCache, Warning: refreshErr, }, nil } } return LoadResult{}, &LoadError{ Refresh: refreshErr, Cache: cacheErr, } }