Skip to content

Commit 7aba06d

Browse files
committed
Add function call syntax for builtin functions
This commit paves the way to adding crypto primitives to the surface language, such as sign(), for painlessly generating signed data, esp. for testing X.509 parsers.
1 parent 68161b2 commit 7aba06d

File tree

4 files changed

+213
-23
lines changed

4 files changed

+213
-23
lines changed

cmd/ascii2der/builtins.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2015 The DER ASCII Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
)
21+
22+
// NOTE: If adding a builtin, remember to document it in language.txt!
23+
24+
var builtins = map[string]func(*scanner, [][]byte) ([]byte, error){
25+
// define(var, val) sets var = val in the scanner's variable table.
26+
// Variables may be redefined. Expands to the empty string.
27+
"define": func(scanner *scanner, args [][]byte) ([]byte, error) {
28+
if len(args) != 2 {
29+
return nil, errors.New("expected two arguments to define()")
30+
}
31+
scanner.vars[string(args[0])] = args[1]
32+
return nil, nil
33+
},
34+
35+
// var(var) expands to whatever var is set to in the scanner's variable table.
36+
// Error if var is not defined.
37+
//
38+
// var(var, default) behaves similarly, except expands to default if var is
39+
// not defined.
40+
"var": func(scanner *scanner, args [][]byte) ([]byte, error) {
41+
switch len(args) {
42+
case 1:
43+
val, ok := scanner.vars[string(args[0])]
44+
if !ok {
45+
return nil, fmt.Errorf("var() with undefined name: %q", string(args[0]))
46+
}
47+
return val, nil
48+
case 2:
49+
val, ok := scanner.vars[string(args[0])]
50+
if !ok {
51+
return args[1], nil
52+
}
53+
return val, nil
54+
default:
55+
return nil, errors.New("expected one or two arguments to var()")
56+
}
57+
},
58+
}
59+
60+
func executeBuiltin(scanner *scanner, name string, args [][]byte) ([]byte, error) {
61+
builtin, ok := builtins[name]
62+
if !ok {
63+
return nil, fmt.Errorf("unrecognized builtin %q", name)
64+
}
65+
66+
return builtin(scanner, args)
67+
}

cmd/ascii2der/scanner.go

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ const (
4343
tokenRightCurly
4444
tokenIndefinite
4545
tokenLongForm
46+
tokenComma
47+
tokenLeftParen
48+
tokenRightParen
49+
tokenWord
4650
tokenEOF
4751
)
4852

