Add utilties for reading and writing UUIDs (#2045)

Motivation:

UUIDs are often sent over the wire but writing and reading their bytes
to/from a buffer is a bit of a pain.

Modifications:

- Add utilties to 'NIOFoundationCompat' for reading/writing and
  getting/setting a UUID on a `ByteBuffer`.

Result:

Easier to write/read UUIDs to/from a buffer.

Co-authored-by: Cory Benfield <lukasa@apple.com>
This commit is contained in:
George Barnett 2022-10-31 13:25:28 +00:00 committed by GitHub
parent d086bab75a
commit edfceecba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 252 additions and 2 deletions

View File

@ -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<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 {
@ -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)!
}
}

View File

@ -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),

View File

@ -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),
]
}
}

View File

@ -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)
}
}