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