Author SHA1 Message Date
clawbot b9583fea27 Drop mimeType and ctime from the manifest format (closes #235)
check / check (push) Canceled after 0s
Nothing goes in the 1.0 manifest that 1.0 does not read or write, and
mfer never wrote either field. mf.proto now reserves the numbers 301 and
303 and both names in MFFilePath, and mf.pb.go is regenerated. The
decoded-size check counts only file entries, hashes and timestamps, and
counts a file entry, 16 bytes smaller without the two fields, at 160
bytes. export prints no ctime, and FileEntry loses the Ctime nothing
set. FORMAT.md and the README no longer describe either field.

The test of entries that decode too large gives each entry a second,
empty hash: an entry with one hash and a modification time can no
longer reach 8 times its size.

Model: opus-5-5
2026-10-19 18:40:56 +00:00
8 changed files with 30 additions and 74 deletions
+1 -1
View File
@@ -335,7 +335,7 @@ func (b *Builder) Build(ctx context.Context, w io.Writer) error {
}
// Create a temporary manifest to use existing serialization
m := &Manifest{
m := &manifest{
pbInner: inner,
signingOptions: b.signingOptions,
fixedUUID: b.fixedUUID,
+1 -1
View File
@@ -123,7 +123,7 @@ func NewChecker(opts *CheckerOptions) (*Checker, error) {
}
var (
m *Manifest
m *manifest
manifestInfo os.FileInfo
err error
)
+11 -7
View File
@@ -48,7 +48,7 @@ func validateUUID(data []byte) error {
// validateOuterHeader checks the outer message's version, compression
// type, and UUID.
func (m *Manifest) validateOuterHeader() error {
func (m *manifest) validateOuterHeader() error {
if m.pbOuter.GetVersion() != MFFileOuter_VERSION_ONE {
return errUnknownVersion
}
@@ -70,7 +70,7 @@ func (m *Manifest) validateOuterHeader() error {
// signature is present, verifies it against the embedded public key, which
// must be one key, and checks that the signer field is that key's
// fingerprint.
func (m *Manifest) verifyOuterIntegrity() error {
func (m *manifest) verifyOuterIntegrity() error {
h := sha256.New()
_, err := h.Write(m.pbOuter.GetInnerMessage())
@@ -117,7 +117,7 @@ func (m *Manifest) verifyOuterIntegrity() error {
// decompressInner decompresses the inner payload, enforcing size limits
// to prevent decompression bombs.
func (m *Manifest) decompressInner() ([]byte, error) {
func (m *manifest) decompressInner() ([]byte, error) {
bb := bytes.NewBuffer(m.pbOuter.GetInnerMessage())
// By default the decoder decodes a payload under 128 KiB in full,
@@ -259,7 +259,7 @@ func validateEntryHashes(entry *MFFilePath) error {
}
}
func (m *Manifest) deserializeInner() error {
func (m *manifest) deserializeInner() error {
err := m.validateOuterHeader()
if err != nil {
return err
@@ -364,8 +364,10 @@ func validateMagic(dat []byte) bool {
// NewManifestFromReader reads a manifest from an io.Reader. It refuses a
// manifest larger than MaxManifestSize, reading at most one byte past it.
func NewManifestFromReader(input io.Reader) (*Manifest, error) {
m := &Manifest{}
//
//nolint:revive // unexported-return: exporting manifest is owner question 13
func NewManifestFromReader(input io.Reader) (*manifest, error) {
m := &manifest{}
dat, err := readAtMost(input, MaxManifestSize)
if err != nil {
@@ -424,7 +426,9 @@ type ManifestFromFileOptions struct {
// NewManifestFromFile reads a manifest from a file. It returns an error if
// opts is nil or its path is empty.
func NewManifestFromFile(opts *ManifestFromFileOptions) (*Manifest, error) {
//
//nolint:revive // unexported-return: exporting manifest is owner question 13
func NewManifestFromFile(opts *ManifestFromFileOptions) (*manifest, error) {
if opts == nil || opts.Path == "" {
return nil, errManifestPathEmpty
}
+1 -1
View File
@@ -85,7 +85,7 @@ func TestValidatePathMessagesVerbatim(t *testing.T) {
func TestSerializeInnerNotSetMessagesVerbatim(t *testing.T) {
t.Parallel()
m := &Manifest{}
m := &manifest{}
err := m.generate(context.Background())
require.ErrorIs(t, err, errInnerNotSet)
+12 -11
View File
@@ -15,10 +15,13 @@ var (
errSHA256NotSet = errors.New("SHA256 hash not set")
)
// Manifest is a manifest loaded by NewManifestFromFile or
// NewManifestFromReader. Its fields are private; only its methods are the
// library's API.
type Manifest struct {
// manifest holds the internal representation of a manifest file.
// Use NewManifestFromFile or NewManifestFromReader to load an existing
// manifest, or use Builder to create a new one.
//
// Whether this type should be exported is an open design question owned by
// the repository owner; see README design question 13.
type manifest struct {
pbInner *MFFile
pbOuter *MFFileOuter
output *bytes.Buffer
@@ -26,9 +29,7 @@ type Manifest struct {
fixedUUID []byte // if set, use this UUID instead of generating one
}
// String returns a short description giving the number of files the
// manifest lists, such as "<Manifest count=3>".
func (m *Manifest) String() string {
func (m *manifest) String() string {
count := 0
if m.pbInner != nil {
count = len(m.pbInner.GetFiles())
@@ -38,7 +39,7 @@ func (m *Manifest) String() string {
}
// Files returns all file entries from a loaded manifest.
func (m *Manifest) Files() []*MFFilePath {
func (m *manifest) Files() []*MFFilePath {
if m.pbInner == nil {
return nil
}
@@ -49,7 +50,7 @@ func (m *Manifest) Files() []*MFFilePath {
// Signer returns the fingerprint of the key that made a loaded manifest's
// signature, which loading the manifest checked, or nil if the manifest is
// not signed.
func (m *Manifest) Signer() []byte {
func (m *manifest) Signer() []byte {
if len(m.pbOuter.GetSignature()) == 0 {
return nil
}
@@ -59,14 +60,14 @@ func (m *Manifest) Signer() []byte {
// CreatedAt returns the createdAt timestamp of a loaded manifest, or nil
// if the manifest has none.
func (m *Manifest) CreatedAt() *Timestamp {
func (m *manifest) CreatedAt() *Timestamp {
return m.pbInner.GetCreatedAt()
}
// signatureString generates the canonical string used for signing/verification.
// Format: MAGIC-UUID-MULTIHASH where UUID and multihash are hex-encoded.
// Requires pbOuter to be set with Uuid and Sha256 fields.
func (m *Manifest) signatureString() (string, error) {
func (m *manifest) signatureString() (string, error) {
if m.pbOuter == nil {
return "", errOuterNotSet
}
-49
View File
@@ -1,49 +0,0 @@
package mfer_test
import (
"bytes"
"context"
"testing"
"github.com/stretchr/testify/require"
"sneak.berlin/go/mfer/mfer"
)
// archive keeps a loaded manifest in a field, as a type in a program that
// imports mfer would.
type archive struct {
manifest *mfer.Manifest
}
// fileCount takes a loaded manifest, as a function in such a program would.
func fileCount(m *mfer.Manifest) int {
return len(m.Files())
}
// A program that imports mfer can name the type of a loaded manifest, keep
// one in a struct field, pass it to its own functions, and read it through
// its methods.
func TestManifestIsUsableOutsideThePackage(t *testing.T) {
t.Parallel()
content := []byte("hello\n")
builder := mfer.NewBuilder()
_, err := builder.AddFile("hello.txt", mfer.FileSize(len(content)),
mfer.ModTime{}, 0, bytes.NewReader(content), nil)
require.NoError(t, err)
var written bytes.Buffer
require.NoError(t, builder.Build(context.Background(), &written))
loaded, err := mfer.NewManifestFromReader(&written)
require.NoError(t, err)
a := archive{manifest: loaded}
require.Equal(t, 1, fileCount(a.manifest))
require.Equal(t, "hello.txt", a.manifest.Files()[0].GetPath())
require.Nil(t, a.manifest.Signer())
require.Nil(t, a.manifest.CreatedAt())
require.Equal(t, "<Manifest count=1>", a.manifest.String())
}
+1 -1
View File
@@ -21,7 +21,7 @@ func TestSignatureString(t *testing.T) {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
require.NoError(t, err)
m := &Manifest{pbOuter: &MFFileOuter{Uuid: manifestUUID[:], Sha256: sha256Hash}}
m := &manifest{pbOuter: &MFFileOuter{Uuid: manifestUUID[:], Sha256: sha256Hash}}
got, err := m.signatureString()
require.NoError(t, err)
+3 -3
View File
@@ -51,7 +51,7 @@ func newTimestampFromTime(t time.Time) *Timestamp {
}
}
func (m *Manifest) generate(ctx context.Context) error {
func (m *manifest) generate(ctx context.Context) error {
if m.pbInner == nil {
return errInnerNotSet
}
@@ -78,7 +78,7 @@ func (m *Manifest) generate(ctx context.Context) error {
return nil
}
func (m *Manifest) generateOuter(ctx context.Context) error {
func (m *manifest) generateOuter(ctx context.Context) error {
if m.pbInner == nil {
return errInternal
}
@@ -145,7 +145,7 @@ func (m *Manifest) generateOuter(ctx context.Context) error {
// signOuter signs the outer message with the secret key in the signing
// options and embeds the signature, the key's fingerprint and its public
// key.
func (m *Manifest) signOuter(ctx context.Context) error {
func (m *manifest) signOuter(ctx context.Context) error {
// Unlocking a protected key can take a while; do not start once ctx
// has ended.
err := ctx.Err()