asn1: Add --decorate=... for internal bookkeeping

This option, `--decorate=TYPE-NAME:FIELD-TYPE:field-name[?]` allows one to add
a field to any struct generated by the ASN.1 compiler for any SET or SEQUENCE
type such that:

 - the field will     be freed by the `free_TYPE_NAME()` function
 - the field will     be copied by the `copy_TYPE_NAME()` function
 - the field will not be printed by the `print_TYPE_NAME()` function
 - the field will NOT be encoded or decoded

This is useful for internal bookkeeping.

The first use of this may well be for adding an optional field to
`Principal` where information about name attributes will be stored,
which will then allow us to have GSS name attributes for the krb5
mechanism w/o having to refactor the mechanism to use a different
structure for representing `gss_name_t` mechnames than the one currently
used (`Principal`; `krb5_principal` happens to be a typedef alias of
`Principal *`).

So w/o massive rototilling of the GSS krb5 mechanism we can have name
attributes, _and_ we'll also be able to have those in the krb5 API as
well w/o any massive rototilling there either.
This commit is contained in:
Nicolas Williams
2021-12-19 22:51:10 -06:00
parent 309d1192df
commit 823fb82477
14 changed files with 246 additions and 18 deletions

View File

@@ -390,7 +390,10 @@ crmf_template_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/crmf.asn1 $(srcdir)/cr
$(ASN1_COMPILE) --one-code-file --template --option-file=$(srcdir)/crmf.opt $(srcdir)/crmf.asn1 crmf_template_asn1 || (rm -f crmf_template_asn1_files ; exit 1)
krb5_template_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/krb5.asn1 $(srcdir)/krb5.opt
$(ASN1_COMPILE) --one-code-file --template --option-file=$(srcdir)/krb5.opt $(srcdir)/krb5.asn1 krb5_template_asn1 || (rm -f krb5_template_asn1_files ; exit 1)
$(ASN1_COMPILE) --one-code-file --template \
--option-file=$(srcdir)/krb5.opt \
--decorate='Principal:PrincipalNameAttrs:nameattrs?' \
$(srcdir)/krb5.asn1 krb5_template_asn1 || (rm -f krb5_template_asn1_files ; exit 1)
ocsp_template_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/ocsp.asn1
$(ASN1_COMPILE) --one-code-file --template --option-file=$(srcdir)/ocsp.opt $(srcdir)/ocsp.asn1 ocsp_template_asn1 || (rm -f ocsp_template_asn1_files ; exit 1)
@@ -429,7 +432,10 @@ crmf_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/crmf.asn1 $(srcdir)/crmf.opt
$(ASN1_COMPILE) --one-code-file $(TEMPLATE_OPTION) --option-file=$(srcdir)/crmf.opt $(srcdir)/crmf.asn1 crmf_asn1 || (rm -f crmf_asn1_files ; exit 1)
krb5_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/krb5.asn1 $(srcdir)/krb5.opt
$(ASN1_COMPILE) --one-code-file $(TEMPLATE_OPTION) --option-file=$(srcdir)/krb5.opt $(srcdir)/krb5.asn1 krb5_asn1 || (rm -f krb5_asn1_files ; exit 1)
$(ASN1_COMPILE) --one-code-file $(TEMPLATE_OPTION) \
--option-file=$(srcdir)/krb5.opt \
--decorate='Principal:PrincipalNameAttrs:nameattrs?' \
$(srcdir)/krb5.asn1 krb5_asn1 || (rm -f krb5_asn1_files ; exit 1)
ocsp_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/ocsp.asn1
$(ASN1_COMPILE) --one-code-file $(TEMPLATE_OPTION) --option-file=$(srcdir)/ocsp.opt $(srcdir)/ocsp.asn1 ocsp_asn1 || (rm -f ocsp_asn1_files ; exit 1)
@@ -462,10 +468,17 @@ x690sample_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/x690sample.asn1
$(ASN1_COMPILE) --one-code-file $(srcdir)/x690sample.asn1 x690sample_asn1 || (rm -f x690sample_asn1_files ; exit 1)
test_template_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/test.asn1
$(ASN1_COMPILE) --one-code-file --template --sequence=TESTSeqOf $(srcdir)/test.asn1 test_template_asn1 || (rm -f test_template_asn1_files ; exit 1)
$(ASN1_COMPILE) --one-code-file \
--template \
--sequence=TESTSeqOf \
--decorate='TESTDecorated:TESTuint32:version2?' \
$(srcdir)/test.asn1 test_template_asn1 || (rm -f test_template_asn1_files ; exit 1)
test_asn1_files: asn1_compile$(EXEEXT) $(srcdir)/test.asn1
$(ASN1_COMPILE) --one-code-file --sequence=TESTSeqOf $(srcdir)/test.asn1 test_asn1 || (rm -f test_asn1_files ; exit 1)
$(ASN1_COMPILE) --one-code-file \
--decorate='TESTDecorated:TESTuint32:version2?' \
--sequence=TESTSeqOf \
$(srcdir)/test.asn1 test_asn1 || (rm -f test_asn1_files ; exit 1)
EXTRA_DIST = \

