• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2019, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//go:build interactive
16// +build interactive
17
18package main
19
20import (
21	"bytes"
22	"encoding/json"
23	"errors"
24	"fmt"
25	"io"
26	neturl "net/url"
27	"os"
28	"os/exec"
29	"os/signal"
30	"path/filepath"
31	"reflect"
32	"strconv"
33	"strings"
34	"syscall"
35
36	"boringssl.googlesource.com/boringssl/util/fipstools/acvp/acvptool/acvp"
37	"golang.org/x/crypto/ssh/terminal"
38)
39
40const interactiveModeSupported = true
41
42func updateTerminalSize(term *terminal.Terminal) {
43	width, height, err := terminal.GetSize(0)
44	if err != nil {
45		return
46	}
47	term.SetSize(width, height)
48}
49
50func skipWS(node *node32) *node32 {
51	for ; node != nil && node.pegRule == ruleWS; node = node.next {
52	}
53	return node
54}
55
56func assertNodeType(node *node32, rule pegRule) {
57	if node.pegRule != rule {
58		panic(fmt.Sprintf("expected %q, found %q", rul3s[rule], rul3s[node.pegRule]))
59	}
60}
61
62type Object interface {
63	String() (string, error)
64	Index(string) (Object, error)
65	Search(acvp.Query) (Object, error)
66	Action(action string, args []string) error
67}
68
69type ServerObjectSet struct {
70	env          *Env
71	name         string
72	searchKeys   map[string][]acvp.Relation
73	resultType   reflect.Type
74	subObjects   map[string]func(*Env, string) (Object, error)
75	canEnumerate bool
76}
77
78func (set ServerObjectSet) String() (string, error) {
79	if !set.canEnumerate {
80		return "[object set " + set.name + "]", nil
81	}
82
83	data := reflect.New(reflect.SliceOf(set.resultType)).Interface()
84	if err := set.env.server.GetPaged(data, "acvp/v1/"+set.name, nil); err != nil {
85		return "", err
86	}
87	ret, err := json.MarshalIndent(data, "", "  ")
88	return string(ret), err
89}
90
91func (set ServerObjectSet) Index(indexStr string) (Object, error) {
92	index, err := strconv.ParseUint(indexStr, 0, 64)
93	if err != nil {
94		return nil, fmt.Errorf("object set indexes must be unsigned integers, trying to parse %q failed: %s", indexStr, err)
95	}
96	return ServerObject{&set, index}, nil
97}
98
99func (set ServerObjectSet) Search(condition acvp.Query) (Object, error) {
100	if set.searchKeys == nil {
101		return nil, errors.New("this object set cannot be searched")
102	}
103
104	for _, conj := range condition {
105	NextCondition:
106		for _, cond := range conj {
107			allowed, ok := set.searchKeys[cond.Param]
108			if !ok {
109				return nil, fmt.Errorf("search key %q not valid for this object set", cond.Param)
110			}
111
112			for _, rel := range allowed {
113				if rel == cond.Relation {
114					continue NextCondition
115				}
116			}
117
118			return nil, fmt.Errorf("search key %q cannot be used with relation %q", cond.Param, cond.Relation.String())
119		}
120	}
121
122	return Search{ServerObjectSet: set, query: condition}, nil
123}
124
125func (set ServerObjectSet) Action(action string, args []string) error {
126	switch action {
127	default:
128		return fmt.Errorf("unknown action %q", action)
129
130	case "new":
131		if len(args) != 0 {
132			return fmt.Errorf("found %d arguments but %q takes none", len(args), action)
133		}
134
135		newContents, err := edit("")
136		if err != nil {
137			return err
138		}
139
140		if strings.TrimSpace(string(newContents)) == "" {
141			io.WriteString(set.env.term, "Resulting file was empty. Ignoring.\n")
142			return nil
143		}
144
145		var result map[string]interface{}
146		if err := set.env.server.Post(&result, "acvp/v1/"+set.name, newContents); err != nil {
147			return err
148		}
149
150		// In case it's a testSession that was just created, poke any access token
151		// into the server's lookup table and the cache.
152		if urlInterface, ok := result["url"]; ok {
153			if url, ok := urlInterface.(string); ok {
154				if tokenInterface, ok := result["accessToken"]; ok {
155					if token, ok := tokenInterface.(string); ok {
156						for strings.HasPrefix(url, "/") {
157							url = url[1:]
158						}
159						set.env.server.PrefixTokens[url] = token
160						if len(set.env.config.SessionTokensCache) > 0 {
161							os.WriteFile(filepath.Join(set.env.config.SessionTokensCache, neturl.PathEscape(url))+".token", []byte(token), 0600)
162						}
163					}
164				}
165			}
166		}
167
168		ret, err := json.MarshalIndent(result, "", "  ")
169		if err != nil {
170			return err
171		}
172		set.env.term.Write(ret)
173		return nil
174	}
175}
176
177type ServerObject struct {
178	set   *ServerObjectSet
179	index uint64
180}
181
182func (obj ServerObject) String() (string, error) {
183	data := reflect.New(obj.set.resultType).Interface()
184	if err := obj.set.env.server.Get(data, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10)); err != nil {
185		return "", err
186	}
187	ret, err := json.MarshalIndent(data, "", "  ")
188	return string(ret), err
189}
190
191func (obj ServerObject) Index(index string) (Object, error) {
192	if obj.set.subObjects == nil {
193		return nil, errors.New("cannot index " + obj.set.name + " objects")
194	}
195	constr, ok := obj.set.subObjects[index]
196	if !ok {
197		return nil, fmt.Errorf("no such subobject %q", index)
198	}
199	return constr(obj.set.env, fmt.Sprintf("%s/%d", obj.set.name, obj.index))
200}
201
202func (ServerObject) Search(condition acvp.Query) (Object, error) {
203	return nil, errors.New("cannot search individual object")
204}
205
206func edit(initialContents string) ([]byte, error) {
207	tmp, err := os.CreateTemp("", "acvp*.json")
208	if err != nil {
209		return nil, err
210	}
211	path := tmp.Name()
212	defer os.Remove(path)
213
214	_, err = io.WriteString(tmp, initialContents)
215	tmp.Close()
216	if err != nil {
217		return nil, err
218	}
219
220	editor := os.Getenv("EDITOR")
221	if len(editor) == 0 {
222		editor = "vim"
223	}
224
225	cmd := exec.Command(editor, path)
226	cmd.Stdout = os.Stdout
227	cmd.Stdin = os.Stdin
228	cmd.Stderr = os.Stderr
229	if err := cmd.Run(); err != nil {
230		return nil, err
231	}
232
233	return os.ReadFile(path)
234}
235
236func (obj ServerObject) Action(action string, args []string) error {
237	switch action {
238	default:
239		return fmt.Errorf("unknown action %q", action)
240
241	case "edit":
242		if len(args) != 0 {
243			return fmt.Errorf("found %d arguments but %q takes none", len(args), action)
244		}
245
246		contents, err := obj.String()
247		if err != nil {
248			return err
249		}
250
251		newContents, err := edit(contents)
252		if err != nil {
253			return err
254		}
255
256		if trimmed := strings.TrimSpace(string(newContents)); len(trimmed) == 0 || trimmed == strings.TrimSpace(contents) {
257			io.WriteString(obj.set.env.term, "Resulting file was equal or empty. Not updating.\n")
258			return nil
259		}
260
261		var status acvp.RequestStatus
262		if err := obj.set.env.server.Put(&status, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10), newContents); err != nil {
263			return err
264		}
265
266		fmt.Fprintf(obj.set.env.term, "%#v\n", status)
267		return nil
268
269	case "delete":
270		if len(args) != 0 {
271			return fmt.Errorf("found %d arguments but %q takes none", len(args), action)
272		}
273		return obj.set.env.server.Delete("acvp/v1/" + obj.set.name + "/" + strconv.FormatUint(obj.index, 10))
274	}
275}
276
277type Search struct {
278	ServerObjectSet
279	query acvp.Query
280}
281
282func (search Search) String() (string, error) {
283	data := reflect.New(reflect.SliceOf(search.resultType)).Interface()
284	fmt.Printf("Searching for %#v\n", search.query)
285	if err := search.env.server.GetPaged(data, "acvp/v1/"+search.name, search.query); err != nil {
286		return "", err
287	}
288	ret, err := json.MarshalIndent(data, "", "  ")
289	return string(ret), err
290}
291
292func (search Search) Index(_ string) (Object, error) {
293	return nil, errors.New("indexing of search results not supported")
294}
295
296func (search Search) Search(condition acvp.Query) (Object, error) {
297	search.query = append(search.query, condition...)
298	return search, nil
299}
300
301func (Search) Action(_ string, _ []string) error {
302	return errors.New("no actions supported on search objects")
303}
304
305type Algorithms struct {
306	ServerObjectSet
307}
308
309func (algos Algorithms) String() (string, error) {
310	var result struct {
311		Algorithms []map[string]interface{} `json:"algorithms"`
312	}
313	if err := algos.env.server.Get(&result, "acvp/v1/algorithms"); err != nil {
314		return "", err
315	}
316	ret, err := json.MarshalIndent(result.Algorithms, "", "  ")
317	return string(ret), err
318}
319
320type Env struct {
321	line      string
322	variables map[string]Object
323	server    *acvp.Server
324	term      *terminal.Terminal
325	config    Config
326}
327
328func (e *Env) bytes(node *node32) []byte {
329	return []byte(e.line[node.begin:node.end])
330}
331
332func (e *Env) contents(node *node32) string {
333	return e.line[node.begin:node.end]
334}
335
336type stringLiteral struct {
337	env      *Env
338	contents string
339}
340
341func (s stringLiteral) String() (string, error) {
342	return s.contents, nil
343}
344
345func (stringLiteral) Index(_ string) (Object, error) {
346	return nil, errors.New("cannot index strings")
347}
348
349func (stringLiteral) Search(_ acvp.Query) (Object, error) {
350	return nil, errors.New("cannot search strings")
351}
352
353func (s stringLiteral) Action(action string, args []string) error {
354	switch action {
355	default:
356		return fmt.Errorf("action %q not supported on string literals", action)
357
358	case "GET":
359		if len(args) != 0 {
360			return fmt.Errorf("found %d arguments but %q takes none", len(args), action)
361		}
362
363		var results map[string]interface{}
364		if err := s.env.server.Get(&results, s.contents); err != nil {
365			return err
366		}
367		ret, err := json.MarshalIndent(results, "", "  ")
368		if err != nil {
369			return err
370		}
371		s.env.term.Write(ret)
372		return nil
373	}
374}
375
376type results struct {
377	env    *Env
378	prefix string
379}
380
381func (r results) String() (string, error) {
382	var results map[string]interface{}
383	if err := r.env.server.Get(&results, "acvp/v1/"+r.prefix+"/results"); err != nil {
384		return "", err
385	}
386	ret, err := json.MarshalIndent(results, "", "  ")
387	return string(ret), err
388}
389
390func (results) Index(_ string) (Object, error) {
391	return nil, errors.New("cannot index results objects")
392}
393
394func (results) Search(_ acvp.Query) (Object, error) {
395	return nil, errors.New("cannot search results objects")
396}
397
398func (results) Action(_ string, _ []string) error {
399	return errors.New("no actions supported on results objects")
400}
401
402func (e *Env) parseStringLiteral(node *node32) string {
403	assertNodeType(node, ruleStringLiteral)
404	in := e.bytes(node)
405	var buf bytes.Buffer
406	for i := 1; i < len(in)-1; i++ {
407		if in[i] == '\\' {
408			switch in[i+1] {
409			case '\\':
410				buf.WriteByte('\\')
411			case 'n':
412				buf.WriteByte('\n')
413			case '"':
414				buf.WriteByte('"')
415			default:
416				panic("unknown escape")
417			}
418			i++
419			continue
420		}
421		buf.WriteByte(in[i])
422	}
423
424	return buf.String()
425}
426
427func (e *Env) evalExpression(node *node32) (obj Object, err error) {
428	switch node.pegRule {
429	case ruleStringLiteral:
430		return stringLiteral{e, e.parseStringLiteral(node)}, nil
431
432	case ruleVariable:
433		varName := e.contents(node)
434		obj, ok := e.variables[varName]
435		if !ok {
436			return nil, fmt.Errorf("unknown variable %q", varName)
437		}
438		return obj, nil
439
440	case ruleIndexing:
441		node = node.up
442		assertNodeType(node, ruleVariable)
443		varName := e.contents(node)
444		obj, ok := e.variables[varName]
445		if !ok {
446			return nil, fmt.Errorf("unknown variable %q", varName)
447		}
448
449		node = node.next
450		for node != nil {
451			assertNodeType(node, ruleIndex)
452			indexStr := e.contents(node)
453			if obj, err = obj.Index(indexStr); err != nil {
454				return nil, err
455			}
456			node = node.next
457		}
458
459		return obj, nil
460
461	case ruleSearch:
462		node = node.up
463		assertNodeType(node, ruleVariable)
464		varName := e.contents(node)
465		obj, ok := e.variables[varName]
466		if !ok {
467			return nil, fmt.Errorf("unknown variable %q", varName)
468		}
469
470		node = skipWS(node.next)
471		assertNodeType(node, ruleQuery)
472		node = node.up
473
474		var query acvp.Query
475		for node != nil {
476			assertNodeType(node, ruleConjunctions)
477			query = append(query, e.parseConjunction(node.up))
478			node = skipWS(node.next)
479		}
480
481		if len(query) == 0 {
482			return nil, errors.New("cannot have empty query")
483		}
484
485		return obj.Search(query)
486	}
487
488	panic("unhandled")
489}
490
491func (e *Env) evalAction(node *node32) error {
492	assertNodeType(node, ruleExpression)
493	obj, err := e.evalExpression(node.up)
494	if err != nil {
495		return err
496	}
497
498	node = node.next
499	assertNodeType(node, ruleCommand)
500	node = node.up
501	assertNodeType(node, ruleFunction)
502	function := e.contents(node)
503	node = node.next
504
505	var args []string
506	for node != nil {
507		assertNodeType(node, ruleArgs)
508		node = node.up
509		args = append(args, e.parseStringLiteral(node))
510
511		node = skipWS(node.next)
512	}
513
514	return obj.Action(function, args)
515}
516
517func (e *Env) parseConjunction(node *node32) (ret acvp.Conjunction) {
518	for node != nil {
519		assertNodeType(node, ruleConjunction)
520		ret = append(ret, e.parseCondition(node.up))
521
522		node = skipWS(node.next)
523		if node != nil {
524			assertNodeType(node, ruleConjunctions)
525			node = node.up
526		}
527	}
528	return ret
529}
530
531func (e *Env) parseCondition(node *node32) (ret acvp.Condition) {
532	assertNodeType(node, ruleField)
533	ret.Param = e.contents(node)
534	node = skipWS(node.next)
535
536	assertNodeType(node, ruleRelation)
537	switch e.contents(node) {
538	case "==":
539		ret.Relation = acvp.Equals
540	case "!=":
541		ret.Relation = acvp.NotEquals
542	case "contains":
543		ret.Relation = acvp.Contains
544	case "startsWith":
545		ret.Relation = acvp.StartsWith
546	case "endsWith":
547		ret.Relation = acvp.EndsWith
548	default:
549		panic("relation not handled: " + e.contents(node))
550	}
551	node = skipWS(node.next)
552
553	ret.Value = e.parseStringLiteral(node)
554
555	return ret
556}
557
558func runInteractive(server *acvp.Server, config Config) {
559	oldState, err := terminal.MakeRaw(0)
560	if err != nil {
561		panic(err)
562	}
563	defer terminal.Restore(0, oldState)
564	term := terminal.NewTerminal(os.Stdin, "> ")
565
566	resizeChan := make(chan os.Signal, 1)
567	go func() {
568		for _ = range resizeChan {
569			updateTerminalSize(term)
570		}
571	}()
572	signal.Notify(resizeChan, syscall.SIGWINCH)
573
574	env := &Env{variables: make(map[string]Object), server: server, term: term, config: config}
575	env.variables["requests"] = ServerObjectSet{
576		env:          env,
577		name:         "requests",
578		resultType:   reflect.TypeOf(&acvp.RequestStatus{}),
579		canEnumerate: true,
580	}
581	env.variables["vendors"] = ServerObjectSet{
582		env:  env,
583		name: "vendors",
584		searchKeys: map[string][]acvp.Relation{
585			"name":        []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
586			"website":     []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
587			"email":       []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
588			"phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
589		},
590		subObjects: map[string]func(*Env, string) (Object, error){
591			"contacts": func(env *Env, prefix string) (Object, error) {
592				return ServerObjectSet{
593					env:          env,
594					name:         prefix + "/contacts",
595					resultType:   reflect.TypeOf(&acvp.Person{}),
596					canEnumerate: true,
597				}, nil
598			},
599			"addresses": func(env *Env, prefix string) (Object, error) {
600				return ServerObjectSet{
601					env:          env,
602					name:         prefix + "/addresses",
603					resultType:   reflect.TypeOf(&acvp.Address{}),
604					canEnumerate: true,
605				}, nil
606			},
607		},
608		resultType: reflect.TypeOf(&acvp.Vendor{}),
609	}
610	env.variables["persons"] = ServerObjectSet{
611		env:  env,
612		name: "persons",
613		searchKeys: map[string][]acvp.Relation{
614			"fullName":    []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
615			"email":       []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
616			"phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
617			"vendorId":    []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual},
618		},
619		resultType: reflect.TypeOf(&acvp.Person{}),
620	}
621	env.variables["modules"] = ServerObjectSet{
622		env:  env,
623		name: "modules",
624		searchKeys: map[string][]acvp.Relation{
625			"name":        []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
626			"version":     []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
627			"website":     []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
628			"description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
629			"type":        []acvp.Relation{acvp.Equals, acvp.NotEquals},
630			"vendorId":    []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual},
631		},
632		resultType: reflect.TypeOf(&acvp.Module{}),
633	}
634	env.variables["oes"] = ServerObjectSet{
635		env:  env,
636		name: "oes",
637		searchKeys: map[string][]acvp.Relation{
638			"name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
639		},
640		resultType: reflect.TypeOf(&acvp.OperationalEnvironment{}),
641	}
642	env.variables["deps"] = ServerObjectSet{
643		env:  env,
644		name: "dependencies",
645		searchKeys: map[string][]acvp.Relation{
646			"name":        []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
647			"type":        []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
648			"description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains},
649		},
650		resultType: reflect.TypeOf(&acvp.Dependency{}),
651	}
652	env.variables["algos"] = Algorithms{
653		ServerObjectSet{
654			env:          env,
655			name:         "algorithms",
656			resultType:   reflect.TypeOf(&acvp.Algorithm{}),
657			canEnumerate: true,
658		},
659	}
660	env.variables["sessions"] = ServerObjectSet{
661		env:          env,
662		name:         "testSessions",
663		resultType:   reflect.TypeOf(&acvp.TestSession{}),
664		canEnumerate: true,
665		subObjects: map[string]func(env *Env, prefix string) (Object, error){
666			"results": func(env *Env, prefix string) (Object, error) {
667				return results{env: env, prefix: prefix}, nil
668			},
669		},
670	}
671
672	for {
673		if env.line, err = term.ReadLine(); err != nil {
674			return
675		}
676		if len(env.line) == 0 {
677			continue
678		}
679
680		stmt := Statement{Buffer: env.line, Pretty: true}
681		stmt.Init()
682		if err := stmt.Parse(); err != nil {
683			io.WriteString(term, err.Error())
684			continue
685		}
686
687		node := skipWS(stmt.AST().up)
688		switch node.pegRule {
689		case ruleExpression:
690			obj, err := env.evalExpression(node.up)
691			var repr string
692			if err == nil {
693				repr, err = obj.String()
694			}
695
696			if err != nil {
697				fmt.Fprintf(term, "error while evaluating expression: %s\n", err)
698			} else {
699				io.WriteString(term, repr)
700				io.WriteString(term, "\n")
701			}
702
703		case ruleAction:
704			if err := env.evalAction(node.up); err != nil {
705				io.WriteString(term, err.Error())
706				io.WriteString(term, "\n")
707			}
708
709		default:
710			fmt.Fprintf(term, "internal error parsing input.\n")
711		}
712	}
713}
714