401 lines
17 KiB
Swift
401 lines
17 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import NIOCore
|
|
import Foundation
|
|
|
|
|
|
/// Errors that may be thrown by ByteBuffer methods that call into Foundation.
|
|
public enum ByteBufferFoundationError: Error {
|
|
/// Attempting to encode the given string failed.
|
|
case failedToEncodeString
|
|
}
|
|
|
|
|
|
/*
|
|
* This is NIO's `NIOFoundationCompat` module which at the moment only adds `ByteBuffer` utility methods
|
|
* for Foundation's `Data` type.
|
|
*
|
|
* The reason that it's not in the `NIO` module is that we don't want to have any direct Foundation dependencies
|
|
* in `NIO` as Foundation is problematic for a few reasons:
|
|
*
|
|
* - its implementation is different on Linux and on macOS which means our macOS tests might be inaccurate
|
|
* - on macOS Foundation is mostly written in ObjC which means the autorelease pool might get populated
|
|
* - `swift-corelibs-foundation` (the OSS Foundation used on Linux) links the world which will prevent anyone from
|
|
* having static binaries. It can also cause problems in the choice of an SSL library as Foundation already brings
|
|
* the platforms OpenSSL in which might cause problems.
|
|
*/
|
|
|
|
extension ByteBuffer {
|
|
/// Controls how bytes are transferred between `ByteBuffer` and other storage types.
|
|
public enum ByteTransferStrategy: Sendable {
|
|
/// Force a copy of the bytes.
|
|
case copy
|
|
|
|
/// Do not copy the bytes if at all possible.
|
|
case noCopy
|
|
|
|
/// Use a heuristic to decide whether to copy the bytes or not.
|
|
case automatic
|
|
}
|
|
|
|
// MARK: - Data APIs
|
|
|
|
/// Read `length` bytes off this `ByteBuffer`, move the reader index forward by `length` bytes and return the result
|
|
/// as `Data`.
|
|
///
|
|
/// `ByteBuffer` will use a heuristic to decide whether to copy the bytes or whether to reference `ByteBuffer`'s
|
|
/// underlying storage from the returned `Data` value. If you want manual control over the byte transferring
|
|
/// behaviour, please use `readData(length:byteTransferStrategy:)`.
|
|
///
|
|
/// - parameters:
|
|
/// - length: The number of bytes to be read from this `ByteBuffer`.
|
|
/// - returns: A `Data` value containing `length` bytes or `nil` if there aren't at least `length` bytes readable.
|
|
public mutating func readData(length: Int) -> Data? {
|
|
return self.readData(length: length, byteTransferStrategy: .automatic)
|
|
}
|
|
|
|
|
|
/// Read `length` bytes off this `ByteBuffer`, move the reader index forward by `length` bytes and return the result
|
|
/// as `Data`.
|
|
///
|
|
/// - parameters:
|
|
/// - length: The number of bytes to be read from this `ByteBuffer`.
|
|
/// - byteTransferStrategy: Controls how to transfer the bytes. See `ByteTransferStrategy` for an explanation
|
|
/// of the options.
|
|
/// - returns: A `Data` value containing `length` bytes or `nil` if there aren't at least `length` bytes readable.
|
|
public mutating func readData(length: Int, byteTransferStrategy: ByteTransferStrategy) -> Data? {
|
|
guard let result = self.getData(at: self.readerIndex, length: length, byteTransferStrategy: byteTransferStrategy) else {
|
|
return nil
|
|
}
|
|
self.moveReaderIndex(forwardBy: length)
|
|
return result
|
|
}
|
|
|
|
/// Return `length` bytes starting at `index` and return the result as `Data`. This will not change the reader index.
|
|
/// The selected bytes must be readable or else `nil` will be returned.
|
|
///
|
|
/// `ByteBuffer` will use a heuristic to decide whether to copy the bytes or whether to reference `ByteBuffer`'s
|
|
/// underlying storage from the returned `Data` value. If you want manual control over the byte transferring
|
|
/// behaviour, please use `getData(at:byteTransferStrategy:)`.
|
|
///
|
|
/// - parameters:
|
|
/// - index: The starting index of the bytes of interest into the `ByteBuffer`
|
|
/// - length: The number of bytes of interest
|
|
/// - returns: A `Data` value containing the bytes of interest or `nil` if the selected bytes are not readable.
|
|
public func getData(at index: Int, length: Int) -> Data? {
|
|
return self.getData(at: index, length: length, byteTransferStrategy: .automatic)
|
|
}
|
|
|
|
/// Return `length` bytes starting at `index` and return the result as `Data`. This will not change the reader index.
|
|
/// The selected bytes must be readable or else `nil` will be returned.
|
|
///
|
|
/// - parameters:
|
|
/// - index: The starting index of the bytes of interest into the `ByteBuffer`
|
|
/// - length: The number of bytes of interest
|
|
/// - byteTransferStrategy: Controls how to transfer the bytes. See `ByteTransferStrategy` for an explanation
|
|
/// of the options.
|
|
/// - returns: A `Data` value containing the bytes of interest or `nil` if the selected bytes are not readable.
|
|
public func getData(at index0: Int, length: Int, byteTransferStrategy: ByteTransferStrategy) -> Data? {
|
|
let index = index0 - self.readerIndex
|
|
guard index >= 0 && length >= 0 && index <= self.readableBytes - length else {
|
|
return nil
|
|
}
|
|
let doCopy: Bool
|
|
switch byteTransferStrategy {
|
|
case .copy:
|
|
doCopy = true
|
|
case .noCopy:
|
|
doCopy = false
|
|
case .automatic:
|
|
doCopy = length <= 256*1024
|
|
}
|
|
|
|
return self.withUnsafeReadableBytesWithStorageManagement { ptr, storageRef in
|
|
if doCopy {
|
|
return Data(bytes: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)),
|
|
count: Int(length))
|
|
} else {
|
|
_ = storageRef.retain()
|
|
return Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: ptr.baseAddress!.advanced(by: index)),
|
|
count: Int(length),
|
|
deallocator: .custom { _, _ in storageRef.release() })
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Foundation String APIs
|
|
|
|
/// Get a `String` decoding `length` bytes starting at `index` with `encoding`. This will not change the reader index.
|
|
/// The selected bytes must be readable or else `nil` will be returned.
|
|
///
|
|
/// - parameters:
|
|
/// - index: The starting index of the bytes of interest into the `ByteBuffer`.
|
|
/// - length: The number of bytes of interest.
|
|
/// - encoding: The `String` encoding to be used.
|
|
/// - returns: A `String` value containing the bytes of interest or `nil` if the selected bytes are not readable or
|
|
/// cannot be decoded with the given encoding.
|
|
public func getString(at index: Int, length: Int, encoding: String.Encoding) -> String? {
|
|
guard let data = self.getData(at: index, length: length) else {
|
|
return nil
|
|
}
|
|
return String(data: data, encoding: encoding)
|
|
}
|
|
|
|
/// Read a `String` decoding `length` bytes with `encoding` from the `readerIndex`, moving the `readerIndex` appropriately.
|
|
///
|
|
/// - parameters:
|
|
/// - length: The number of bytes to read.
|
|
/// - encoding: The `String` encoding to be used.
|
|
/// - returns: A `String` value containing the bytes of interest or `nil` if the `ByteBuffer` doesn't contain enough bytes, or
|
|
/// if those bytes cannot be decoded with the given encoding.
|
|
public mutating func readString(length: Int, encoding: String.Encoding) -> String? {
|
|
guard length <= self.readableBytes else {
|
|
return nil
|
|
}
|
|
|
|
guard let string = self.getString(at: self.readerIndex, length: length, encoding: encoding) else {
|
|
return nil
|
|
}
|
|
self.moveReaderIndex(forwardBy: length)
|
|
return string
|
|
}
|
|
|
|
/// Write `string` into this `ByteBuffer` using the encoding `encoding`, moving the writer index forward appropriately.
|
|
///
|
|
/// - parameters:
|
|
/// - string: The string to write.
|
|
/// - encoding: The encoding to use to encode the string.
|
|
/// - returns: The number of bytes written.
|
|
@discardableResult
|
|
public mutating func writeString(_ string: String, encoding: String.Encoding) throws -> Int {
|
|
let written = try self.setString(string, encoding: encoding, at: self.writerIndex)
|
|
self.moveWriterIndex(forwardBy: written)
|
|
return written
|
|
}
|
|
|
|
/// Write `string` into this `ByteBuffer` at `index` using the encoding `encoding`. Does not move the writer index.
|
|
///
|
|
/// - parameters:
|
|
/// - string: The string to write.
|
|
/// - encoding: The encoding to use to encode the string.
|
|
/// - index: The index for the first serialized byte.
|
|
/// - returns: The number of bytes written.
|
|
@discardableResult
|
|
public mutating func setString(_ string: String, encoding: String.Encoding, at index: Int) throws -> Int {
|
|
guard let data = string.data(using: encoding) else {
|
|
throw ByteBufferFoundationError.failedToEncodeString
|
|
}
|
|
return self.setBytes(data, at: index)
|
|
}
|
|
|
|
public init(data: Data) {
|
|
self = ByteBufferAllocator().buffer(data: data)
|
|
}
|
|
|
|
// MARK: ContiguousBytes and DataProtocol
|
|
/// Write `bytes` into this `ByteBuffer` at the writer index, moving the writer index forward appropriately.
|
|
///
|
|
/// - parameters:
|
|
/// - bytes: The bytes to write.
|
|
/// - returns: The number of bytes written.
|
|
@inlinable
|
|
@discardableResult
|
|
public mutating func writeContiguousBytes<Bytes: ContiguousBytes>(_ bytes: Bytes) -> Int {
|
|
let written = self.setContiguousBytes(bytes, at: self.writerIndex)
|
|
self.moveWriterIndex(forwardBy: written)
|
|
return written
|
|
}
|
|
|
|
/// Write `bytes` into this `ByteBuffer` at `index`. Does not move the writer index.
|
|
///
|
|
/// - parameters:
|
|
/// - bytes: The bytes to write.
|
|
/// - index: The index for the first byte.
|
|
/// - returns: The number of bytes written.
|
|
@inlinable
|
|
@discardableResult
|
|
public mutating func setContiguousBytes<Bytes: ContiguousBytes>(_ bytes: Bytes, at index: Int) -> Int {
|
|
return bytes.withUnsafeBytes { bufferPointer in
|
|
self.setBytes(bufferPointer, at: index)
|
|
}
|
|
}
|
|
|
|
/// Write the bytes of `data` into this `ByteBuffer` at the writer index, moving the writer index forward appropriately.
|
|
///
|
|
/// - parameters:
|
|
/// - data: The data to write.
|
|
/// - returns: The number of bytes written.
|
|
@inlinable
|
|
@discardableResult
|
|
public mutating func writeData<D: DataProtocol>(_ data: D) -> Int {
|
|
let written = self.setData(data, at: self.writerIndex)
|
|
self.moveWriterIndex(forwardBy: written)
|
|
return written
|
|
}
|
|
|
|
/// Write the bytes of `data` into this `ByteBuffer` at `index`. Does not move the writer index.
|
|
///
|
|
/// - parameters:
|
|
/// - data: The data to write.
|
|
/// - index: The index for the first byte.
|
|
/// - returns: The number of bytes written.
|
|
@inlinable
|
|
@discardableResult
|
|
public mutating func setData<D: DataProtocol>(_ data: D, at index: Int) -> Int {
|
|
// DataProtocol refines RandomAccessCollection, so getting `count` must be O(1). This avoids
|
|
// intermediate allocations in the awkward case by ensuring we definitely have sufficient
|
|
// space for these writes.
|
|
self.reserveCapacity(minimumWritableBytes: data.count)
|
|
|
|
var written = 0
|
|
for region in data.regions {
|
|
written += self.setContiguousBytes(region, at: index + written)
|
|
}
|
|
return written
|
|
}
|
|
|
|
// MARK: - UUID
|
|
|
|
/// Get a `UUID` from the 16 bytes starting at `index`. This will not change the reader index.
|
|
/// If there are less than 16 bytes starting at `index` then `nil` will be returned.
|
|
///
|
|
/// - Parameters:
|
|
/// - index: The starting index of the bytes of interest into the `ByteBuffer`.
|
|
/// - Returns: A `UUID` value containing the bytes of interest or `nil` if the selected bytes
|
|
/// are not readable or there were not enough bytes.
|
|
public func getUUIDBytes(at index: Int) -> UUID? {
|
|
guard let chunk1 = self.getInteger(at: index, as: UInt64.self),
|
|
let chunk2 = self.getInteger(at: index + 8, as: UInt64.self) else {
|
|
return nil
|
|
}
|
|
|
|
let uuidBytes = (
|
|
UInt8(truncatingIfNeeded: chunk1 >> 56),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 48),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 40),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 32),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 24),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 16),
|
|
UInt8(truncatingIfNeeded: chunk1 >> 8),
|
|
UInt8(truncatingIfNeeded: chunk1),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 56),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 48),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 40),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 32),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 24),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 16),
|
|
UInt8(truncatingIfNeeded: chunk2 >> 8),
|
|
UInt8(truncatingIfNeeded: chunk2)
|
|
)
|
|
|
|
return UUID(uuid: uuidBytes)
|
|
}
|
|
|
|
/// Set the bytes of the `UUID` into this `ByteBuffer` at `index`, allocating more storage if
|
|
/// necessary. Does not move the writer index.
|
|
///
|
|
/// - Parameters:
|
|
/// - uuid: The UUID to set.
|
|
/// - index: The index into the buffer where `uuid` should be written.
|
|
/// - Returns: The number of bytes written.
|
|
@discardableResult
|
|
public mutating func setUUIDBytes(_ uuid: UUID, at index: Int) -> Int {
|
|
let bytes = uuid.uuid
|
|
|
|
// Pack the bytes into two 'UInt64's and set them.
|
|
let chunk1 = UInt64(bytes.0) << 56
|
|
| UInt64(bytes.1) << 48
|
|
| UInt64(bytes.2) << 40
|
|
| UInt64(bytes.3) << 32
|
|
| UInt64(bytes.4) << 24
|
|
| UInt64(bytes.5) << 16
|
|
| UInt64(bytes.6) << 8
|
|
| UInt64(bytes.7)
|
|
|
|
let chunk2 = UInt64(bytes.8) << 56
|
|
| UInt64(bytes.9) << 48
|
|
| UInt64(bytes.10) << 40
|
|
| UInt64(bytes.11) << 32
|
|
| UInt64(bytes.12) << 24
|
|
| UInt64(bytes.13) << 16
|
|
| UInt64(bytes.14) << 8
|
|
| UInt64(bytes.15)
|
|
|
|
var written = self.setInteger(chunk1, at: index)
|
|
written &+= self.setInteger(chunk2, at: index &+ written)
|
|
assert(written == 16)
|
|
return written
|
|
}
|
|
|
|
/// Read a `UUID` from the first 16 bytes in the buffer. Advances the reader index.
|
|
///
|
|
/// - Returns: The `UUID` or `nil` if the buffer did not contain enough bytes.
|
|
public mutating func readUUIDBytes() -> UUID? {
|
|
guard let uuid = self.getUUIDBytes(at: self.readerIndex) else {
|
|
return nil
|
|
}
|
|
self.moveReaderIndex(forwardBy: MemoryLayout<uuid_t>.size)
|
|
return uuid
|
|
}
|
|
|
|
/// Write a `UUID` info the buffer and advances the writer index.
|
|
///
|
|
/// - Parameter uuid: The `UUID` to write into the buffer.
|
|
/// - Returns: The number of bytes written.
|
|
@discardableResult
|
|
public mutating func writeUUIDBytes(_ uuid: UUID) -> Int {
|
|
let written = self.setUUIDBytes(uuid, at: self.writerIndex)
|
|
self.moveWriterIndex(forwardBy: written)
|
|
return written
|
|
}
|
|
}
|
|
|
|
extension ByteBufferAllocator {
|
|
/// Create a fresh `ByteBuffer` containing the bytes contained in the given `Data`.
|
|
///
|
|
/// This will allocate a new `ByteBuffer` with enough space to fit the bytes of the `Data` and potentially
|
|
/// some extra space using Swift's default allocator.
|
|
public func buffer(data: Data) -> ByteBuffer {
|
|
var buffer = self.buffer(capacity: data.count)
|
|
buffer.writeBytes(data)
|
|
return buffer
|
|
}
|
|
}
|
|
|
|
// MARK: - Conformances
|
|
extension ByteBufferView: ContiguousBytes {}
|
|
|
|
extension ByteBufferView: DataProtocol {
|
|
public typealias Regions = CollectionOfOne<ByteBufferView>
|
|
|
|
public var regions: CollectionOfOne<ByteBufferView> {
|
|
return .init(self)
|
|
}
|
|
}
|
|
|
|
extension ByteBufferView: MutableDataProtocol {}
|
|
|
|
// MARK: - Data
|
|
extension Data {
|
|
|
|
/// Creates a `Data` from a given `ByteBuffer`. The entire readable portion of the buffer will be read.
|
|
/// - parameter buffer: The buffer to read.
|
|
public init(buffer: ByteBuffer, byteTransferStrategy: ByteBuffer.ByteTransferStrategy = .automatic) {
|
|
var buffer = buffer
|
|
self = buffer.readData(length: buffer.readableBytes, byteTransferStrategy: byteTransferStrategy)!
|
|
}
|
|
|
|
}
|