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