LCOV - code coverage report
Current view: top level - pebble/internal/binfmt - binfmt.go (source / functions) Hit Total Coverage
Test: 2024-07-06 08:17Z fad89cfb - tests + meta.lcov Lines: 88 124 71.0 %
Date: 2024-07-06 08:18:35 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(int(math.Log10(float64(len(data)-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           0 : func (f *Formatter) Offset() int {
      59           0 :         return f.off
      60           0 : }
      61             : 
      62             : // PeekInt reads a little-endian integer of the specified width at the current
      63             : // offset.
      64           1 : func (f *Formatter) PeekInt(w int) int {
      65           1 :         switch w {
      66           0 :         case 1:
      67           0 :                 return int(f.data[f.off])
      68           0 :         case 2:
      69           0 :                 return int(binary.LittleEndian.Uint16(f.data[f.off:]))
      70           1 :         case 4:
      71           1 :                 return int(binary.LittleEndian.Uint32(f.data[f.off:]))
      72           1 :         case 8:
      73           1 :                 return int(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           0 : func (f *Formatter) Byte(format string, args ...interface{}) int {
      82           0 :         f.printOffsets(1)
      83           0 :         f.printf("b %08b", f.data[f.off])
      84           0 :         f.off++
      85           0 :         f.newline(f.buf.String(), fmt.Sprintf(format, args...))
      86           0 :         return 1
      87           0 : }
      88             : 
      89             : // CommentLine adds a full-width comment line to the output.
      90           0 : func (f *Formatter) CommentLine(format string, args ...interface{}) {
      91           0 :         f.newline("", strings.TrimSpace(fmt.Sprintf(format, args...)))
      92           0 : }
      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           0 :                 printLine()
     112           0 :         }
     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           0 :                                 // There's no binary data on this line, just a comment. Print
     138           0 :                                 // the comment left-aligned.
     139           0 :                                 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           1 : func (f *Formatter) newline(binaryData, comment string) {
     159           1 :         f.lines = append(f.lines, [2]string{binaryData, comment})
     160           1 :         f.buf.Reset()
     161           1 : }
     162             : 
     163           1 : func (f *Formatter) printOffsets(n int) {
     164           1 :         f.printf(f.offsetFormatStr, f.off, f.off+n)
     165           1 : }
     166             : 
     167           1 : func (f *Formatter) printf(format string, args ...interface{}) {
     168           1 :         fmt.Fprintf(&f.buf, format, args...)
     169           1 : }
     170             : 
     171             : // Line is a pending line of formatted binary output.
     172             : type Line struct {
     173             :         f *Formatter
     174             :         n int
     175             :         i int
     176             : }
     177             : 
     178             : // Append appends the provided string to the current line.
     179           1 : func (l Line) Append(s string) Line {
     180           1 :         fmt.Fprint(&l.f.buf, s)
     181           1 :         return l
     182           1 : }
     183             : 
     184             : // Binary formats the next n bytes in binary format, displaying each bit as
     185             : // a zero or one.
     186           1 : func (l Line) Binary(n int) Line {
     187           1 :         if n+l.i > l.n {
     188           0 :                 panic("binary data exceeds consumed line length")
     189             :         }
     190           1 :         for i := 0; i < n; i++ {
     191           1 :                 l.f.printf("%08b", l.f.data[l.f.off+l.i])
     192           1 :                 l.i++
     193           1 :         }
     194           1 :         return l
     195             : }
     196             : 
     197             : // HexBytes formats the next n bytes in hexadecimal format.
     198           1 : func (l Line) HexBytes(n int) Line {
     199           1 :         if n+l.i > l.n {
     200           0 :                 panic("binary data exceeds consumed line length")
     201             :         }
     202           1 :         l.f.printf("%0"+strconv.Itoa(n*2)+"x", l.f.data[l.f.off+l.i:l.f.off+l.i+n])
     203           1 :         l.i += n
     204           1 :         return l
     205             : }
     206             : 
     207             : // Done finishes the line, appending the provided comment if any.
     208           1 : func (l Line) Done(format string, args ...interface{}) int {
     209           1 :         if l.n != l.i {
     210           0 :                 panic("unconsumed data in line")
     211             :         }
     212           1 :         l.f.newline(l.f.buf.String(), fmt.Sprintf(format, args...))
     213           1 :         l.f.off += l.n
     214           1 :         return l.n
     215             : }

Generated by: LCOV version 1.14