LCOV - code coverage report
Current view: top level - pebble/internal/binfmt - binfmt.go (source / functions) Hit Total Coverage
Test: 2024-09-09 08:16Z 376d455b - tests only.lcov Lines: 113 127 89.0 %
Date: 2024-09-09 08:17:26 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             : 
      19             : // New constructs a new binary formatter.
      20           1 : func New(data []byte) *Formatter {
      21           1 :         offsetWidth := strconv.Itoa(max(int(math.Log10(float64(len(data)-1)))+1, 1))
      22           1 :         return &Formatter{
      23           1 :                 data:            data,
      24           1 :                 lineWidth:       40,
      25           1 :                 offsetFormatStr: "%0" + offsetWidth + "d-%0" + offsetWidth + "d: ",
      26           1 :         }
      27           1 : }
      28             : 
      29             : // Formatter is a utility for formatting binary data with descriptive comments.
      30             : type Formatter struct {
      31             :         buf   bytes.Buffer
      32             :         lines [][2]string // (binary data, comment) tuples
      33             :         data  []byte
      34             :         off   int
      35             : 
      36             :         // config
      37             :         lineWidth       int
      38             :         offsetFormatStr string
      39             : }
      40             : 
      41             : // LineWidth sets the Formatter's maximum line width for binary data.
      42           1 : func (f *Formatter) LineWidth(width int) *Formatter {
      43           1 :         f.lineWidth = width
      44           1 :         return f
      45           1 : }
      46             : 
      47             : // More returns true if there is more data in the byte slice that can be formatted.
      48           0 : func (f *Formatter) More() bool {
      49           0 :         return f.off < len(f.data)
      50           0 : }
      51             : 
      52             : // Remaining returns the number of unformatted bytes remaining in the byte slice.
      53           0 : func (f *Formatter) Remaining() int {
      54           0 :         return len(f.data) - f.off
      55           0 : }
      56             : 
      57             : // Offset returns the current offset within the original data slice.
      58           1 : func (f *Formatter) Offset() int {
      59           1 :         return f.off
      60           1 : }
      61             : 
      62             : // PeekUint reads a little-endian unsigned integer of the specified width at the
      63             : // current offset.
      64           1 : func (f *Formatter) PeekUint(w int) uint64 {
      65           1 :         switch w {
      66           1 :         case 1:
      67           1 :                 return uint64(f.data[f.off])
      68           1 :         case 2:
      69           1 :                 return uint64(binary.LittleEndian.Uint16(f.data[f.off:]))
      70           1 :         case 4:
      71           1 :                 return uint64(binary.LittleEndian.Uint32(f.data[f.off:]))
      72           1 :         case 8:
      73           1 :                 return binary.LittleEndian.Uint64(f.data[f.off:])
      74           0 :         default:
      75           0 :                 panic("unsupported width")
      76             :         }
      77             : }
      78             : 
      79             : // Byte formats a single byte in binary format, displaying each bit as a zero or
      80             : // one.
      81           1 : func (f *Formatter) Byte(format string, args ...interface{}) int {
      82           1 :         f.printOffsets(1)
      83           1 :         f.printf("b %08b", f.data[f.off])
      84           1 :         f.off++
      85           1 :         f.newline(f.buf.String(), fmt.Sprintf(format, args...))
      86           1 :         return 1
      87           1 : }
      88             : 
      89             : // CommentLine adds a full-width comment line to the output.
      90           1 : func (f *Formatter) CommentLine(format string, args ...interface{}) {
      91           1 :         f.newline("", strings.TrimSpace(fmt.Sprintf(format, args...)))
      92           1 : }
      93             : 
      94             : // HexBytesln formats the next n bytes in hexadecimal format, appending the
      95             : // formatted comment string to each line and ending on a newline.
      96           1 : func (f *Formatter) HexBytesln(n int, format string, args ...interface{}) int {
      97           1 :         commentLine := strings.TrimSpace(fmt.Sprintf(format, args...))
      98           1 :         printLine := func() {
      99           1 :                 bytesInLine := min(f.lineWidth/2, n)
     100           1 :                 if f.buf.Len() == 0 {
     101           1 :                         f.printOffsets(bytesInLine)
     102           1 :                 }
     103           1 :                 f.printf("x %0"+strconv.Itoa(bytesInLine*2)+"x", f.data[f.off:f.off+bytesInLine])
     104           1 :                 f.newline(f.buf.String(), commentLine)
     105           1 :                 f.off += bytesInLine
     106           1 :                 n -= bytesInLine
     107             :         }
     108           1 :         printLine()
     109           1 :         commentLine = "(continued...)"
     110           1 :         for n > 0 {
     111           1 :                 printLine()
     112           1 :         }
     113           1 :         return n
     114             : }
     115             : 
     116             : // Line prepares a single line of formatted output that will consume n bytes,
     117             : // but formatting those n bytes in multiple ways. The line will be prefixed with
     118             : // the offsets for the line's entire data.
     119           1 : func (f *Formatter) Line(n int) Line {
     120           1 :         f.printOffsets(n)
     121           1 :         return Line{f: f, n: n, i: 0}
     122           1 : }
     123             : 
     124             : // String returns the current formatted output.
     125           1 : func (f *Formatter) String() string {
     126           1 :         f.buf.Reset()
     127           1 :         // Identify the max width of the binary data so that we can add padding to
     128           1 :         // align comments on the right.
     129           1 :         binaryLineWidth := 0
     130           1 :         for _, lineData := range f.lines {
     131           1 :                 binaryLineWidth = max(binaryLineWidth, len(lineData[0]))
     132           1 :         }
     133           1 :         for _, lineData := range f.lines {
     134           1 :                 fmt.Fprint(&f.buf, lineData[0])
     135           1 :                 if len(lineData[1]) > 0 {
     136           1 :                         if len(lineData[0]) == 0 {
     137           1 :                                 // There's no binary data on this line, just a comment. Print
     138           1 :                                 // the comment left-aligned.
     139           1 :                                 fmt.Fprint(&f.buf, "# ")
     140           1 :                         } else {
     141           1 :                                 // Align the comment to the right of the binary data.
     142           1 :                                 fmt.Fprint(&f.buf, strings.Repeat(" ", binaryLineWidth-len(lineData[0])))
     143           1 :                                 fmt.Fprint(&f.buf, " # ")
     144           1 :                         }
     145           1 :                         fmt.Fprint(&f.buf, lineData[1])
     146             :                 }
     147           1 :                 fmt.Fprintln(&f.buf)
     148             :         }
     149           1 :         return f.buf.String()
     150             : }
     151             : 
     152             : // Pointer returns a pointer into the original data slice at the specified
     153             : // offset.
     154           0 : func (f *Formatter) Pointer(off int) unsafe.Pointer {
     155           0 :         return unsafe.Pointer(&f.data[f.off+off])
     156           0 : }
     157             : 
     158             : // Data returns the original data slice. Offset may be used to retrieve the
     159             : // current offset within the slice.
     160           1 : func (f *Formatter) Data() []byte {
     161           1 :         return f.data
     162           1 : }
     163             : 
     164           1 : func (f *Formatter) newline(binaryData, comment string) {
     165           1 :         f.lines = append(f.lines, [2]string{binaryData, comment})
     166           1 :         f.buf.Reset()
     167           1 : }
     168             : 
     169           1 : func (f *Formatter) printOffsets(n int) {
     170           1 :         f.printf(f.offsetFormatStr, f.off, f.off+n)
     171           1 : }
     172             : 
     173           1 : func (f *Formatter) printf(format string, args ...interface{}) {
     174           1 :         fmt.Fprintf(&f.buf, format, args...)
     175           1 : }
     176             : 
     177             : // Line is a pending line of formatted binary output.
     178             : type Line struct {
     179             :         f *Formatter
     180             :         n int
     181             :         i int
     182             : }
     183             : 
     184             : // Append appends the provided string to the current line.
     185           1 : func (l Line) Append(s string) Line {
     186           1 :         fmt.Fprint(&l.f.buf, s)
     187           1 :         return l
     188           1 : }
     189             : 
     190             : // Binary formats the next n bytes in binary format, displaying each bit as
     191             : // a zero or one.
     192           1 : func (l Line) Binary(n int) Line {
     193           1 :         if n+l.i > l.n {
     194           0 :                 panic("binary data exceeds consumed line length")
     195             :         }
     196           1 :         for i := 0; i < n; i++ {
     197           1 :                 l.f.printf("%08b", l.f.data[l.f.off+l.i])
     198           1 :                 l.i++
     199           1 :         }
     200           1 :         return l
     201             : }
     202             : 
     203             : // HexBytes formats the next n bytes in hexadecimal format.
     204           1 : func (l Line) HexBytes(n int) Line {
     205           1 :         if n+l.i > l.n {
     206           0 :                 panic("binary data exceeds consumed line length")
     207             :         }
     208           1 :         l.f.printf("%0"+strconv.Itoa(n*2)+"x", l.f.data[l.f.off+l.i:l.f.off+l.i+n])
     209           1 :         l.i += n
     210           1 :         return l
     211             : }
     212             : 
     213             : // Done finishes the line, appending the provided comment if any.
     214           1 : func (l Line) Done(format string, args ...interface{}) int {
     215           1 :         if l.n != l.i {
     216           0 :                 panic("unconsumed data in line")
     217             :         }
     218           1 :         l.f.newline(l.f.buf.String(), fmt.Sprintf(format, args...))
     219           1 :         l.f.off += l.n
     220           1 :         return l.n
     221             : }

Generated by: LCOV version 1.14