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

Add Slice#hexdump(io : IO) overload #10496

Merged
merged 1 commit into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("")
Comment on lines +283 to +285
Copy link
Member

Choose a reason for hiding this comment

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

Using String.build might make these examples a bit more concise.

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