Fixed syntax problems in generated code
[asn2quickder] / asn1ate / asn2quickder.py
1 #!/usr/bin/env python
2 #
3 # asn2quickder -- Generate header files for C for use with Quick `n' Easy DER
4 #
5 # This program owes a lot to asn1ate, which was built to generate pyasn1
6 # classes, but which was so well-written that it could be extended with a
7 # code generator for Quick DER.
8 #
9 # Much of the code below is diagonally inspired on the pyasn1 backend, so
10 # a very big thank you to Schneider Electric Buildings AB for helping to
11 # make this program possible!
12 #
13 # Copyright 2016 InternetWide.org and the ARPA2.net project.
14
15
16 import sys
17 import os.path
18
19 from asn1ate import parser
20 from asn1ate.sema import * 
21
22
23 def toCsym (name):
24     """Replace unsupported characters in ASN.1 symbol names"""
25     return str(name).replace(' ', '').replace('-', '_')
26
27
28 class QuickDERgen():
29     """Generate the C header files for Quick DER, a.k.a. Quick and Easy DER.
30
31        There are two things that are generated for each of the ASN.1 syntax
32        declaration symbol of a unit:
33
34        #define DER_PACK_unit_SyntaxDeclSym \
35             DER_PACK_ENTER | ..., \
36             ... \
37             DER_PACK_LEAVE, \
38             DER_PACK_END
39
40        this is a walking path for the der_pack() and der_unpack() instructions.
41        In addition, there will be a struct for each of the symbols:
42
43        struct unit_SyntaxDeclSym_ovly {
44            dercursor field1;
45            dercursor field2;
46            struct unit_EmbeddedSym_ovly field3;
47            dercursor field4;
48        };
49
50        The unit prefix will be set to the filename of the module, usually
51        something like rfc5280 when the parsed file is rfc5280.asn1 and the
52        output is then written to rfc5280.h for easy inclusion by the C code.
53     """
54
55     def __init__(self, semamod, outfn, refmods):
56         self.semamod = semamod
57         self.refmods = refmods
58         if outfn [-2:] == '.h':
59             raise Exception('File cannot overwrite itself -- use another extension than .h for input files')
60         self.unit = toCsym(outfn.rsplit('.', 1) [0])
61         self.outfile = open(self.unit + '.h', 'w')
62         self.wout = self.outfile.write
63         # Setup function maps
64         self.ovly_funmap = {
65             DefinedType: self.ovlyDefinedType,
66             ValueAssignment: self.ignore_node,
67             TypeAssignment: self.ovlyTypeAssignment,
68             TaggedType: self.ovlyTaggedType,
69             SimpleType: self.ovlySimpleType,
70             BitStringType: self.ovlySimpleType,
71             ValueListType: self.ovlySimpleType,
72             SequenceType: self.ovlyConstructedType,
73             SetType: self.ovlyConstructedType,
74             ChoiceType: self.ovlyConstructedType,
75             SequenceOfType: self.ovlySimpleType,    # var sized
76             SetOfType: self.ovlySimpleType,         # var sized
77             ComponentType: self.ovlySimpleType,  #TODO#
78         }
79         self.pack_funmap = {
80             DefinedType: self.packDefinedType,
81             ValueAssignment: self.ignore_node,
82             TypeAssignment: self.packTypeAssignment,
83             TaggedType: self.packTaggedType,
84             SimpleType: self.packSimpleType,
85             BitStringType: self.packSimpleType,
86             ValueListType: self.ovlySimpleType,
87             SequenceType: self.packSequenceType,
88             SetType: self.packSetType,
89             ChoiceType: self.packChoiceType,
90             SequenceOfType: self.packSequenceOfType,
91             SetOfType: self.packSetOfType,
92             ComponentType: self.packSimpleType,  #TODO#
93         }
94
95     def newcomma(self, comma, firstcomma=''):
96         self.comma0 = firstcomma
97         self.comma1 = comma
98
99     def comma(self):
100         self.wout(self.comma0)
101         self.comma0 = self.comma1
102
103     def close(self):
104         self.outfile.close()
105
106     def generate_head(self):
107         self.wout('/*\n * asn2quickder output for ' + self.semamod.name + ' -- automatically generated\n *\n * For information on Quick `n\' Easy DER, see https://github.com/vanrein/quick-der\n *\n * For information on the code generator, see https://github.com/vanrein/asn2quickder\n *\n */\n\n\n#include <quick-der/api.h>\n\n\n')
108         closer = ''
109         for rm in self.semamod.imports.module2symbols.keys():
110             rmfn = toCsym(rm.rsplit('.', 1) [0]).lower()
111             self.wout('#include <quick-der/' + rmfn + '.h>\n')
112             closer = '\n\n'
113         self.wout(closer)
114         closer = ''
115         for rm in self.semamod.imports.module2symbols.keys():
116             rmfn = toCsym(rm.rsplit('.', 1) [0]).lower()
117             for sym in self.semamod.imports.module2symbols [rm]:
118                 self.wout('typedef DER_OVLY_' + toCsym(rmfn) + '_' + toCsym(sym) + ' DER_OVLY_' + toCsym(self.unit) + '_' + toCsym(sym) + ';\n')
119                 closer = '\n\n'
120         self.wout(closer)
121         closer = ''
122         for rm in self.semamod.imports.module2symbols.keys():
123             rmfn = toCsym(rm.rsplit('.', 1) [0]).lower()
124             for sym in self.semamod.imports.module2symbols [rm]:
125                 self.wout('#define DER_PACK_' + toCsym(self.unit) + '_' + toCsym(sym) + ' DER_PACK_' + toCsym(rmfn) + '_' + toCsym(sym) + '\n')
126                 closer = '\n\n'
127         self.wout(closer)
128
129     def generate_tail(self):
130         self.wout('\n\n/* asn2quickder output for ' + self.semamod.name + ' ends here */\n')
131
132     def generate_ovly(self):
133         self.wout('\n\n/* Overlay structures with ASN.1 derived nesting and labelling */\n\n')
134         for assigncompos in dependency_sort(self.semamod.assignments):
135             for assign in assigncompos:
136                 self.generate_ovly_node(assign)
137
138     def generate_pack(self):
139         self.wout('\n\n/* Parser definitions in terms of ASN.1 derived bytecode instructions */\n\n')
140         for assigncompos in dependency_sort(self.semamod.assignments):
141             for assign in assigncompos:
142                 tnm = type(assign)
143                 if tnm in self.pack_funmap:
144                     self.pack_funmap [tnm](assign)
145                 else:
146                     print 'No pack generator for ' + str(tnm)
147
148     def generate_ovly_node(self, node):
149         tnm = type(node)
150         if tnm in self.ovly_funmap:
151             self.ovly_funmap [tnm](node)
152         else:
153             print('No overlay generator for ' + str(tnm))
154             raise Exception('RAISED WHERE?')
155
156     def generate_pack_node(self, node):
157         tnm = type(node)
158         if tnm in self.pack_funmap:
159             self.pack_funmap [tnm](node)
160         else:
161             print('No pack generator for ' + str(tnm))
162
163     def ignore_node(self, node):
164         pass
165
166     def ovlyTypeAssignment(self, node):
167         self.wout('typedef ')
168         self.generate_ovly_node(node.type_decl)
169         self.wout(' DER_OVLY_' + self.unit + '_' + toCsym(node.type_name) + ';\n\n')
170
171     def packTypeAssignment(self, node):
172         self.wout('#define DER_PACK_' + self.unit + '_' + toCsym(node.type_name))
173         self.newcomma(', \\\n\t', ' \\\n\t')
174         self.generate_pack_node(node.type_decl)
175         self.wout('\n\n')
176
177     def ovlyDefinedType(self, node):
178         mod = node.module_name or self.unit
179         self.wout('DER_OVLY_' + toCsym(mod) + '_' + toCsym(node.type_name))
180
181     def packDefinedType(self, node):
182         mod = node.module_name or self.unit
183         self.comma()
184         self.wout('DER_PACK_' + toCsym(mod) + '_' + toCsym(node.type_name))
185
186     def ovlySimpleType(self, node):
187         self.wout('dercursor')
188
189     def packSimpleType(self, node):
190         self.comma()
191         self.wout('DER_PACK_STORE | DER_TAG_' + node.type_name.replace(' ', '').upper())
192
193     def ovlyTaggedType(self, node):
194         # tag = str(node) 
195         # tag = tag [:tag.find(']')] + ']'
196         # self.wout('/* ' + tag + ' */ ')
197         # if node.implicity == TagImplicity.IMPLICIT:
198         #     tag = tag + ' IMPLICIT'
199         # elif node.implicity == TagImplicity.IMPLICIT:
200         #     tag = tag + ' EXPLICIT'
201         self.generate_ovly_node(node.type_decl)
202
203     def packTaggedType(self, node):
204         #TODO# Need to push down node.implicity == TagImplicity.IMPLICIT
205         #TODO# Need to process tag class
206         self.comma()
207         self.wout('DER_PACK_ENTER | DER_TAG_' +(node.class_name or 'CONTEXT') + '(' + node.class_number + ')')
208         self.generate_pack_node(node.type_decl)
209         self.comma()
210         self.wout('DER_PACK_LEAVE')
211
212     # Sequence, Set, Choice
213     def ovlyConstructedType(self, node):
214         self.wout('struct {\n');
215         for comp in node.components:
216             if isinstance(comp, ExtensionMarker):
217                 self.wout('\t/* ...extensions... */\n')
218                 continue
219             if isinstance(comp, ComponentType) and comp.components_of_type is not None:
220                 self.wout('\t/* TODO: COMPONENTS OF TYPE ' + str(comp.components_of_type) + ' */\n')
221                 continue
222             self.wout('\t')
223             self.generate_ovly_node(comp.type_decl)
224             self.wout(' ' + toCsym(comp.identifier) + '; // ' + str(comp.type_decl) + '\n')
225         self.wout('}')
226
227     def packSequenceType(self, node):
228         self.comma()
229         self.wout('DER_PACK_ENTER | DER_TAG_SEQUENCE')
230         for comp in node.components:
231             if isinstance(comp, ExtensionMarker):
232                 self.comma()
233                 self.wout('/* ...ASN.1 extensions... */')
234                 continue
235             if comp.optional:
236                 self.comma()
237                 self.wout('DER_PACK_OPTIONAL')
238             if comp.type_decl is not None:
239                 # TODO: None would be due to components_of_type
240                 self.generate_pack_node(comp.type_decl)
241         self.comma()
242         self.wout('DER_PACK_LEAVE')
243
244     def packSetType(self, node):
245         self.comma()
246         self.wout('DER_PACK_ENTER | DER_TAG_SET')
247         for comp in node.components:
248             if isinstance(comp, ExtensionMarker):
249                 self.comma()
250                 self.wout('/* ...extensions... */')
251                 continue
252             if comp.optional:
253                 self.comma()
254                 self.wout('DER_PACK_OPTIONAL')
255             if comp.type_decl is not None:
256                 # TODO: None would be due to components_of_type
257                 self.generate_pack_node(comp.type_decl)
258         self.comma()
259         self.wout('DER_PACK_LEAVE')
260
261     def packChoiceType(self, node):
262         self.comma()
263         self.wout('DER_PACK_CHOICE_BEGIN')
264         for comp in node.components:
265             if isinstance(comp, ExtensionMarker):
266                 self.comma()
267                 self.wout('/* ...extensions... */')
268                 continue
269             if comp.type_decl is not None:
270                 # TODO: None would be due to components_of_type
271                 self.generate_pack_node(comp.type_decl)
272         self.comma()
273         self.wout('DER_PACK_CHOICE_END')
274
275     def packSequenceOfType(self, node):
276         self.comma()
277         self.wout('DER_PACK_STORE | DER_TAG_SEQUENCE')
278
279     def packSetOfType(self, node):
280         self.comma()
281         self.wout('DER_PACK_STORE | DER_TAG_SEQUENCE')
282
283
284 """The main program asn2quickder is called with one or more .asn1 files,
285    the first of which is mapped to a C header file and the rest is
286    loaded to fulfil dependencies.
287 """
288
289 if len(sys.argv) < 2:
290     sys.stderr.write('Usage: %s main[.asn1] dependency[.asn1]...\n'
291         % sys.argv [0])
292     sys.exit(1)
293
294 mods = []
295 for file in sys.argv [1:]:
296     print('Parsing', file)
297     with open(file, 'r') as asn1fh:
298         asn1tree = parser.parse_asn1(asn1fh.read())
299     asn1tree = parser.parse_asn1(asn1txt)
300     print('Building semantic model for', file)
301     asn1sem = build_semantic_model(asn1tree)
302     mods.insert(0, asn1sem [0])
303     print('Realised semantic model for', file)
304
305 cogen = QuickDERgen(mods [-1], os.path.basename(sys.argv [1]), mods [1:])
306
307 cogen.generate_head()
308 cogen.generate_ovly()
309 cogen.generate_pack()
310 cogen.generate_tail()
311
312 cogen.close()