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 }