Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ByteBuffer.hexDump in xxd and hexdump -C formats #2475

Merged
merged 30 commits into from
Aug 15, 2023

Conversation

natikgadzhi
Copy link
Contributor

@natikgadzhi natikgadzhi commented Jul 14, 2023

This PR adds a bunch of hexDump functions to ByteBuffer.

Closes #2447

Motivation:

@weissi says he ends up writing hex dump helper functions in most projects with NIO, so they would be useful to have straight out of the box.

Modifications:

  • Adds a String(byte: UInt, padding: Int) initializer to format bytes.
  • Changes the output format of ByteBuffer.Storage.dumpBytes and re-uses it.
  • Adds hexDump implementations for different formats.
  • Tests for hexDump functions that this PR adds.

TODO

  • Rework reading data from the buffer in long-format dump and avoid using get*.
  • Add test cases for situations where reader and writer indices are in non-default states.
  • Add a test case for a single line output in long-format dump.
  • Adds ByteBuffer.HexDumpFormat enum and a public function to get a hex dump of a ByteBuffer providing the desired format.
  • Documentation for public hexDump functions.
  • Add hexDumpLong(maxLength:) implementation for long-format clipped hex dumps
  • Cleanup the code and wire up the clipped length dump in the public hexDump() function
  • Perhaps more tests?

Result:

We'll have one new public function on ByteBuffer that accepts desired HexDumpFormat and returns the formatted hex dump string.

Copy link
Contributor Author

@natikgadzhi natikgadzhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cleaned up the short hexdump helpers for now, but will work on the long-format more. Not yet ready for a thorough review.

Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start @natikgadzhi! Sorry for the delay, I've been out of office for a while. I added a little to weissi's feedback.

Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-conversions.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-conversions.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Tests/NIOCoreTests/ByteBufferTest.swift Outdated Show resolved Hide resolved
Tests/NIOCoreTests/ByteBufferTest.swift Outdated Show resolved Hide resolved
@natikgadzhi
Copy link
Contributor Author

@glbrntt thank you for the review! I was slow last week, but will get back at it today — should have the next stage to review on Wednesday.

@natikgadzhi
Copy link
Contributor Author

natikgadzhi commented Aug 1, 2023

@glbrntt, I cleaned up what I already had — but I still have to write the clipped version of the hexdump, and then implement the format enum and the switcher method. Time to get some sleep, but I hope to get it to work tomorrow.

UPD: added the public API that switches formats. Take a look at the implementations. If they make sense overall, I'll bang my head against a wall and figure out a way to clip the long output.

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coming along nicely!

Sources/NIOCore/ByteBuffer-core.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-conversions.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
@natikgadzhi
Copy link
Contributor Author

I've worked through most of your feedback, but left the clipped hexdump format implementation for tomorrow. I hope to find time and work on it in the morning, though.

One tricky part (to me) is to figure out what to use for the placeholder when clipping, if the clipping happens within a line.

