Skip to content

Commit

Permalink
Merge pull request #2102 from nghttp2/rewrite-hexdump
Browse files Browse the repository at this point in the history
Rewrite hexdump
  • Loading branch information
tatsuhiro-t committed Mar 17, 2024
2 parents 061998e + be512f8 commit 7c644e4
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 50 deletions.
196 changes: 148 additions & 48 deletions src/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1348,66 +1348,166 @@ StringRef make_hostport(BlockAllocator &balloc, const StringRef &host,
}

namespace {
void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) {
auto stop = std::min(first + 8, last);
for (auto k = first; k != stop; ++k) {
fprintf(out, "%02x ", *k);
uint8_t *hexdump_addr(uint8_t *dest, size_t addr) {
// Lower 32 bits are displayed.
for (size_t i = 0; i < 4; ++i) {
auto a = (addr >> (3 - i) * 8) & 0xff;

*dest++ = LOWER_XDIGITS[a >> 4];
*dest++ = LOWER_XDIGITS[a & 0xf];
}
// each byte needs 3 spaces (2 hex value and space)
for (; stop != first + 8; ++stop) {
fputs(" ", out);

return dest;
}
} // namespace

namespace {
uint8_t *hexdump_ascii(uint8_t *dest, const uint8_t *data, size_t datalen) {
*dest++ = '|';

for (size_t i = 0; i < datalen; ++i) {
if (0x20 <= data[i] && data[i] <= 0x7e) {
*dest++ = data[i];
} else {
*dest++ = '.';
}
}
// we have extra space after 8 bytes
fputc(' ', out);

*dest++ = '|';

return dest;
}
} // namespace

void hexdump(FILE *out, const uint8_t *src, size_t len) {
if (len == 0) {
return;
namespace {
uint8_t *hexdump8(uint8_t *dest, const uint8_t *data, size_t datalen) {
size_t i;

for (i = 0; i < datalen; ++i) {
*dest++ = LOWER_XDIGITS[data[i] >> 4];
*dest++ = LOWER_XDIGITS[data[i] & 0xf];
*dest++ = ' ';
}
size_t buflen = 0;

for (; i < 8; ++i) {
*dest++ = ' ';
*dest++ = ' ';
*dest++ = ' ';
}

return dest;
}
} // namespace

namespace {
uint8_t *hexdump16(uint8_t *dest, const uint8_t *data, size_t datalen) {
if (datalen > 8) {
dest = hexdump8(dest, data, 8);
*dest++ = ' ';
dest = hexdump8(dest, data + 8, datalen - 8);
*dest++ = ' ';
} else {
dest = hexdump8(dest, data, datalen);
*dest++ = ' ';
dest = hexdump8(dest, nullptr, 0);
*dest++ = ' ';
}

return dest;
}
} // namespace

namespace {
uint8_t *hexdump_line(uint8_t *dest, const uint8_t *data, size_t datalen,
size_t addr) {
dest = hexdump_addr(dest, addr);
*dest++ = ' ';
*dest++ = ' ';

dest = hexdump16(dest, data, datalen);

return hexdump_ascii(dest, data, datalen);
}
} // namespace

namespace {
int hexdump_write(int fd, const uint8_t *data, size_t datalen) {
ssize_t nwrite;

for (; (nwrite = write(fd, data, datalen)) == -1 && errno == EINTR;)
;
if (nwrite == -1) {
return -1;
}

return 0;
}
} // namespace

int hexdump(FILE *out, const void *data, size_t datalen) {
if (datalen == 0) {
return 0;
}

// min_space is the additional minimum space that the buffer must
// accept, which is the size of a single full line output + one
// repeat line marker ("*\n"). If the remaining buffer size is less
// than that, flush the buffer and reset.
constexpr size_t min_space = 79 + 2;

auto fd = fileno(out);
std::array<uint8_t, 4096> buf;
auto last = buf.data();
auto in = reinterpret_cast<const uint8_t *>(data);
auto repeated = false;
std::array<uint8_t, 16> buf{};
auto end = src + len;
auto i = src;
for (;;) {
auto nextlen =
std::min(static_cast<size_t>(16), static_cast<size_t>(end - i));
if (nextlen == buflen &&
std::equal(std::begin(buf), std::begin(buf) + buflen, i)) {
// as long as adjacent 16 bytes block are the same, we just
// print single '*'.
if (!repeated) {
repeated = true;
fputs("*\n", out);

for (size_t offset = 0; offset < datalen; offset += 16) {
auto n = datalen - offset;
auto s = in + offset;

if (n >= 16) {
n = 16;

if (offset > 0) {
if (std::equal(s - 16, s, s)) {
if (repeated) {
continue;
}

repeated = true;

*last++ = '*';
*last++ = '\n';

continue;
}

repeated = false;
}
i += nextlen;
continue;
}
repeated = false;
fprintf(out, "%08lx", static_cast<unsigned long>(i - src));
if (i == end) {
fputc('\n', out);
break;
}
fputs(" ", out);
hexdump8(out, i, end);
hexdump8(out, i + 8, std::max(i + 8, end));
fputc('|', out);
auto stop = std::min(i + 16, end);
buflen = stop - i;
auto p = buf.data();
for (; i != stop; ++i) {
*p++ = *i;
if (0x20 <= *i && *i <= 0x7e) {
fputc(*i, out);
} else {
fputc('.', out);

last = hexdump_line(last, s, n, offset);
*last++ = '\n';

auto len = static_cast<size_t>(last - buf.data());
if (len + min_space > buf.size()) {
if (hexdump_write(fd, buf.data(), len) != 0) {
return -1;
}

last = buf.data();
}
fputs("|\n", out);
}

last = hexdump_addr(last, datalen);
*last++ = '\n';

auto len = static_cast<size_t>(last - buf.data());
if (len) {
return hexdump_write(fd, buf.data(), len);
}

return 0;
}

void put_uint16be(uint8_t *buf, uint16_t n) {
Expand Down
6 changes: 4 additions & 2 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,10 @@ StringRef make_http_hostport(OutputIt first, const StringRef &host,
return StringRef{first, p};
}

// Dumps |src| of length |len| in the format similar to `hexdump -C`.
void hexdump(FILE *out, const uint8_t *src, size_t len);
// hexdump dumps |data| of length |datalen| in the format similar to
// hexdump(1) with -C option. This function returns 0 if it succeeds,
// or -1.
int hexdump(FILE *out, const void *data, size_t datalen);

// Copies 2 byte unsigned integer |n| in host byte order to |buf| in
// network byte order.
Expand Down

0 comments on commit 7c644e4

Please sign in to comment.