View File

@@ -202,6 +202,7 @@ $(gen_files_krb5) $(OBJ)\krb5_asn1.hx: $(BINDIR)\asn1_compile.exe krb5.asn1 krb5
$(BINDIR)\asn1_compile.exe \
--template \
--one-code-file \
--decorate="Principal:PrincipalNameAttrs:nameattrs?" \
--option-file=$(SRCDIR)\krb5.opt \
$(SRCDIR)\krb5.asn1 krb5_asn1 \
|| ($(RM) $(OBJ)\krb5_asn1.h ; exit /b 1)
@@ -315,6 +316,7 @@ $(gen_files_test) $(OBJ)\test_asn1.hx: $(BINDIR)\asn1_compile.exe test.asn1
cd $(OBJ)
$(BINDIR)\asn1_compile.exe \
--template \
--decorate='TESTDecorated:TESTuint32:version2?' \
--one-code-file --sequence=TESTSeqOf \
$(SRCDIR)\test.asn1 test_asn1 \
|| ($(RM) $(OBJ)\test_asn1.h ; exit /b 1)
@@ -324,7 +326,8 @@ $(gen_files_test_template) $(OBJ)\test_template_asn1.hx: $(BINDIR)\asn1_compile.
cd $(OBJ)
$(BINDIR)\asn1_compile.exe \
--template \
--one-code-file --template \
--decorate='TESTDecorated:TESTuint32:version2?' \
--one-code-file \
--sequence=TESTSeqOf \
$(SRCDIR)\test.asn1 test_template_asn1 \
|| ($(RM) $(OBJ)\test_template_asn1.h ; exit /b 1)

View File

