xref: /aosp_15_r20/external/pigweed/pw_hex_dump/hex_dump.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_hex_dump/hex_dump.h"
16 
17 #include <cctype>
18 #include <cstddef>
19 #include <string_view>
20 
21 #include "pw_status/status_with_size.h"
22 #include "pw_string/string_builder.h"
23 #include "pw_string/type_to_string.h"
24 
25 using pw::string::HexDigitCount;
26 using pw::string::IntToHexString;
27 
28 namespace pw::dump {
29 namespace {
30 
31 constexpr const std::string_view kAddressSeparator(": ");
32 constexpr const std::string_view kSectionSeparator("  ");
33 constexpr const std::string_view kAddressHeader("Address");
34 constexpr const std::string_view kOffsetHeader("Offs.");
35 constexpr const std::string_view kAsciiHeader("Text");
36 
37 // Minimum number of hex characters to use when displaying dump offset.
38 constexpr const size_t kMinOffsetChars = 4;
39 
PrintableChar(std::byte b)40 char PrintableChar(std::byte b) {
41   if (std::isprint(std::to_integer<char>(b)) == 0) {
42     return '.';
43   }
44   return std::to_integer<char>(b);
45 }
46 
AddGroupingByte(size_t byte_index,FormattedHexDumper::Flags & flags,StringBuilder & builder)47 void AddGroupingByte(size_t byte_index,
48                      FormattedHexDumper::Flags& flags,
49                      StringBuilder& builder) {
50   // Never add grouping when it is disabled.
51   if (flags.group_every == 0) {
52     return;
53   }
54   // If this byte isn't at the end of a group, don't add a space.
55   if ((byte_index + 1) % flags.group_every != 0) {
56     return;
57   }
58   // If this byte is the last byte in a line, don't add a grouping byte
59   // (prevents trailing spaces).
60   if (byte_index + 1 == flags.bytes_per_line) {
61     return;
62   }
63 
64   builder << ' ';
65 }
66 
67 }  // namespace
68 
DumpAddr(span<char> dest,uintptr_t addr)69 Status DumpAddr(span<char> dest, uintptr_t addr) {
70   if (dest.data() == nullptr) {
71     return Status::InvalidArgument();
72   }
73   // Include null terminator.
74   if (dest.size() < kHexAddrStringSize + 1) {
75     return Status::ResourceExhausted();
76   }
77   dest[0] = '0';
78   dest[1] = 'x';
79 
80   return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status();
81 }
82 
PrintFormatHeader()83 Status FormattedHexDumper::PrintFormatHeader() {
84   StringBuilder builder(dest_);
85 
86   if (flags.prefix_mode != AddressMode::kDisabled) {
87     std::string_view header(flags.prefix_mode == AddressMode::kOffset
88                                 ? kOffsetHeader
89                                 : kAddressHeader);
90     // Pad to align to address width.
91     size_t padding = 0;
92     if (flags.prefix_mode == AddressMode::kOffset) {
93       size_t offs_width =
94           HexDigitCount(source_data_.size_bytes() + current_offset_);
95       padding = std::max(offs_width, kMinOffsetChars);
96     } else {
97       padding = kHexAddrStringSize;
98     }
99 
100     padding += kAddressSeparator.length();
101     padding -= header.size();
102 
103     builder << header;
104     builder.append(padding, ' ');
105   }
106 
107   // Print offsets.
108   for (size_t i = 0; i < static_cast<size_t>(flags.bytes_per_line); ++i) {
109     // Early loop termination for when bytes_remaining <
110     // bytes_per_line.
111     if (flags.group_every != 0 &&
112         i % static_cast<uint8_t>(flags.group_every) == 0) {
113       builder << std::byte(i);
114     } else {
115       builder.append(2, ' ');
116     }
117     AddGroupingByte(i, flags, builder);
118   }
119 
120   if (flags.show_ascii) {
121     builder << kSectionSeparator;
122     builder << kAsciiHeader;
123   }
124 
125   return builder.status();
126 }
127 
DumpLine()128 Status FormattedHexDumper::DumpLine() {
129   if (source_data_.empty()) {
130     return Status::ResourceExhausted();
131   }
132 
133   if (!ValidateBufferSize().ok() || dest_.data() == nullptr) {
134     return Status::FailedPrecondition();
135   }
136 
137   if (dest_[0] == 0 && flags.show_header) {
138     // First line, print out dump format header.
139     return PrintFormatHeader();
140   }
141 
142   StringBuilder builder(dest_);
143   // Dump address/offset prefix.
144   // TODO(amontanez): This block can be much nicer if StringBuilder exposed an
145   // easy way to control zero padding for hex address.
146   if (flags.prefix_mode != AddressMode::kDisabled) {
147     uintptr_t val;
148     size_t significant;
149     if (flags.prefix_mode == AddressMode::kAbsolute) {
150       val = reinterpret_cast<uintptr_t>(source_data_.data());
151       builder << "0x";
152       significant = HexDigitCount(val);
153       builder.append(sizeof(uintptr_t) * 2 - significant, '0');
154     } else {
155       val = current_offset_;
156       significant = HexDigitCount(val);
157       if (significant < kMinOffsetChars) {
158         builder.append(kMinOffsetChars - significant, '0');
159       }
160     }
161     if (val != 0) {
162       builder << reinterpret_cast<void*>(val);
163     } else {
164       builder.append(significant, '0');
165     }
166     builder << kAddressSeparator;
167   }
168 
169   size_t bytes_in_line = std::min(source_data_.size_bytes(),
170                                   static_cast<size_t>(flags.bytes_per_line));
171   // Convert raw bytes to hex characters.
172   for (size_t i = 0; i < bytes_in_line; ++i) {
173     // Early loop termination for when bytes_remaining <
174     // bytes_per_line.
175     builder << source_data_[i];
176     AddGroupingByte(i, flags, builder);
177   }
178   // Add padding spaces to ensure lines are aligned.
179   if (flags.show_ascii) {
180     for (size_t i = bytes_in_line;
181          i < static_cast<size_t>(flags.bytes_per_line);
182          ++i) {
183       builder.append(2, ' ');
184       AddGroupingByte(i, flags, builder);
185     }
186   }
187 
188   // Interpret bytes as characters.
189   if (flags.show_ascii) {
190     builder << kSectionSeparator;
191     for (size_t i = 0; i < bytes_in_line; ++i) {
192       builder << PrintableChar(source_data_[i]);
193     }
194   }
195 
196   source_data_ = source_data_.subspan(bytes_in_line);
197   current_offset_ += bytes_in_line;
198   return builder.status();
199 }
200 
SetLineBuffer(span<char> dest)201 Status FormattedHexDumper::SetLineBuffer(span<char> dest) {
202   if (dest.data() == nullptr || dest.size_bytes() == 0) {
203     return Status::InvalidArgument();
204   }
205   dest_ = dest;
206   return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted();
207 }
208 
BeginDump(ConstByteSpan data)209 Status FormattedHexDumper::BeginDump(ConstByteSpan data) {
210   current_offset_ = 0;
211   source_data_ = data;
212   if (data.data() == nullptr) {
213     return Status::InvalidArgument();
214   }
215   if (dest_.data() != nullptr && dest_.size_bytes() > 0) {
216     dest_[0] = 0;
217   }
218   return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition();
219 }
220 
ValidateBufferSize()221 Status FormattedHexDumper::ValidateBufferSize() {
222   // Minimum size is number of bytes per line as hex pairs plus the null
223   // terminator.
224   size_t required_size = flags.bytes_per_line * 2 + 1;
225   if (flags.show_ascii) {
226     required_size += kSectionSeparator.length() + flags.bytes_per_line;
227   }
228   if (flags.prefix_mode == AddressMode::kAbsolute) {
229     required_size += kHexAddrStringSize;
230     required_size += kAddressSeparator.length();
231   } else if (flags.prefix_mode == AddressMode::kOffset) {
232     required_size +=
233         HexDigitCount(std::max(source_data_.size_bytes(), kMinOffsetChars));
234     required_size += kAddressSeparator.length();
235   }
236   if (flags.group_every != 0) {
237     required_size += (flags.bytes_per_line - 1) / flags.group_every;
238   }
239 
240   if (dest_.size_bytes() < required_size) {
241     return Status::ResourceExhausted();
242   }
243 
244   return OkStatus();
245 }
246 
247 }  // namespace pw::dump
248