Skip to content

Commit

Permalink
Issue-2748 - Add ByteBuffer Hex init & write
Browse files Browse the repository at this point in the history
  • Loading branch information
ali-ahsan-ali committed Aug 11, 2024
1 parent ce524be commit 28cf942
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 1 deletion.
18 changes: 18 additions & 0 deletions Sources/NIOCore/ByteBuffer-aux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ extension ByteBuffer {
)
}

// MARK: Hex encoded string APIs
/// Write `hex encoded string` into this `ByteBuffer`, moving the writer index forward appropriately.
///
/// - parameters:
/// - string: The hex encoded string to write.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeHexEncodedBytes(_ string: String) -> Int {
let hexPlainDecodedBytes = string.hexPlainDecodedBytes
guard !hexPlainDecodedBytes.isEmpty else {
return 0
}
let written = self.setBytes(hexPlainDecodedBytes, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
return written
}

// MARK: String APIs
/// Write `string` into this `ByteBuffer` using UTF-8 encoding, moving the writer index forward appropriately.
///
Expand Down
2 changes: 1 addition & 1 deletion Sources/NIOCore/ByteBuffer-conversions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension String {
///
/// - parameters:
/// - radix: radix base to use for conversion.
/// - padding: the desired lenght of the resulting string.
/// - padding: the desired length of the resulting string.
@inlinable
internal init<Value>(_ value: Value, radix: Int, padding: Int) where Value: BinaryInteger {
let formatted = String(value, radix: radix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,30 @@
//
//===----------------------------------------------------------------------===//

import Foundation

extension ByteBuffer {

/// Create a fresh `ByteBuffer` containing the `bytes` decoded from the string representation of `hexEncodedBytes`.
///
/// This will allocate a new `ByteBuffer` with enough space to fit the hex decoded `bytes` and potentially some extra space
/// using the default allocator.
///
/// - info: If you have access to a `Channel`, `ChannelHandlerContext`, or `ByteBufferAllocator` we
/// recommend using `channel.allocator.buffer(integer:)`. Or if you want to write multiple items into the
/// buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeHexEncodedBytes` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
init(hexEncodedBytes string: String) {
let hexPlainDecodedBytes = string.hexPlainDecodedBytes
guard !hexPlainDecodedBytes.isEmpty else {
self = ByteBufferAllocator.zeroCapacityWithDefaultAllocator
return
}

self = ByteBufferAllocator().buffer(bytes: hexPlainDecodedBytes)
}

/// Describes a ByteBuffer hexDump format.
/// Can be either xxd output compatible, or hexdump compatible.
public struct HexDumpFormat: Hashable, Sendable {
Expand Down Expand Up @@ -263,3 +285,23 @@ extension ByteBuffer {
}
}
}

extension String {
/// Plain decode a string representing a hexadecimal sequence them into an UInt8 sequence
/// - Complexity: O(n)
public var hexPlainDecodedBytes: [UInt8] {
let stringWithoutWhiteSpaces = self.replacingOccurrences(of: " ", with: "")
return sequence(
state: stringWithoutWhiteSpaces,
next: { remainder in
guard remainder.count >= 2 else {
precondition(remainder.count == 0, "Uneven number of hex encoded bytes within string")
return nil
}
let nextTwo = remainder.prefix(2)
remainder.removeFirst(2)
return UInt8(nextTwo, radix: 16)
}
).map { $0 }
}
}
34 changes: 34 additions & 0 deletions Tests/NIOCoreTests/ByteBufferTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,17 @@ class ByteBufferTest: XCTestCase {
XCTAssertNil(self.buf.getString(at: 0, length: capacity + 1))
}

func testWriteEmptyByteArray() throws {
var buffer = ByteBufferAllocator().buffer(capacity: 32)
buffer.moveWriterIndex(to: 16)
buffer.moveReaderIndex(to: 16)
XCTAssertEqual(buffer.setBytes([], at: 16), 0)
XCTAssertEqual(buffer.readableBytes, 0)
XCTAssertEqual(buffer.writableBytes, 16)
XCTAssertEqual(buffer.writerIndex, 16)
XCTAssertEqual(buffer.readerIndex, 16)
}

func testSetGetBytesAllFine() throws {
self.buf.moveReaderIndex(to: 0)
self.buf.setBytes([1, 2, 3, 4], at: 0)
Expand Down Expand Up @@ -1878,6 +1889,29 @@ class ByteBufferTest: XCTestCase {
XCTAssertEqual(expected, actual)
}

func testWriteHexEncodedBytes() throws {
var buffer = ByteBuffer(hexEncodedBytes: "68 65 6c 6c 6f 20 77 6f 72 6c 64 0a")
XCTAssertEqual(buffer.writeHexEncodedBytes("68656c6c6f20776f726c64"), 11)
XCTAssertEqual(buffer.writeHexEncodedBytes(" 0a "), 1)
XCTAssertEqual(buffer.writeHexEncodedBytes(""), 0)
XCTAssertEqual(buffer.writeHexEncodedBytes(" "), 0)
XCTAssertEqual(ByteBuffer(string: "hello world\nhello world\n"), buffer)
}

func testHexInitialiser() {
var allBytes = ByteBufferAllocator().buffer(capacity: Int(UInt8.max))
for x in UInt8.min...UInt8.max {
allBytes.writeInteger(x)
}

let allBytesHex = allBytes.hexDump(format: .plain)
let allBytesDecoded = ByteBuffer(hexEncodedBytes: allBytesHex)
XCTAssertEqual(allBytes, allBytesDecoded)

// Edge case
XCTAssertEqual(ByteBuffer(hexEncodedBytes: " "), ByteBufferAllocator.zeroCapacityWithDefaultAllocator)
}

func testHexDumpPlain() {
let buf = ByteBuffer(string: "Hello")
XCTAssertEqual("48 65 6c 6c 6f", buf.hexDump(format: .plain))
Expand Down

0 comments on commit 28cf942

Please sign in to comment.