import Foundation /// Checksum algorithms supported for offload/MHL. Element names match /// ASC MHL v2.0 spec hash-element vocabulary. public enum HashAlgorithm: String, Sendable, CaseIterable { case xxh64 case xxh3 case xxh128 case md5 case sha1 public var mhlElement: String { rawValue } /// In-memory one-shot (test/reference path). public static func hashInMemory(_ data: Data, algorithm: HashAlgorithm) -> String { switch algorithm { case .xxh64: return XXHash64.hexString(XXHash64.hash(data)) case .xxh3: return XXH3.hexString64(XXH3.hash64(data)) case .xxh128: return XXH3.hash128(data).hexString case .md5: return MD5.hexString(data) case .sha1: return SHA1.hexString(data) } } } extension FileHasher { /// Chunked file hash with algorithm dispatch. public static func hash(path: String, algorithm: HashAlgorithm) throws -> String { switch algorithm { case .xxh64: return try xxh64(path: path) case .md5: return try md5(path: path) case .xxh3: let s = XXH3.Streaming(bits: .h64) try streamFile(path: path) { s.update($0) } return XXH3.hexString64(s.digest64()) case .xxh128: let s = XXH3.Streaming(bits: .h128) try streamFile(path: path) { s.update($0) } return s.digest128().hexString case .sha1: var sha = SHA1() try streamFile(path: path) { sha.update($0) } return sha.finalize().map { String(format: "%02x", $0) }.joined() } } static func streamFile(path: String, _ body: (Data) -> Void) throws { guard let handle = FileHandle(forReadingAtPath: path) else { throw OffloadError.fileNotFound(path) } defer { try? handle.close() } while true { guard let chunk = try handle.read(upToCount: chunkSize), !chunk.isEmpty else { break } body(chunk) } } }