Introduced Quick DER code generation
[asn2quickder] / asn1ate / asn2quickder.py
1 #!/usr/bin/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 # From: Rick van Rein <rick@openfortress.nl>
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__ (ik, semamod, outfn, refmods):
56                 ik.semamod = semamod
57                 ik.refmods = refmods
58                 if outfn [-2:] == '.h':
59                         raise Exception ('File cannot overwrite itself -- use another extension than .h for input files')
60                 ik.unit = toCsym (outfn.rsplit ('.', 1) [0])
61                 ik.outfile = open (ik.unit + '.h', 'w')
62                 ik.wout = ik.outfile.write
63                 # Setup function maps
64                 ik.ovly_funmap = {
65                         DefinedType: ik.ovlyDefinedType,
66                         ValueAssignment: ik.ignore_node,
67                         TypeAssignment: ik.ovlyTypeAssignment,
68                         TaggedType: ik.ovlyTaggedType,
69                         SimpleType: ik.ovlySimpleType,
70                         BitStringType: ik.ovlySimpleType,
71                         ValueListType: ik.ovlySimpleType,
72                         SequenceType: ik.ovlyConstructedType,
73                         SetType: ik.ovlyConstructedType,
74                         ChoiceType: ik.ovlyConstructedType,
75                         SequenceOfType: ik.ovlySimpleType,      # var sized
76                         SetOfType: ik.ovlySimpleType,           # var sized
77                         ComponentType: ik.ovlySimpleType,  #TODO#
78                 }
79                 ik.pack_funmap = {
80                         DefinedType: ik.packDefinedType,
81                         ValueAssignment: ik.ignore_node,
82                         TypeAssignment: ik.packTypeAssignment,
83                         TaggedType: ik.packTaggedType,
84                         SimpleType: ik.packSimpleType,
85                         BitStringType: ik.packSimpleType,
86                         ValueListType: ik.ovlySimpleType,
87                         SequenceType: ik.packSequenceType,
88                         SetType: ik.packSetType,
89                         ChoiceType: ik.packChoiceType,
90                         SequenceOfType: ik.packSequenceOfType,
91                         SetOfType: ik.packSetOfType,
92                         ComponentType: ik.packSimpleType,  #TODO#
93                 }
94
95         def newcomma (ik, comma, firstcomma=''):
96                 ik.comma0 = firstcomma
97                 ik.comma1 = comma
98
99         def comma (ik):
100                 ik.wout (ik.comma0)
101                 ik.comma0 = ik.comma1
102
103         def close (ik):
104                 ik.outfile.close ()
105
106         def generate_head (ik):
107                 ik.wout ('/*\n * asn2quickder output for ' + ik.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                 # ik.wout ('/* This module ' + toCsym (ik.semamod.name) + ' depends on:\n')
109                 # for rm in ik.refmods:
110                 #       ik.wout (' *   ' + toCsym (rm.name) + '\n')
111                 # if len (ik.refmods) == 0:
112                 #       ik.wout (' *   (no other modules)\n')
113                 # ik.wout (' */\n\n')
114                 closer = ''
115                 for rm in ik.refmods:
116                         rmfn = toCsym (rm.rsplit ('.', 1) [0])
117                         ik.wout ('#include <quick-der/' + rmfn + '\n')
118                         closer = '\n\n'
119                 ik.wout (closer)
120
121         def generate_tail (ik):
122                 ik.wout ('\n\n/* asn2quickder output for ' + ik.semamod.name + ' ends here */\n')
123
124         def generate_ovly (ik):
125                 ik.wout ('\n\n/* Overlay structures with ASN.1 derived nesting and labelling */\n\n')
126                 for assigncompos in dependency_sort (ik.semamod.assignments):
127                         for assign in assigncompos:
128                                 ik.generate_ovly_node (assign)
129
130         def generate_pack (ik):
131                 ik.wout ('\n\n/* Parser definitions in terms of ASN.1 derived bytecode instructions */\n\n')
132                 for assigncompos in dependency_sort (ik.semamod.assignments):
133                         for assign in assigncompos:
134                                 tnm = type (assign)
135                                 if tnm in ik.pack_funmap:
136                                         ik.pack_funmap [tnm] (assign)
137                                 else:
138                                         print 'No pack generator for ' + str (tnm)
139
140         def generate_ovly_node (ik, node):
141                 tnm = type (node)
142                 if tnm in ik.ovly_funmap:
143                         ik.ovly_funmap [tnm] (node)
144                 else:
145                         print 'No overlay generator for ' + str (tnm)
146                         raise Exception ('RAISED WHERE?')
147
148         def generate_pack_node (ik, node):
149                 tnm = type (node)
150                 if tnm in ik.pack_funmap:
151                         ik.pack_funmap [tnm] (node)
152                 else:
153                         print 'No pack generator for ' + str (tnm)
154
155         def ignore_node (ik, node):
156                 pass
157
158         def ovlyTypeAssignment (ik, node):
159                 ik.wout ('typedef ')
160                 ik.generate_ovly_node (node.type_decl)
161                 ik.wout (' DER_OVLY_' + ik.unit + '_' + toCsym (node.type_name) + ';\n\n')
162
163         def packTypeAssignment (ik, node):
164                 ik.wout ('#define DER_PACK_' + ik.unit + '_' + toCsym (node.type_name))
165                 ik.newcomma (', \\\\\n\t', ' \\\\\n\t')
166                 ik.generate_pack_node (node.type_decl)
167                 ik.wout ('\n\n')
168
169         def ovlyDefinedType (ik, node):
170                 mod = node.module_name or ik.unit
171                 ik.wout ('DER_OVLY_' + toCsym (mod) + '_' + toCsym (node.type_name))
172
173         def packDefinedType (ik, node):
174                 mod = node.module_name or ik.unit
175                 ik.comma ()
176                 ik.wout ('DER_PACK_' + toCsym (mod) + '_' + toCsym (node.type_name))
177
178         def ovlySimpleType (ik, node):
179                 ik.wout ('dercursor')
180
181         def packSimpleType (ik, node):
182                 ik.comma ()
183                 ik.wout ('DER_PACK_STORE | DER_TAG_' + node.type_name.replace (' ', '').upper ())
184
185         def ovlyTaggedType (ik, node):
186                 # tag = str (node) 
187                 # tag = tag [:tag.find (']')] + ']'
188                 # ik.wout ('/* ' + tag + ' */ ')
189                 # if node.implicity == TagImplicity.IMPLICIT:
190                 #       tag = tag + ' IMPLICIT'
191                 # elif node.implicity == TagImplicity.IMPLICIT:
192                 #       tag = tag + ' EXPLICIT'
193                 ik.generate_ovly_node (node.type_decl)
194
195         def packTaggedType (ik, node):
196                 #TODO# Need to push down node.implicity == TagImplicity.IMPLICIT
197                 #TODO# Need to process tag class
198                 ik.comma ()
199                 ik.wout ('DER_PACK_ENTER | DER_' + (node.class_name or 'CONTEXT') + '_TAG(' + node.class_number + ')')
200                 ik.generate_pack_node (node.type_decl)
201                 ik.comma ()
202                 ik.wout ('DER_PACK_LEAVE')
203
204         # Sequence, Set, Choice
205         def ovlyConstructedType (ik, node):
206                 ik.wout ('struct {\n');
207                 for comp in node.components:
208                         if isinstance (comp, ExtensionMarker):
209                                 ik.wout ('\t/* ...extensions... */\n')
210                                 continue
211                         if isinstance (comp, ComponentType) and comp.components_of_type is not None:
212                                 ik.wout ('\t/* TODO: COMPONENTS OF TYPE ' + str (comp.components_of_type) + ' */\n')
213                                 continue
214                         ik.wout ('\t')
215                         ik.generate_ovly_node (comp.type_decl)
216                         ik.wout (' ' + toCsym (comp.identifier) + '; // ' + str (comp.type_decl) + '\n')
217                 ik.wout ('}')
218
219         def packSequenceType (ik, node):
220                 ik.comma ()
221                 ik.wout ('DER_PACK_ENTER | DER_TAG_SEQUENCE')
222                 for comp in node.components:
223                         if isinstance (comp, ExtensionMarker):
224                                 ik.comma ()
225                                 ik.wout ('/* ...ASN.1 extensions... */')
226                                 continue
227                         if comp.optional:
228                                 ik.comma ()
229                                 ik.wout ('DER_PACK_OPTIONAL')
230                         if comp.type_decl is not None:
231                                 # TODO: None would be due to components_of_type
232                                 ik.generate_pack_node (comp.type_decl)
233                 ik.comma ()
234                 ik.wout ('DER_PACK_LEAVE')
235
236         def packSetType (ik, node):
237                 ik.comma ()
238                 ik.wout ('DER_PACK_ENTER | DER_TAG_SET')
239                 for comp in node.components:
240                         if isinstance (comp, ExtensionMarker):
241                                 ik.comma ()
242                                 ik.wout ('/* ...extensions... */')
243                                 continue
244                         if comp.optional:
245                                 ik.comma ()
246                                 ik.wout ('DER_PACK_OPTIONAL')
247                         if comp.type_decl is not None:
248                                 # TODO: None would be due to components_of_type
249                                 ik.generate_pack_node (comp.type_decl)
250                 ik.comma ()
251                 ik.wout ('DER_PACK_LEAVE')
252
253         def packChoiceType (ik, node):
254                 ik.comma ()
255                 ik.wout ('DER_PACK_CHOICE_BEGIN')
256                 for comp in node.components:
257                         if isinstance (comp, ExtensionMarker):
258                                 ik.comma ()
259                                 ik.wout ('/* ...extensions... */')
260                                 continue
261                         if comp.type_decl is not None:
262                                 # TODO: None would be due to components_of_type
263                                 ik.generate_pack_node (comp.type_decl)
264                 ik.comma ()
265                 ik.wout ('DER_PACK_CHOICE_END')
266
267         def packSequenceOfType (ik, node):
268                 ik.comma ()
269                 ik.wout ('DER_PACK_STORE | DER_TAG_SEQUENCE')
270
271         def packSetOfType (ik, node):
272                 ik.comma ()
273                 ik.wout ('DER_PACK_STORE | DER_TAG_SEQUENCE')
274
275
276 """The main program asn2quickder is called with one or more .asn1 files,
277    the first of which is mapped to a C header file and the rest is
278    loaded to fulfil dependencies.
279 """
280
281 if len (sys.argv) < 2:
282         sys.stderr.write ('Usage: %s main[.asn1] dependency[.asn1]...\n'
283                 % sys.argv [0])
284         sys.exit (1)
285
286 mods = []
287 for file in sys.argv [1:]:
288         print 'Parsing', file
289         asn1fh = open (file, 'r')
290         asn1txt = asn1fh.read ()
291         asn1fh.close ()
292         asn1tree = parser.parse_asn1 (asn1txt)
293         print 'Building semantic model for', file
294         asn1sem = build_semantic_model (asn1tree)
295         mods.insert (0, asn1sem [0])
296         print 'Realised semantic model for', file
297
298 cogen = QuickDERgen (mods [-1], os.path.basename (sys.argv [1]), mods [1:])
299
300 cogen.generate_head ()
301 cogen.generate_ovly ()
302 cogen.generate_pack ()
303 cogen.generate_tail ()
304
305 cogen.close ()
306
307