github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/cmd/devp2p/enrcmd.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package main 19 20 import ( 21 "bytes" 22 "encoding/base64" 23 "encoding/hex" 24 "fmt" 25 "io/ioutil" 26 "net" 27 "os" 28 "strconv" 29 "strings" 30 31 "github.com/AigarNetwork/aigar/p2p/enode" 32 "github.com/AigarNetwork/aigar/p2p/enr" 33 "github.com/AigarNetwork/aigar/rlp" 34 "gopkg.in/urfave/cli.v1" 35 ) 36 37 var enrdumpCommand = cli.Command{ 38 Name: "enrdump", 39 Usage: "Pretty-prints node records", 40 Action: enrdump, 41 Flags: []cli.Flag{ 42 cli.StringFlag{Name: "file"}, 43 }, 44 } 45 46 func enrdump(ctx *cli.Context) error { 47 var source string 48 if file := ctx.String("file"); file != "" { 49 if ctx.NArg() != 0 { 50 return fmt.Errorf("can't dump record from command-line argument in -file mode") 51 } 52 var b []byte 53 var err error 54 if file == "-" { 55 b, err = ioutil.ReadAll(os.Stdin) 56 } else { 57 b, err = ioutil.ReadFile(file) 58 } 59 if err != nil { 60 return err 61 } 62 source = string(b) 63 } else if ctx.NArg() == 1 { 64 source = ctx.Args()[0] 65 } else { 66 return fmt.Errorf("need record as argument") 67 } 68 69 r, err := parseRecord(source) 70 if err != nil { 71 return fmt.Errorf("INVALID: %v", err) 72 } 73 fmt.Print(dumpRecord(r)) 74 return nil 75 } 76 77 // dumpRecord creates a human-readable description of the given node record. 78 func dumpRecord(r *enr.Record) string { 79 out := new(bytes.Buffer) 80 if n, err := enode.New(enode.ValidSchemes, r); err != nil { 81 fmt.Fprintf(out, "INVALID: %v\n", err) 82 } else { 83 fmt.Fprintf(out, "Node ID: %v\n", n.ID()) 84 } 85 kv := r.AppendElements(nil)[1:] 86 fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) 87 fmt.Fprint(out, dumpRecordKV(kv, 2)) 88 return out.String() 89 } 90 91 func dumpRecordKV(kv []interface{}, indent int) string { 92 // Determine the longest key name for alignment. 93 var out string 94 var longestKey = 0 95 for i := 0; i < len(kv); i += 2 { 96 key := kv[i].(string) 97 if len(key) > longestKey { 98 longestKey = len(key) 99 } 100 } 101 // Print the keys, invoking formatters for known keys. 102 for i := 0; i < len(kv); i += 2 { 103 key := kv[i].(string) 104 val := kv[i+1].(rlp.RawValue) 105 pad := longestKey - len(key) 106 out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) 107 formatter := attrFormatters[key] 108 if formatter == nil { 109 formatter = formatAttrRaw 110 } 111 fmtval, ok := formatter(val) 112 if ok { 113 out += fmtval + "\n" 114 } else { 115 out += hex.EncodeToString(val) + " (!)\n" 116 } 117 } 118 return out 119 } 120 121 // parseNode parses a node record and verifies its signature. 122 func parseNode(source string) (*enode.Node, error) { 123 if strings.HasPrefix(source, "enode://") { 124 return enode.ParseV4(source) 125 } 126 r, err := parseRecord(source) 127 if err != nil { 128 return nil, err 129 } 130 return enode.New(enode.ValidSchemes, r) 131 } 132 133 // parseRecord parses a node record from hex, base64, or raw binary input. 134 func parseRecord(source string) (*enr.Record, error) { 135 bin := []byte(source) 136 if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { 137 bin = d 138 } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { 139 bin = d 140 } 141 var r enr.Record 142 err := rlp.DecodeBytes(bin, &r) 143 return &r, err 144 } 145 146 func decodeRecordHex(b []byte) ([]byte, bool) { 147 if bytes.HasPrefix(b, []byte("0x")) { 148 b = b[2:] 149 } 150 dec := make([]byte, hex.DecodedLen(len(b))) 151 _, err := hex.Decode(dec, b) 152 return dec, err == nil 153 } 154 155 func decodeRecordBase64(b []byte) ([]byte, bool) { 156 if bytes.HasPrefix(b, []byte("enr:")) { 157 b = b[4:] 158 } 159 dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) 160 n, err := base64.RawURLEncoding.Decode(dec, b) 161 return dec[:n], err == nil 162 } 163 164 // attrFormatters contains formatting functions for well-known ENR keys. 165 var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ 166 "id": formatAttrString, 167 "ip": formatAttrIP, 168 "ip6": formatAttrIP, 169 "tcp": formatAttrUint, 170 "tcp6": formatAttrUint, 171 "udp": formatAttrUint, 172 "udp6": formatAttrUint, 173 } 174 175 func formatAttrRaw(v rlp.RawValue) (string, bool) { 176 s := hex.EncodeToString(v) 177 return s, true 178 } 179 180 func formatAttrString(v rlp.RawValue) (string, bool) { 181 content, _, err := rlp.SplitString(v) 182 return strconv.Quote(string(content)), err == nil 183 } 184 185 func formatAttrIP(v rlp.RawValue) (string, bool) { 186 content, _, err := rlp.SplitString(v) 187 if err != nil || len(content) != 4 && len(content) != 6 { 188 return "", false 189 } 190 return net.IP(content).String(), true 191 } 192 193 func formatAttrUint(v rlp.RawValue) (string, bool) { 194 var x uint64 195 if err := rlp.DecodeBytes(v, &x); err != nil { 196 return "", false 197 } 198 return strconv.FormatUint(x, 10), true 199 }