package hash import ( "fmt" "os" ) // HashPackage 是哈希值包,对应需求1-AC5。 // 仅包含哈希与元数据,绝不包含原始内容(需求20-AC2)。 type HashPackage struct { FileSHA256 string `json:"file_sha256"` MerkleRoot string `json:"merkle_root"` SegmentHashes []string `json:"segment_hashes"` PerceptualHash string `json:"perceptual_hash,omitempty"` FileSize int64 `json:"file_size"` SegmentSize int `json:"segment_size"` } // Options 控制哈希计算行为。 type Options struct { SegmentSize int // 分段大小;<=0 用默认 PerceptualHash string // 上层已抽帧并算好的感知哈希(可选) } // ComputeFile 对母版文件计算完整哈希值包(文件哈希 + 分段 Merkle)。 // 感知哈希需上层先抽帧,再通过 opts.PerceptualHash 传入或单独调用 AHash/DHash。 func ComputeFile(path string, opts Options) (*HashPackage, error) { info, err := os.Stat(path) if err != nil { return nil, fmt.Errorf("hash: stat file: %w", err) } if info.IsDir() { return nil, fmt.Errorf("hash: path is a directory: %s", path) } if info.Size() == 0 { return nil, ErrEmptyInput } segSize := opts.SegmentSize if segSize <= 0 { segSize = DefaultSegmentSize } fileHash, err := FileSHA256(path) if err != nil { return nil, fmt.Errorf("hash: file sha256: %w", err) } segments, err := SegmentHashes(path, segSize) if err != nil { return nil, fmt.Errorf("hash: segment hashes: %w", err) } tree := BuildMerkleTree(segments) return &HashPackage{ FileSHA256: fileHash, MerkleRoot: tree.Root(), SegmentHashes: segments, PerceptualHash: opts.PerceptualHash, FileSize: info.Size(), SegmentSize: segSize, }, nil } // Validate 校验哈希值包的完整性(需求2-AC5:缺文件哈希/Merkle根则非法)。 func (p *HashPackage) Validate() error { if p == nil { return fmt.Errorf("hash: nil package") } if p.FileSHA256 == "" { return fmt.Errorf("hash: missing file_sha256") } if p.MerkleRoot == "" { return fmt.Errorf("hash: missing merkle_root") } return nil }