Coverage Report

Created: 2026-10-13 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOCore/ByteBuffer-views.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
15
/// A view into a portion of a `ByteBuffer`.
16
///
17
/// A `ByteBufferView` is useful whenever a `Collection where Element == UInt8` representing a portion of a
18
/// `ByteBuffer` is needed.
19
public struct ByteBufferView: RandomAccessCollection, Sendable {
20
    public typealias Element = UInt8
21
    public typealias Index = Int
22
    public typealias SubSequence = ByteBufferView
23
24
    @usableFromInline var _buffer: ByteBuffer
25
    @usableFromInline var _range: Range<Index>
26
27
    @inlinable
28
0
    internal init(buffer: ByteBuffer, range: Range<Index>) {
29
0
        precondition(range.lowerBound >= 0 && range.upperBound <= buffer.capacity)
30
0
        self._buffer = buffer
31
0
        self._range = range
32
0
    }
33
34
    /// Creates a `ByteBufferView` from the readable bytes of the given `buffer`.
35
    @inlinable
36
0
    public init(_ buffer: ByteBuffer) {
37
0
        self = ByteBufferView(buffer: buffer, range: buffer.readerIndex..<buffer.writerIndex)
38
0
    }
39
40
    @inlinable
41
    public func withUnsafeBytes<R, ErrorType: Error>(
42
        _ body: (UnsafeRawBufferPointer) throws(ErrorType) -> R
43
0
    ) throws(ErrorType) -> R {
44
0
        try self._buffer.withVeryUnsafeBytes { (ptr: UnsafeRawBufferPointer) throws(ErrorType) -> R in
45
0
            try body(
46
0
                UnsafeRawBufferPointer(
47
0
                    start: ptr.baseAddress!.advanced(by: self._range.lowerBound),
48
0
                    count: self._range.count
49
0
                )
50
0
            )
51
0
        }
52
0
    }
53
54
    @inlinable
55
0
    public var startIndex: Index {
56
0
        self._range.lowerBound
57
0
    }
58
59
    @inlinable
60
0
    public var endIndex: Index {
61
0
        self._range.upperBound
62
0
    }
63
64
    @inlinable
65
0
    public func index(after i: Index) -> Index {
66
0
        i + 1
67
0
    }
68
69
    @inlinable
70
0
    public var count: Int {
71
0
        // Unchecked is safe here: Range enforces that upperBound is strictly greater than
72
0
        // lower bound, and we guarantee that _range.lowerBound >= 0.
73
0
        self._range.upperBound &- self._range.lowerBound
74
0
    }
75
76
    @inlinable
77
    public subscript(position: Index) -> UInt8 {
78
0
        get {
79
0
            guard position >= self._range.lowerBound && position < self._range.upperBound else {
80
0
                preconditionFailure("index \(position) out of range")
81
0
            }
82
0
            return self._buffer.getInteger(at: position)!  // range check above
83
0
        }
84
0
        set {
85
0
            guard position >= self._range.lowerBound && position < self._range.upperBound else {
86
0
                preconditionFailure("index \(position) out of range")
87
0
            }
88
0
            self._buffer.setInteger(newValue, at: position)
89
0
        }
90
    }
91
92
    @inlinable
93
    public subscript(range: Range<Index>) -> ByteBufferView {
94
0
        get {
95
0
            ByteBufferView(buffer: self._buffer, range: range)
96
0
        }
97
0
        set {
98
0
            self.replaceSubrange(range, with: newValue)
99
0
        }
100
    }
101
102
    @inlinable
103
    public func withContiguousStorageIfAvailable<R, ErrorType: Error>(
104
        _ body: (UnsafeBufferPointer<UInt8>) throws(ErrorType) -> R
105
0
    ) throws(ErrorType) -> R? {
106
0
        try self.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) throws(ErrorType) -> R in
107
0
            try body(bytes.bindMemory(to: UInt8.self))
108
0
        }
109
0
    }
110
111
    @inlinable
112
0
    public func _customIndexOfEquatableElement(_ element: Element) -> Index?? {
113
0
        .some(
114
0
            self.withUnsafeBytes { ptr -> Index? in
115
0
                ptr.firstIndex(of: element).map { $0 + self._range.lowerBound }
116
0
            }
117
0
        )
118
0
    }
119
120
    @inlinable
121
0
    public func _customLastIndexOfEquatableElement(_ element: Element) -> Index?? {
122
0
        .some(
123
0
            self.withUnsafeBytes { ptr -> Index? in
124
0
                ptr.lastIndex(of: element).map { $0 + self._range.lowerBound }
125
0
            }
126
0
        )
127
0
    }
128
129
    @inlinable
130
0
    public func _customContainsEquatableElement(_ element: Element) -> Bool? {
131
0
        .some(
132
0
            self.withUnsafeBytes { ptr -> Bool in
133
0
                ptr.contains(element)
134
0
            }
135
0
        )
136
0
    }
137
138
    @inlinable
