LCOV - code coverage report
Current view: top level - pebble/internal/binfmt - binfmt.go (source / functions) Hit Total Coverage
Test: 2024-12-19 08:17Z d5339521 - tests + meta.lcov Lines: 120 173 69.4 %
Date: 2024-12-19 08:18:10 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : // Package binfmt exposes utilities for formatting binary data with descriptive
       6             : // comments.
       7             : package binfmt
       8             : 
       9             : import (
      10             :         "bytes"
      11             :         "encoding/binary"
      12             :         "fmt"
      13             :         "math"
      14             :         "strconv"
      15             :         "strings"
      16             :         "unsafe"
      17             : 
      18             :         "github.com/cockroachdb/pebble/internal/treeprinter"
      19             : )
      20             : 
      21             : // New constructs a new binary formatter.
      22           1 : func New(data []byte) *Formatter {
      23           1 :         offsetWidth := strconv.Itoa(max(int(math.Log10(float64(len(data)-1)))+1, 1))
      24           1 :         return &Formatter{
      25           1 :                 data:            data,
      26           1 :                 lineWidth:       40,
      27           1 :                 offsetFormatStr: "%0" + offsetWidth + "d-%0" + offsetWidth + "d: ",
      28           1 :         }
      29           1 : }
      30             : 
      31             : // Formatter is a utility for formatting binary data with descriptive comments.
      32             : type Formatter struct {
      33             :         buf       bytes.Buffer
      34             :         lines     [][2]string // (binary data, comment) tuples
      35             :         data      []byte
      36             :         off       int
      37             :         anchorOff int
      38             : 
      39             :         // config
      40             :         lineWidth       int
      41             :         linePrefix      string
      42             :         offsetFormatStr string
      43             : }
      44             : 
      45             : // SetLinePrefix sets a prefix for each line of formatted output.
      46           0 : func (f *Formatter) SetLinePrefix(prefix string) {
      47           0 :         f.linePrefix = prefix
      48           0 : }
      49             : 
      50             : // SetAnchorOffset sets the reference point for relative offset calculations to
      51             : // the current offset. Future calls to RelativeOffset() will return an offset
      52             : // relative to the current offset.
      53           1 : func (f *Formatter) SetAnchorOffset() {
      54           1 :         f.anchorOff = f.off
      55           1 : }
      56             : 
      57             : // RelativeOffset retrieves the current offset relative to the offset at the
      58             : // last time SetAnchorOffset was called. If SetAnchorOffset was never called,
      59             : // RelativeOffset is equivalent to Offset.
      60           1 : func (f *Formatter) RelativeOffset() int {
      61           1 :         return f.off - f.anchorOff
      62           1 : }
      63             : 
      64             : // RelativeData returns the subslice of the original data slice beginning at the
      65             : // offset at which SetAnchorOffset was last called. If SetAnchorOffset was never
      66             : // called, RelativeData is equivalent to Data.
      67           1 : func (f *Formatter) RelativeData() []byte {
      68           1 :         return f.data[f.anchorOff:]
      69           1 : }
      70             : 
      71             : // LineWidth sets the Formatter's maximum line width for binary data.
      72           1 : func (f *Formatter) LineWidth(width int) *Formatter {
      73           1 :         f.lineWidth = width
      74           1 :         return f
      75           1 : }
      76             : 
      77             : // More returns true if there is more data in the byte slice that can be formatted.
      78           0 : func (f *Formatter) More() bool {
      79           0 :         return f.off < len(f.data)
      80           0 : }
      81             : 
      82             : // Remaining returns the number of unformatted bytes remaining in the byte slice.
      83           0 : func (f *Formatter) Remaining() int {
      84           0 :         return len(f.data) - f.off
      85           0 : }
      86             : 
      87             : // Offset returns the current offset within the original data slice.
      88           1 : func (f *Formatter) Offset() int {
      89           1 :         return f.off
      90           1 : }
      91             : 
      92             : // PeekUint reads a little-endian unsigned integer of the specified width at the
      93             : // current offset.
      94           1 : func (f *Formatter) PeekUint(w int) uint64 {
      95           1 :         switch w {
      96           1 :         case 1:
      97           1 :                 return uint64(f.data[f.off])
      98           1 :         case 2:
      99           1 :                 return uint64(binary.LittleEndian.Uint16(f.data[f.off:]))
     100           1 :         case 4:
     101           1 :                 return uint64(binary.LittleEndian.Uint32(f.data[f.off:]))
     102           1 :         case 8:
     103           1 :                 return binary.LittleEndian.Uint64(f.data[f.off:])
     104           0 :         default:
     105           0 :                 panic("unsupported width")
     106             :         }
     107             : }
     108             : 
     109             : // Byte formats a single byte in binary format, displaying each bit as a zero or
     110             : // one.
     111           1 : func (f *Formatter) Byte(format string, args ...interface{}) int {
     112           1 :         f.printOffsets(1)
     113           1 :         f.printf("b %08b", f.data[f.off])
     114           1 :         f.off++
     115           1 :         f.newline(f.buf.String(), fmt.Sprintf(format, args...))
     116           1 :         return 1
     117           1 : }
     118             : 
     119             : // HexBytesln formats the next n bytes in hexadecimal format, appending the
     120             : // formatted comment string to each line and ending on a newline.
     121           1 : func (f *Formatter) HexBytesln(n int, format string, args ...interface{}) int {
     122           1 :         commentLine := strings.TrimSpace(fmt.Sprintf(format, args...))
     123           1 :         printLine := func() {
     124           1 :                 bytesInLine := min(f.lineWidth/2, n)
     125           1 :                 if f.buf.Len() == 0 {
     126           1 :                         f.printOffsets(bytesInLine)
     127           1 :                 }
     128           1 :                 f.printf("x %0"+strconv.Itoa(bytesInLine*2)+"x", f.data[f.off:f.off+bytesInLine])
     129           1 :                 f.newline(f.buf.String(), commentLine)
     130           1 :                 f.off += bytesInLine
     131           1 :                 n -= bytesInLine
     132             :         }
     133           1 :         printLine()
     134           1 :         commentLine = "(continued...)"
     135           1 :         for n > 0 {
     136           1 :                 printLine()
     137           1 :         }
     138           1 :         return n
     139             : }
     140             : 
     141             : // HexTextln formats the next n bytes in hexadecimal format, appending a comment
     142             : // to each line showing the ASCII equivalent characters for each byte for bytes
     143             : // that are human-readable.
     144           0 : func (f *Formatter) HexTextln(n int) int {
     145           0 :         printLine := func() {
     146           0 :                 bytesInLine := min(f.lineWidth/2, n)
     147           0 :                 if f.buf.Len() == 0 {
     148           0 :                         f.printOffsets(bytesInLine)
     149           0 :                 }
     150           0 :                 f.printf("x %0"+strconv.Itoa(bytesInLine*2)+"x", f.data[f.off:f.off+bytesInLine])
     151           0 :                 commentLine := asciiChars(f.data[f.off : f.off+bytesInLine])
     152           0 :                 f.newline(f.buf.String(), commentLine)
     153           0 :                 f.off += bytesInLine
     154           0 :                 n -= bytesInLine
     155             :         }
     156           0 :         printLine()
     157           0 :         for n > 0 {
     158           0 :                 printLine()
     159           0 :         }
     160           0 :         return n
     161             : }
     162             : 
     163             : // Uvarint decodes the bytes at the current offset as a uvarint, formatting them
     164             : // in hexadecimal and prefixing the comment with the encoded decimal value.
     165           0 : func (f *Formatter) Uvarint(format string, args ...interface{}) {
     166           0 :         comment := fmt.Sprintf(format, args...)
     167           0 :         v, n := binary.Uvarint(f.data[f.off:])
     168           0 :         f.HexBytesln(n, "uvarint(%d): %s", v, comment)
     169           0 : }
     170             : 
     171             : // Line prepares a single line of formatted output that will consume n bytes,
     172             : // but formatting those n bytes in multiple ways. The line will be prefixed with
     173             : // the offsets for the line's entire data.
     174           1 : func (f *Formatter) Line(n int) Line {
     175           1 :         f.printOffsets(n)
     176           1 :         return Line{f: f, n: n, i: 0}
     177           1 : }
     178             : 
     179             : // String returns the current formatted output.
     180           1 : func (f *Formatter) String() string {
     181           1 :         f.buf.Reset()
     182           1 :         // Identify the max width of the binary data so that we can add padding to
     183           1 :         // align comments on the right.
     184           1 :         binaryLineWidth := 0
     185           1 :         for _, lineData := range f.lines {
     186           1 :                 binaryLineWidth = max(binaryLineWidth, len(lineData[0]))
     187           1 :         }
     188           1 :         for _, lineData := range f.lines {
     189           1 :                 fmt.Fprint(&f.buf, f.linePrefix)
     190           1 :                 fmt.Fprint(&f.buf, lineData[0])
     191           1 :                 if len(lineData[1]) > 0 {
     192           1 :                         if len(lineData[0]) == 0 {
     193           0 :                                 // There's no binary data on this line, just a comment. Print
     194           0 :                                 // the comment left-aligned.
     195           0 :                                 fmt.Fprint(&f.buf, "# ")
     196           1 :                         } else {
     197           1 :                                 // Align the comment to the right of the binary data.
     198           1 :                                 fmt.Fprint(&f.buf, strings.Repeat(" ", binaryLineWidth-len(lineData[0])))
     199           1 :                                 fmt.Fprint(&f.buf, " # ")
     200           1 :                         }
     201           1 :                         fmt.Fprint(&f.buf, lineData[1])
     202             :                 }
     203           1 :                 fmt.Fprintln(&f.buf)
     204             :         }
     205           1 :         return f.buf.String()
     206             : }
     207             : 
     208             : // ToTreePrinter formats the current output and creates a treeprinter child node
     209             : // for each line. The current output is reset; the position within the binary
     210             : // buffer is not.
     211           1 : func (f *Formatter) ToTreePrinter(tp treeprinter.Node) {
     212           1 :         for _, l := range strings.Split(strings.TrimRight(f.String(), "\n"), "\n") {
     213           1 :                 tp.Child(l)
     214           1 :         }
     215           1 :         f.buf.Reset()
     216           1 :         f.lines = f.lines[:0]
     217             : }
     218             : 
     219             : // Pointer returns a pointer into the original data slice at the specified
     220             : // offset.
     221           0 : func (f *Formatter) Pointer(off int) unsafe.Pointer {
     222           0 :         return unsafe.Pointer(&f.data[f.off+off])
     223           0 : }
     224             : 
     225             : // Data returns the original data slice. Offset may be used to retrieve the
     226             : // current offset within the slice.
     227           0 : func (f *Formatter) Data() []byte {
     228           0 :         return f.data
     229           0 : }
     230             : 
     231           1 : func (f *Formatter) newline(binaryData, comment string) {
     232           1 :         f.lines = append(f.lines, [2]string{binaryData, comment})
     233           1 :         f.buf.Reset()
     234           1 : }
     235             : 
     236           1 : func (f *Formatter) printOffsets(n int) {
     237           1 :         f.printf(f.offsetFormatStr, f.off, f.off+n)
     238           1 : }
     239             : 
     240           1 : func (f *Formatter) printf(format string, args ...interface{}) {
     241           1 :         fmt.Fprintf(&f.buf, format, args...)
     242           1 : }
     243             : 
     244             : // Line is a pending line of formatted binary output.
     245             : type Line struct {
     246             :         f *Formatter
     247             :         n int
     248             :         i int
     249             : }
     250             : 
     251             : // Append appends the provided string to the current line.
     252           1 : func (l Line) Append(s string) Line {
     253           1 :         fmt.Fprint(&l.f.buf, s)
     254           1 :         return l
     255           1 : }
     256             : 
     257             : // Binary formats the next n bytes in binary format, displaying each bit as
     258             : // a zero or one.
     259           1 : func (l Line) Binary(n int) Line {
     260           1 :         if n+l.i > l.n {
     261           0 :                 panic("binary data exceeds consumed line length")
     262             :         }
     263           1 :         for i := 0; i < n; i++ {
     264           1 :                 l.f.printf("%08b", l.f.data[l.f.off+l.i])
     265           1 :                 l.i++
     266           1 :         }
     267           1 :         return l
     268             : }
     269             : 
     270             : // HexBytes formats the next n bytes in hexadecimal format.
     271           1 : func (l Line) HexBytes(n int) Line {
     272           1 :         if n+l.i > l.n {
     273           0 :                 panic("binary data exceeds consumed line length")
     274             :         }
     275           1 :         l.f.printf("%0"+strconv.Itoa(n*2)+"x", l.f.data[l.f.off+l.i:l.f.off+l.i+n])
     276           1 :         l.i += n
     277           1 :         return l
     278             : }
     279             : 
     280             : // Done finishes the line, appending the provided comment if any.
     281           1 : func (l Line) Done(format string, args ...interface{}) int {
     282           1 :         if l.n != l.i {
     283           0 :                 panic("unconsumed data in line")
     284             :         }
     285           1 :         l.f.newline(l.f.buf.String(), fmt.Sprintf(format, args...))
     286           1 :         l.f.off += l.n
     287           1 :         return l.n
     288             : }
     289             : 
     290           0 : func asciiChars(b []byte) string {
     291           0 :         s := make([]byte, len(b))
     292           0 :         for i := range b {
     293           0 :                 if b[i] >= 32 && b[i] <= 126 {
     294           0 :                         s[i] = b[i]
     295           0 :                 } else {
     296           0 :                         s[i] = '.'
     297           0 :                 }
     298             :         }
     299           0 :         return string(s)
     300             : }

Generated by: LCOV version 1.14