Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 1 | // The following enables go generate to generate the doc.go file. |
Jiri Simsa | 24a7155 | 2015-02-27 11:31:36 -0800 | [diff] [blame^] | 2 | //go:generate go run $VANADIUM_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 3 | |
| 4 | package main |
| 5 | |
| 6 | import ( |
| 7 | "bytes" |
| 8 | "encoding/hex" |
| 9 | "fmt" |
| 10 | "io" |
| 11 | "io/ioutil" |
| 12 | "os" |
| 13 | "strings" |
| 14 | "unicode" |
| 15 | |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 16 | "v.io/v23/vdl" |
| 17 | "v.io/v23/vom" |
Jiri Simsa | 24a7155 | 2015-02-27 11:31:36 -0800 | [diff] [blame^] | 18 | "v.io/x/lib/cmdline" |
Todd Wang | 991ecf1 | 2015-02-25 17:38:37 -0800 | [diff] [blame] | 19 | ) |
| 20 | |
| 21 | func main() { |
| 22 | os.Exit(cmdVom.Main()) |
| 23 | } |
| 24 | |
| 25 | var cmdVom = &cmdline.Command{ |
| 26 | Name: "vom", |
| 27 | Short: "Veyron Object Marshaling debugging tool", |
| 28 | Long: ` |
| 29 | The vom tool helps debug the Veyron Object Marshaling (vom) protocol. |
| 30 | `, |
| 31 | Children: []*cmdline.Command{cmdDecode, cmdDump}, |
| 32 | } |
| 33 | |
| 34 | var cmdDecode = &cmdline.Command{ |
| 35 | Run: runDecode, |
| 36 | Name: "decode", |
| 37 | Short: "Decode data encoded in the vom format", |
| 38 | Long: ` |
| 39 | Decode decodes data encoded in the vom format. If no arguments are provided, |
| 40 | decode reads the data from stdin, otherwise the argument is the data. |
| 41 | |
| 42 | By default the data is assumed to be represented in hex, with all whitespace |
| 43 | anywhere in the data ignored. Use the -data flag to specify other data |
| 44 | representations. |
| 45 | |
| 46 | `, |
| 47 | ArgsName: "[data]", |
| 48 | ArgsLong: "[data] is the data to decode; if not specified, reads from stdin", |
| 49 | } |
| 50 | |
| 51 | var cmdDump = &cmdline.Command{ |
| 52 | Run: runDump, |
| 53 | Name: "dump", |
| 54 | Short: "Dump data encoded in the vom format into formatted output", |
| 55 | Long: ` |
| 56 | Dump dumps data encoded in the vom format, generating formatted output |
| 57 | describing each portion of the encoding. If no arguments are provided, dump |
| 58 | reads the data from stdin, otherwise the argument is the data. |
| 59 | |
| 60 | By default the data is assumed to be represented in hex, with all whitespace |
| 61 | anywhere in the data ignored. Use the -data flag to specify other data |
| 62 | representations. |
| 63 | |
| 64 | Calling "vom dump" with no flags and no arguments combines the default stdin |
| 65 | mode with the default hex mode. This default mode is special; certain non-hex |
| 66 | characters may be input to represent commands: |
| 67 | . (period) Calls Dumper.Status to get the current decoding status. |
| 68 | ; (semicolon) Calls Dumper.Flush to flush output and start a new message. |
| 69 | |
| 70 | This lets you cut-and-paste hex strings into your terminal, and use the commands |
| 71 | to trigger status or flush calls; i.e. a rudimentary debugging UI. |
| 72 | |
| 73 | See v.io/v23/vom.Dumper for details on the dump output. |
| 74 | `, |
| 75 | ArgsName: "[data]", |
| 76 | ArgsLong: "[data] is the data to dump; if not specified, reads from stdin", |
| 77 | } |
| 78 | |
| 79 | var ( |
| 80 | flagDataRep = dataRepHex |
| 81 | ) |
| 82 | |
| 83 | func init() { |
| 84 | cmdDecode.Flags.Var(&flagDataRep, "data", |
| 85 | "Data representation, one of "+fmt.Sprint(dataRepAll)) |
| 86 | cmdDump.Flags.Var(&flagDataRep, "data", |
| 87 | "Data representation, one of "+fmt.Sprint(dataRepAll)) |
| 88 | } |
| 89 | |
| 90 | func runDecode(cmd *cmdline.Command, args []string) error { |
| 91 | // Convert all inputs into a reader over binary bytes. |
| 92 | var data string |
| 93 | switch { |
| 94 | case len(args) > 1: |
| 95 | return cmd.UsageErrorf("too many args") |
| 96 | case len(args) == 1: |
| 97 | data = args[0] |
| 98 | default: |
| 99 | bytes, err := ioutil.ReadAll(os.Stdin) |
| 100 | if err != nil { |
| 101 | return err |
| 102 | } |
| 103 | data = string(bytes) |
| 104 | } |
| 105 | binbytes, err := dataToBinaryBytes(data) |
| 106 | if err != nil { |
| 107 | return err |
| 108 | } |
| 109 | reader := bytes.NewBuffer(binbytes) |
| 110 | // Decode the binary bytes. |
| 111 | // TODO(toddw): Add a flag to set a specific type to decode into. |
| 112 | decoder, err := vom.NewDecoder(reader) |
| 113 | if err != nil { |
| 114 | return err |
| 115 | } |
| 116 | var result *vdl.Value |
| 117 | if err := decoder.Decode(&result); err != nil { |
| 118 | return err |
| 119 | } |
| 120 | fmt.Fprintln(cmd.Stdout(), result) |
| 121 | if reader.Len() != 0 { |
| 122 | return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String()) |
| 123 | } |
| 124 | return nil |
| 125 | } |
| 126 | |
| 127 | func runDump(cmd *cmdline.Command, args []string) error { |
| 128 | // Handle non-streaming cases. |
| 129 | switch { |
| 130 | case len(args) > 1: |
| 131 | return cmd.UsageErrorf("too many args") |
| 132 | case len(args) == 1: |
| 133 | binbytes, err := dataToBinaryBytes(args[0]) |
| 134 | if err != nil { |
| 135 | return err |
| 136 | } |
| 137 | fmt.Fprintln(cmd.Stdout(), vom.Dump(binbytes)) |
| 138 | return nil |
| 139 | } |
| 140 | // Handle streaming from stdin. |
| 141 | // TODO(toddw): Add a flag to configure stdout/stderr dumping. |
| 142 | dumper := vom.NewDumper(dumpWriter{cmd.Stdout(), cmd.Stdout()}) |
| 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 | } |
| 252 | |
| 253 | type dumpWriter struct { |
| 254 | atom, status io.Writer |
| 255 | } |
| 256 | |
| 257 | var _ vom.DumpWriter = dumpWriter{} |
| 258 | |
| 259 | func (w dumpWriter) WriteAtom(atom vom.DumpAtom) { |
| 260 | w.atom.Write([]byte(atom.String() + "\n")) |
| 261 | } |
| 262 | |
| 263 | func (w dumpWriter) WriteStatus(status vom.DumpStatus) { |
| 264 | w.status.Write([]byte("\n" + status.String() + "\n")) |
| 265 | } |