One option would be to always clip on 16-byte increments (but that won't be precise). Another option would be to use "." or space to show that that byte was omitted. And always insert one empty line with "..." in the middle to show that this line signifies the clipped part.

So it would be something like this:

        let buf = ByteBuffer(string: "Goodbye, world! It was nice knowing you.\n")
        let expected = """
        00000000  47 6f 6f 64 62 79 65 2c  20 77 6f 72 6c           |Goodbye, worl   |

                                         ...

        00000010                                       6b 6e 6f 77  |            know|
        00000020  69 6e 67 20 79 6f 75 2e  0a                       |ing you..|
        00000029
        """

But that looks weird to me. Admittedly, I also didn't use hex dumps very often. What format should I implement, @glbrntt?

@1proprogrammerchant
Copy link

I like this

@natikgadzhi
Copy link
Contributor Author

@glbrntt, okay, done with a little update!

  • I think I combed through your feedback. I left the safe to unwrap comments there, but I think I acted on the rest.
  • I've reworked how .detailed format prints out the dump to accommodate the format. It's a little bit tricky, and I had to write some repetitive code to get it to work. I'm not entirely happy with it, and tried to document it thoroughly. I think there's a trade-off between the dump readability (format) and code complexity to get it dumped in said format.
  • Caught a bug in a case when the dump with maxBytes is long enough for the clipped pieces to take multiple lines and fixed it.

I think this is ready for a CI run and a review.

@natikgadzhi
Copy link
Contributor Author

@weissi, @glbrntt, would you like me to remove Storage.dumpBytes and change the spots that used it to use the new hexDump instead, maybe in another PR to keep this moving and unblock others?

@glbrntt
Copy link
Contributor

glbrntt commented Aug 10, 2023

@swift-server-bot test this please

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good. I mostly left doc/typo fixes but also left a little feedback on hexDumpLine. Thanks for sticking with this!

Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Sources/NIOCore/ByteBuffer-hexdump.swift Outdated Show resolved Hide resolved
Comment on lines 112 to 125
result += String(repeating: " ", count: paddingBefore * 3)
// If the padding is 8 or more bytes, pad with extra space to match hexdump -C format.
if paddingBefore >= 8 {
result += " "
}

// Iterate over the bytes in the line, and dump them one by one, insert a separator if needed.
for (byteIndex, byte) in self.readableBytesView.enumerated() {
result += String(byte, radix: 16, padding: 2)
result += " "
if byteIndex + paddingBefore == 7 {
result += " "
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is a little hard to follow because of the extra spaces being added conditionally. Can I suggest something like this?

    result += String(repeating: " ", count: paddingBefore * 3)

    // Add the left side of the central column
    if paddingBefore < 8 {
        for byte in self.readableBytesView.prefix(8 - paddingBefore) {
            result += String(byte, radix: 16, padding: 2)
            result += " "
        }
    }

    // Add an extra space for the centre column.
    result += " "

    // Add the right side of the central column.
    let bytesToSkip = paddingBefore < 8 ? 8 - paddingBefore : 0
    for byte in self.readableBytesView.dropFirst(bytesToSkip) {
        result += String(byte, radix: 16, padding: 2)
        result += " "
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked it based on your feedback: d057109

That way, all if statements can go away, seems more readable?

func testHexDumpDetailedWithMultilineFrontAndBack() {
let buf = ByteBuffer(string: """
Goodbye, world! It was nice knowing you.
I will miss this pull request with all of it's 94+ comments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep it going if you like? 😅 (Also, thank you again, this is a really awesome addition!)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

such good changes

Copy link
Contributor Author

@natikgadzhi natikgadzhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready for the next round ;)

Comment on lines 112 to 125
result += String(repeating: " ", count: paddingBefore * 3)
// If the padding is 8 or more bytes, pad with extra space to match hexdump -C format.
if paddingBefore >= 8 {
result += " "
}

// Iterate over the bytes in the line, and dump them one by one, insert a separator if needed.
for (byteIndex, byte) in self.readableBytesView.enumerated() {
result += String(byte, radix: 16, padding: 2)
result += " "
if byteIndex + paddingBefore == 7 {
result += " "
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked it based on your feedback: d057109

That way, all if statements can go away, seems more readable?

@glbrntt
Copy link
Contributor

glbrntt commented Aug 14, 2023

@swift-server-bot test this please

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me now, great job on this and thanks for sticking with it 🙂

/// - radix: radix base to use for conversion.
/// - padding: the desired lenght of the resulting string.
@inlinable
internal init<T>(_ value: T, radix: Int, padding: Int) where T: BinaryInteger {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give this a better generic type parameter name? Maybe Value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Done in 96c077b.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicely done

@yim-lee
Copy link
Member

yim-lee commented Aug 14, 2023

@swift-server-bot add to allowlist

@yim-lee
Copy link
Member

yim-lee commented Aug 14, 2023

@swift-server-bot test this please

@natikgadzhi
Copy link
Contributor Author

Huh, let me do a clean build and fix those.

@natikgadzhi
Copy link
Contributor Author

  • Fixed a test failure of storage.dumpBytes. I dropped [ and ] earlier, but then reverted it, and forgot to revert the test. That was the only test failure on Swift 5.7 -> nightly.
  • Fixed a Swift 5.6 compile error in dc65676 — the code is a tad cleaner that way, too.

It should be good now. @glbrntt can you poke the swift CI gods, please?

@yim-lee
Copy link
Member

yim-lee commented Aug 15, 2023

@natikgadzhi I added you to the allow list, so your pushes to the PR should automatically trigger CI from now on. (I just fixed the soundness check job permissions)

@swift-server-bot test this please

@natikgadzhi
Copy link
Contributor Author

@yim-lee, thank you <3

@1proprogrammerchant
Copy link

@natikgadzhi I added you to the allow list, so your pushes to the PR should automatically trigger CI from now on. (I just fixed the soundness check job permissions)

@swift-server-bot test this please

cool person

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks @natikgadzhi!

@Lukasa Lukasa enabled auto-merge (squash) August 15, 2023 09:15
@Lukasa Lukasa merged commit cdfbb20 into apple:main Aug 15, 2023
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ByteBuffer should have a bunch of hex dump helpers
6 participants