Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 71 additions & 8 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
package cache

import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
Expand All @@ -22,6 +23,15 @@ import (

const defaultTTL = time.Hour

const (
entrySuffix = ".json"
metaSuffix = ".meta.json"
)

type metadata struct {
URL string `json:"url"`
}

// Entry is a cached HTTP response.
type Entry struct {
URL string `json:"url"`
Expand Down Expand Up @@ -91,7 +101,7 @@ func (c *Cache) loadAll() error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".json") {
if d.IsDir() || !strings.HasSuffix(path, entrySuffix) || strings.HasSuffix(path, metaSuffix) {
return nil
}
f, err := os.Open(path)
Expand All @@ -108,28 +118,81 @@ func (c *Cache) loadAll() error {
// Written by a version that still cached error responses. Dropping it here
// keeps a poisoned entry from outliving the restart that was meant to clear it.
slog.Warn("dropping cached error response", "url", e.URL, "status", e.Status)
if err := os.Remove(path); err != nil {
if err := removeEntry(path); err != nil {
slog.Warn("removing cached error response failed", "path", path, "err", err)
}
return nil
}
if prev, ok := c.entries[e.URL]; ok && !e.FreshUntil.After(prev.FreshUntil) {
// A copy of the same URL under a different file name, and not the fresher one.
if err := removeEntry(path); err != nil {
slog.Warn("removing duplicate cache entry failed", "path", path, "err", err)
}
return nil
}
c.entries[e.URL] = &e
if path != c.entryPath(e.URL) {
if err := c.writeDisk(&e); err != nil {
slog.Warn("migrating cache entry failed", "path", path, "err", err)
return nil
}
if err := removeEntry(path); err != nil {
slog.Warn("removing migrated cache entry failed", "path", path, "err", err)
}
}
return nil
})
}

func (c *Cache) entryPath(url string) string {
sum := sha256.Sum256([]byte(url))
Comment thread
charludo marked this conversation as resolved.
return filepath.Join(c.dir, hex.EncodeToString(sum[:])+entrySuffix)
}

// metaPath is the sidecar belonging to the entry stored at path.
func metaPath(path string) string {
return strings.TrimSuffix(path, entrySuffix) + metaSuffix
}

// removeEntry deletes an entry and its sidecar, if it has one.
func removeEntry(path string) error {
err := os.Remove(path)
if metaErr := os.Remove(metaPath(path)); metaErr != nil && !errors.Is(metaErr, fs.ErrNotExist) {
err = errors.Join(err, metaErr)
}
return err
}

func (c *Cache) writeDisk(e *Entry) error {
path := filepath.Join(c.dir, base64.RawURLEncoding.EncodeToString([]byte(e.URL))+".json")
f, err := os.CreateTemp(c.dir, "tmp-*")
blob, err := json.Marshal(e)
if err != nil {
return err
}
path := c.entryPath(e.URL)
if err := c.writeFile(path, append(blob, '\n')); err != nil {
return err
}
// The sidecar is a debugging aid, so failing to write it should not fail the Put.
meta, err := json.Marshal(metadata{URL: e.URL})
if err == nil {
err = c.writeFile(metaPath(path), append(meta, '\n'))
}
if err != nil {
slog.Warn("writing cache entry sidecar failed", "url", e.URL, "err", err)
}
return nil
}

if err := json.NewEncoder(f).Encode(e); err != nil {
return errors.Join(err, f.Close())
func (c *Cache) writeFile(path string, data []byte) error {
f, err := os.CreateTemp(c.dir, "tmp-*")
if err != nil {
return err
}
if _, err := f.Write(data); err != nil {
return errors.Join(err, f.Close(), os.Remove(f.Name()))
}
if err := f.Close(); err != nil {
return err
return errors.Join(err, os.Remove(f.Name()))
}
return os.Rename(f.Name(), path)
}
Expand Down
84 changes: 84 additions & 0 deletions internal/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -160,6 +161,89 @@ func TestPoisonedEntryDroppedOnLoad(t *testing.T) {
assert.NoFileExists(t, path)
}

func TestLongURLIsStored(t *testing.T) {
dir := t.TempDir()
c, err := New(dir)
require.NoError(t, err)

url := "https://kdsintf.amd.com/vcek/v1/Genoa/" + strings.Repeat("ab", 64) + "?blSPL=10&teeSPL=0&snpSPL=27&ucodeSPL=84"
_, err = c.Put(url, 200, http.Header{}, []byte("cert"))
require.NoError(t, err)

c2, err := New(dir)
require.NoError(t, err)
got, fresh := c2.Get(url)
require.NotNil(t, got, "entry did not survive a reopen")
assert.True(t, fresh)
assert.Equal(t, "cert", string(got.Body))
}

func TestSidecarRecordsURL(t *testing.T) {
dir := t.TempDir()
c, err := New(dir)
require.NoError(t, err)
url := "https://kdsintf.amd.com/vcek/v1/Genoa/" + strings.Repeat("ab", 64)
_, err = c.Put(url, 200, http.Header{}, []byte("cert"))
require.NoError(t, err)

raw, err := os.ReadFile(metaPath(c.entryPath(url)))
require.NoError(t, err, "no sidecar next to the entry")
var meta metadata
require.NoError(t, json.Unmarshal(raw, &meta))
assert.Equal(t, url, meta.URL, "sidecar should name the URL the file was stored under")

c2, err := New(dir)
require.NoError(t, err)
assert.Len(t, c2.entries, 1)
got, fresh := c2.Get(url)
require.NotNil(t, got)
assert.True(t, fresh)
assert.FileExists(t, metaPath(c.entryPath(url)))
}

func TestSidecarRemovedWithEntry(t *testing.T) {
dir := t.TempDir()
c, err := New(dir)
require.NoError(t, err)
url := "https://example/x"
_, err = c.Put(url, 200, http.Header{}, []byte("hi"))
require.NoError(t, err)

poisoned := Entry{URL: url, Status: 500, Header: http.Header{}, FreshUntil: time.Now().Add(time.Hour)}
raw, err := json.Marshal(poisoned)
require.NoError(t, err)
require.NoError(t, os.WriteFile(c.entryPath(url), raw, 0o600))

_, err = New(dir)
require.NoError(t, err)
assert.NoFileExists(t, c.entryPath(url))
assert.NoFileExists(t, metaPath(c.entryPath(url)), "sidecar outlived its entry")
}

func TestLegacyEntryMigratedOnLoad(t *testing.T) {
dir := t.TempDir()
legacy := Entry{
URL: "https://example/x",
Status: 200,
Header: http.Header{},
Body: []byte("hi"),
FreshUntil: time.Now().Add(time.Hour),
}
raw, err := json.Marshal(legacy)
require.NoError(t, err)
legacyPath := filepath.Join(dir, base64.RawURLEncoding.EncodeToString([]byte(legacy.URL))+".json")
require.NoError(t, os.WriteFile(legacyPath, raw, 0o600))

c, err := New(dir)
require.NoError(t, err)
got, fresh := c.Get(legacy.URL)
require.NotNil(t, got, "entry written by an older version should still be loaded")
assert.True(t, fresh)
assert.NoFileExists(t, legacyPath, "legacy file should have been migrated away")
assert.FileExists(t, c.entryPath(legacy.URL))
assert.FileExists(t, metaPath(c.entryPath(legacy.URL)), "migration should write a sidecar")
}

func TestStaleEntryStillReturned(t *testing.T) {
dir := t.TempDir()
c, err := New(dir)
Expand Down
Loading