Skip to content

Commit

Permalink
Add utilties for reading and writing UUIDs (#2045)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
glbrntt and Lukasa authored Oct 31, 2022
1 parent d086bab commit edfceec
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 2 deletions.
99 changes: 97 additions & 2 deletions Sources/NIOFoundationCompat/ByteBuffer-foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)!
}

}
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
41 changes: 41 additions & 0 deletions Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests+XCTest.swift
Original file line number Diff line number Diff line change
@@ -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),
]
}
}

113 changes: 113 additions & 0 deletions Tests/NIOFoundationCompatTests/ByteBuffer+UUIDTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit edfceec

Please sign in to comment.