Skip to content

Commit

Permalink
Add Slice#hexdump(IO) overload (#10496)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Apr 14, 2021
1 parent 5298587 commit 5f79cfc
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 48 deletions.
55 changes: 53 additions & 2 deletions spec/std/slice_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ describe "Slice" do

it "does hexdump for empty slice" do
Bytes.empty.hexdump.should eq("")

io = IO::Memory.new
Bytes.empty.hexdump(io).should eq(0)
io.to_s.should eq("")
end

it "does hexdump" do
Expand All @@ -288,7 +292,7 @@ describe "Slice" do
00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_
00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.\n
EOF

slice = Bytes.new(96) { |i| i.to_u8 + 32 }
Expand All @@ -301,11 +305,58 @@ describe "Slice" do
00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_
00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
00000060 80 81 82 83 84 .....
00000060 80 81 82 83 84 .....\n
EOF

plus = Bytes.new(101) { |i| i.to_u8 + 32 }
plus.hexdump.should eq(ascii_table_plus)

ascii_table_num = <<-EOF
00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n
EOF

num = Bytes.new(10) { |i| i.to_u8 + 48 }
num.hexdump.should eq(ascii_table_num)
end

it "does hexdump to IO" do
ascii_table = <<-EOF
00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_
00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.\n
EOF

slice = Bytes.new(96) { |i| i.to_u8 + 32 }
io = IO::Memory.new
slice.hexdump(io).should eq(ascii_table.bytesize)
io.to_s.should eq(ascii_table)

ascii_table_plus = <<-EOF
00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./
00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>?
00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_
00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno
00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~.
00000060 80 81 82 83 84 .....\n
EOF

plus = Bytes.new(101) { |i| i.to_u8 + 32 }
io = IO::Memory.new
plus.hexdump(io).should eq(ascii_table_plus.bytesize)
io.to_s.should eq(ascii_table_plus)

ascii_table_num = <<-EOF
00000000 30 31 32 33 34 35 36 37 38 39 0123456789\n
EOF

num = Bytes.new(10) { |i| i.to_u8 + 48 }
io = IO::Memory.new
num.hexdump(io).should eq(ascii_table_num.bytesize)
io.to_s.should eq(ascii_table_num)
end

it "does iterator" do
Expand Down
4 changes: 2 additions & 2 deletions src/io/hexdump.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ class IO::Hexdump < IO

def read(buf : Bytes)
@io.read(buf).tap do |read_bytes|
@output.puts buf[0, read_bytes].hexdump if @read && read_bytes
buf[0, read_bytes].hexdump(@output) if @read && read_bytes
end
end

def write(buf : Bytes) : Nil
return if buf.empty?

@io.write(buf).tap do
@output.puts buf.hexdump if @write
buf.hexdump(@output) if @write
end
end

Expand Down
134 changes: 90 additions & 44 deletions src/slice.cr
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ struct Slice(T)
#
# ```
# slice = UInt8.slice(97, 62, 63, 8, 255)
# slice.hexdump # => "00000000 61 3e 3f 08 ff a>?.."
# slice.hexdump # => "00000000 61 3e 3f 08 ff a>?..\n"
# ```
def hexdump
self.as(Slice(UInt8))
Expand All @@ -474,59 +474,105 @@ struct Slice(T)

full_lines, leftover = size.divmod(16)
if leftover == 0
str_size = full_lines * 77 - 1
lines = full_lines
str_size = full_lines * 77
else
str_size = (full_lines + 1) * 77 - (16 - leftover) - 1
lines = full_lines + 1
str_size = (full_lines + 1) * 77 - (16 - leftover)
end

String.new(str_size) do |buf|
index_offset = 0
hex_offset = 10
ascii_offset = 60

# Ensure we don't write outside the buffer:
# slower, but safer (speed is not very important when hexdump is used)
buffer = Slice.new(buf, str_size)

each_with_index do |v, i|
if i % 16 == 0
0.upto(7) do |j|
buffer[index_offset + 7 - j] = to_hex((i >> (4 * j)) & 0xf)
end
buffer[index_offset + 8] = ' '.ord.to_u8
buffer[index_offset + 9] = ' '.ord.to_u8
index_offset += 77
end

buffer[hex_offset] = to_hex(v >> 4)
buffer[hex_offset + 1] = to_hex(v & 0x0f)
buffer[hex_offset + 2] = ' '.ord.to_u8
hex_offset += 3

buffer[ascii_offset] = (v > 31 && v < 127) ? v : '.'.ord.to_u8
ascii_offset += 1

if i % 8 == 7
buffer[hex_offset] = ' '.ord.to_u8
hex_offset += 1
end

if i % 16 == 15 && ascii_offset < str_size
buffer[ascii_offset] = '\n'.ord.to_u8
hex_offset += 27
ascii_offset += 61
end
pos = 0
offset = 0

while pos < size
# Ensure we don't write outside the buffer:
# slower, but safer (speed is not very important when hexdump is used)
hexdump_line(Slice.new(buf + offset, {77, str_size - offset}.min), pos)
pos += 16
offset += 77
end

while hex_offset % 77 < 60
buffer[hex_offset] = ' '.ord.to_u8
{str_size, str_size}
end
end

# Writes a hexdump of this slice, assuming it's a `Slice(UInt8)`, to the given *io*.
# This method is specially useful for debugging binary data and
# incoming/outgoing data in protocols.
#
# Returns the number of bytes written to *io*.
#
# ```
# slice = UInt8.slice(97, 62, 63, 8, 255)
# slice.hexdump(STDOUT)
# ```
#
# Prints:
#
# ```text
# 00000000 61 3e 3f 08 ff a>?..
# ```
def hexdump(io : IO)
self.as(Slice(UInt8))

return 0 if empty?

line = uninitialized UInt8[77]
line_slice = line.to_slice
count = 0

pos = 0
while pos < size
line_bytes = hexdump_line(line_slice, pos)
io.write(line_slice[0, line_bytes])
count += line_bytes
pos += 16
end

io.flush
count
end

private def hexdump_line(line, start_pos)
hex_offset = 10
ascii_offset = 60

0.upto(7) do |j|
line[7 - j] = to_hex((start_pos >> (4 * j)) & 0xf)
end
line[8] = 0x20_u8
line[9] = 0x20_u8

pos = start_pos
16.times do |i|
break if pos >= size
v = unsafe_fetch(pos)
pos += 1

line[hex_offset] = to_hex(v >> 4)
line[hex_offset + 1] = to_hex(v & 0x0f)
line[hex_offset + 2] = 0x20_u8
hex_offset += 3

if i == 7
line[hex_offset] = 0x20_u8
hex_offset += 1
end

{str_size, str_size}
line[ascii_offset] = 0x20_u8 <= v <= 0x7e_u8 ? v : 0x2e_u8
ascii_offset += 1
end

while hex_offset < 60
line[hex_offset] = 0x20_u8
hex_offset += 1
end

if ascii_offset < line.size
line[ascii_offset] = 0x0a_u8
ascii_offset += 1
end

ascii_offset
end

private def to_hex(c)
Expand Down

0 comments on commit 5f79cfc

Please sign in to comment.