blob: 8de57fffce9f329a5488b43d60335cbad1e2c395 [file] [log] [blame]
Pete Bentleya5c947b2019-08-09 14:24:27 +00001package 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}