package osimage import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "os" "strings" ) func VerifyTargetSHA256(ctx context.Context, targetPath string, expectedSize int64, bufferSize int, progress ProgressFunc) (string, error) { if targetPath == "" { return "", fmt.Errorf("target path is required") } if expectedSize <= 0 { return "", fmt.Errorf("expected raw size is required for verification") } if bufferSize <= 0 { bufferSize = 4 * 1024 * 1024 } f, err := os.Open(targetPath) if err != nil { return "", fmt.Errorf("open target for verify: %w", err) } defer f.Close() h := sha256.New() buf := make([]byte, bufferSize) var readTotal int64 limited := io.LimitReader(f, expectedSize) for { select { case <-ctx.Done(): return "", ctx.Err() default: } n, err := limited.Read(buf) if n > 0 { if _, werr := h.Write(buf[:n]); werr != nil { return "", fmt.Errorf("hash target: %w", werr) } readTotal += int64(n) if progress != nil { progress(Progress{ Stage: "verify", BytesComplete: readTotal, BytesTotal: expectedSize, }) } } if err == io.EOF { break } if err != nil { return "", fmt.Errorf("read target: %w", err) } } if readTotal != expectedSize { return "", fmt.Errorf("verify size mismatch: got %d want %d", readTotal, expectedSize) } return hex.EncodeToString(h.Sum(nil)), nil } func ValidateApplyOptions(opts ApplyOptions) error { if opts.URL == "" { return fmt.Errorf("url is required") } if opts.TargetPath == "" { return fmt.Errorf("target path is required") } if opts.ExpectedRawSHA256 == "" { return fmt.Errorf("expected raw sha256 is required") } if opts.ExpectedRawSize <= 0 { return fmt.Errorf("expected raw size must be > 0") } return nil } func VerifySHA256(got, expected string) error { expected = NormalizeSHA256(expected) if expected == "" { return nil } got = NormalizeSHA256(got) if got != expected { return fmt.Errorf("sha256 mismatch: got %s want %s", got, expected) } return nil } func NormalizeSHA256(s string) string { return strings.ToLower(strings.TrimSpace(s)) }