github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/cmd/rlpdump/main.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // rlpdump is a pretty-printer for RLP data.
    18  package main
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"container/list"
    24  	"encoding/hex"
    25  	"flag"
    26  	"fmt"
    27  	"io"
    28  	"math"
    29  	"os"
    30  	"strconv"
    31  	"strings"
    32  
    33  	"github.com/ethereum/go-ethereum/common"
    34  	"github.com/ethereum/go-ethereum/rlp"
    35  )
    36  
    37  var (
    38  	hexMode     = flag.String("hex", "", "dump given hex data")
    39  	reverseMode = flag.Bool("reverse", false, "convert ASCII to rlp")
    40  	noASCII     = flag.Bool("noascii", false, "don't print ASCII strings readably")
    41  	single      = flag.Bool("single", false, "print only the first element, discard the rest")
    42  	showpos     = flag.Bool("pos", false, "display element byte posititions")
    43  )
    44  
    45  func init() {
    46  	flag.Usage = func() {
    47  		fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex <data>][-reverse] [filename]")
    48  		flag.PrintDefaults()
    49  		fmt.Fprintln(os.Stderr, `
    50  Dumps RLP data from the given file in readable form.
    51  If the filename is omitted, data is read from stdin.`)
    52  	}
    53  }
    54  
    55  func main() {
    56  	flag.Parse()
    57  
    58  	var r *inStream
    59  	switch {
    60  	case *hexMode != "":
    61  		data, err := hex.DecodeString(strings.TrimPrefix(*hexMode, "0x"))
    62  		if err != nil {
    63  			die(err)
    64  		}
    65  		r = newInStream(bytes.NewReader(data), int64(len(data)))
    66  
    67  	case flag.NArg() == 0:
    68  		r = newInStream(bufio.NewReader(os.Stdin), 0)
    69  
    70  	case flag.NArg() == 1:
    71  		fd, err := os.Open(flag.Arg(0))
    72  		if err != nil {
    73  			die(err)
    74  		}
    75  		defer fd.Close()
    76  		var size int64
    77  		finfo, err := fd.Stat()
    78  		if err == nil {
    79  			size = finfo.Size()
    80  		}
    81  		r = newInStream(bufio.NewReader(fd), size)
    82  
    83  	default:
    84  		fmt.Fprintln(os.Stderr, "Error: too many arguments")
    85  		flag.Usage()
    86  		os.Exit(2)
    87  	}
    88  
    89  	out := os.Stdout
    90  	if *reverseMode {
    91  		data, err := textToRlp(r)
    92  		if err != nil {
    93  			die(err)
    94  		}
    95  		fmt.Printf("%#x\n", data)
    96  		return
    97  	} else {
    98  		err := rlpToText(r, out)
    99  		if err != nil {
   100  			die(err)
   101  		}
   102  	}
   103  }
   104  
   105  func rlpToText(in *inStream, out io.Writer) error {
   106  	stream := rlp.NewStream(in, 0)
   107  	for {
   108  		if err := dump(in, stream, 0, out); err != nil {
   109  			if err != io.EOF {
   110  				return err
   111  			}
   112  			break
   113  		}
   114  		fmt.Fprintln(out)
   115  		if *single {
   116  			break
   117  		}
   118  	}
   119  	return nil
   120  }
   121  
   122  func dump(in *inStream, s *rlp.Stream, depth int, out io.Writer) error {
   123  	if *showpos {
   124  		fmt.Fprintf(out, "%s: ", in.posLabel())
   125  	}
   126  	kind, size, err := s.Kind()
   127  	if err != nil {
   128  		return err
   129  	}
   130  	switch kind {
   131  	case rlp.Byte, rlp.String:
   132  		str, err := s.Bytes()
   133  		if err != nil {
   134  			return err
   135  		}
   136  		if len(str) == 0 || !*noASCII && isASCII(str) {
   137  			fmt.Fprintf(out, "%s%q", ws(depth), str)
   138  		} else {
   139  			fmt.Fprintf(out, "%s%x", ws(depth), str)
   140  		}
   141  	case rlp.List:
   142  		s.List()
   143  		defer s.ListEnd()
   144  		if size == 0 {
   145  			fmt.Fprintf(out, ws(depth)+"[]")
   146  		} else {
   147  			fmt.Fprintln(out, ws(depth)+"[")
   148  			for i := 0; ; i++ {
   149  				if i > 0 {
   150  					fmt.Fprint(out, ",\n")
   151  				}
   152  				if err := dump(in, s, depth+1, out); err == rlp.EOL {
   153  					break
   154  				} else if err != nil {
   155  					return err
   156  				}
   157  			}
   158  			fmt.Fprint(out, ws(depth)+"]")
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  func isASCII(b []byte) bool {
   165  	for _, c := range b {
   166  		if c < 32 || c > 126 {
   167  			return false
   168  		}
   169  	}
   170  	return true
   171  }
   172  
   173  func ws(n int) string {
   174  	return strings.Repeat("  ", n)
   175  }
   176  
   177  func die(args ...interface{}) {
   178  	fmt.Fprintln(os.Stderr, args...)
   179  	os.Exit(1)
   180  }
   181  
   182  // textToRlp converts text into RLP (best effort).
   183  func textToRlp(r io.Reader) ([]byte, error) {
   184  	// We're expecting the input to be well-formed, meaning that
   185  	// - each element is on a separate line
   186  	// - each line is either an (element OR a list start/end) + comma
   187  	// - an element is either hex-encoded bytes OR a quoted string
   188  	var (
   189  		scanner = bufio.NewScanner(r)
   190  		obj     []interface{}
   191  		stack   = list.New()
   192  	)
   193  	for scanner.Scan() {
   194  		t := strings.TrimSpace(scanner.Text())
   195  		if len(t) == 0 {
   196  			continue
   197  		}
   198  		switch t {
   199  		case "[": // list start
   200  			stack.PushFront(obj)
   201  			obj = make([]interface{}, 0)
   202  		case "]", "],": // list end
   203  			parent := stack.Remove(stack.Front()).([]interface{})
   204  			obj = append(parent, obj)
   205  		case "[],": // empty list
   206  			obj = append(obj, make([]interface{}, 0))
   207  		default: // element
   208  			data := []byte(t)[:len(t)-1] // cut off comma
   209  			if data[0] == '"' {          // ascii string
   210  				data = []byte(t)[1 : len(data)-1]
   211  			} else { // hex data
   212  				data = common.FromHex(string(data))
   213  			}
   214  			obj = append(obj, data)
   215  		}
   216  	}
   217  	if err := scanner.Err(); err != nil {
   218  		return nil, err
   219  	}
   220  	data, err := rlp.EncodeToBytes(obj[0])
   221  	return data, err
   222  }
   223  
   224  type inStream struct {
   225  	br      rlp.ByteReader
   226  	pos     int
   227  	columns int
   228  }
   229  
   230  func newInStream(br rlp.ByteReader, totalSize int64) *inStream {
   231  	col := int(math.Ceil(math.Log10(float64(totalSize))))
   232  	return &inStream{br: br, columns: col}
   233  }
   234  
   235  func (rc *inStream) Read(b []byte) (n int, err error) {
   236  	n, err = rc.br.Read(b)
   237  	rc.pos += n
   238  	return n, err
   239  }
   240  
   241  func (rc *inStream) ReadByte() (byte, error) {
   242  	b, err := rc.br.ReadByte()
   243  	if err == nil {
   244  		rc.pos++
   245  	}
   246  	return b, err
   247  }
   248  
   249  func (rc *inStream) posLabel() string {
   250  	l := strconv.FormatInt(int64(rc.pos), 10)
   251  	if len(l) < rc.columns {
   252  		l = strings.Repeat(" ", rc.columns-len(l)) + l
   253  	}
   254  	return l
   255  }