From 2c57a15a083c672c4ecdf3386f5f99d5cd52d453 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Sun, 12 Jan 2025 13:45:02 -0800 Subject: [PATCH 1/4] Adds `Int(buffer:)` initializer to FixedWidthInteger types. Motivation: Makes it possible to make integers out of ByteBuffers directly with an initializer. I.e. `UInt32(buffer: ByteBuffer(bytes: [0, 1, 2, 3]))`. Modifications: - Adds the `Int(buffer:)` initializer in `ByteBuffer-int.swift` - Adds tests in `ByteBufferTest.swift`. _Holy hell this file is huge._ Result: Nicer direct initializer for integers from ByteBuffers. --- Sources/NIOCore/ByteBuffer-int.swift | 16 +++++++++++ Tests/NIOCoreTests/ByteBufferTest.swift | 37 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/Sources/NIOCore/ByteBuffer-int.swift b/Sources/NIOCore/ByteBuffer-int.swift index 87e0c9abb5..7c2bae49cd 100644 --- a/Sources/NIOCore/ByteBuffer-int.swift +++ b/Sources/NIOCore/ByteBuffer-int.swift @@ -134,6 +134,22 @@ extension FixedWidthInteger { return 1 << ((Self.bitWidth - 1) - self.leadingZeroBitCount) } + + /// Initialize an integer from a ByteBuffer. The buffer must contain enough bytes to represent the integer. + /// The bytes will be read using the host system's endianness. + /// + /// - Parameters: + /// - buffer: The ByteBuffer to read from + /// + /// - Returns: The integer value read from the buffer, or nil if the value could not be read. + @inlinable + public init?(buffer: ByteBuffer) { + var buffer = buffer + guard let value = buffer.readInteger(endianness: .host, as: Self.self) else { + return nil + } + self = value + } } extension UInt32 { diff --git a/Tests/NIOCoreTests/ByteBufferTest.swift b/Tests/NIOCoreTests/ByteBufferTest.swift index 6be0ce37a6..3010bce714 100644 --- a/Tests/NIOCoreTests/ByteBufferTest.swift +++ b/Tests/NIOCoreTests/ByteBufferTest.swift @@ -3499,6 +3499,43 @@ extension ByteBufferTest { } +// MARK: - Int / FixedWidthInteger init +extension ByteBufferTest { + func testCreateInt32From3BytesFails() { + let bytes: [UInt8] = [0, 1, 2] + let buffer = ByteBuffer(bytes: bytes) + + XCTAssertNil(UInt32(buffer: buffer)) + } + + func testCreateIntegersFromByteBuffer() { + // 8-bit + let uint8Buffer = ByteBuffer(bytes: [42]) + XCTAssertEqual(UInt8(buffer: uint8Buffer), 42) + XCTAssertEqual(Int8(buffer: uint8Buffer), 42) + + // 16-bit + let uint16Bytes: [UInt8] = Endianness.host == .little ? [0x02, 0x01] : [0x01, 0x02] + let uint16Buffer = ByteBuffer(bytes: uint16Bytes) + XCTAssertEqual(UInt16(buffer: uint16Buffer), 0x0102) + XCTAssertEqual(Int16(buffer: uint16Buffer), 0x0102) + + // 32-bit + let uint32Bytes: [UInt8] = Endianness.host == .little ? [0x04, 0x03, 0x02, 0x01] : [0x01, 0x02, 0x03, 0x04] + let uint32Buffer = ByteBuffer(bytes: uint32Bytes) + XCTAssertEqual(UInt32(buffer: uint32Buffer), 0x01020304) + XCTAssertEqual(Int32(buffer: uint32Buffer), 0x01020304) + + // 64-bit + let uint64Bytes: [UInt8] = Endianness.host == .little ? + [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01] : + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + let uint64Buffer = ByteBuffer(bytes: uint64Bytes) + XCTAssertEqual(UInt64(buffer: uint64Buffer), 0x0102030405060708) + XCTAssertEqual(Int64(buffer: uint64Buffer), 0x0102030405060708) + } +} + // MARK: - DispatchData init extension ByteBufferTest { From a56be34a3f60d2cf13171c264351c0ab792743a3 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Sun, 12 Jan 2025 13:57:51 -0800 Subject: [PATCH 2/4] make it fail loudly instead of returning an optional --- Sources/NIOCore/ByteBuffer-int.swift | 10 ++++------ Tests/NIOCoreTests/ByteBufferTest.swift | 7 ------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Sources/NIOCore/ByteBuffer-int.swift b/Sources/NIOCore/ByteBuffer-int.swift index 7c2bae49cd..8272aea192 100644 --- a/Sources/NIOCore/ByteBuffer-int.swift +++ b/Sources/NIOCore/ByteBuffer-int.swift @@ -137,18 +137,16 @@ extension FixedWidthInteger { /// Initialize an integer from a ByteBuffer. The buffer must contain enough bytes to represent the integer. /// The bytes will be read using the host system's endianness. + /// If the buffer doesn't contain enough bytes to represent the integer, the program will crash. /// /// - Parameters: /// - buffer: The ByteBuffer to read from /// - /// - Returns: The integer value read from the buffer, or nil if the value could not be read. + /// - Returns: The integer value read from the buffer @inlinable - public init?(buffer: ByteBuffer) { + public init(buffer: ByteBuffer) { var buffer = buffer - guard let value = buffer.readInteger(endianness: .host, as: Self.self) else { - return nil - } - self = value + self = buffer.readInteger(endianness: .host, as: Self.self)! } } diff --git a/Tests/NIOCoreTests/ByteBufferTest.swift b/Tests/NIOCoreTests/ByteBufferTest.swift index 3010bce714..ac7c88afee 100644 --- a/Tests/NIOCoreTests/ByteBufferTest.swift +++ b/Tests/NIOCoreTests/ByteBufferTest.swift @@ -3501,13 +3501,6 @@ extension ByteBufferTest { // MARK: - Int / FixedWidthInteger init extension ByteBufferTest { - func testCreateInt32From3BytesFails() { - let bytes: [UInt8] = [0, 1, 2] - let buffer = ByteBuffer(bytes: bytes) - - XCTAssertNil(UInt32(buffer: buffer)) - } - func testCreateIntegersFromByteBuffer() { // 8-bit let uint8Buffer = ByteBuffer(bytes: [42]) From 6b9e0d716f0a7b36414767ed886eb5656168f5a8 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Tue, 14 Jan 2025 20:46:45 -0800 Subject: [PATCH 3/4] make Int(buffer:) failable(optional) --- Sources/NIOCore/ByteBuffer-int.swift | 14 ++++++--- Tests/NIOCoreTests/ByteBufferTest.swift | 41 ++++++++++--------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Sources/NIOCore/ByteBuffer-int.swift b/Sources/NIOCore/ByteBuffer-int.swift index 8272aea192..6cb5eba7d9 100644 --- a/Sources/NIOCore/ByteBuffer-int.swift +++ b/Sources/NIOCore/ByteBuffer-int.swift @@ -116,6 +116,7 @@ extension ByteBuffer { } extension FixedWidthInteger { + /// Returns the next power of two. @inlinable func nextPowerOf2() -> Self { @@ -135,18 +136,21 @@ extension FixedWidthInteger { return 1 << ((Self.bitWidth - 1) - self.leadingZeroBitCount) } - /// Initialize an integer from a ByteBuffer. The buffer must contain enough bytes to represent the integer. + /// Initialize an integer from a byte buffer of exactly the right size. /// The bytes will be read using the host system's endianness. - /// If the buffer doesn't contain enough bytes to represent the integer, the program will crash. /// /// - Parameters: /// - buffer: The ByteBuffer to read from + /// - endianness: The endianness to use when reading the integer, defaults to the host system's endianness. /// - /// - Returns: The integer value read from the buffer + /// - Returns: The integer value read from the buffer, or nil if the buffer size did not match the integer type. @inlinable - public init(buffer: ByteBuffer) { + public init?(buffer: ByteBuffer, endianness: Endianness = .host) { var buffer = buffer - self = buffer.readInteger(endianness: .host, as: Self.self)! + guard let value = buffer.readInteger(endianness: endianness, as: Self.self), buffer.readableBytes == 0 else { + return nil + } + self = value } } diff --git a/Tests/NIOCoreTests/ByteBufferTest.swift b/Tests/NIOCoreTests/ByteBufferTest.swift index ac7c88afee..91e0ac0df0 100644 --- a/Tests/NIOCoreTests/ByteBufferTest.swift +++ b/Tests/NIOCoreTests/ByteBufferTest.swift @@ -3502,30 +3502,23 @@ extension ByteBufferTest { // MARK: - Int / FixedWidthInteger init extension ByteBufferTest { func testCreateIntegersFromByteBuffer() { - // 8-bit - let uint8Buffer = ByteBuffer(bytes: [42]) - XCTAssertEqual(UInt8(buffer: uint8Buffer), 42) - XCTAssertEqual(Int8(buffer: uint8Buffer), 42) - - // 16-bit - let uint16Bytes: [UInt8] = Endianness.host == .little ? [0x02, 0x01] : [0x01, 0x02] - let uint16Buffer = ByteBuffer(bytes: uint16Bytes) - XCTAssertEqual(UInt16(buffer: uint16Buffer), 0x0102) - XCTAssertEqual(Int16(buffer: uint16Buffer), 0x0102) - - // 32-bit - let uint32Bytes: [UInt8] = Endianness.host == .little ? [0x04, 0x03, 0x02, 0x01] : [0x01, 0x02, 0x03, 0x04] - let uint32Buffer = ByteBuffer(bytes: uint32Bytes) - XCTAssertEqual(UInt32(buffer: uint32Buffer), 0x01020304) - XCTAssertEqual(Int32(buffer: uint32Buffer), 0x01020304) - - // 64-bit - let uint64Bytes: [UInt8] = Endianness.host == .little ? - [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01] : - [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] - let uint64Buffer = ByteBuffer(bytes: uint64Bytes) - XCTAssertEqual(UInt64(buffer: uint64Buffer), 0x0102030405060708) - XCTAssertEqual(Int64(buffer: uint64Buffer), 0x0102030405060708) + let uint32BufferLE = ByteBuffer(bytes: [0x04, 0x03, 0x02, 0x01]) + let uint32BufferBE = ByteBuffer(bytes: [0x01, 0x02, 0x03, 0x04]) + let tooSmallInt32Buffer = ByteBuffer(bytes: [0x01, 0x02, 0x03]) + + XCTAssertEqual(Int32(buffer: uint32BufferLE, endianness: .little), 0x0102_0304) + XCTAssertEqual(Int32(buffer: uint32BufferBE, endianness: .big), 0x0102_0304) + XCTAssertNil(Int32(buffer: tooSmallInt32Buffer)) + + let uint64BufferLE = ByteBuffer(bytes: [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) + let uint64BufferBE = ByteBuffer(bytes: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]) + let tooSmallInt64Buffer = ByteBuffer(bytes: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]) + let tooBigInt64Buffer = ByteBuffer(bytes: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]) + + XCTAssertEqual(Int64(buffer: uint64BufferLE, endianness: .little), 0x0102_0304_0506_0708) + XCTAssertEqual(Int64(buffer: uint64BufferBE, endianness: .big), 0x0102_0304_0506_0708) + XCTAssertNil(Int64(buffer: tooSmallInt64Buffer)) + XCTAssertNil(Int64(buffer: tooBigInt64Buffer)) } } From 0dc066ca99a01c906bc6e7b61a366ea89b306143 Mon Sep 17 00:00:00 2001 From: Natik Gadzhi Date: Thu, 16 Jan 2025 10:30:27 -0800 Subject: [PATCH 4/4] Update Sources/NIOCore/ByteBuffer-int.swift --- Sources/NIOCore/ByteBuffer-int.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/NIOCore/ByteBuffer-int.swift b/Sources/NIOCore/ByteBuffer-int.swift index 6cb5eba7d9..5e39004e9f 100644 --- a/Sources/NIOCore/ByteBuffer-int.swift +++ b/Sources/NIOCore/ByteBuffer-int.swift @@ -142,8 +142,6 @@ extension FixedWidthInteger { /// - Parameters: /// - buffer: The ByteBuffer to read from /// - endianness: The endianness to use when reading the integer, defaults to the host system's endianness. - /// - /// - Returns: The integer value read from the buffer, or nil if the buffer size did not match the integer type. @inlinable public init?(buffer: ByteBuffer, endianness: Endianness = .host) { var buffer = buffer