Skip to content

Commit cddbde5

Browse files
Adding RawSpan support to writeBytes in ByteBuffer (#3269)
Motivation: With the new Swift version (6.2) we get access to spans for safely interacting with the contiguous memory owned by other types. Allow users write bytes to the ByteBuffer with RawSpans. Modifications: * ByteBuffer-core: Overload the setBytes, _setBytes, and _setBytesAssumingUniqueBufferAccess to use RawSpan instead of UnsafeRawBufferPointer * ByteBuffer-aux: Overload the writeBytes to use RawSpan instead of UnsafeRawBufferPointer Result: Users can now write the contiguous memory of other types into ByteBuffer using the new RawSpan overloads, removing the need for unsafe pointer handling.
1 parent 357955a commit cddbde5

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

Sources/NIOCore/ByteBuffer-aux.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,22 @@ extension ByteBuffer {
517517
return written
518518
}
519519

520+
#if compiler(>=6.2)
521+
/// Write `bytes` into this `ByteBuffer`. Moves the writer index forward by the number of bytes written.
522+
///
523+
/// - Parameters:
524+
/// - bytes: A `RawSpan`
525+
/// - Returns: The number of bytes written or `bytes.byteCount`.
526+
@discardableResult
527+
@inlinable
528+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
529+
public mutating func writeBytes(_ bytes: RawSpan) -> Int {
530+
let written = self.setBytes(bytes, at: self.writerIndex)
531+
self._moveWriterIndex(forwardBy: written)
532+
return written
533+
}
534+
#endif
535+
520536
/// Writes `byte` `count` times. Moves the writer index forward by the number of bytes written.
521537
///
522538
/// - Parameters:

Sources/NIOCore/ByteBuffer-core.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,34 @@ public struct ByteBuffer {
508508
targetPtr.copyMemory(from: bytes)
509509
}
510510

511+
#if compiler(>=6.2)
512+
@inlinable
513+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
514+
mutating func _setBytes(_ bytes: RawSpan, at index: _Index) -> _Capacity {
515+
let bytesCount = bytes.byteCount
516+
let newEndIndex: _Index = index + _toIndex(bytesCount)
517+
if !isKnownUniquelyReferenced(&self._storage) {
518+
let extraCapacity = newEndIndex > self._slice.upperBound ? newEndIndex - self._slice.upperBound : 0
519+
self._copyStorageAndRebase(extraCapacity: extraCapacity)
520+
}
521+
self._ensureAvailableCapacity(_Capacity(bytesCount), at: index)
522+
self._setBytesAssumingUniqueBufferAccess(bytes, at: index)
523+
return _toCapacity(bytesCount)
524+
}
525+
526+
@inlinable
527+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
528+
mutating func _setBytesAssumingUniqueBufferAccess(_ bytes: RawSpan, at index: _Index) {
529+
let targetPtr = UnsafeMutableRawBufferPointer(
530+
rebasing: self._slicedStorageBuffer.dropFirst(Int(index))
531+
)
532+
533+
bytes.withUnsafeBytes {
534+
targetPtr.copyMemory(from: $0)
535+
}
536+
}
537+
#endif
538+
511539
@inline(never)
512540
@inlinable
513541
@_specialize(where Bytes == CircularBuffer<UInt8>)
@@ -1035,6 +1063,16 @@ extension ByteBuffer {
10351063
Int(self._setBytes(bytes, at: _toIndex(index)))
10361064
}
10371065

1066+
#if compiler(>=6.2)
1067+
/// Copy `bytes` from a `RawSpan` into the `ByteBuffer` at `index`. Does not move the writer index.
1068+
@discardableResult
1069+
@inlinable
1070+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
1071+
public mutating func setBytes(_ bytes: RawSpan, at index: Int) -> Int {
1072+
Int(self._setBytes(bytes, at: _toIndex(index)))
1073+
}
1074+
#endif
1075+
10381076
/// Move the reader index forward by `offset` bytes.
10391077
///
10401078
/// - warning: By contract the bytes between (including) `readerIndex` and (excluding) `writerIndex` must be

Tests/NIOCoreTests/ByteBufferTest.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4420,3 +4420,46 @@ extension ByteBufferTest {
44204420
XCTAssertNil(result, "peekUUIDBytes() should return nil when fewer than 16 bytes are readable.")
44214421
}
44224422
}
4423+
4424+
#if compiler(>=6.2)
4425+
extension ByteBufferTest {
4426+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
4427+
func testWriteBytesRawSpan() {
4428+
// Write 16 bytes into buffer using a RawSpan
4429+
let byteArray: [UInt8] = Array(0..<16)
4430+
let rawSpan = byteArray.span.bytes
4431+
let writeLength = self.buf.writeBytes(rawSpan)
4432+
XCTAssertEqual(writeLength, rawSpan.byteCount)
4433+
4434+
let read = self.buf.readBytes(length: 4)
4435+
XCTAssertEqual([0, 1, 2, 3], read)
4436+
XCTAssertEqual(buf.readerIndex, 4)
4437+
4438+
let peek = self.buf.peekBytes(length: 4)
4439+
XCTAssertEqual([4, 5, 6, 7], peek)
4440+
XCTAssertEqual(buf.readerIndex, 4)
4441+
4442+
let rest = self.buf.readBytes(length: 12)
4443+
XCTAssertEqual([4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], rest)
4444+
XCTAssertEqual(buf.readerIndex, 16)
4445+
}
4446+
4447+
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
4448+
func testSetBytesRawSpan() {
4449+
// Write 4 bytes using setBytes
4450+
let byteArray: [UInt8] = Array(0..<4)
4451+
let rawSpan = byteArray.span.bytes
4452+
let writeLength = self.buf.setBytes(rawSpan, at: 0)
4453+
XCTAssertEqual(writeLength, rawSpan.byteCount)
4454+
4455+
// Should not be readable as writer index is not moved by setBytes
4456+
let shouldBeNil = self.buf.readBytes(length: 4)
4457+
XCTAssertNil(shouldBeNil)
4458+
4459+
// Move writer index
4460+
self.buf.moveWriterIndex(to: 4)
4461+
let result = self.buf.readBytes(length: 4)
4462+
XCTAssertEqual(Array(0..<4), result!)
4463+
}
4464+
}
4465+
#endif

0 commit comments

Comments
 (0)