Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 5 | // The following enables go generate to generate the doc.go file. |
Jiri Simsa | 32f76fb | 2015-04-07 15:39:23 -0700 | [diff] [blame] | 6 | //go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 7 | |
| 8 | package main |
| 9 | |
| 10 | import ( |
| 11 | "bytes" |
| 12 | "encoding/hex" |
| 13 | "fmt" |
| 14 | "io" |
| 15 | "io/ioutil" |
| 16 | "os" |
| 17 | "strings" |
| 18 | "unicode" |
| 19 | |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 20 | "v.io/v23/vdl" |
| 21 | "v.io/v23/vom" |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 22 | "v.io/x/lib/cmdline" |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 23 | ) |
| 24 | |
| 25 | func main() { |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 26 | cmdline.Main(cmdVom) |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 27 | } |
| 28 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 29 | var cmdVom = &cmdline.Command{ |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 30 | Name: "vom", |
Todd Wang | 6ed3b6c | 2015-04-08 14:37:04 -0700 | [diff] [blame] | 31 | Short: "helps debug the Vanadium Object Marshaling wire protocol", |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 32 | Long: ` |
Todd Wang | 6ed3b6c | 2015-04-08 14:37:04 -0700 | [diff] [blame] | 33 | Command vom helps debug the Vanadium Object Marshaling wire protocol. |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 34 | `, |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 35 | Children: []*cmdline.Command{cmdDecode, cmdDump}, |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 36 | } |
| 37 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 38 | var cmdDecode = &cmdline.Command{ |
| 39 | Runner: cmdline.RunnerFunc(runDecode), |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 40 | Name: "decode", |
| 41 | Short: "Decode data encoded in the vom format", |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 42 | Long: ` |
| 43 | Decode decodes data encoded in the vom format. If no arguments are provided, |
| 44 | decode reads the data from stdin, otherwise the argument is the data. |
| 45 | |
| 46 | By default the data is assumed to be represented in hex, with all whitespace |
| 47 | anywhere in the data ignored. Use the -data flag to specify other data |
| 48 | representations. |
| 49 | |
| 50 | `, |
| 51 | ArgsName: "[data]", |
| 52 | ArgsLong: "[data] is the data to decode; if not specified, reads from stdin", |
| 53 | } |
| 54 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 55 | var cmdDump = &cmdline.Command{ |
| 56 | Runner: cmdline.RunnerFunc(runDump), |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 57 | Name: "dump", |
| 58 | Short: "Dump data encoded in the vom format into formatted output", |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 59 | Long: ` |
| 60 | Dump dumps data encoded in the vom format, generating formatted output |
| 61 | describing each portion of the encoding. If no arguments are provided, dump |
| 62 | reads the data from stdin, otherwise the argument is the data. |
| 63 | |
| 64 | By default the data is assumed to be represented in hex, with all whitespace |
| 65 | anywhere in the data ignored. Use the -data flag to specify other data |
| 66 | representations. |
| 67 | |
| 68 | Calling "vom dump" with no flags and no arguments combines the default stdin |
| 69 | mode with the default hex mode. This default mode is special; certain non-hex |
| 70 | characters may be input to represent commands: |
| 71 | . (period) Calls Dumper.Status to get the current decoding status. |
| 72 | ; (semicolon) Calls Dumper.Flush to flush output and start a new message. |
| 73 | |
| 74 | This lets you cut-and-paste hex strings into your terminal, and use the commands |
| 75 | to trigger status or flush calls; i.e. a rudimentary debugging UI. |
| 76 | |
| 77 | See v.io/v23/vom.Dumper for details on the dump output. |
| 78 | `, |
| 79 | ArgsName: "[data]", |
| 80 | ArgsLong: "[data] is the data to dump; if not specified, reads from stdin", |
| 81 | } |
| 82 | |
| 83 | var ( |
| 84 | flagDataRep = dataRepHex |
| 85 | ) |
| 86 | |
| 87 | func init() { |
| 88 | cmdDecode.Flags.Var(&flagDataRep, "data", |
| 89 | "Data representation, one of "+fmt.Sprint(dataRepAll)) |
| 90 | cmdDump.Flags.Var(&flagDataRep, "data", |
| 91 | "Data representation, one of "+fmt.Sprint(dataRepAll)) |
| 92 | } |
| 93 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 94 | func runDecode(env *cmdline.Env, args []string) error { |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 95 | // Convert all inputs into a reader over binary bytes. |
| 96 | var data string |
| 97 | switch { |
| 98 | case len(args) > 1: |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 99 | return env.UsageErrorf("too many args") |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 100 | case len(args) == 1: |
| 101 | data = args[0] |
| 102 | default: |
| 103 | bytes, err := ioutil.ReadAll(os.Stdin) |
| 104 | if err != nil { |
| 105 | return err |
| 106 | } |
| 107 | data = string(bytes) |
| 108 | } |
| 109 | binbytes, err := dataToBinaryBytes(data) |
| 110 | if err != nil { |
| 111 | return err |
| 112 | } |
| 113 | reader := bytes.NewBuffer(binbytes) |
| 114 | // Decode the binary bytes. |
| 115 | // TODO(toddw): Add a flag to set a specific type to decode into. |
Jungho Ahn | 5d1fe97 | 2015-04-27 17:51:32 -0700 | [diff] [blame] | 116 | decoder := vom.NewDecoder(reader) |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 117 | var result *vdl.Value |
| 118 | if err := decoder.Decode(&result); err != nil { |
| 119 | return err |
| 120 | } |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 121 | fmt.Fprintln(env.Stdout, result) |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 122 | if reader.Len() != 0 { |
| 123 | return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String()) |
| 124 | } |
| 125 | return nil |
| 126 | } |
| 127 | |
Todd Wang | 9560b9c | 2015-05-11 13:27:58 -0700 | [diff] [blame] | 128 | func runDump(env *cmdline.Env, args []string) error { |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 129 | // Handle non-streaming cases. |
| 130 | switch { |
| 131 | case len(args) > 1: |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 132 | return env.UsageErrorf("too many args") |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 133 | case len(args) == 1: |
| 134 | binbytes, err := dataToBinaryBytes(args[0]) |
| 135 | if err != nil { |
| 136 | return err |
| 137 | } |
Todd Wang | f1550cf | 2015-05-11 10:58:41 -0700 | [diff] [blame] | 138 | fmt.Fprintln(env.Stdout, vom.Dump(binbytes)) |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 139 | return nil |
| 140 | } |
| 141 | // Handle streaming from stdin. |
Todd Wang | 0c03446 | 2015-05-27 18:05:28 -0700 | [diff] [blame] | 142 | dumper := vom.NewDumper(vom.NewDumpWriter(env.Stdout)) |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 143 | defer dumper.Close() |
| 144 | // Handle simple non-hex cases. |
| 145 | switch flagDataRep { |
| 146 | case dataRepBinary: |
| 147 | _, err := io.Copy(dumper, os.Stdin) |
| 148 | return err |
| 149 | } |
| 150 | return runDumpHexStream(dumper) |
| 151 | } |
| 152 | |
| 153 | // runDumpHexStream handles the hex stdin-streaming special-case, with commands |
| 154 | // for status and flush. This is tricky because we need to strip whitespace, |
| 155 | // handle commands where they appear in the stream, and deal with the fact that |
| 156 | // it takes two hex characters to encode a single byte. |
| 157 | // |
| 158 | // The strategy is to run a ReadLoop that reads into a reasonably-sized buffer. |
| 159 | // Inside the ReadLoop we take the buffer, strip whitespace, and keep looping to |
| 160 | // process all data up to each command, and then process the command. If a |
| 161 | // command appears in the middle of two hex characters representing a byte, we |
| 162 | // send the command first, before sending the byte. |
| 163 | // |
| 164 | // Any leftover non-command single byte is stored in buf and bufStart is set, so |
| 165 | // that the next iteration of ReadLoop can read after those bytes. |
| 166 | func runDumpHexStream(dumper *vom.Dumper) error { |
| 167 | buf := make([]byte, 1024) |
| 168 | bufStart := 0 |
| 169 | ReadLoop: |
| 170 | for { |
| 171 | n, err := os.Stdin.Read(buf[bufStart:]) |
| 172 | switch { |
| 173 | case n == 0 && err == io.EOF: |
| 174 | return nil |
| 175 | case n == 0 && err != nil: |
| 176 | return err |
| 177 | } |
| 178 | // We may have hex interspersed with spaces and commands. The strategy is |
| 179 | // to strip all whitespace, and process each even-sized chunk of hex bytes |
| 180 | // up to a command or the end of the buffer. |
| 181 | // |
| 182 | // Data that appears before a command is written before the command, and |
| 183 | // data after the command is written after. But if a command appears in the |
| 184 | // middle of two hex characters representing a byte, we send the command |
| 185 | // first, before sending the byte. |
| 186 | hexbytes := bytes.Map(dropWhitespace, buf[:bufStart+n]) |
| 187 | for len(hexbytes) > 0 { |
| 188 | end := len(hexbytes) |
| 189 | cmdIndex := bytes.IndexAny(hexbytes, ".;") |
| 190 | if cmdIndex != -1 { |
| 191 | end = cmdIndex |
| 192 | } else if end == 1 { |
| 193 | // We have a single non-command byte left in hexbytes; copy it into buf |
| 194 | // and set bufStart. |
| 195 | copy(buf, hexbytes[0:1]) |
| 196 | bufStart = 1 |
| 197 | continue ReadLoop |
| 198 | } |
| 199 | if end%2 == 1 { |
| 200 | end -= 1 // Ensure the end is on an even boundary. |
| 201 | } |
| 202 | // Write this even-sized chunk of hex bytes to the dumper. |
| 203 | binbytes, err := hex.DecodeString(string(hexbytes[:end])) |
| 204 | if err != nil { |
| 205 | return err |
| 206 | } |
| 207 | if _, err := dumper.Write(binbytes); err != nil { |
| 208 | return err |
| 209 | } |
| 210 | // Handle commands. |
| 211 | if cmdIndex != -1 { |
| 212 | switch cmd := hexbytes[cmdIndex]; cmd { |
| 213 | case '.': |
| 214 | dumper.Status() |
| 215 | case ';': |
| 216 | dumper.Flush() |
| 217 | default: |
| 218 | return fmt.Errorf("unhandled command %q", cmd) |
| 219 | } |
| 220 | // Move data after the command forward. |
| 221 | copy(hexbytes[cmdIndex:], hexbytes[cmdIndex+1:]) |
| 222 | hexbytes = hexbytes[:len(hexbytes)-1] |
| 223 | } |
| 224 | // Move data after the end forward. |
| 225 | copy(hexbytes, hexbytes[end:]) |
| 226 | hexbytes = hexbytes[:len(hexbytes)-end] |
| 227 | } |
| 228 | bufStart = 0 |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | func dataToBinaryBytes(data string) ([]byte, error) { |
| 233 | // Transform all data representations to binary. |
| 234 | switch flagDataRep { |
| 235 | case dataRepHex: |
| 236 | // Remove all whitespace out of the hex string. |
| 237 | binbytes, err := hex.DecodeString(strings.Map(dropWhitespace, data)) |
| 238 | if err != nil { |
| 239 | return nil, err |
| 240 | } |
| 241 | return binbytes, nil |
| 242 | } |
| 243 | return []byte(data), nil |
| 244 | } |
| 245 | |
| 246 | func dropWhitespace(r rune) rune { |
| 247 | if unicode.IsSpace(r) { |
| 248 | return -1 |
| 249 | } |
| 250 | return r |
| 251 | } |