Skip to content
go-libfossil Artifact [82a04d70c0]
Login

Artifact [82a04d70c0]

Artifact 82a04d70c086ca26147c78f25ac6034ea4627671b401206d3ef27090549d1564:


package verify

import (
	"fmt"

	"github.com/danmestas/go-libfossil/internal/content"
	libfossil "github.com/danmestas/go-libfossil/internal/fsltype"
	"github.com/danmestas/go-libfossil/internal/hash"
	"github.com/danmestas/go-libfossil/internal/repo"
)

// checkBlobs verifies blob integrity: content hashing and delta application.
// Walks every non-phantom blob, expands delta chains, hashes content, and compares against stored UUID.
func checkBlobs(r *repo.Repo, report *Report) error {
	if r == nil {
		panic("checkBlobs: nil *repo.Repo")
	}
	if report == nil {
		panic("checkBlobs: nil *Report")
	}

	rows, err := r.DB().Query("SELECT rid, uuid FROM blob WHERE size >= 0")
	if err != nil {
		return fmt.Errorf("checkBlobs: query: %w", err)
	}
	defer rows.Close()

	type blobEntry struct {
		rid  int64
		uuid string
	}
	var entries []blobEntry
	for rows.Next() {
		var e blobEntry
		if err := rows.Scan(&e.rid, &e.uuid); err != nil {
			return fmt.Errorf("checkBlobs: scan: %w", err)
		}
		entries = append(entries, e)
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("checkBlobs: rows: %w", err)
	}

	for _, e := range entries {
		report.BlobsChecked++
		rid := libfossil.FslID(e.rid)
		data, err := content.Expand(r.DB(), rid)
		if err != nil {
			report.BlobsFailed++
			report.addIssue(Issue{
				Kind:    IssueBlobCorrupt,
				RID:     rid,
				UUID:    e.uuid,
				Table:   "blob",
				Message: fmt.Sprintf("rid %d: expand failed: %v", e.rid, err),
			})
			continue
		}
		var computed string
		if len(e.uuid) == 64 {
			computed = hash.SHA3(data)
		} else {
			computed = hash.SHA1(data)
		}
		if computed != e.uuid {
			report.BlobsFailed++
			report.addIssue(Issue{
				Kind:    IssueHashMismatch,
				RID:     rid,
				UUID:    e.uuid,
				Table:   "blob",
				Message: fmt.Sprintf("rid %d: hash mismatch (stored=%s computed=%s)", e.rid, e.uuid, computed),
			})
			continue
		}
		report.BlobsOK++
	}

	// Postcondition: every checked blob is either OK or failed.
	if report.BlobsChecked != report.BlobsOK+report.BlobsFailed {
		panic("checkBlobs: postcondition: BlobsChecked != BlobsOK + BlobsFailed")
	}

	return nil
}