blob: 70d138e58e28a7b15f8d6f3bbbe75385ead4e80c [file]
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
static constexpr std::array<char, 16> digitsArray = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
inline std::string intToHexString(uint64_t value, size_t digits) {
std::string rc(digits, '0');
size_t bitIndex = (digits - 1) * 4;
for (size_t digitIndex = 0; digitIndex < digits; digitIndex++) {
rc[digitIndex] = digitsArray[(value >> bitIndex) & 0x0f];
bitIndex -= 4;
}
return rc;
}
inline std::string bytesToHexString(const std::vector<uint8_t>& bytes) {
std::string rc(bytes.size() * 2, '0');
for (size_t i = 0; i < bytes.size(); ++i) {
rc[i * 2] = digitsArray[(bytes[i] & 0xf0) >> 4];
rc[i * 2 + 1] = digitsArray[bytes[i] & 0x0f];
}
return rc;
}
// Returns nibble.
inline uint8_t hexCharToNibble(char ch) {
uint8_t rc = 16;
if (ch >= '0' && ch <= '9') {
rc = static_cast<uint8_t>(ch - '0');
} else if (ch >= 'A' && ch <= 'F') {
rc = static_cast<uint8_t>(ch - 'A' + 10);
} else if (ch >= 'a' && ch <= 'f') {
rc = static_cast<uint8_t>(ch - 'a' + 10);
}
return rc;
}
// Returns empty vector in case of malformed hex-string.
inline std::vector<uint8_t> hexStringToBytes(const std::string& str) {
std::vector<uint8_t> rc(str.size() / 2, 0);
for (size_t i = 0; i < str.length(); i += 2) {
uint8_t hi = hexCharToNibble(str[i]);
if (i == str.length() - 1) {
return {};
}
uint8_t lo = hexCharToNibble(str[i + 1]);
if (lo == 16 || hi == 16) {
return {};
}
rc[i / 2] = static_cast<uint8_t>(hi << 4) | lo;
}
return rc;
}
inline std::string bytesToHexDump(const std::vector<uint8_t>& bytes) {
std::string hex_dump;
if (bytes.empty()) {
return hex_dump;
}
const size_t rowLength = 16;
for (size_t i = 0; i < bytes.size(); i += rowLength) {
// Offset
hex_dump += intToHexString(i, 4);
hex_dump += ": ";
// Hex bytes
size_t chunkLength = std::min(rowLength, bytes.size() - i);
for (size_t j = 0; j < rowLength; ++j) {
if (j < chunkLength) {
uint8_t byte = bytes[i + j];
// Bitwise AND 0xf0 extracts the top 4 bits.
// Shifting right by 4 puts those bits in the 0-15 range.
// digitsArray maps 0-15 to the corresponding hex char '0'-'F'.
hex_dump += digitsArray[(byte & 0xf0) >> 4];
// Bitwise AND 0x0f extracts the bottom 4 bits.
hex_dump += digitsArray[byte & 0x0f];
} else {
// If we don't have bytes remaining to pad to the end of the line,
// we print two spaces to match the two hex characters of a printed byte
// and preserve the ASCII representation alignment.
hex_dump += " ";
}
// Formatting spaces.
if (j == 7) {
// For readability, add two spaces in the middle of each 16-byte row.
hex_dump += " ";
} else if (j < rowLength - 1) {
// Space between bytes to separate them.
hex_dump += ' ';
}
}
// Separator between the hex and ASCII representation blocks.
hex_dump += " | ";
// ASCII representation snippet mapping byte values to characters
for (size_t j = 0; j < chunkLength; ++j) {
uint8_t byte = bytes[i + j];
// Range 32 to 126 contains the standard printable ASCII characters.
// Control characters (0-31, 127) and extended ASCII (128+)
// are omitted since they will either not print correctly or will mess up
// the terminal output.
if (byte >= 32 && byte <= 126) {
// Cast the uint8_t byte to char. This is required so that the string's
// `operator+=` appends the character representation instead of possibly
// picking an overload that formats the integer.
hex_dump += static_cast<char>(byte);
} else {
// If the character is not printable, we append a safe placeholder '.'
// to maintain the alignment and clearly show unprintable data.
hex_dump += '.';
}
}
hex_dump += " |\n";
}
return hex_dump;
}