rainbow-dragon/Sources/ForgeOffload/HashAlgorithm.swift

67 lines
2.1 KiB
Swift
Raw Permalink Normal View History

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)
}
}
}