@@ -843,7 +843,23 @@ In recent times the following features have been added:
## Compiler Usage
First, see the manual page `asn1_compile.1`:
The various options for the Heimdal ASN.1 compiler are described in its manual
page, which is included below.
The `--option-file=FILE` option is particularly useful, as it allows additional
compiler options to be read from a file.
The `--preserve-binary=TYPE-NAME` option is critical for signature validation
as it causes the decoder to save the encoding of the given type so that
signature validation code can easily find the original encoding and thus avoid
having to re-encode or resort to other hacks. E.g., we use this for preserving
the original encoding of the `tbsCertificate` field of `Certificate`.
The `--sequence=TYPE-NAME` causes the compiler to generate additional utility
functions for adding or removing items from the named type when it is a
`SEQUENCE OF` or `SET OF` type.
See the manual page `asn1_compile.1`:
```
ASN1_COMPILE(1) HEIMDAL General Commands Manual ASN1_COMPILE(1)
@@ -856,6 +872,7 @@ SYNOPSIS
[--encode-rfc1510-bit-string] [--decode-dce-ber]
[--support-ber] [--preserve-binary=TYPE-NAME]
[--sequence=TYPE-NAME] [--one-code-file] [--gen-name=NAME]
[--decorate=TYPE-NAME:FIELD-TYPE:field-name[?]]
[--option-file=FILE] [--original-order] [--no-parse-units]
[--type-file=C-HEADER-FILE] [--version] [--help]
[FILE.asn1 [NAME]]
@@ -870,7 +887,7 @@ DESCRIPTION
Use the “template” backend instead of the “codegen” backend
(which is the default backend). The template backend generates
“templates” which are akin to bytecode, and which are interpreted
at run-time. The codegen backend generates C code for all func
at run-time. The codegen backend generates C code for all func-
tions directly, with no template interpretation. The template
backend scales better than the codegen backend because as we add
support for more encoding rules the templates stay mostly the
@@ -897,13 +914,22 @@ DESCRIPTION
--preserve-binary=TYPE-NAME
Generate _save fields in structs to preserve the original
encoding of some sub-value. This is useful for cryptographic
applications to avoid having to re-encode values to check signa
applications to avoid having to re-encode values to check signa-
tures, etc.
--sequence=TYPE-NAME
Generate add/remove functions for SET OF and SEQUENCE OF
types.
--decorate=TYPE-NAME:FIELD-TYPE:field-name[?]
Add to the TYPE-NAME SET or SEQUENCE type a field of the given
FIELD-TYPE and field-name, but do not encode or decode this
field. If the field-name ends in a question mark, then treat the
field as OPTIONAL for the purposes of copy/free function stubs.
This is useful for adding fields to existing types that can be
used for internal bookkeeping but which do not affect interoper-
ability because they are not encoded.
--one-code-file
Generate a single source code file. Otherwise a separate code
file will be generated for every type.
@@ -916,7 +942,7 @@ DESCRIPTION
--original-order
Attempt to preserve the original order of type definition in the
ASN.1 module. By default the compiler generates types in a topo
ASN.1 module. By default the compiler generates types in a topo-
logical sort order.
--no-parse-units
@@ -1065,9 +1091,9 @@ SYNOPSIS
[-l -v | --version] [-l -h | --help] [FILE [TypeName...]]
DESCRIPTION
asn1_print Dumps ASN.1 DER-encoded values. If one or more TypeName argu
ments are given, then asn1_print will print the value in a JSON-like for
mat using its knowledge of the ASN.1 modules defining those types, stop
asn1_print Dumps ASN.1 DER-encoded values. If one or more TypeName argu-
ments are given, then asn1_print will print the value in a JSON-like for-
mat using its knowledge of the ASN.1 modules defining those types, stop-
ping at the first type for which it can successfully decode the value.
If TypeNames are given, they must be the names of ASN.1 types exported by
an ASN.1 modules that are compiled into asn1_print. Use the

View File

@@ -169,6 +169,7 @@
#define A1_OP_OPENTYPE_ID (0xb0000000)
#define A1_OP_OPENTYPE (0xc0000000)
#define A1_OP_NAME (0xd0000000)
#define A1_OP_TYPE_DECORATE (0xe0000000)
#define A1_FLAG_MASK (0x0f000000)
#define A1_FLAG_OPTIONAL (0x01000000)

View File

@@ -48,6 +48,7 @@
.Op Fl Fl support-ber
.Op Fl Fl preserve-binary=TYPE-NAME
.Op Fl Fl sequence=TYPE-NAME
.Op Fl Fl decorate=TYPE-NAME:FIELD-TYPE:field-name[?]
.Op Fl Fl one-code-file
.Op Fl Fl gen-name=NAME
.Op Fl Fl option-file=FILE
@@ -109,6 +110,21 @@ Generate add/remove functions for
and
.Sq SEQUENCE OF
types.
.It Fl Fl decorate=TYPE-NAME:FIELD-TYPE:field-name[?]
Add to the
.Va TYPE-NAME
SET or SEQUENCE type a field of the given
.Va FIELD-TYPE
and
.Va field-name ,
but do not encode or decode this field.
If the
.Va field-name
ends in a question mark, then treat the field as OPTIONAL for
the purposes of copy/free function stubs.
This is useful for adding fields to existing types that can be used
for internal bookkeeping but which do not affect interoperability
because they are not encoded.
.It Fl Fl one-code-file
Generate a single source code file.
Otherwise a separate code file will be generated for every type.

View File

@@ -100,9 +100,9 @@ test_principal (void)
Principal values[] = {
{ { KRB5_NT_PRINCIPAL, { 1, lha_principal } }, "SU.SE" },
{ { KRB5_NT_PRINCIPAL, { 2, lharoot_princ } }, "SU.SE" },
{ { KRB5_NT_SRV_HST, { 2, datan_princ } }, "E.KTH.SE" }
{ { KRB5_NT_PRINCIPAL, { 1, lha_principal } }, "SU.SE", NULL },
{ { KRB5_NT_PRINCIPAL, { 2, lharoot_princ } }, "SU.SE", NULL },
{ { KRB5_NT_SRV_HST, { 2, datan_princ } }, "E.KTH.SE", NULL }
};
int i, ret;
int ntests = sizeof(tests) / sizeof(*tests);
@@ -1016,6 +1016,50 @@ test_choice (void)
return ret;
}
/* Test --decorate=TYPE:FIELD-TYPE:field-name[?] */
static int
test_decorated(void)
{
TESTNotDecorated tnd;
TESTDecorated td;
size_t len, size;
void *ptr;
int ret;
memset(&td, 0, sizeof(td));
memset(&tnd, 0, sizeof(tnd));
td.version = 3;
if ((td.version2 = malloc(sizeof(*td.version2))) == NULL)
errx(1, "out of memory");
*td.version2 = 5;
ASN1_MALLOC_ENCODE(TESTDecorated, ptr, len, &td, &size, ret);
if (ret) {
warnx("could not encode a TESTDecorated struct");
return 1;
}
ret = decode_TESTNotDecorated(ptr, len, &tnd, &size);
if (ret) {
warnx("could not decode a TESTDecorated struct as TESTNotDecorated");
return 1;
}
if (size != len) {
warnx("TESTDecorated encoded size mismatch");
return 1;
}
if (td.version != tnd.version) {
warnx("TESTDecorated did not decode as a TESTNotDecorated correctly");
return 1;
}
free_TESTDecorated(&td);
if (td.version2) {
warnx("free_TESTDecorated() did not work correctly");
return 1;
}
return 0;
}
static int
cmp_TESTImplicit (void *a, void *b)
{
@@ -2493,6 +2537,8 @@ main(int argc, char **argv)
DO_ONE(test_default);
DO_ONE(test_decorated);
#if ASN1_IOS_SUPPORTED
DO_ONE(test_ios);
#endif

View File

@@ -1355,6 +1355,8 @@ define_type(int level, const char *name, const char *basename, Type *pt, Type *t
case TSet:
case TSequence: {
Member *m;
char *ft, *fn;
int deco_opt;
getnewbasename(&newbasename, typedefp || level == 0, basename, name);
@@ -1395,6 +1397,13 @@ define_type(int level, const char *name, const char *basename, Type *pt, Type *t
fprintf(jsonfile, ",\"opentype\":");
define_open_type(level, newbasename, name, basename, t, t);
}
if (decorate_type(newbasename, &ft, &fn, &deco_opt)) {
space(level + 1);
fprintf(headerfile, "%s %s%s;\n", ft, deco_opt ? "*" : "", fn);
fprintf(jsonfile, ",\"decorate\":{\"type\":\"%s\",\"name\":\"%s\", \"optional\":%s}", ft, fn, deco_opt ? "true" : "false");
free(ft);
free(fn);
}
space(level);
fprintf (headerfile, "} %s;\n", name);
break;

View File

@@ -229,6 +229,9 @@ void
generate_type_copy (const Symbol *s)
{
int preserve = preserve_type(s->name) ? TRUE : FALSE;
int save_used_fail = used_fail;
int deco_opt;
char *ft, *fn;
used_fail = 0;
@@ -238,6 +241,19 @@ generate_type_copy (const Symbol *s)
"memset(to, 0, sizeof(*to));\n",
s->gen_name, s->gen_name, s->gen_name);
copy_type ("from", "to", s->type, preserve);
if (decorate_type(s->gen_name, &ft, &fn, &deco_opt)) {
if (deco_opt) {
fprintf(codefile, "if (from->%s) {\n", fn);
fprintf(codefile, "(to)->%s = malloc(sizeof(*(to)->%s));\n", fn, fn);
fprintf(codefile, "if (copy_%s((from)->%s, (to)->%s)) goto fail;\n", ft, fn, fn);
fprintf(codefile, "}\n");
} else {
fprintf(codefile, "if (copy_%s(&(from)->%s, &(to)->%s)) goto fail;\n", ft, fn, fn);
}
used_fail++;
free(ft);
free(fn);
}
fprintf (codefile, "return 0;\n");
if (used_fail)
@@ -248,5 +264,6 @@ generate_type_copy (const Symbol *s)
fprintf(codefile,
"}\n\n");
used_fail = save_used_fail;
}

View File

@@ -119,7 +119,7 @@ free_type (const char *name, const Type *t, int preserve)
have_ellipsis->label,
name, have_ellipsis->gen_name);
fprintf(codefile, "}\n");
}
}
break;
}
case TSetOf:
@@ -179,6 +179,8 @@ void
generate_type_free (const Symbol *s)
{
int preserve = preserve_type(s->name) ? TRUE : FALSE;
int deco_opt;
char *ft, *fn;
fprintf (codefile, "void ASN1CALL\n"
"free_%s(%s *data)\n"
@@ -186,6 +188,19 @@ generate_type_free (const Symbol *s)
s->gen_name, s->gen_name);
free_type ("data", s->type, preserve);
if (decorate_type(s->gen_name, &ft, &fn, &deco_opt)) {
if (deco_opt) {
fprintf(codefile, "if ((data)->%s) {\n", fn);
fprintf(codefile, "free_%s((data)->%s);\n", ft, fn);
fprintf(codefile, "free((data)->%s);\n", fn);
fprintf(codefile, "(data)->%s = NULL;\n", fn);
fprintf(codefile, "}\n");
} else {
fprintf(codefile, "free_%s(&(data)->%s);\n", ft, fn);
}
free(ft);
free(fn);
}
fprintf (codefile, "}\n\n");
}

View File

@@ -144,6 +144,7 @@ int is_tagged_type(const Type *);
int preserve_type(const char *);
int seq_type(const char *);
int decorate_type(const char *, char **, char **, int *);
void generate_header_of_codefile(const char *);
void close_codefile(void);

View File

@@ -1063,6 +1063,8 @@ template_members(struct templatehead *temp,
Member *m;
size_t i = 0, typeididx = 0, opentypeidx = 0;
int is_array_of_open_type = 0;
int deco_opt;
char *ft, *fn;
if (isstruct && t->actual_parameter)
get_open_type_defn_fields(t, &typeidmember, &opentypemember,
@@ -1102,6 +1104,17 @@ template_members(struct templatehead *temp,
typeidfield, opentypefield, opentypemember,
is_array_of_open_type);
if (decorate_type(basetype, &ft, &fn, &deco_opt)) {
char *poffset2;
poffset2 = partial_offset(basetype, fn, 1, isstruct);
add_line_pointer(temp, ft, poffset2, "A1_OP_TYPE_DECORATE %s",
deco_opt ? "|A1_FLAG_OPTIONAL" : "");
free(poffset2);
free(ft);
free(fn);
}
if (isstruct)
template_names(temp, basetype, t);
break;
@@ -1114,6 +1127,8 @@ template_members(struct templatehead *temp,
Member *m;
size_t i = 0, typeididx = 0, opentypeidx = 0;
int is_array_of_open_type = 0;
int deco_opt;
char *ft, *fn;
if (isstruct && t->actual_parameter)
get_open_type_defn_fields(t, &typeidmember, &opentypemember,
@@ -1153,6 +1168,17 @@ template_members(struct templatehead *temp,
typeidfield, opentypefield, opentypemember,
is_array_of_open_type);
if (decorate_type(basetype, &ft, &fn, &deco_opt)) {
char *poffset2;
poffset2 = partial_offset(basetype, fn, 1, isstruct);
add_line_pointer(temp, ft, poffset2, "A1_OP_TYPE_DECORATE %s",
deco_opt ? "|A1_FLAG_OPTIONAL" : "");
free(poffset2);
free(ft);
free(fn);
}
if (isstruct)
template_names(temp, basetype, t);
break;

View File

@@ -39,6 +39,7 @@ extern FILE *yyin;
static getarg_strings preserve;
static getarg_strings seq;
static getarg_strings decorate;
int
preserve_type(const char *p)
@@ -53,13 +54,49 @@ preserve_type(const char *p)
int
seq_type(const char *p)
{
int i;
size_t i;
for (i = 0; i < seq.num_strings; i++)
if (strcmp(seq.strings[i], p) == 0)
return 1;
return 0;
}
int
decorate_type(const char *p, char **field_type, char **field_name, int *opt)
{
size_t plen = strlen(p);
size_t i;
*field_type = NULL;
*field_name = NULL;
*opt = 0;
for (i = 0; i < decorate.num_strings; i++) {
const char *r;
char *q;
if (strncmp(decorate.strings[i], p, plen) != 0)
continue;
if (decorate.strings[i][plen] != ':')
errx(1, "--decorate argument missing field type");
p = &decorate.strings[i][plen + 1];
if ((r = strchr(p, ':')) == NULL)
errx(1, "--decorate argument missing field name");
r++;
*field_type = estrdup(p);
*(strchr(*field_type, ':')) = '\0';
*field_name = estrdup(r);
if ((q = strchr(*field_name, '?'))) {
*q = '\0';
*opt = 1;
}
return 1;
}
return 0;
}
static const char *
my_basename(const char *fn)
{
@@ -113,6 +150,8 @@ struct getargs args[] = {
"verification)", "TYPE-NAME" },
{ "sequence", 0, arg_strings, &seq,
"Generate add/remove functions for SEQUENCE OF types", "TYPE-NAME" },
{ "decorate", 0, arg_strings, &decorate,
"Generate private field for SEQUENCE/SET type", "TYPE-NAME:FIELD_TYPE:field_name[?]" },
{ "one-code-file", 0, arg_flag, &one_code_file, NULL, NULL },
{ "gen-name", 0, arg_string, &name,
"Name of generated module", "NAME" },

View File

@@ -774,6 +774,7 @@ _asn1_decode(const struct asn1_template *t, unsigned flags,
return ret;
break;
}
case A1_OP_TYPE_DECORATE: break;
case A1_OP_NAME: break;
case A1_OP_DEFVAL:
tdefval = t;
@@ -1417,6 +1418,7 @@ _asn1_encode(const struct asn1_template *t, unsigned char *p, size_t len, const
}
case A1_OP_NAME: break;
case A1_OP_DEFVAL: break;
case A1_OP_TYPE_DECORATE: break;
case A1_OP_TYPE:
case A1_OP_TYPE_EXTERN: {
size_t newsize;
@@ -1992,6 +1994,7 @@ _asn1_length(const struct asn1_template *t, const void *data)
}
case A1_OP_NAME: break;
case A1_OP_DEFVAL: break;
case A1_OP_TYPE_DECORATE: break;
case A1_OP_TYPE:
case A1_OP_TYPE_EXTERN: {
const void *el = DPOC(data, t->offset);
@@ -2253,6 +2256,7 @@ _asn1_free(const struct asn1_template *t, void *data)
}
case A1_OP_NAME: break;
case A1_OP_DEFVAL: break;
case A1_OP_TYPE_DECORATE:
case A1_OP_TYPE:
case A1_OP_TYPE_EXTERN: {
void *el = DPO(data, t->offset);
@@ -2264,7 +2268,7 @@ _asn1_free(const struct asn1_template *t, void *data)
el = *pel;
}
if ((t->tt & A1_OP_MASK) == A1_OP_TYPE) {
if ((t->tt & A1_OP_MASK) == A1_OP_TYPE || (t->tt & A1_OP_MASK) == A1_OP_TYPE_DECORATE) {
_asn1_free(t->ptr, el);
} else {
const struct asn1_type_func *f = t->ptr;
@@ -2541,6 +2545,7 @@ _asn1_print(const struct asn1_template *t,
break;
case A1_OP_NAME: break;
case A1_OP_DEFVAL: break;
case A1_OP_TYPE_DECORATE: break; /* We could probably print this though */
case A1_OP_TYPE:
case A1_OP_TYPE_EXTERN: {
const void *el = DPOC(data, t->offset);
@@ -2858,6 +2863,7 @@ _asn1_copy(const struct asn1_template *t, const void *from, void *to)
}
case A1_OP_NAME: break;
case A1_OP_DEFVAL: break;
case A1_OP_TYPE_DECORATE:
case A1_OP_TYPE:
case A1_OP_TYPE_EXTERN: {
const void *fel = DPOC(from, t->offset);

View File

@@ -286,4 +286,14 @@ TESTExtensible ::= SEQUENCE {
extensions SEQUENCE OF TESTExtension
}
TESTDecorated ::= SEQUENCE {
version TESTuint32
-- gets decorated
}
TESTNotDecorated ::= SEQUENCE {
version TESTuint32
-- should have the same encoding as TESTDecorated
}
END