@@ -78,10 +82,11 @@ var (
7882
type scanner struct {
7983
text string
8084
pos position
85+
vars map[string][]byte
8186
}
8287

8388
func newScanner(text string) *scanner {
84-
return &scanner{text: text, pos: position{Line: 1}}
89+
return &scanner{text: text, pos: position{Line: 1}, vars: make(map[string][]byte)}
8590
}
8691

8792
func (s *scanner) parseEscapeSequence() (rune, error) {
@@ -274,6 +279,15 @@ again:
274279
case '}':
275280
s.advance()
276281
return token{Kind: tokenRightCurly, Pos: s.pos}, nil
282+
case ',':
283+
s.advance()
284+
return token{Kind: tokenComma, Pos: s.pos}, nil
285+
case '(':
286+
s.advance()
287+
return token{Kind: tokenLeftParen, Pos: s.pos}, nil
288+
case ')':
289+
s.advance()
290+
return token{Kind: tokenRightParen, Pos: s.pos}, nil
277291
case '"':
278292
return s.parseQuotedString()
279293
case 'u':
@@ -366,7 +380,7 @@ again:
366380
loop:
367381
for !s.isEOF() {
368382
switch s.text[s.pos.Offset] {
369-
case ' ', '\t', '\n', '\r', '{', '}', '[', ']', '`', '"', '#':
383+
case ' ', '\t', '\n', '\r', ',', '(', ')', '{', '}', '[', ']', '`', '"', '#':
370384
break loop
371385
default:
372386
s.advance()
@@ -431,7 +445,7 @@ loop:
431445
return token{Kind: tokenLongForm, Length: l}, nil
432446
}
433447

434-
return token{}, fmt.Errorf("unrecognized symbol %q", symbol)
448+
return token{Kind: tokenWord, Value: []byte(symbol), Pos: s.pos}, nil
435449
}
436450

437451
func (s *scanner) isEOF() bool {
@@ -469,24 +483,32 @@ func (s *scanner) consumeUpTo(b byte) (string, bool) {
469483
return "", false
470484
}
471485

472-
func asciiToDERImpl(scanner *scanner, leftCurly *token) ([]byte, error) {
486+
// asciiToDERImpl consumes tokens from |scanner| and returns the concatenation
487+
// of their byte values, as well as every token processed.
488+
func asciiToDERImpl(scanner *scanner, left *token) ([]byte, []token, error) {
473489
var out []byte
490+
var tokens []token
474491
var lengthModifier *token
492+
var word *token
475493
for {
476494
token, err := scanner.Next()
477495
if err != nil {
478-
return nil, err
496+
return nil, nil, err
479497
}
498+
tokens = append(tokens, token)
480499
if lengthModifier != nil && token.Kind != tokenLeftCurly {
481-
return nil, &parseError{lengthModifier.Pos, errors.New("length modifier was not followed by '{'")}
500+
return nil, nil, &parseError{lengthModifier.Pos, errors.New("length modifier was not followed by '{'")}
501+
}
502+
if word != nil && token.Kind != tokenLeftParen {
503+
return nil, nil, &parseError{word.Pos, fmt.Errorf("unrecognized symbol %q", string(token.Value))}
482504
}
483505
switch token.Kind {
484506
case tokenBytes:
485507
out = append(out, token.Value...)
486508
case tokenLeftCurly:
487-
child, err := asciiToDERImpl(scanner, &token)
509+
child, _, err := asciiToDERImpl(scanner, &token)
488510
if err != nil {
489-
return nil, err
511+
return nil, nil, err
490512
}
491513
var lengthOverride int
492514
if lengthModifier != nil {
@@ -504,22 +526,68 @@ func asciiToDERImpl(scanner *scanner, leftCurly *token) ([]byte, error) {
504526
out, err = appendLength(out, len(child), lengthOverride)
505527
if err != nil {
506528
// appendLength may fail if the lengthModifier was incompatible.
507-
return nil, &parseError{lengthModifier.Pos, err}
529+
return nil, tokens, &parseError{lengthModifier.Pos, err}
508530
}
509531
out = append(out, child...)
510532
lengthModifier = nil
533+
case tokenLeftParen:
534+
if word == nil {
535+
return nil, tokens, &parseError{token.Pos, errors.New("missing function name")}
536+
}
537+
var args [][]byte
538+
argLoop:
539+
for {
540+
arg, prev, err := asciiToDERImpl(scanner, &token)
541+
if err != nil {
542+
return nil, tokens, err
543+
}
544+
args = append(args, arg)
545+
lastToken := prev[len(prev)-1]
546+
switch lastToken.Kind {
547+
case tokenComma:
548+
if len(prev) < 2 {
549+
return nil, nil, &parseError{lastToken.Pos, errors.New("function arguments cannot be empty")}
550+
}
551+
case tokenRightParen:
552+
if len(prev) < 2 {
553+
// Actually foo(), so the argument list is nil.
554+
args = nil
555+
}
556+
break argLoop
557+
default:
558+
return nil, nil, &parseError{lastToken.Pos, errors.New("expected ',' or ')'")}
559+
}
560+
}
561+
bytes, err := executeBuiltin(scanner, string(word.Value), args)
562+
if err != nil {
563+
return nil, nil, err
564+
}
565+
word = nil
566+
out = append(out, bytes...)
511567
case tokenRightCurly:
512-
if leftCurly != nil {
513-
return out, nil
568+
if left != nil && left.Kind == tokenLeftCurly {
569+
return out, tokens, nil
570+
}
571+
return nil, nil, &parseError{token.Pos, errors.New("unmatched '}'")}
572+
case tokenRightParen:
573+
if left != nil && left.Kind == tokenLeftParen {
574+
return out, tokens, nil
514575
}
515-
return nil, &parseError{token.Pos, errors.New("unmatched '}'")}
576+
return nil, nil, &parseError{token.Pos, errors.New("unmatched '('")}
516577
case tokenLongForm, tokenIndefinite:
517578
lengthModifier = &token
579+
case tokenComma:
580+
return out, tokens, nil
581+
case tokenWord:
582+
word = &token
518583
case tokenEOF:
519-
if leftCurly == nil {
520-
return out, nil
584+
if left == nil {
585+
return out, tokens, nil
586+
} else if left.Kind == tokenLeftCurly {
587+
return nil, nil, &parseError{left.Pos, errors.New("unmatched '{'")}
588+
} else {
589+
return nil, nil, &parseError{left.Pos, errors.New("unmatched '('")}
521590
}
522-
return nil, &parseError{leftCurly.Pos, errors.New("unmatched '{'")}
523591
default:
524592
panic(token)
525593
}
@@ -528,5 +596,6 @@ func asciiToDERImpl(scanner *scanner, leftCurly *token) ([]byte, error) {
528596

529597
func asciiToDER(input string) ([]byte, error) {
530598
scanner := newScanner(input)
531-
return asciiToDERImpl(scanner, nil)
599+
bytes, _, err := asciiToDERImpl(scanner, nil)
600+
return bytes, err
532601
}

cmd/ascii2der/scanner_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,6 @@ indefinite long-form:2`,
9494
},
9595
true,
9696
},
97-
// Garbage tokens.
98-
{"SEQUENC", nil, false},
99-
{"1...2", nil, false},
100-
{"true", nil, false},
101-
{"false", nil, false},
10297
// Unmatched [.
10398
{"[SEQUENCE", nil, false},
10499
// Unmatched ".
@@ -426,9 +421,18 @@ var asciiToDERTests = []struct {
426421
ok bool
427422
}{
428423
{"SEQUENCE { INTEGER { 42 } INTEGER { 1 } }", []byte{0x30, 0x06, 0x02, 0x01, 0x2a, 0x02, 0x01, 0x01}, true},
424+
// Garbage words.
425+
{"SEQUENC", nil, false},
426+
{"1...2", nil, false},
427+
{"true", nil, false},
428+
{"false", nil, false},
429429
// Mismatched curlies.
430430
{"{", nil, false},
431431
{"}", nil, false},
432+
{"(", nil, false},
433+
{")", nil, false},
434+
{"({)}", nil, false},
435+
{"{(})", nil, false},
432436
// Invalid token.
433437
{"BOGUS", nil, false},
434438
// Length overrides.
@@ -442,6 +446,28 @@ var asciiToDERTests = []struct {
442446
// Too long of length modifiers.
443447
{"[long-form:1 99999]", nil, false},
444448
{"SEQUENCE long-form:1 { `" + strings.Repeat("a", 1024) + "` }", nil, false},
449+
// Function call without function name.
450+
{"()", nil, false},
451+
// Unknown function.
452+
{"BOGUS()", nil, false},
453+
// Basic variable usage.
454+
{`DEFINE("foo", 42) VAR("foo") VAR("foo")`, []byte{42, 42}, true},
455+
{
456+
`
457+
DEFINE(42, 42) VAR(42)
458+
DEFINE(42, "a") VAR(42)
459+
`,
460+
[]byte{42, byte('a')},
461+
true,
462+
},
463+
{`VAR("missing")`, nil, false},
464+
{`VAR("missing", 42)`, []byte{42}, true},
465+
// Empty parens -> zero args.
466+
// TODO(mcyoung): if we ever add a zero-argument function, use it in this
467+
// test, instead.
468+
{"VAR()", nil, false},
469+
// Empty token streams are not valid arguments.
470+
{"DEFINE(, 42)", nil, false},
445471
}
446472

447473
func TestASCIIToDER(t *testing.T) {

language.txt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ FALSE
151151
[PRIVATE 2]
152152
[UNIVERSAL 16] # This is a SEQUENCE.
153153
[UNIVERSAL 2 PRIMITIVE] # This is an INTEGER.
154-
[long-form:2 UNIVERSAL 2 PRIMTIVE] # This is `1f0002` instead of `02`.
154+
[long-form:2 UNIVERSAL 2 PRIMITIVE] # This is `1f0002` instead of `02`.
155155

156156
# As a shorthand, one may write type names from ASN.1, replacing spaces with
157157
# underscore. These specify tag, number, and the constructed bit. The
@@ -236,7 +236,6 @@ INTEGER long-form:1 { 5 }
236236
INTEGER { `00ff` }
237237
}
238238

239-
240239
# Examples.
241240

242241
# These primitives may be combined with raw byte strings to produce other
@@ -278,6 +277,35 @@ SEQUENCE `aabbcc`
278277
INTEGER { 2 }
279278

280279

280+
# DER ASCII provides a selection of builtin functions for building more
281+
# complex DER structures. Builtins are spelled builtin(arg1, arg2, ...), where
282+
# each argument is an arbitrary but non-empty sequence of DER ASCII tokens.
283+
# Tokens are fully expanded (but not emitted) before the builtin executes.
284+
# Builtin calls expand to a byte string just like any other token.
285+
#
286+
# There is explicitly no support for user-defined functions.
287+
#
288+
# The syntax and output of anything using builtins is subject to change, and
289+
# doesn't have the same stability expectations as the rest of DER ASCII.
290+
291+
# define(var, value) defines a variable to the given value. The name of the
292+
# variable may be an arbitrary byte string. Variables may be redefined at any
293+
# point. define() always expands to an empty byte string.
294+
define("payload", SEQUENCE {
295+
INTEGER { 1 }
296+
OCTET_STRING { "hello, world" }
297+
})
298+
define(`ffff`, "payload")
299+
300+
# var(var) expands to a variable previously defined with define(). The main use
301+
# of var() is to factor a complex structure that will be repeated many times,
302+
# such as complex issuer and subject fields in a self-signed X.509 cert.
303+
#
304+
# It is an error to access a var() that has not been previously defined.
305+
var("payload")
306+
var(var(`ffff`)) # Same as above, since var(`ffff`) expands to "payload".
307+
308+
281309
# Disassembler.
282310

283311
# Although the conversion from DER ASCII to a byte string is well-defined, the

0 commit comments

Comments
 (0)