139
    public func _copyContents(
140
        initializing ptr: UnsafeMutableBufferPointer<UInt8>
141
0
    ) -> (Iterator, UnsafeMutableBufferPointer<UInt8>.Index) {
142
0
        precondition(ptr.count >= self.count)
143
0
144
0
        let bytesToWrite = self.count
145
0
146
0
        let endIndex = self.withContiguousStorageIfAvailable { ourBytes in
147
0
            ptr.initialize(from: ourBytes).1
148
0
        }
149
0
        precondition(endIndex == bytesToWrite)
150
0
151
0
        let iterator = self[self.endIndex..<self.endIndex].makeIterator()
152
0
        return (iterator, bytesToWrite)
153
0
    }
154
155
    // These are implemented as no-ops for performance reasons.
156
    @inlinable
157
0
    public func _failEarlyRangeCheck(_ index: Index, bounds: Range<Index>) {}
158
159
    @inlinable
160
0
    public func _failEarlyRangeCheck(_ index: Index, bounds: ClosedRange<Index>) {}
161
162
    @inlinable
163
0
    public func _failEarlyRangeCheck(_ range: Range<Index>, bounds: Range<Index>) {}
164
}
165
166
extension ByteBufferView: MutableCollection {}
167
168
extension ByteBufferView: RangeReplaceableCollection {
169
    // required by `RangeReplaceableCollection`
170
    @inlinable
171
0
    public init() {
172
0
        self = ByteBufferView(ByteBuffer())
173
0
    }
174
175
    /// Reserves enough space in the underlying `ByteBuffer` such that this view can
176
    /// store the specified number of bytes without reallocation.
177
    ///
178
    /// See the documentation for ``ByteBuffer/reserveCapacity(_:)`` for more details.
179
    @inlinable
180
0
    public mutating func reserveCapacity(_ minimumCapacity: Int) {
181
0
        let additionalCapacity = minimumCapacity - self.count
182
0
        if additionalCapacity > 0 {
183
0
            self._buffer.reserveCapacity(self._buffer.capacity + additionalCapacity)
184
0
        }
185
0
    }
186
187
    /// Writes a single byte to the underlying `ByteBuffer`.
188
    @inlinable
189
0
    public mutating func append(_ byte: UInt8) {
190
0
        // ``CollectionOfOne`` has no witness for
191
0
        // ``Sequence.withContiguousStorageIfAvailable(_:)``. so we do this instead:
192
0
        self._buffer.setInteger(byte, at: self._range.upperBound)
193
0
        self._range = self._range.lowerBound..<self._range.upperBound.advanced(by: 1)
194
0
        self._buffer.moveWriterIndex(to: self._range.upperBound)
195
0
    }
196
197
    /// Writes a sequence of bytes to the underlying `ByteBuffer`.
198
    @inlinable
199
0
    public mutating func append<Bytes: Sequence>(contentsOf bytes: Bytes) where Bytes.Element == UInt8 {
200
0
        let written = self._buffer.setBytes(bytes, at: self._range.upperBound)
201
0
        self._range = self._range.lowerBound..<self._range.upperBound.advanced(by: written)
202
0
        self._buffer.moveWriterIndex(to: self._range.upperBound)
203
0
    }
204
205
    @inlinable
206
    public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C)
207
0
    where ByteBufferView.Element == C.Element {
208
0
        precondition(
209
0
            subrange.startIndex >= self.startIndex && subrange.endIndex <= self.endIndex,
210
0
            "subrange out of bounds"
211
0
        )
212
0
213
0
        if newElements.count == subrange.count {
214
0
            self._buffer.setBytes(newElements, at: subrange.startIndex)
215
0
        } else if newElements.count < subrange.count {
216
0
            // Replace the subrange.
217
0
            self._buffer.setBytes(newElements, at: subrange.startIndex)
218
0
219
0
            // Remove the unwanted bytes between the newly copied bytes and the end of the subrange.
220
0
            // try! is fine here: the copied range is within the view and the length can't be negative.
221
0
            try! self._buffer.copyBytes(
222
0
                at: subrange.endIndex,
223
0
                to: subrange.startIndex.advanced(by: newElements.count),
224
0
                length: subrange.endIndex.distance(to: self._buffer.writerIndex)
225
0
            )
226
0
227
0
            // Shorten the range.
228
0
            let removedBytes = subrange.count - newElements.count
229
0
            self._buffer.moveWriterIndex(to: self._buffer.writerIndex - removedBytes)
230
0
            self._range = self._range.dropLast(removedBytes)
231
0
        } else {
232
0
            // Make space for the new elements.
233
0
            // try! is fine here: the copied range is within the view and the length can't be negative.
234
0
            try! self._buffer.copyBytes(
235
0
                at: subrange.endIndex,
236
0
                to: subrange.startIndex.advanced(by: newElements.count),
237
0
                length: subrange.endIndex.distance(to: self._buffer.writerIndex)
238
0
            )
239
0
240
0
            // Replace the bytes.
241
0
            self._buffer.setBytes(newElements, at: subrange.startIndex)
242
0
243
0
            // Widen the range.
244
0
            let additionalByteCount = newElements.count - subrange.count
245
0
            self._buffer.moveWriterIndex(forwardBy: additionalByteCount)
246
0
            self._range = self._range.startIndex..<self._range.endIndex.advanced(by: additionalByteCount)
247
0
        }
248
0
    }
