diff --git a/Sources/NIOFoundationCompat/ByteBuffer-foundation.swift b/Sources/NIOFoundationCompat/ByteBuffer-foundation.swift index 27f062d792..be9c7d8d83 100644 --- a/Sources/NIOFoundationCompat/ByteBuffer-foundation.swift +++ b/Sources/NIOFoundationCompat/ByteBuffer-foundation.swift @@ -265,6 +265,101 @@ extension ByteBuffer { } 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.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 { @@ -294,12 +389,12 @@ 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)! } - + } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 9e2c0e3261..bd01f79936 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -55,6 +55,7 @@ class LinuxMainRunner { testCase(ByteBufferDataProtocolTests.allTests), testCase(ByteBufferLengthPrefixTests.allTests), testCase(ByteBufferTest.allTests), + testCase(ByteBufferUUIDTests.allTests), testCase(ByteBufferUtilsTest.allTests), testCase(ByteBufferViewDataProtocolTests.allTests), testCase(ByteToMessageDecoderTest.allTests), diff --git a/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests+XCTest.swift b/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests+XCTest.swift new file mode 100644 index 0000000000..caed7ae97d --- /dev/null +++ b/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests+XCTest.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2022 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 +// +//===----------------------------------------------------------------------===// +// +// ByteBuffer+UUIDTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension ByteBufferUUIDTests { + + @available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings") + static var allTests : [(String, (ByteBufferUUIDTests) -> () throws -> Void)] { + return [ + ("testSetUUIDBytes", testSetUUIDBytes), + ("testSetUUIDBytesBlatsExistingBytes", testSetUUIDBytesBlatsExistingBytes), + ("testGetUUIDEmptyBuffer", testGetUUIDEmptyBuffer), + ("testGetUUIDAfterSet", testGetUUIDAfterSet), + ("testWriteUUIDBytesIntoEmptyBuffer", testWriteUUIDBytesIntoEmptyBuffer), + ("testWriteUUIDBytesIntoNonEmptyBuffer", testWriteUUIDBytesIntoNonEmptyBuffer), + ("testReadUUID", testReadUUID), + ("testReadUUIDNotEnoughBytes", testReadUUIDNotEnoughBytes), + ] + } +} + diff --git a/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests.swift b/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests.swift new file mode 100644 index 0000000000..9505725a30 --- /dev/null +++ b/Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests.swift @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2022 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 Foundation +import NIOCore +import NIOFoundationCompat +import XCTest + +final class ByteBufferUUIDTests: XCTestCase { + func testSetUUIDBytes() { + let uuid = UUID(uuid: (0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf)) + var buffer = ByteBuffer() + + XCTAssertEqual(buffer.storageCapacity, 0) + XCTAssertEqual(buffer.setUUIDBytes(uuid, at: 0), 16) + XCTAssertEqual(buffer.writerIndex, 0) + XCTAssertEqual(buffer.readableBytes, 0) + XCTAssertGreaterThanOrEqual(buffer.storageCapacity, 16) + + buffer.moveWriterIndex(forwardBy: 16) + let bytes = buffer.getBytes(at: buffer.readerIndex, length: 16) + XCTAssertEqual(bytes, Array(0..<16)) + } + + func testSetUUIDBytesBlatsExistingBytes() { + var buffer = ByteBuffer() + buffer.writeRepeatingByte(.max, count: 32) + + let uuid = UUID(uuid: (0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf)) + buffer.setUUIDBytes(uuid, at: buffer.readerIndex + 4) + + XCTAssertEqual(buffer.readBytes(length: 4), Array(repeating: .max, count: 4)) + XCTAssertEqual(buffer.readBytes(length: 16), Array(0..<16)) + XCTAssertEqual(buffer.readBytes(length: 12), Array(repeating: .max, count: 12)) + XCTAssertEqual(buffer.readableBytes, 0) + } + + func testGetUUIDEmptyBuffer() { + let buffer = ByteBuffer() + XCTAssertNil(buffer.getUUIDBytes(at: 0)) + } + + func testGetUUIDAfterSet() { + let uuid = UUID() + var buffer = ByteBuffer() + XCTAssertEqual(buffer.setUUIDBytes(uuid, at: 0), 16) + // nil because there are no bytes to read + XCTAssertNil(buffer.getUUIDBytes(at: 0)) + } + + func testWriteUUIDBytesIntoEmptyBuffer() { + let uuid = UUID(uuid: (0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf)) + var buffer = ByteBuffer() + + XCTAssertEqual(buffer.writeUUIDBytes(uuid), 16) + XCTAssertEqual(buffer.readableBytesView, [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf]) + XCTAssertEqual(buffer.readableBytes, 16) + XCTAssertEqual(buffer.writerIndex, 16) + } + + func testWriteUUIDBytesIntoNonEmptyBuffer() { + let uuid = UUID(uuid: (0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf)) + + var buffer = ByteBuffer() + buffer.writeRepeatingByte(42, count: 10) + XCTAssertEqual(buffer.writeUUIDBytes(uuid), 16) + XCTAssertEqual(buffer.readableBytes, 26) + XCTAssertEqual(buffer.writerIndex, 26) + + XCTAssertEqual(buffer.readableBytesView.dropFirst(10), + [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf]) + } + + func testReadUUID() { + let uuid = UUID(uuid: (0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf)) + var buffer = ByteBuffer() + XCTAssertEqual(buffer.writeUUIDBytes(uuid), 16) + XCTAssertEqual(buffer.readUUIDBytes(), uuid) + XCTAssertEqual(buffer.readableBytes, 0) + } + + func testReadUUIDNotEnoughBytes() { + var buffer = ByteBuffer() + XCTAssertNil(buffer.readUUIDBytes()) + XCTAssertEqual(buffer.readerIndex, 0) + + buffer.writeRepeatingByte(0, count: 8) + XCTAssertNil(buffer.readUUIDBytes()) + XCTAssertEqual(buffer.readerIndex, 0) + + buffer.writeRepeatingByte(0, count: 8) + XCTAssertEqual(buffer.readUUIDBytes(), + UUID(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))) + XCTAssertEqual(buffer.readerIndex, 16) + } +}