From 5f79cfc6b22b6a2ef721178dba5dcef05e91192f Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Wed, 14 Apr 2021 21:02:17 +0800 Subject: [PATCH] Add Slice#hexdump(IO) overload (#10496) --- spec/std/slice_spec.cr | 55 ++++++++++++++++- src/io/hexdump.cr | 4 +- src/slice.cr | 134 +++++++++++++++++++++++++++-------------- 3 files changed, 145 insertions(+), 48 deletions(-) diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 155024adeee1..ebf255b49e88 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -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 @@ -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 } @@ -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 diff --git a/src/io/hexdump.cr b/src/io/hexdump.cr index 3df681f62ef8..b48d36ac8bfa 100644 --- a/src/io/hexdump.cr +++ b/src/io/hexdump.cr @@ -28,7 +28,7 @@ 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 @@ -36,7 +36,7 @@ class IO::Hexdump < IO return if buf.empty? @io.write(buf).tap do - @output.puts buf.hexdump if @write + buf.hexdump(@output) if @write end end diff --git a/src/slice.cr b/src/slice.cr index 3b3edec8de45..936e7d3a66e8 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -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)) @@ -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)