blob: 4b55635f212dadc9e62221d33af2008f47e2d5d0 [file] [log] [blame]
Todd Wang991ecf12015-02-25 17:38:37 -08001// The following enables go generate to generate the doc.go file.
Jiri Simsa24a71552015-02-27 11:31:36 -08002//go:generate go run $VANADIUM_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
Todd Wang991ecf12015-02-25 17:38:37 -08003
4package main
5
6import (
7 "bytes"
8 "encoding/hex"
9 "fmt"
10 "io"
11 "io/ioutil"
12 "os"
13 "strings"
14 "unicode"
15
Todd Wang991ecf12015-02-25 17:38:37 -080016 "v.io/v23/vdl"
17 "v.io/v23/vom"
Jiri Simsa24a71552015-02-27 11:31:36 -080018 "v.io/x/lib/cmdline"
Todd Wang991ecf12015-02-25 17:38:37 -080019)
20
21func main() {
22 os.Exit(cmdVom.Main())
23}
24
25var cmdVom = &cmdline.Command{
26 Name: "vom",
27 Short: "Veyron Object Marshaling debugging tool",
28 Long: `
29The vom tool helps debug the Veyron Object Marshaling (vom) protocol.
30`,
31 Children: []*cmdline.Command{cmdDecode, cmdDump},
32}
33
34var cmdDecode = &cmdline.Command{
35 Run: runDecode,
36 Name: "decode",
37 Short: "Decode data encoded in the vom format",
38 Long: `
39Decode decodes data encoded in the vom format. If no arguments are provided,
40decode reads the data from stdin, otherwise the argument is the data.
41
42By default the data is assumed to be represented in hex, with all whitespace
43anywhere in the data ignored. Use the -data flag to specify other data
44representations.
45
46`,
47 ArgsName: "[data]",
48 ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
49}
50
51var cmdDump = &cmdline.Command{
52 Run: runDump,
53 Name: "dump",
54 Short: "Dump data encoded in the vom format into formatted output",
55 Long: `
56Dump dumps data encoded in the vom format, generating formatted output
57describing each portion of the encoding. If no arguments are provided, dump
58reads the data from stdin, otherwise the argument is the data.
59
60By default the data is assumed to be represented in hex, with all whitespace
61anywhere in the data ignored. Use the -data flag to specify other data
62representations.
63
64Calling "vom dump" with no flags and no arguments combines the default stdin
65mode with the default hex mode. This default mode is special; certain non-hex
66characters 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
70This lets you cut-and-paste hex strings into your terminal, and use the commands
71to trigger status or flush calls; i.e. a rudimentary debugging UI.
72
73See 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
79var (
80 flagDataRep = dataRepHex
81)
82
83func 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
90func 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
127func 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.
166func runDumpHexStream(dumper *vom.Dumper) error {
167 buf := make([]byte, 1024)
168 bufStart := 0
169ReadLoop:
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
232func 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
246func dropWhitespace(r rune) rune {
247 if unicode.IsSpace(r) {
248 return -1
249 }
250 return r
251}
252
253type dumpWriter struct {
254 atom, status io.Writer
255}
256
257var _ vom.DumpWriter = dumpWriter{}
258
259func (w dumpWriter) WriteAtom(atom vom.DumpAtom) {
260 w.atom.Write([]byte(atom.String() + "\n"))
261}
262
263func (w dumpWriter) WriteStatus(status vom.DumpStatus) {
264 w.status.Write([]byte("\n" + status.String() + "\n"))
265}