Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ByteBuffer methods getUTF8ValidatedString and readUTF8ValidatedString #2973

Merged
merged 14 commits into from
Nov 21, 2024
Merged
45 changes: 45 additions & 0 deletions Sources/NIOCore/ByteBuffer-aux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -902,3 +902,48 @@ extension Optional where Wrapped == ByteBuffer {
}
}
}

#if compiler(>=6)
extension ByteBuffer {
/// Get the string at `index` from this `ByteBuffer` decoding using the UTF-8 encoding. Does not move the reader index.
/// The selected bytes must be readable or else `nil` will be returned.
///
/// This is an alternative to `ByteBuffer.getString(at:length:)` which ensures the returned string is valid UTF8
///
/// - Parameters:
/// - index: The starting index into `ByteBuffer` containing the string of interest.
/// - length: The number of bytes making up the string.
/// - Returns: A `String` value containing the UTF-8 decoded selected bytes from this `ByteBuffer` or `nil` if
/// the requested bytes are not readable.
@inlinable
@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)
public func getUTF8ValidatedString(at index: Int, length: Int) -> String? {
guard let range = self.rangeWithinReadableBytes(index: index, length: length) else {
return nil
}
return self.withUnsafeReadableBytes { pointer in
assert(range.lowerBound >= 0 && (range.upperBound - range.lowerBound) <= pointer.count)
return String(validating: UnsafeRawBufferPointer(fastRebase: pointer[range]), as: Unicode.UTF8.self)
adam-fowler marked this conversation as resolved.
Show resolved Hide resolved
}
}
adam-fowler marked this conversation as resolved.
Show resolved Hide resolved

/// Read `length` bytes off this `ByteBuffer`, decoding it as `String` using the UTF-8 encoding. Move the reader index
/// forward by `length`.
///
/// This is an alternative to `ByteBuffer.readString(length:)` which ensures the returned string is valid UTF8. If the
/// string is not valid UTF8 then the reader index is not advanced.
///
/// - Parameters:
/// - length: The number of bytes making up the string.
/// - Returns: A `String` value deserialized from this `ByteBuffer` or `nil` if there aren't at least `length` bytes readable.
@inlinable
@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)
public mutating func readUTF8ValidatedString(length: Int) -> String? {
guard let result = self.getUTF8ValidatedString(at: self.readerIndex, length: length) else {
return nil
}
self.moveReaderIndex(forwardBy: length)
return result
}
}
#endif // compiler(>=6)
20 changes: 20 additions & 0 deletions Tests/NIOCoreTests/ByteBufferTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,26 @@ class ByteBufferTest: XCTestCase {
XCTAssertEqual("a", buf.readString(length: 1))
}

@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)
func testReadUTF8ValidatedString() throws {
buf.clear()
let expected = "hello"
buf.writeString(expected)
let actual = buf.readUTF8ValidatedString(length: expected.utf8.count)
XCTAssertEqual(expected, actual)
XCTAssertEqual("", buf.readUTF8ValidatedString(length: 0))
XCTAssertNil(buf.readUTF8ValidatedString(length: 1))
}

@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *)
func testReadUTF8InvalidString() throws {
buf.clear()
buf.writeBytes([UInt8](repeating: 255, count: 16))
let actual = buf.readUTF8ValidatedString(length: 16)
XCTAssertNil(actual)
XCTAssertEqual(buf.readableBytes, 16)
}

func testSetIntegerBeyondCapacity() throws {
var buf = ByteBufferAllocator().buffer(capacity: 32)
XCTAssertLessThan(buf.capacity, 200)
Expand Down