diff --git a/Sources/tart/OCI/Digest.swift b/Sources/tart/OCI/Digest.swift index 51c4201..5fb8d92 100644 --- a/Sources/tart/OCI/Digest.swift +++ b/Sources/tart/OCI/Digest.swift @@ -7,6 +7,8 @@ enum DigestError: Error { } class Digest { + static let fileReadChunkSize = 4 * 1024 * 1024 + var hash: SHA256 = SHA256() func update(_ data: Data) { @@ -22,7 +24,16 @@ class Digest { } static func hash(_ url: URL) throws -> String { - hash(try Data(contentsOf: url)) + let fh = try FileHandle(forReadingFrom: url) + defer { try? fh.close() } + + let digest = Digest() + + while let data = try fh.read(upToCount: fileReadChunkSize), !data.isEmpty { + digest.update(data) + } + + return digest.finalize() } static func hash(_ url: URL, offset: UInt64, size: UInt64) throws -> String { diff --git a/Tests/TartTests/DigestTests.swift b/Tests/TartTests/DigestTests.swift index 1c6fa56..9fedb4d 100644 --- a/Tests/TartTests/DigestTests.swift +++ b/Tests/TartTests/DigestTests.swift @@ -21,4 +21,13 @@ final class DigestTests: XCTestCase { XCTAssertEqual(Digest.hash(data), "sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592") } + + func testFileHashingReadsMultipleChunks() throws { + let data = Data(repeating: 0xab, count: Digest.fileReadChunkSize + 1) + let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try data.write(to: url) + defer { try? FileManager.default.removeItem(at: url) } + + XCTAssertEqual(try Digest.hash(url), Digest.hash(data)) + } }