blob: 1c7398fff86057c47f4de421fad7d54b55f4fadc [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// 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 Wang991ecf12015-02-25 17:38:37 -08005// The following enables go generate to generate the doc.go file.
Jiri Simsa32f76fb2015-04-07 15:39:23 -07006//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
Todd Wang991ecf12015-02-25 17:38:37 -08007
8package main
9
10import (
11 "bytes"
12 "encoding/hex"
13 "fmt"
14 "io"
15 "io/ioutil"
16 "os"
17 "strings"
18 "unicode"
19
Matt Rosencrantz83b0b7f2015-03-26 16:17:01 -070020 "v.io/v23"
Todd Wang991ecf12015-02-25 17:38:37 -080021 "v.io/v23/vdl"
22 "v.io/v23/vom"
Jiri Simsa24a71552015-02-27 11:31:36 -080023 "v.io/x/lib/cmdline"
Matt Rosencrantz83b0b7f2015-03-26 16:17:01 -070024 _ "v.io/x/ref/profiles/static"
Todd Wang991ecf12015-02-25 17:38:37 -080025)
26
27func main() {
Todd Wang1624bf92015-04-22 16:53:57 -070028 cmdline.HideGlobalFlagsExcept()
Todd Wang991ecf12015-02-25 17:38:37 -080029 os.Exit(cmdVom.Main())
30}
31
Matt Rosencrantz83b0b7f2015-03-26 16:17:01 -070032func 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 Wang991ecf12015-02-25 17:38:37 -080040var cmdVom = &cmdline.Command{
41 Name: "vom",
Todd Wang6ed3b6c2015-04-08 14:37:04 -070042 Short: "helps debug the Vanadium Object Marshaling wire protocol",
Todd Wang991ecf12015-02-25 17:38:37 -080043 Long: `
Todd Wang6ed3b6c2015-04-08 14:37:04 -070044Command vom helps debug the Vanadium Object Marshaling wire protocol.
Todd Wang991ecf12015-02-25 17:38:37 -080045`,
46 Children: []*cmdline.Command{cmdDecode, cmdDump},
47}
48
49var cmdDecode = &cmdline.Command{
Matt Rosencrantz83b0b7f2015-03-26 16:17:01 -070050 Run: runHelper(runDecode),
Todd Wang991ecf12015-02-25 17:38:37 -080051 Name: "decode",
52 Short: "Decode data encoded in the vom format",
53 Long: `
54Decode decodes data encoded in the vom format. If no arguments are provided,
55decode reads the data from stdin, otherwise the argument is the data.
56
57By default the data is assumed to be represented in hex, with all whitespace
58anywhere in the data ignored. Use the -data flag to specify other data
59representations.
60
61`,
62 ArgsName: "[data]",
63 ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
64}
65
66var cmdDump = &cmdline.Command{
Matt Rosencrantz83b0b7f2015-03-26 16:17:01 -070067 Run: runHelper(runDump),
Todd Wang991ecf12015-02-25 17:38:37 -080068 Name: "dump",
69 Short: "Dump data encoded in the vom format into formatted output",
70 Long: `
71Dump dumps data encoded in the vom format, generating formatted output
72describing each portion of the encoding. If no arguments are provided, dump
73reads the data from stdin, otherwise the argument is the data.
74
75By default the data is assumed to be represented in hex, with all whitespace
76anywhere in the data ignored. Use the -data flag to specify other data
77representations.
78
79Calling "vom dump" with no flags and no arguments combines the default stdin
80mode with the default hex mode. This default mode is special; certain non-hex
81characters 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
85This lets you cut-and-paste hex strings into your terminal, and use the commands
86to trigger status or flush calls; i.e. a rudimentary debugging UI.
87
88See 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
94var (
95 flagDataRep = dataRepHex
96)
97
98func 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
105func 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 Ahn5d1fe972015-04-27 17:51:32 -0700127 decoder := vom.NewDecoder(reader)
Todd Wang991ecf12015-02-25 17:38:37 -0800128 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
139func 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.
178func runDumpHexStream(dumper *vom.Dumper) error {
179 buf := make([]byte, 1024)
180 bufStart := 0
181ReadLoop:
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
244func 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
258func dropWhitespace(r rune) rune {
259 if unicode.IsSpace(r) {
260 return -1
261 }
262 return r
263}
264
265type dumpWriter struct {
266 atom, status io.Writer
267}
268
269var _ vom.DumpWriter = dumpWriter{}
270
271func (w dumpWriter) WriteAtom(atom vom.DumpAtom) {
272 w.atom.Write([]byte(atom.String() + "\n"))
273}
274
275func (w dumpWriter) WriteStatus(status vom.DumpStatus) {
Todd Wangbfa12a52015-04-29 19:21:57 -0700276 w.status.Write([]byte(status.String() + "\n"))
Todd Wang991ecf12015-02-25 17:38:37 -0800277}