1// Package hexdump renders bytes in the classic xxd/hexdump -C layout, as a2// pure, reusable package.3//4// The format is the familiar one: an 8-digit hex offset, then 16 bytes as hex5// in two 8-byte groups separated by an extra space, then the same bytes as6// ASCII between pipes with non-printables shown as ".". Short final lines are7// padded so the ASCII column stays aligned — the whole point of the layout.8//9// Useful on chain for exactly the reason it is useful off it: when a value is10// not what you expected, the bytes tell you why. Encoding bugs, stray NULs,11// UTF-8 that is not what it claims, trailing whitespace — all invisible in a12// rendered string and obvious in a dump.13//14// A live demo of this package is at15// [r/moul/x/daily/hexdumpdemo](/r/moul/x/daily/hexdumpdemo/v0).16package hexdump1718import "strings"1920// MaxBytes bounds a dump so gas stays predictable. Input beyond this is21// truncated and reported by Dump's second return value.22const MaxBytes = 40962324// BytesPerLine is the classic 16.25const BytesPerLine = 162627const hexDigits = "0123456789abcdef"2829// Dump renders b in the xxd -C layout. It returns the dump and the number of30// bytes rendered, which is less than len(b) when the input exceeds MaxBytes.31func Dump(b []byte) (string, int) {32 n := len(b)33 if n > MaxBytes {34 n = MaxBytes35 }36 if n == 0 {37 return "", 038 }3940 var sb strings.Builder41 for off := 0; off < n; off += BytesPerLine {42 end := off + BytesPerLine43 if end > n {44 end = n45 }46 sb.WriteString(Line(off, b[off:end]))47 sb.WriteString("\n")48 }49 return sb.String(), n50}5152// DumpString is Dump over a string's bytes.53func DumpString(s string) (string, int) { return Dump([]byte(s)) }5455// Line renders one line: offset, hex columns, ASCII gutter. chunk must hold at56// most BytesPerLine bytes; a shorter chunk is padded so columns stay aligned.57func Line(offset int, chunk []byte) string {58 var sb strings.Builder59 sb.WriteString(Offset(offset))60 sb.WriteString(" ")6162 for i := 0; i < BytesPerLine; i++ {63 if i == BytesPerLine/2 {64 sb.WriteString(" ") // the classic gap between the two 8-byte groups65 }66 if i < len(chunk) {67 sb.WriteString(Hex(chunk[i]))68 } else {69 sb.WriteString(" ") // pad so the ASCII gutter never shifts70 }71 sb.WriteString(" ")72 }7374 sb.WriteString(" |")75 sb.WriteString(ASCII(chunk))76 sb.WriteString("|")77 return sb.String()78}7980// Offset formats an 8-digit lowercase hex offset.81func Offset(n int) string {82 if n < 0 {83 n = 084 }85 out := make([]byte, 8)86 for i := 7; i >= 0; i-- {87 out[i] = hexDigits[n&0xf]88 n >>= 489 }90 return string(out)91}9293// Hex formats one byte as two lowercase hex digits.94func Hex(b byte) string {95 return string([]byte{hexDigits[b>>4], hexDigits[b&0xf]})96}9798// ASCII renders the printable-ASCII view of chunk: bytes outside 0x20..0x7e99// become ".". It is NOT padded — Line handles alignment.100func ASCII(chunk []byte) string {101 out := make([]byte, len(chunk))102 for i, c := range chunk {103 if c >= 0x20 && c <= 0x7e {104 out[i] = c105 } else {106 out[i] = '.'107 }108 }109 return string(out)110}111112// Printable reports whether c renders as itself rather than as ".".113func Printable(c byte) bool { return c >= 0x20 && c <= 0x7e }114Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.