diff --git a/Sources/NIOCore/ByteBuffer-aux.swift b/Sources/NIOCore/ByteBuffer-aux.swift index e95ab8f37d..39db01bc4f 100644 --- a/Sources/NIOCore/ByteBuffer-aux.swift +++ b/Sources/NIOCore/ByteBuffer-aux.swift @@ -58,6 +58,43 @@ extension ByteBuffer { return result } + #if compiler(>=6.2) + @inlinable + @available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *) + public mutating func readInlineArray< + let count: Int, + IntegerType: FixedWidthInteger + >( + endianness: Endianness = .big, + as: InlineArray.Type = InlineArray.self + ) -> InlineArray? { + // use stride to account for padding bytes + let stride = MemoryLayout.stride + let bytesRequired = stride * count + + guard self.readableBytes >= bytesRequired else { + return nil + } + + let inlineArray = InlineArray { (outputSpan: inout OutputSpan) in + for index in 0..= bytesRequired' above, + // so this is safe to force-unwrap as it's guaranteed to exist + let integer = self.getInteger( + // this is less than 'bytesRequired' so is safe to multiply + at: stride &* index, + endianness: endianness, + as: IntegerType.self + )! + outputSpan.append(integer) + } + } + // already made sure of 'self.readableBytes >= bytesRequired' above + self._moveReaderIndex(forwardBy: bytesRequired) + return inlineArray + } + #endif + /// Returns the Bytes at the current reader index without advancing it. /// /// This method is equivalent to calling `getBytes(at: readerIndex, ...)` diff --git a/Tests/NIOCoreTests/ByteBufferTest.swift b/Tests/NIOCoreTests/ByteBufferTest.swift index 73694bbd54..7885d463ce 100644 --- a/Tests/NIOCoreTests/ByteBufferTest.swift +++ b/Tests/NIOCoreTests/ByteBufferTest.swift @@ -4461,5 +4461,66 @@ extension ByteBufferTest { let result = self.buf.readBytes(length: 4) XCTAssertEqual(Array(0..<4), result!) } + + @available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *) + func testReadInlineArrayOfUInt8() throws { + let bytes = (0..<10).map { _ in UInt8.random(in: .min ... .max) } + + let startWriterIndex = self.buf.writerIndex + let written = self.buf.writeBytes(bytes) + XCTAssertEqual(startWriterIndex + written, self.buf.writerIndex) + XCTAssertEqual(written, self.buf.readableBytes) + + let result = try XCTUnwrap( + self.buf.readInlineArray(as: InlineArray<10, UInt8>.self) + ) + XCTAssertEqual(10, result.count) + for idx in result.indices { + XCTAssertEqual(bytes[idx], result[idx]) + } + XCTAssertEqual(0, self.buf.readableBytes) + XCTAssertEqual(10, self.buf.readerIndex) + } + + @available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *) + func testReadInlineArrayOfUInt64() throws { + let bytes = (0..<15).map { _ in UInt64.random(in: .min ... .max) } + + let startWriterIndex = self.buf.writerIndex + var written = 0 + for byte in bytes { + written += self.buf.writeInteger(byte) + } + XCTAssertEqual(startWriterIndex + written, self.buf.writerIndex) + XCTAssertEqual(written, self.buf.readableBytes) + + let result = try XCTUnwrap( + self.buf.readInlineArray(as: InlineArray<15, UInt64>.self) + ) + XCTAssertEqual(15, result.count) + for idx in result.indices { + XCTAssertEqual(bytes[idx], result[idx]) + } + XCTAssertEqual(0, self.buf.readableBytes) + XCTAssertEqual(120, self.buf.readerIndex) + } + + @available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *) + func testNotEnoughBytesToReadInlineArrayOfInt32() throws { + let startWriterIndex = self.buf.writerIndex + var written = 0 + /// Write 15 bytes. This won't be enough to read an `InlineArray<5, Int32>`. + for _ in 0..<15 { + written += self.buf.writeInteger(UInt8.random(in: .min ... .max)) + } + XCTAssertEqual(startWriterIndex + written, self.buf.writerIndex) + XCTAssertEqual(written, self.buf.readableBytes) + + let result = self.buf.readInlineArray(as: InlineArray<5, Int32>.self) + + XCTAssertNil(result) + XCTAssertEqual(written, self.buf.readableBytes) + XCTAssertEqual(0, self.buf.readerIndex) + } } #endif