249
250
    /// Returns the first index in which a byte satisfies the given predicate.
251
    ///
252
    /// - Parameter predicate: A closure that takes a byte as its argument
253
    ///   and returns a Boolean value that indicates whether the passed byte
254
    ///   represents a match.
255
    /// - Returns: The index of the first byte for which `predicate` returns
256
    ///   `true`. If no bytes in the collection satisfy the given predicate,
257
    ///   returns `nil`.
258
    ///
259
    /// - Complexity: O(*n*), where *n* is the length of the collection.
260
    @inlinable
261
0
    public func firstIndex(where predicate: (UInt8) throws -> Bool) rethrows -> Index? {
262
0
        try self.withUnsafeBytes { ptr in
263
0
            try ptr.firstIndex(where: predicate).map { $0 + self._range.lowerBound }
264
0
        }
265
0
    }
266
267
    /// Returns the index of the last byte that matches the given predicate.
268
    ///
269
    /// - Parameter predicate: A closure that takes a byte as its argument
270
    ///   and returns a Boolean value that indicates whether the passed byte
271
    ///   represents a match.
272
    /// - Returns: The index of the last byte in the collection that matches
273
    ///   `predicate`, or `nil` if no bytes match.
274
    ///
275
    /// - Complexity: O(*n*), where *n* is the length of the collection.
276
    @inlinable
277
0
    public func lastIndex(where predicate: (UInt8) throws -> Bool) rethrows -> Index? {
278
0
        try self.withUnsafeBytes { ptr in
279
0
            try ptr.lastIndex(where: predicate).map { $0 + self._range.lowerBound }
280
0
        }
281
0
    }
282
}
283
284
extension ByteBuffer {
285
    /// A view into the readable bytes of the `ByteBuffer`.
286
    @inlinable
287
0
    public var readableBytesView: ByteBufferView {
288
0
        ByteBufferView(self)
289
0
    }
290
291
    /// Returns a view into some portion of the readable bytes of a `ByteBuffer`.
292
    ///
293
    /// - Parameters:
294
    ///   - index: The index the view should start at
295
    ///   - length: The length of the view (in bytes)
296
    /// - Returns: A view into a portion of a `ByteBuffer` or `nil` if the requested bytes were not readable.
297
    @inlinable
298
0
    public func viewBytes(at index: Int, length: Int) -> ByteBufferView? {
299
0
        guard length >= 0 && index >= self.readerIndex && index <= self.writerIndex - length else {
300
0
            return nil
301
0
        }
302
0
303
0
        return ByteBufferView(buffer: self, range: index..<(index + length))
304
0
    }
305
306
    /// Create a `ByteBuffer` from the given `ByteBufferView`s range.
307
    ///
308
    /// - Parameter view: The `ByteBufferView` which you want to get a `ByteBuffer` from.
309
    @inlinable
310
0
    public init(_ view: ByteBufferView) {
311
0
        self = view._buffer.getSlice(at: view.startIndex, length: view.count)!
312
0
    }
313
}
314
315
extension ByteBufferView: Equatable {
316
    /// required by `Equatable`
317
    @inlinable
318
0
    public static func == (lhs: ByteBufferView, rhs: ByteBufferView) -> Bool {
319
0
320
0
        guard lhs._range.count == rhs._range.count else {
321
0
            return false
322
0
        }
323
0
324
0
        // A well-formed ByteBufferView can never have a range that is out-of-bounds of the backing ByteBuffer.
325
0
        // As a result, these getSlice calls can never fail, and we'd like to know it if they do.
326
0
        let leftBufferSlice = lhs._buffer.getSlice(at: lhs._range.startIndex, length: lhs._range.count)!
327
0
        let rightBufferSlice = rhs._buffer.getSlice(at: rhs._range.startIndex, length: rhs._range.count)!
328
0
329
0
        return leftBufferSlice == rightBufferSlice
330
0
    }
331
}
332
333
extension ByteBufferView: Hashable {
334
    /// required by `Hashable`
335
    @inlinable
336
0
    public func hash(into hasher: inout Hasher) {
337
0
        // A well-formed ByteBufferView can never have a range that is out-of-bounds of the backing ByteBuffer.
338
0
        // As a result, this getSlice call can never fail, and we'd like to know it if it does.
339
0
        hasher.combine(self._buffer.getSlice(at: self._range.startIndex, length: self._range.count)!)
340
0
    }
341
}
342
343
extension ByteBufferView: ExpressibleByArrayLiteral {
344
    /// required by `ExpressibleByArrayLiteral`
345
    @inlinable
346
0
    public init(arrayLiteral elements: Element...) {
347
0
        self.init(elements)
348
0
    }
349
}