blob: e14a0bd79271bf5e3f1176565bf6c48d0d78d267 [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
Todd Wang991ecf12015-02-25 17:38:37 -080020 "v.io/v23/vdl"
21 "v.io/v23/vom"
Todd Wang9560b9c2015-05-11 13:27:58 -070022 "v.io/x/lib/cmdline"
Todd Wang991ecf12015-02-25 17:38:37 -080023)
24
25func main() {
Todd Wang9560b9c2015-05-11 13:27:58 -070026 cmdline.Main(cmdVom)
Todd Wang991ecf12015-02-25 17:38:37 -080027}
28
Todd Wang9560b9c2015-05-11 13:27:58 -070029var cmdVom = &cmdline.Command{
Todd Wang991ecf12015-02-25 17:38:37 -080030 Name: "vom",
Todd Wang6ed3b6c2015-04-08 14:37:04 -070031 Short: "helps debug the Vanadium Object Marshaling wire protocol",
Todd Wang991ecf12015-02-25 17:38:37 -080032 Long: `
Todd Wang6ed3b6c2015-04-08 14:37:04 -070033Command vom helps debug the Vanadium Object Marshaling wire protocol.
Todd Wang991ecf12015-02-25 17:38:37 -080034`,
Todd Wang9560b9c2015-05-11 13:27:58 -070035 Children: []*cmdline.Command{cmdDecode, cmdDump},
Todd Wang991ecf12015-02-25 17:38:37 -080036}
37
Todd Wang9560b9c2015-05-11 13:27:58 -070038var cmdDecode = &cmdline.Command{
39 Runner: cmdline.RunnerFunc(runDecode),
Todd Wangf1550cf2015-05-11 10:58:41 -070040 Name: "decode",
41 Short: "Decode data encoded in the vom format",
Todd Wang991ecf12015-02-25 17:38:37 -080042 Long: `
43Decode decodes data encoded in the vom format. If no arguments are provided,
44decode reads the data from stdin, otherwise the argument is the data.
45
46By default the data is assumed to be represented in hex, with all whitespace
47anywhere in the data ignored. Use the -data flag to specify other data
48representations.
49
50`,
51 ArgsName: "[data]",
52 ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
53}
54
Todd Wang9560b9c2015-05-11 13:27:58 -070055var cmdDump = &cmdline.Command{
56 Runner: cmdline.RunnerFunc(runDump),
Todd Wangf1550cf2015-05-11 10:58:41 -070057 Name: "dump",
58 Short: "Dump data encoded in the vom format into formatted output",
Todd Wang991ecf12015-02-25 17:38:37 -080059 Long: `
60Dump dumps data encoded in the vom format, generating formatted output
61describing each portion of the encoding. If no arguments are provided, dump
62reads the data from stdin, otherwise the argument is the data.
63
64By default the data is assumed to be represented in hex, with all whitespace
65anywhere in the data ignored. Use the -data flag to specify other data
66representations.
67
68Calling "vom dump" with no flags and no arguments combines the default stdin
69mode with the default hex mode. This default mode is special; certain non-hex
70characters 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
74This lets you cut-and-paste hex strings into your terminal, and use the commands
75to trigger status or flush calls; i.e. a rudimentary debugging UI.
76
77See 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
83var (
84 flagDataRep = dataRepHex
85)
86
87func 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 Wang9560b9c2015-05-11 13:27:58 -070094func runDecode(env *cmdline.Env, args []string) error {
Todd Wang991ecf12015-02-25 17:38:37 -080095 // Convert all inputs into a reader over binary bytes.
96 var data string
97 switch {
98 case len(args) > 1:
Todd Wangf1550cf2015-05-11 10:58:41 -070099 return env.UsageErrorf("too many args")
Todd Wang991ecf12015-02-25 17:38:37 -0800100 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 Ahn5d1fe972015-04-27 17:51:32 -0700116 decoder := vom.NewDecoder(reader)
Todd Wang991ecf12015-02-25 17:38:37 -0800117 var result *vdl.Value
118 if err := decoder.Decode(&result); err != nil {
119 return err
120 }
Todd Wangf1550cf2015-05-11 10:58:41 -0700121 fmt.Fprintln(env.Stdout, result)
Todd Wang991ecf12015-02-25 17:38:37 -0800122 if reader.Len() != 0 {
123 return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String())
124 }
125 return nil
126}
127
Todd Wang9560b9c2015-05-11 13:27:58 -0700128func runDump(env *cmdline.Env, args []string) error {
Todd Wang991ecf12015-02-25 17:38:37 -0800129 // Handle non-streaming cases.
130 switch {
131 case len(args) > 1:
Todd Wangf1550cf2015-05-11 10:58:41 -0700132 return env.UsageErrorf("too many args")
Todd Wang991ecf12015-02-25 17:38:37 -0800133 case len(args) == 1:
134 binbytes, err := dataToBinaryBytes(args[0])
135 if err != nil {
136 return err
137 }
Todd Wangf1550cf2015-05-11 10:58:41 -0700138 fmt.Fprintln(env.Stdout, vom.Dump(binbytes))
Todd Wang991ecf12015-02-25 17:38:37 -0800139 return nil
140 }
141 // Handle streaming from stdin.
Todd Wang0c034462015-05-27 18:05:28 -0700142 dumper := vom.NewDumper(vom.NewDumpWriter(env.Stdout))
Todd Wang991ecf12015-02-25 17:38:37 -0800143 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}