Skip to content

Commit 611fa09

Browse files
authored
Throw error when the max read amount is greater than ByteBuffer can tolerate (#2911)
Motivation: As described in issue [#2878](#2878), NIOFileSystem crashes when reading more than `ByteBuffer` capacity. Modifications: - Add a new static property, `byteBufferCapacity`, to `ByteCount` representing the maximum amount of bytes that can be written to `ByteBuffer`. - Throw a `FileSystemError` in `ReadableFileHandleProtocol.readToEnd(fromAbsoluteOffset:maximumSizeAllowed:)` when `maximumSizeAllowed` is more than `ByteCount.byteBufferCapacity`. Result: NIOFileSystem will `throw` instead of crashing.
1 parent 9ff5fdd commit 611fa09

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

Sources/NIOFileSystem/ByteCount.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ public struct ByteCount: Hashable, Sendable {
8080
}
8181
}
8282

83+
extension ByteCount {
84+
/// A ``ByteCount`` for the maximum amount of bytes that can be written to `ByteBuffer`.
85+
internal static var byteBufferCapacity: ByteCount {
86+
#if arch(arm) || arch(i386) || arch(arm64_32)
87+
// on 32-bit platforms we can't make use of a whole UInt32.max (as it doesn't fit in an Int)
88+
let byteBufferMaxIndex = UInt32(Int.max)
89+
#else
90+
// on 64-bit platforms we're good
91+
let byteBufferMaxIndex = UInt32.max
92+
#endif
93+
94+
return ByteCount(bytes: Int64(byteBufferMaxIndex))
95+
}
96+
}
97+
8398
extension ByteCount: AdditiveArithmetic {
8499
public static var zero: ByteCount { ByteCount(bytes: 0) }
85100

Sources/NIOFileSystem/FileHandleProtocol.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,9 @@ extension ReadableFileHandleProtocol {
328328
/// - offset: The absolute offset into the file to read from. Defaults to zero.
329329
/// - maximumSizeAllowed: The maximum size of file to read, as a ``ByteCount``.
330330
/// - Returns: The bytes read from the file.
331-
/// - Throws: ``FileSystemError`` with code ``FileSystemError/Code-swift.struct/resourceExhausted`` if there
332-
/// are more bytes to read than `maximumBytesAllowed`.
333-
/// ``FileSystemError/Code-swift.struct/unsupported`` if file is unseekable and
334-
/// `offset` is not 0.
331+
/// - Throws: ``FileSystemError`` with code ``FileSystemError/Code-swift.struct/resourceExhausted``
332+
/// if `maximumSizeAllowed` is more than can be written to `ByteBuffer`. Or if there are more bytes to read than
333+
/// `maximumBytesAllowed`.
335334
public func readToEnd(
336335
fromAbsoluteOffset offset: Int64 = 0,
337336
maximumSizeAllowed: ByteCount
@@ -340,6 +339,20 @@ extension ReadableFileHandleProtocol {
340339
let fileSize = Int64(info.size)
341340
let readSize = max(Int(fileSize - offset), 0)
342341

342+
if maximumSizeAllowed > .byteBufferCapacity {
343+
throw FileSystemError(
344+
code: .resourceExhausted,
345+
message: """
346+
The maximum size allowed (\(maximumSizeAllowed)) is more than the maximum \
347+
amount of bytes that can be written to ByteBuffer \
348+
(\(ByteCount.byteBufferCapacity)). You can read the file in smaller chunks by \
349+
calling readChunks().
350+
""",
351+
cause: nil,
352+
location: .here()
353+
)
354+
}
355+
343356
if readSize > maximumSizeAllowed.bytes {
344357
throw FileSystemError(
345358
code: .resourceExhausted,

Tests/NIOFileSystemIntegrationTests/FileSystemTests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,21 @@ extension FileSystemTests {
18031803
XCTAssertEqual(byteCount, Int(size))
18041804
}
18051805
}
1806+
1807+
func testReadMoreThanByteBufferCapacity() async throws {
1808+
let path = try await self.fs.temporaryFilePath()
1809+
1810+
try await self.fs.withFileHandle(forReadingAndWritingAt: path) { fileHandle in
1811+
await XCTAssertThrowsFileSystemErrorAsync {
1812+
// Set `maximumSizeAllowed` to 1 byte more than can be written to `ByteBuffer`.
1813+
try await fileHandle.readToEnd(
1814+
maximumSizeAllowed: .byteBufferCapacity + .bytes(1)
1815+
)
1816+
} onError: { error in
1817+
XCTAssertEqual(error.code, .resourceExhausted)
1818+
}
1819+
}
1820+
}
18061821
}
18071822

18081823
#if !canImport(Darwin) && swift(<5.9.2)

0 commit comments

Comments
 (0)