/src/swift-nio/Sources/NIOCore/ByteBuffer-binaryEncodedLengthPrefix.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2024 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 | | /// Describes a way to encode and decode an integer as bytes. |
16 | | /// |
17 | | /// For more information, see <doc:ByteBuffer-lengthPrefix> |
18 | | public protocol NIOBinaryIntegerEncodingStrategy { |
19 | | /// Read an integer from a buffer. |
20 | | /// If there are not enough bytes to read an integer of this encoding, return nil, and do not move the reader index. |
21 | | /// If the the full integer can be read, move the reader index to after the integer, and return the integer. |
22 | | /// - Parameters: |
23 | | /// - as: The type of integer to be read. |
24 | | /// - buffer: The buffer to read from. |
25 | | /// - Returns: The integer that was read, or nil if it was not possible to read it. |
26 | | func readInteger<IntegerType: FixedWidthInteger>( |
27 | | as: IntegerType.Type, |
28 | | from buffer: inout ByteBuffer |
29 | | ) -> IntegerType? |
30 | | |
31 | | /// Write an integer to a buffer. Move the writer index to after the written integer. |
32 | | /// - Parameters: |
33 | | /// - integer: The integer to write. |
34 | | /// - buffer: The buffer to write to. |
35 | | /// - Returns: The number of bytes used to write the integer. |
36 | | func writeInteger<IntegerType: FixedWidthInteger>( |
37 | | _ integer: IntegerType, |
38 | | to buffer: inout ByteBuffer |
39 | | ) -> Int |
40 | | |
41 | | /// An estimate of the number of bytes required to write integers using this strategy. |
42 | | /// Callers may use this to reserve bytes before writing the integer. |
43 | | /// If the actual bytes used by the write function is more or less than this, it may be necessary to shuffle bytes. |
44 | | /// Therefore, an accurate prediction here will improve performance. |
45 | | /// This function will be called from ``ByteBuffer/writeLengthPrefixed(strategy:writeData:)`` |
46 | | var requiredBytesHint: Int { get } |
47 | | |
48 | | /// Write an integer to a buffer. Move the writer index to after the written integer. |
49 | | /// This function will be called when an integer needs to be written, and some capacity has already been reserved for it. |
50 | | /// Implementers should consider using a less efficient encoding, if possible, to fit exactly within the reserved capacity. |
51 | | /// Otherwise, the caller will need to shift bytes to reconcile the difference. |
52 | | /// It is up to the implementer to find the balance between performance and size. |
53 | | /// - Parameters: |
54 | | /// - integer: The integer to write |
55 | | /// - reservedCapacity: The capacity already reserved for writing this integer |
56 | | /// - buffer: The buffer to write into. |
57 | | /// - Returns: The number of bytes used to write the integer. |
58 | | func writeInteger( |
59 | | _ integer: Int, |
60 | | reservedCapacity: Int, |
61 | | to buffer: inout ByteBuffer |
62 | | ) -> Int |
63 | | } |
64 | | |
65 | | extension NIOBinaryIntegerEncodingStrategy { |
66 | | @inlinable |
67 | 0 | public var requiredBytesHint: Int { 1 } |
68 | | |
69 | | @inlinable |
70 | | public func writeInteger<IntegerType: FixedWidthInteger>( |
71 | | _ integer: IntegerType, |
72 | | reservedCapacity: Int, |
73 | | to buffer: inout ByteBuffer |
74 | 0 | ) -> Int { |
75 | 0 | self.writeInteger(integer, to: &buffer) |
76 | 0 | } |
77 | | } |
78 | | |
79 | | extension ByteBuffer { |
80 | | /// Read a binary encoded integer, moving the `readerIndex` appropriately. |
81 | | /// If there are not enough bytes, nil is returned. |
82 | | @inlinable |
83 | | public mutating func readEncodedInteger<Strategy: NIOBinaryIntegerEncodingStrategy, Integer: FixedWidthInteger>( |
84 | | as: Integer.Type = Integer.self, |
85 | | strategy: Strategy |
86 | 0 | ) -> Integer? { |
87 | 0 | strategy.readInteger(as: Integer.self, from: &self) |
88 | 0 | } |
89 | | |
90 | | /// Write a binary encoded integer. |
91 | | /// |
92 | | /// - Returns: The number of bytes written. |
93 | | @discardableResult |
94 | | @inlinable |
95 | | public mutating func writeEncodedInteger< |
96 | | Integer: FixedWidthInteger, |
97 | | Strategy: NIOBinaryIntegerEncodingStrategy |
98 | | >( |
99 | | _ value: Integer, |
100 | | strategy: Strategy |
101 | 0 | ) -> Int { |
102 | 0 | strategy.writeInteger(value, to: &self) |
103 | 0 | } |
104 | | |
105 | | /// Prefixes bytes written by `writeData` with the number of bytes written. |
106 | | /// The number of bytes written is encoded using `strategy`. |
107 | | /// |
108 | | /// - Note: This function works by reserving the number of bytes suggested by `strategy` before the data. |
109 | | /// It then writes the data, and then goes back to write the length. |
110 | | /// If the reserved capacity turns out to be too little or too much, then the data will be shifted. |
111 | | /// Therefore, this function is most performant if the strategy is able to use the same number of bytes that it reserved. |
112 | | /// |
113 | | /// - Parameters: |
114 | | /// - strategy: The strategy to use for encoding the length. |
115 | | /// - writeData: A closure that takes a buffer, writes some data to it, and returns the number of bytes written. |
116 | | /// - Returns: Number of total bytes written. This is the length of the written data + the number of bytes used to write the length before it. |
117 | | @discardableResult |
118 | | @inlinable |
119 | | public mutating func writeLengthPrefixed<Strategy: NIOBinaryIntegerEncodingStrategy, ErrorType: Error>( |
120 | | strategy: Strategy, |
121 | | writeData: (_ buffer: inout ByteBuffer) throws(ErrorType) -> Int |
122 | 0 | ) throws(ErrorType) -> Int { |
123 | 0 | /// The index at which we write the length |
124 | 0 | let lengthPrefixIndex = self.writerIndex |
125 | 0 | /// The space which we reserve for writing the length |
126 | 0 | let reservedCapacity = strategy.requiredBytesHint |
127 | 0 | self.writeRepeatingByte(0, count: reservedCapacity) |
128 | 0 |
|
129 | 0 | /// The index at which we start writing the data originally. We may later move the data if the reserved space for the length wasn't right |
130 | 0 | let originalDataStartIndex = self.writerIndex |
131 | 0 | /// The length of the data written |
132 | 0 | let dataLength: Int |
133 | 0 | do { |
134 | 0 | dataLength = try writeData(&self) |
135 | 0 | } catch { |
136 | 0 | // Clean up our write so that it as if we never did it. |
137 | 0 | self.moveWriterIndex(to: lengthPrefixIndex) |
138 | 0 | throw error |
139 | 0 | } |
140 | 0 | /// The index at the end of the written data originally. We may later move the data if the reserved space for the length wasn't right |
141 | 0 | let originalDataEndIndex = self.writerIndex |
142 | 0 |
|
143 | 0 | // Quick check to make sure the user didn't do something silly |
144 | 0 | precondition( |
145 | 0 | originalDataEndIndex - originalDataStartIndex == dataLength, |
146 | 0 | "writeData returned \(dataLength) bytes, but actually \(originalDataEndIndex - originalDataStartIndex) bytes were written. They must be the same." |
147 | 0 | ) |
148 | 0 |
|
149 | 0 | // We write the length after the data to begin with. We will move it later |
150 | 0 |
|
151 | 0 | /// The actual number of bytes used to write the length written. The user may write more or fewer bytes than what we reserved |
152 | 0 | let actualIntegerLength = strategy.writeInteger( |
153 | 0 | dataLength, |
154 | 0 | reservedCapacity: reservedCapacity, |
155 | 0 | to: &self |
156 | 0 | ) |
157 | 0 |
|
158 | 0 | switch actualIntegerLength { |
159 | 0 | case reservedCapacity: |
160 | 0 | // Good, exact match, swap the values and then "delete" the trailing bytes by moving the index back |
161 | 0 | self._moveBytes(from: originalDataEndIndex, to: lengthPrefixIndex, size: actualIntegerLength) |
162 | 0 | self.moveWriterIndex(to: originalDataEndIndex) |
163 | 0 | case ..<reservedCapacity: |
164 | 0 | // We wrote fewer bytes. We now have to move the length bytes from the end, and |
165 | 0 | // _then_ shrink the rest of the buffer onto it. |
166 | 0 | self._moveBytes(from: originalDataEndIndex, to: lengthPrefixIndex, size: actualIntegerLength) |
167 | 0 | let newDataStartIndex = lengthPrefixIndex + actualIntegerLength |
168 | 0 | self._moveBytes( |
169 | 0 | from: originalDataStartIndex, |
170 | 0 | to: newDataStartIndex, |
171 | 0 | size: dataLength |
172 | 0 | ) |
173 | 0 | self.moveWriterIndex(to: newDataStartIndex + dataLength) |
174 | 0 | case reservedCapacity...: |
175 | 0 | // We wrote more bytes. We now have to create enough space. Once we do, we have the same |
176 | 0 | // implementation as the matching case. |
177 | 0 | let extraSpaceNeeded = actualIntegerLength - reservedCapacity |
178 | 0 | self._createSpace(before: lengthPrefixIndex, requiredSpace: extraSpaceNeeded) |
179 | 0 |
|
180 | 0 | // Clean up the indices. |
181 | 0 | let newDataEndIndex = originalDataEndIndex + extraSpaceNeeded |
182 | 0 | // We wrote the length after the data, so we have to move those bytes to the space at the front |
183 | 0 | self._moveBytes(from: newDataEndIndex, to: lengthPrefixIndex, size: actualIntegerLength) |
184 | 0 | self.moveWriterIndex(to: newDataEndIndex) |
185 | 0 | default: |
186 | 0 | fatalError("Unreachable") |
187 | 0 | } |
188 | 0 |
|
189 | 0 | let totalBytesWritten = self.writerIndex - lengthPrefixIndex |
190 | 0 | return totalBytesWritten |
191 | 0 | } |
192 | | |
193 | | /// Reads a slice which is prefixed with a length. The length will be read using `strategy`, and then that many bytes will be read to create a slice. |
194 | | /// - Parameter strategy: The encoding strategy to use. |
195 | | /// - Returns: The slice, if there are enough bytes to read it fully. In this case, the readerIndex will move to after the slice. |
196 | | /// If there are not enough bytes to read the full slice, the readerIndex will stay unchanged. |
197 | | @inlinable |
198 | | public mutating func readLengthPrefixedSlice<Strategy: NIOBinaryIntegerEncodingStrategy>( |
199 | | strategy: Strategy |
200 | 0 | ) -> ByteBuffer? { |
201 | 0 | let originalReaderIndex = self.readerIndex |
202 | 0 | guard let length = strategy.readInteger(as: Int.self, from: &self), let slice = self.readSlice(length: length) |
203 | 0 | else { |
204 | 0 | self.moveReaderIndex(to: originalReaderIndex) |
205 | 0 | return nil |
206 | 0 | } |
207 | 0 | return slice |
208 | 0 | } |
209 | | } |
210 | | |
211 | | // MARK: - Helpers for writing length-prefixed things |
212 | | |
213 | | extension ByteBuffer { |
214 | | /// Write the length of `buffer` using `strategy`. Then write the buffer. |
215 | | /// - Parameters: |
216 | | /// - buffer: The buffer to be written. |
217 | | /// - strategy: The encoding strategy to use. |
218 | | /// - Returns: The total bytes written. This is the bytes needed to write the length, plus the length of the buffer itself. |
219 | | @discardableResult |
220 | | @inlinable |
221 | | public mutating func writeLengthPrefixedBuffer< |
222 | | Strategy: NIOBinaryIntegerEncodingStrategy |
223 | | >( |
224 | | _ buffer: ByteBuffer, |
225 | | strategy: Strategy |
226 | 0 | ) -> Int { |
227 | 0 | self.reserveCapacity(minimumWritableBytes: buffer.readableBytes + strategy.requiredBytesHint) |
228 | 0 | var written = 0 |
229 | 0 | written += self.writeEncodedInteger(buffer.readableBytes, strategy: strategy) |
230 | 0 | written += self.writeImmutableBuffer(buffer) |
231 | 0 | return written |
232 | 0 | } |
233 | | |
234 | | /// Write the length of `string` using `strategy`. Then write the string. |
235 | | /// - Parameters: |
236 | | /// - string: The string to be written. |
237 | | /// - strategy: The encoding strategy to use. |
238 | | /// - Returns: The total bytes written. This is the bytes needed to write the length, plus the length of the string itself. |
239 | | @discardableResult |
240 | | @inlinable |
241 | | public mutating func writeLengthPrefixedString< |
242 | | Strategy: NIOBinaryIntegerEncodingStrategy |
243 | | >( |
244 | | _ string: String, |
245 | | strategy: Strategy |
246 | 0 | ) -> Int { |
247 | 0 | // writeString always writes the String as UTF8 bytes, without a null-terminator |
248 | 0 | // So the length will be the utf8 count |
249 | 0 | self.reserveCapacity(minimumWritableBytes: string.utf8.count + strategy.requiredBytesHint) |
250 | 0 | var written = 0 |
251 | 0 | written += self.writeEncodedInteger(string.utf8.count, strategy: strategy) |
252 | 0 | written += self.writeString(string) |
253 | 0 | return written |
254 | 0 | } |
255 | | |
256 | | /// Write the length of `bytes` using `strategy`. Then write the bytes. |
257 | | /// - Parameters: |
258 | | /// - bytes: The bytes to be written. |
259 | | /// - strategy: The encoding strategy to use. |
260 | | /// - Returns: The total bytes written. This is the bytes needed to write the length, plus the length of the bytes themselves. |
261 | | @discardableResult |
262 | | @inlinable |
263 | | public mutating func writeLengthPrefixedBytes< |
264 | | Bytes: Sequence, |
265 | | Strategy: NIOBinaryIntegerEncodingStrategy |
266 | | >( |
267 | | _ bytes: Bytes, |
268 | | strategy: Strategy |
269 | | ) -> Int |
270 | 0 | where Bytes.Element == UInt8 { |
271 | 0 | let numberOfBytes = bytes.withContiguousStorageIfAvailable { b in |
272 | 0 | UnsafeRawBufferPointer(b).count |
273 | 0 | } |
274 | 0 | if let numberOfBytes { |
275 | 0 | self.reserveCapacity(minimumWritableBytes: numberOfBytes + strategy.requiredBytesHint) |
276 | 0 | var written = 0 |
277 | 0 | written += self.writeEncodedInteger(numberOfBytes, strategy: strategy) |
278 | 0 | written += self.writeBytes(bytes) |
279 | 0 | return written |
280 | 0 | } else { |
281 | 0 | return self.writeLengthPrefixed(strategy: strategy) { buffer in |
282 | 0 | buffer.writeBytes(bytes) |
283 | 0 | } |
284 | 0 | } |
285 | 0 | } |
286 | | } |
287 | | |
288 | | // MARK: - Helpers for reading length-prefixed things |
289 | | |
290 | | extension ByteBuffer { |
291 | | /// Reads a string which is prefixed with a length. The length will be read using `strategy`, and then that many bytes will be read to create a string. |
292 | | /// - Parameter strategy: The encoding strategy to use. |
293 | | /// - Returns: The string, if there are enough bytes to read it fully. In this case, the readerIndex will move to after the string. |
294 | | /// If there are not enough bytes to read the full string, the readerIndex will stay unchanged. |
295 | | @inlinable |
296 | | public mutating func readLengthPrefixedString<Strategy: NIOBinaryIntegerEncodingStrategy>( |
297 | | strategy: Strategy |
298 | 0 | ) -> String? { |
299 | 0 | self.readLengthPrefixedSlice(strategy: strategy).map { String(buffer: $0) } |
300 | 0 | } |
301 | | |
302 | | /// Reads bytes which are prefixed with a length. The length will be read using `strategy`, and then that many bytes will be read to create an array of bytes. |
303 | | /// - Parameter strategy: The encoding strategy to use. |
304 | | /// - Returns: The array of bytes, if there are enough bytes to read it fully. In this case, the readerIndex will move to after the bytes. |
305 | | /// If there are not enough bytes to read the full array of bytes, the readerIndex will stay unchanged. |
306 | | @inlinable |
307 | | public mutating func readLengthPrefixedBytes<Strategy: NIOBinaryIntegerEncodingStrategy>( |
308 | | strategy: Strategy |
309 | 0 | ) -> [UInt8]? { |
310 | 0 | self.readLengthPrefixedSlice(strategy: strategy).map { Array(buffer: $0) } |
311 | 0 | } |
312 | | } |
313 | | |
314 | | extension ByteBuffer { |
315 | | /// Creates `requiredSpace` bytes of free space immediately before `index`. |
316 | | /// e.g. given [a, b, c, d, e, f, g, h, i, j] and calling this function with (before: 4, requiredSpace: 2) would result in |
317 | | /// [a, b, c, d, 0, 0, e, f, g, h, i, j] |
318 | | /// 2 extra bytes of space were created before index 4 (the letter e). |
319 | | /// The total bytes written will be equal to `requiredSpace`, and the writer index will be moved accordingly. |
320 | | @usableFromInline |
321 | 0 | mutating func _createSpace(before index: Int, requiredSpace: Int) { |
322 | 0 | precondition(index >= self.readerIndex) |
323 | 0 | let bytesToMove = self.writerIndex - index |
324 | 0 |
|
325 | 0 | // Add the required number of bytes to the end first |
326 | 0 | self.writeRepeatingByte(0, count: requiredSpace) |
327 | 0 | // Move the data forward by that many bytes, to make space at the front |
328 | 0 | // The precondition above makes this safe: our indices are in the valid range, so we can safely use them here |
329 | 0 | try! self.copyBytes(at: index, to: index + requiredSpace, length: bytesToMove) |
330 | 0 | } |
331 | | |
332 | | /// Move the `size` bytes starting from `source` to `destination`. |
333 | | /// `source` and `destination` must both be within the writable range. |
334 | | @usableFromInline |
335 | 0 | mutating func _moveBytes(from source: Int, to destination: Int, size: Int) { |
336 | 0 | precondition(source >= self.readerIndex && destination < self.writerIndex && source >= destination) |
337 | 0 | precondition(source + size <= self.writerIndex) |
338 | 0 |
|
339 | 0 | // The precondition above makes this safe: our indices are in the valid range, so we can safely use them here |
340 | 0 | try! self.copyBytes(at: source, to: destination, length: size) |
341 | 0 | } |
342 | | } |