asnParser.py 47.6 KB
Newer Older
1
#!/usr/bin/env python3
2
3
4
5
6
7
8
9
10
11
12
#
# (C) Semantix Information Technologies.
#
# Semantix Information Technologies is licensing the code of the
# Data Modelling Tools (DMT) in the following dual-license mode:
#
# Commercial Developer License:
#       The DMT Commercial Developer License is the appropriate version
# to use for the development of proprietary and/or commercial software.
# This version is for developers/companies who do not want to share
# the source code they develop with others or otherwise comply with the
Maxime Perrotin's avatar
Maxime Perrotin committed
13
# terms of the GNU Lesser General Public License version 3.
14
#
Maxime Perrotin's avatar
Maxime Perrotin committed
15
# GNU LGPL v. 2.1:
16
17
#       This version of DMT is the one to use for the development of
# non-commercial applications, when you are willing to comply
Maxime Perrotin's avatar
Maxime Perrotin committed
18
# with the terms of the GNU Lesser General Public License version 3.
19
20
21
22
#
# The features of the two licenses are summarized below:
#
#                       Commercial
Maxime Perrotin's avatar
Maxime Perrotin committed
23
#                       Developer               LGPL
24
25
26
27
28
29
30
31
32
#                       License
#
# License cost          License fee charged     No license fee
#
# Must provide source
# code changes to DMT   No, modifications can   Yes, all source code
#                       be closed               must be provided back
#
# Can create            Yes, that is,           No, applications are subject
Maxime Perrotin's avatar
Maxime Perrotin committed
33
# proprietary           no source code needs    to the LGPL and all source code
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# applications          to be disclosed         must be made available
#
# Support               Yes, 12 months of       No, but available separately
#                       premium technical       for purchase
#                       support
#
# Charge for Runtimes   None                    None
#
'''
ASN.1 Parser

This module parses ASN.1 grammars and creates an abstract syntax tree (AST)
inside configMT.inputCodeAST, for use with the code generators.
'''
48
49
50

# pylint: disable=too-many-lines

51
52
53
54
55
import os
import sys
import copy
import tempfile
import re
56
from distutils import spawn
Maxime Perrotin's avatar
Maxime Perrotin committed
57
import hashlib
58

59
import xml.sax  # type: ignore
60
from typing import IO, TypeVar, Type, Optional, Callable, Union, List, Dict, Tuple, Any  # NOQA pylint: disable=W0611
61

Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
62
63
from . import configMT
from . import utility
64

65
66
67
68
69
from .asnAST import (
    AsnBasicNode, AsnEnumerated, AsnSequence, AsnChoice, AsnSequenceOf,
    AsnSet, AsnSetOf, AsnMetaMember, AsnMetaType, AsnInt, AsnReal, AsnNode,
    AsnComplexNode, AsnBool, AsnOctetString, AsnAsciiString
)
70
71
72
73
74
75

g_asnFilename = ""

g_filename = ''
g_metatypes = {}

76
77
78
79
80
81
82
# MyPy type aliases
Typename = str
Filename = str
AST_Lookup = Dict[Typename, AsnNode]
AST_TypenamesOfFile = Dict[Filename, List[str]]  # pylint: disable=invalid-sequence-index
AST_TypesOfFile = Dict[Filename, List[AsnNode]]  # pylint: disable=invalid-sequence-index
AST_Leaftypes = Dict[Typename, str]
83
AST_Modules = Dict[str, List[Typename]]  # pylint: disable=invalid-sequence-index
84
85
86
87
88

g_names = {}         # type: AST_Lookup
g_typesOfFile = {}   # type: AST_TypenamesOfFile
g_leafTypeDict = {}  # type: AST_Leaftypes
g_astOfFile = {}     # type: AST_TypesOfFile
89
g_modules = {}       # type: AST_Modules
90

91
g_checkedSoFarForKeywords = {}  # type: Dict[str, int]
92
93

g_invalidKeywords = [
94
    "active", "adding", "all", "alternative", "and", "any", "as", "atleast", "axioms", "block", "call", "channel", "comment", "connect", "connection", "constant", "constants", "create", "dcl", "decision", "default", "else", "endalternative", "endblock", "endchannel", "endconnection", "enddecision", "endgenerator", "endmacro", "endnewtype", "endoperator", "endpackage", "endprocedure", "endprocess", "endrefinement", "endselect", "endservice", "endstate", "endsubstructure", "endsyntype", "endsystem", "env", "error", "export", "exported", "external", "fi", "finalized", "for", "fpar", "from", "gate", "generator", "if", "import", "imported", "in", "inherits", "input", "interface", "join", "literal", "literals", "macro", "macrodefinition", "macroid", "map", "mod", "nameclass", "newtype", "nextstate", "nodelay", "noequality", "none", "not", "now", "offspring", "operator", "operators", "or", "ordering", "out", "output", "package", "parent", "priority", "procedure", "process", "provided", "redefined", "referenced", "refinement", "rem", "remote", "reset", "return", "returns", "revealed", "reverse", "save", "select", "self", "sender", "service", "set", "signal", "signallist", "signalroute", "signalset", "spelling", "start", "state", "stop", "struct", "substructure", "synonym", "syntype", "system", "task", "then", "this", "to", "type", "use", "via", "view", "viewed", "virtual", "with", "xor", "end", "i", "j", "auto", "const",
95
96
97
    # From Nicolas Gillet/Astrium for SCADE
    "abstract", "activate", "and", "assume", "automaton", "bool", "case", "char", "clock", "const", "default", "div", "do", "else", "elsif", "emit", "end", "enum", "every", "false", "fby", "final", "flatten", "fold", "foldi", "foldw", "foldwi", "function", "guarantee", "group", "if", "imported", "initial", "int", "is", "last", "let", "make", "map", "mapfold", "mapi", "mapw", "mapwi", "match", "merge", "mod", "node", "not", "numeric", "of", "onreset", "open", "or", "package", "parameter", "pre", "private", "probe", "public", "real", "restart", "resume", "returns", "reverse", "sensor", "sig", "specialize", "state", "synchro", "tel", "then", "times", "transpose", "true", "type", "unless", "until", "var", "when", "where", "with", "xor",
    # From Maxime - ESA GNC Team
98
    "open", "close", "flag", "device", "range", "name"
99
100
101
]

tokens = (
102
    'DEFINITIONS', 'APPLICATION', 'AUTOMATIC', 'IMPLICIT', 'EXPLICIT', 'TAGS', 'BEGIN', 'IMPORTS', 'EXPORTS', 'FROM', 'ALL', 'CHOICE', 'SEQUENCE', 'SET', 'OF', 'END', 'OPTIONAL', 'INTEGER', 'REAL', 'OCTET', 'STRING', 'BOOLEAN', 'TRUE', 'FALSE', 'ASCIISTRING', 'NUMBERSTRING', 'VISIBLESTRING', 'PRINTABLESTRING', 'UTF8STRING', 'ENUMERATED', 'SEMI', 'LPAREN', 'RPAREN', 'LBRACKET', 'RBRACKET', 'BLOCK_END', 'BLOCK_BEGIN', 'DEF', 'NAME', 'COMMA', 'INTVALUE', 'REALVALUE', 'DEFAULT', 'SIZE', 'DOTDOT', 'DOTDOTDOT', 'WITH', 'COMPONENTS', 'MANTISSA', 'BASE', 'EXPONENT'  # 'BIT',
103
104
105
106
107
108
109
110
111
)

lotokens = [tkn.lower() for tkn in tokens]


# Parsing rules

#    'BIT': 'BIT',
reserved = {
112
    'DEFINITIONS': 'DEFINITIONS', 'APPLICATION': 'APPLICATION', 'TAGS': 'TAGS', 'BEGIN': 'BEGIN', 'CHOICE': 'CHOICE',
113
114
115
116
117
    'SEQUENCE': 'SEQUENCE', 'SET': 'SET', 'OF': 'OF', 'END': 'END', 'OPTIONAL': 'OPTIONAL', 'BOOLEAN': 'BOOLEAN', 'INTEGER': 'INTEGER',
    'REAL': 'REAL', 'OCTET': 'OCTET', 'STRING': 'STRING', 'UTF8String': 'UTF8STRING', 'AsciiString': 'ASCIISTRING',
    'NumberString': 'NUMBERSTRING', 'VisibleString': 'VISIBLESTRING', 'PrintableString': 'PRINTABLESTRING', 'ENUMERATED': 'ENUMERATED',
    'AUTOMATIC': 'AUTOMATIC', 'SIZE': 'SIZE', 'IMPLICIT': 'IMPLICIT', 'EXPLICIT': 'EXPLICIT', 'TRUE': 'TRUE', 'FALSE': 'FALSE',
    'DEFAULT': 'DEFAULT', 'mantissa': 'MANTISSA', 'base': 'BASE', 'exponent': 'EXPONENT', 'WITH': 'WITH', 'FROM': 'FROM',
118
    'IMPORTS': 'IMPORTS', 'EXPORTS': 'EXPORTS', 'ALL': 'ALL', 'COMPONENTS': 'COMPONENTS'
119
120
}

121

122
def KnownType(node: AsnNode, names: AST_Lookup) -> bool:
123
    retVal = True
124
125
    if isinstance(node, str):
        utility.panic("Referenced type (%s) does not exist!\n" % node)
126
127
128
    if isinstance(node, (AsnBasicNode, AsnEnumerated)):
        pass
    elif isinstance(node, (AsnSequence, AsnChoice, AsnSet)):
129
130
131
132
        for x in node._members:
            if not KnownType(x[1], names):
                return False
    elif isinstance(node, AsnMetaMember):
133
134
        retVal = KnownType(names.get(node._containedType, node._containedType), names)
    elif isinstance(node, (AsnSequenceOf, AsnSetOf)):
135
136
137
        containedType = node._containedType
        while isinstance(containedType, str):
            containedType = names[containedType]
138
        retVal = KnownType(containedType, names)
139
    elif isinstance(node, AsnMetaType):
140
        retVal = KnownType(names.get(node._containedType, node._containedType), names)
141
    else:
142
143
        utility.panic("Unknown node type (%s)!\n" % str(node))
    return retVal
144
145


146
def CleanNameForAST(name: str) -> str:
147
148
149
    return re.sub(r'[^a-zA-Z0-9_]', '_', name)


150
151
152
153
154
155
156
def VerifyAndFixAST() -> Dict[str, str]:
    '''Check that all types are defined and are not missing.
    It returns a map providing the leafType of each type.
    '''
    unknownTypes = {}  # type: Dict[str, int]
    knownTypes = {}    # type: Dict[str, str]
    equivalents = {}   # type: Dict[str, List[str]]
157
    while True:  # pylint: disable=too-many-nested-blocks
158
159
160
        lastUnknownTypes = copy.copy(unknownTypes)
        lastKnownTypes = copy.copy(knownTypes)
        lastEquivalents = copy.copy(equivalents)
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
161
        for nodeTypename in list(g_names.keys()):
162

163
            node = g_names[nodeTypename]  # type: AsnNode
164
165
166

            # AsnMetaMembers can only appear inside SEQUENCEs and CHOICEs,
            # not at the top level!
167
            assert not isinstance(node, AsnMetaMember)
168
169
170
171
172
173
174
175
176
177

            # Type level typedefs are stored in the equivalents dictionary
            if isinstance(node, AsnMetaType):
                # A ::= B
                # A and B are nodeTypename  and  node._containedType
                equivalents.setdefault(node._containedType, [])
                # Add A to the list of types that are equivalent to B
                equivalents[node._containedType].append(nodeTypename)
                # and if we know B's leafType, then we also know A's
                if node._containedType in knownTypes:
178
                    knownTypes[nodeTypename] = node._containedType
179
                else:
180
                    unknownTypes[nodeTypename] = 1
181
182
183
184
185
            # AsnBasicNode type assignments are also equivalents
            elif isinstance(node, AsnBasicNode):
                # node._leafType is one of BOOLEAN, OCTET STRING, INTEGER, etc
                equivalents.setdefault(node._leafType, [])
                equivalents[node._leafType].append(nodeTypename)
186
                knownTypes[nodeTypename] = node._leafType
187
188
189
            # AsnEnumerated types are known types - they don't have external refs
            elif isinstance(node, AsnEnumerated):
                # node._leafType is ENUMERATED
190
                knownTypes[nodeTypename] = node._leafType
191
            # SEQUENCEs and CHOICEs: check their children for unknown AsnMetaMembers
192
            elif isinstance(node, (AsnSequence, AsnChoice, AsnSet)):
193
194
195
196
197
198
                bFoundUnknown = False
                for x in node._members:
                    if isinstance(x[1], AsnMetaMember) and x[1]._containedType not in knownTypes:
                        bFoundUnknown = True
                        break
                if bFoundUnknown:
199
                    unknownTypes[nodeTypename] = 1
200
201
                else:
                    # node._leafType is SEQUENCE or CHOICE
202
                    knownTypes[nodeTypename] = node._leafType
203
            # SEQUENCE OFs: check their contained type
204
            elif isinstance(node, (AsnSequenceOf, AsnSetOf)):
205
                if node._containedType in knownTypes or isinstance(node._containedType, AsnBasicNode):
206
                    knownTypes[nodeTypename] = node._leafType
207
                elif isinstance(node._containedType, AsnComplexNode):
208
                    knownTypes[nodeTypename] = node._leafType
209
                else:
210
                    unknownTypes[nodeTypename] = 1
211
212
213
214

        # We have completed a sweep over all AST entries.
        # now check the knownTypes and unknownTypes information
        # to see if we have figured out (leafType wise) all nodes
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
215
        for known in list(knownTypes.keys()):
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
            # for each of the nodes we know (leafType wise)

            # remove it from the unknownTypes dictionary
            if known in unknownTypes:
                del unknownTypes[known]

            # remove all it's equivalents, too (from the unknownTypes)
            if known in equivalents:
                for alsoKnown in equivalents[known]:
                    if alsoKnown in unknownTypes:
                        del unknownTypes[alsoKnown]

                    # Additionally, follow the chain to the last knownType
                    seed = known
                    while seed in knownTypes:
231
232
                        if seed != knownTypes[seed]:
                            seed = knownTypes[seed]
233
234
235
                        else:
                            break
                    # and update knownTypes dictionary to contain leafType
236
                    knownTypes[alsoKnown] = seed
237
238
239
240
241

        # If this pass has not changed the knownTypes and the unknownTypes and the equivalents, we are done
        if lastEquivalents == equivalents and lastKnownTypes == knownTypes and lastUnknownTypes == unknownTypes:
            break

242
    if unknownTypes:
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
243
        utility.panic('AsnParser: Types remain unknown after symbol fixup:\n%s\n' % list(unknownTypes.keys()))
244
245
246

    # Remove all AsnMetaTypes from the ast
    # by using the g_names lookup on their _containedType
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
247
    for nodeTypename in list(g_names.keys()):
248
249
250
251
252
        # Min, Max: to cope with ReferenceTypes that redefine their
        # constraints (for now, ASN1SCC provides only INTEGERs)
        Min = Max = None
        node = g_names[nodeTypename]
        if hasattr(node, "_Min") and Min is None:
253
            Min = node._Min  # type: ignore
254
        if hasattr(node, "_Max") and Max is None:
255
            Max = node._Max  # type: ignore
256
257
258
259
260
        originalNode = node
        while isinstance(node, AsnMetaType):
            g_metatypes[nodeTypename] = node._containedType
            node = g_names[node._containedType]
            if hasattr(node, "_Min") and Min is None:
261
                Min = node._Min  # type: ignore
262
            if hasattr(node, "_Max") and Max is None:
263
                Max = node._Max  # type: ignore
264
265
266
        # To cope with ReferenceTypes that redefine their
        # constraints (for now, ASN1SCC provides only INTEGERs)
        if isinstance(originalNode, AsnMetaType):
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
267
268
            target = copy.copy(node)  # type: ignore
            # we need to keep the _asnFilename
269
            target._asnFilename = originalNode._asnFilename
270
            if isinstance(node, AsnInt) and Min is not None and Max is not None:
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
271
                target._range = [Min, Max]  # type: ignore
272
273
        elif isinstance(node, AsnInt) and Min is not None and Max is not None:
            target = copy.copy(node)  # we need to keep the Min/Max
274
            target._range = [Min, Max]
275
276
277
278
        else:
            target = node
        g_names[nodeTypename] = target

Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
279
    for name, node in list(g_names.items()):
280
281
282
        if not KnownType(node, g_names):
            utility.panic("Node %s not resolvable (%s)!\n" % (name, node.Location()))
        for i in ["_Min", "_Max"]:
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
283
            cast = float if isinstance(node, AsnReal) else int
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
            if hasattr(node, i) and getattr(node, i) is not None:
                setattr(node, i, cast(getattr(node, i)))

    knownTypes['INTEGER'] = 'INTEGER'
    knownTypes['REAL'] = 'REAL'
    knownTypes['BOOLEAN'] = 'BOOLEAN'
    knownTypes['OCTET STRING'] = 'OCTET STRING'
    knownTypes['AsciiString'] = 'OCTET STRING'
    knownTypes['NumberString'] = 'OCTET STRING'
    knownTypes['VisibleString'] = 'OCTET STRING'
    knownTypes['PrintableString'] = 'OCTET STRING'
    knownTypes['UTF8String'] = 'OCTET STRING'

    # Find all the SEQUENCE, CHOICE and SEQUENCE OFs
    # and if the contained type is not one of AsnBasicNode, AsnEnumerated, AsnMetaMember,
    # define a name and use it... (for SEQUENCEOFs/SETOFs, allow also 'str')
    internalNo = 1
    addedNewPseudoType = True
302
    while addedNewPseudoType:  # pylint: disable=too-many-nested-blocks
303
304
305
306
        addedNewPseudoType = False
        listOfTypenames = sorted(g_names.keys())
        for nodeTypename in listOfTypenames:
            node = g_names[nodeTypename]
307
            if isinstance(node, (AsnChoice, AsnSequence, AsnSet)):
308
309
310
311
312
                for child in node._members:
                    if not isinstance(child[1], AsnBasicNode) and \
                            not isinstance(child[1], AsnEnumerated) and \
                            not isinstance(child[1], AsnMetaMember):
                        # It will be an internal sequence, choice or sequenceof
313
                        assert isinstance(child[1], (AsnChoice, AsnSet, AsnSetOf, AsnSequence, AsnSequenceOf))
314
315
316
317
318
319
320
321
322
                        internalName = newname = nodeTypename + '_' + CleanNameForAST(child[0])
                        while internalName in g_names:
                            internalName = (newname + "_%d") % internalNo
                            internalNo += 1
                        g_names[internalName] = child[1]
                        child[1]._isArtificial = True
                        g_leafTypeDict[internalName] = child[1]._leafType
                        child[1] = AsnMetaMember(asnFilename=child[1]._asnFilename, containedType=internalName)
                        addedNewPseudoType = True
323
            elif isinstance(node, (AsnSequenceOf, AsnSetOf)):
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
                if not isinstance(node._containedType, str) and \
                        not isinstance(node._containedType, AsnBasicNode) and \
                        not isinstance(node._containedType, AsnEnumerated):
                    internalName = newname = nodeTypename + "_elm"
                    while internalName in g_names:
                        internalName = (newname + "_%d") % internalNo
                        internalNo += 1
                    g_names[internalName] = node._containedType
                    node._containedType._isArtificial = True
                    g_leafTypeDict[internalName] = node._containedType._leafType
                    node._containedType = internalName
                    addedNewPseudoType = True

    # return the leafType dictionary
    return knownTypes


341
def IsInvalidType(name: str) -> bool:
342
343
344
345
346
347
    return \
        (name.lower() in g_invalidKeywords) or \
        (name.lower() in lotokens) or \
        any([name.lower().endswith(x) for x in ["-buffer", "-buffer-max"]])


348
349
350
351
352
353
354
355
def CheckForInvalidKeywords(node_or_str: Union[str, AsnNode]) -> None:
    if isinstance(node_or_str, str):
        if IsInvalidType(node_or_str):
            utility.panic(
                "TASTE disallows certain type names for various reasons.\n'%s' is not allowed" % node_or_str)
        node = g_names[node_or_str]  # type: AsnNode
    else:
        node = node_or_str
356

357
    if isinstance(node, (AsnBasicNode, AsnEnumerated)):
358
        pass
359
    elif isinstance(node, (AsnSequence, AsnChoice, AsnSet)):
360
361
362
363
364
365
366
367
368
369
370
        for child in node._members:
            if child[0].lower() in g_invalidKeywords or child[0].lower() in lotokens:
                utility.panic(
                    "TASTE disallows certain field names because they are used in various modelling tools.\n" +
                    "Invalid field name '%s' used in type defined in %s" % (child[0], node.Location()))
            if isinstance(child[1], AsnMetaMember) and child[1]._containedType not in g_checkedSoFarForKeywords:
                if IsInvalidType(child[1]._containedType.lower()):
                    utility.panic(
                        "TASTE disallows certain type names for various reasons.\n" +
                        "Invalid type name '%s' used in type defined in %s" % (child[1]._containedType, node.Location()))
                if child[1]._containedType not in g_checkedSoFarForKeywords:
371
                    g_checkedSoFarForKeywords[child[1]._containedType] = 1
372
373
374
375
                    CheckForInvalidKeywords(g_names[child[1]._containedType])
            if isinstance(child[1], AsnMetaMember) and child[1]._containedType.lower() == child[0].lower():
                utility.panic(
                    "Ada mappers won't allow SEQUENCE/CHOICE fields with same names as their types.\n" +
376
                    "Fix declaration at %s ('%s')" % (node.Location(), child[1]._containedType.lower()))
377
    elif isinstance(node, (AsnSequenceOf, AsnSetOf)):
378
379
380
381
382
383
        if isinstance(node._containedType, str):
            if IsInvalidType(node._containedType):
                utility.panic(
                    "TASTE disallows certain type names for various reasons.\n" +
                    "Invalid type name '%s' used in type defined in %s" % (node._containedType, node.Location()))
            if node._containedType not in g_checkedSoFarForKeywords:
384
                g_checkedSoFarForKeywords[node._containedType] = 1
385
386
387
                CheckForInvalidKeywords(g_names[node._containedType])


388
def ParseAsnFileList(listOfFilenames: List[str]) -> None:  # pylint: disable=invalid-sequence-index
Maxime Perrotin's avatar
Maxime Perrotin committed
389
    # Add basic ASN.1 caching to avoid calling the ASN.1 compiler over and over
390
    projectCache = os.getenv("PROJECT_CACHE")
391
    if projectCache is not None and not os.path.isdir(projectCache):
392
393
394
395
396
        try:
            os.mkdir(projectCache)
        except:
            utility.panic(
                "The configured cache folder:\n\n\t" + projectCache + "\n\n...is not there!\n")
Maxime Perrotin's avatar
Maxime Perrotin committed
397
    xmlAST = xmlAST2 = None
Maxime Perrotin's avatar
Maxime Perrotin committed
398
    someFilesHaveChanged = False
Maxime Perrotin's avatar
Maxime Perrotin committed
399
    if projectCache is not None:
Maxime Perrotin's avatar
Maxime Perrotin committed
400
401
        filehash = hashlib.md5()
        for each in sorted(listOfFilenames):
Maxime Perrotin's avatar
Maxime Perrotin committed
402
            filehash.update(open(each).read().encode('utf-8'))
Maxime Perrotin's avatar
Maxime Perrotin committed
403
404
405
406
407
408
409
            # also hash the file path: it is used in the AST in XML, so it is
            # not enough to hash the content of the ASN.1 files, as two sets
            # of files may have the same hash, that would lead to different XML
            # content.
            filehash.update(each.encode('utf-8'))
        newHash = filehash.hexdigest()
        # set the name of the XML files containing the dumped ASTs
410
        xmlAST = projectCache + os.sep + newHash + "_ast_v4.xml"
Maxime Perrotin's avatar
Maxime Perrotin committed
411
        xmlAST2 = projectCache + os.sep + newHash + "_ast_v1.xml"
Maxime Perrotin's avatar
Maxime Perrotin committed
412
        if not os.path.exists(xmlAST) or not os.path.exists(xmlAST2):
Maxime Perrotin's avatar
Maxime Perrotin committed
413
            someFilesHaveChanged = True
414
            print("[DMT] No cached model found for", ",".join(listOfFilenames))
Maxime Perrotin's avatar
Maxime Perrotin committed
415
416
417
418
    else:
        # no projectCache set, so xmlAST and xmlAST2 are set to None
        someFilesHaveChanged = True
    if not someFilesHaveChanged:
419
        print("[DMT] Reusing cached ASN.1 AST for ", ",".join(listOfFilenames))
Maxime Perrotin's avatar
Maxime Perrotin committed
420

Maxime Perrotin's avatar
Maxime Perrotin committed
421
422
423
424
    if not xmlAST:
        (dummy, xmlAST) = tempfile.mkstemp()
        os.fdopen(dummy).close()
        xmlAST2 = xmlAST + "2"
Maxime Perrotin's avatar
Maxime Perrotin committed
425

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
    if someFilesHaveChanged:
        asn1SccPath = spawn.find_executable('asn1.exe')
        if asn1SccPath is None:
            utility.panic("ASN1SCC seems not installed on your system (asn1.exe not found in PATH).\n")
        asn1SccDir = os.path.dirname(os.path.abspath(asn1SccPath))
        spawnResult = os.system("mono \"" + asn1SccPath + "\" -customStg \"" + asn1SccDir + "/xml.stg:" + xmlAST + "\" -typePrefix asn1Scc -fp AUTO -customStgAstVersion 4 \"" + "\" \"".join(listOfFilenames) + "\"")
        if spawnResult != 0:
            errCode = spawnResult / 256
            if errCode == 1:
                utility.panic("ASN1SCC reported syntax errors. Aborting...")
            elif errCode == 2:
                utility.panic("ASN1SCC reported semantic errors (or mono failed). Aborting...")
            elif errCode == 3:
                utility.panic("ASN1SCC reported internal error. Contact ESA with this input. Aborting...")
            elif errCode == 4:
                utility.panic("ASN1SCC reported usage error. Aborting...")
            else:
                utility.panic("ASN1SCC generic error. Contact ESA with this input. Aborting...")
    ParseASN1SCC_AST(xmlAST)
    if projectCache is None:
        os.unlink(xmlAST)
    g_names.update(g_names)
    g_leafTypeDict.update(g_leafTypeDict)
    g_checkedSoFarForKeywords.update(g_checkedSoFarForKeywords)
    g_typesOfFile.update(g_typesOfFile)

    # We also need to mark the artificial types -
    # So spawn the custom type output at level 1 (unfiltered)
    # and mark any types not inside it as artificial.
    if someFilesHaveChanged:
        os.system("mono \"" + asn1SccPath + "\" -customStg \"" + asn1SccDir + "/xml.stg:" + xmlAST2 + "\" -fp AUTO -typePrefix asn1Scc -customStgAstVersion 1 \"" + "\" \"".join(listOfFilenames) + "\"")
    realTypes = {}
    for line in os.popen("grep  'ExportedType\>' \"" + xmlAST2 + "\"").readlines():  # flake8: noqa pylint: disable=anomalous-backslash-in-string
        line = re.sub(r'^.*Name="', '', line.strip())
        line = re.sub(r'" />$', '', line)
        realTypes[line] = 1
    if projectCache is None:
        os.unlink(xmlAST2)
    for nodeTypename in list(g_names.keys()):
        if nodeTypename not in realTypes:
            g_names[nodeTypename]._isArtificial = True
467
468


469
def Dump() -> None:
470
471
472
    for nodeTypename in sorted(g_names.keys()):
        if g_names[nodeTypename]._isArtificial:
            continue
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
473
474
475
        print("\n===== From", g_names[nodeTypename]._asnFilename)
        print(nodeTypename)
        print("::", g_names[nodeTypename], g_leafTypeDict[nodeTypename])
476
477


478
def test_asn1() -> None:
479
480
481
482
483
484
485
486
487
488
489
490
491
492
    if "-debug" in sys.argv:
        configMT.debugParser = True
        sys.argv.remove("-debug")
    if len(sys.argv) < 2:
        sys.stderr.write('Usage: %s input.asn [input2.asn] ...\n' % sys.argv[0])
        sys.exit(1)
    for f in sys.argv[1:]:
        if not os.path.isfile(f):
            sys.stderr.write("'%s' is not a file!\n" % f)
            sys.exit(1)

    ParseAsnFileList(sys.argv[1:])
    Dump()

493
494
495
496
497
498
499

g_xmlASTrootNode = None

g_lineno = -1


class Element:
500
    def __init__(self, name: str, attrs: Dict[str, Any]) -> None:
501
502
        self._name = name
        self._attrs = attrs
503
        self._children = []  # type: List[Element]
504
505


506
class InputFormatXMLHandler(xml.sax.ContentHandler):  # type: ignore
507
    def __init__(self, debug: bool = False) -> None:
508
        xml.sax.ContentHandler.__init__(self)  # type: ignore
509
510
511
512
513
514
515
        self._debug = False
        if debug:
            self._debug = True  # pragma: no cover
            self._indent = ""  # pragma: no cover
        self._root = Element('root', {})
        self._roots = [self._root]

516
    def startElement(self, name: str, attrs: Dict[str, Any]) -> None:
517
518
519
520
521
522
523
524
        if self._debug:
            print(self._indent + "(", name, ")", ", ".join(list(attrs.keys())))  # pragma: no cover
            self._indent += "    "  # pragma: no cover
        newElement = Element(name, attrs)
        self._roots[-1]._children.append(newElement)
        self._roots.append(newElement)

    # def endElement(self, name):
525
    def endElement(self, _: Any) -> None:
526
527
528
529
530
531
532
533
534
535
536
537
        if self._debug:
            if len(self._indent) > 4:  # pragma: no cover
                self._indent = self._indent[:len(self._indent) - 4]  # pragma: no cover
            # print self._indent + "(", name, ") ends" # pragma: no cover
        self._roots.pop()

# def Travel(indent, node):
#     print indent + node._name, ",".join(node._attrs.keys())
#     for c in node._children:
#        Travel(indent+"    ", c)


538
539
540
541
Action = Callable[[Element], Any]


def VisitAll(node: Element, expectedType: str, action: Action) -> List[Any]:  # pylint: disable=invalid-sequence-index
542
    results = []  # type: List[Any]
543
544
    if node is not None:
        if node._name == expectedType:
545
            results = [action(node)]
546
        for child in node._children:
547
            results += VisitAll(child, expectedType, action)
548
549
550
    return results


551
def GetAttr(node: Element, attrName: str) -> Optional[Any]:
552
553
554
555
556
557
    if attrName not in list(node._attrs.keys()):
        return None
    else:
        return node._attrs[attrName]


558
def GetChild(node: Element, childName: str) -> Optional[Element]:
559
560
561
562
563
564
565
    for x in node._children:
        if x._name == childName:
            return x
    return None  # pragma: no cover


class Pretty:
566
    def __repr__(self) -> str:
567
568
569
570
571
572
573
574
575
        result = ""  # pragma: no cover
        for i in dir(self):  # pragma: no cover
            if i != "__repr__":  # pragma: no cover
                result += chr(27) + "[32m" + i + chr(27) + "[0m:"  # pragma: no cover
                result += repr(getattr(self, i))  # pragma: no cover
                result += "\n"  # pragma: no cover
        return result  # pragma: no cover


576
577
578
579
580
581
582
583
584
585
586
587
class Module(Pretty):
    _id = None                 # type: str
    _asnFilename = None        # type: str
    _exportedTypes = None      # type: List[str]
    _exportedVariables = None  # type: List[str]

    # (tuples of ModuleName, imported types, imported vars)
    _importedModules = None    # type: List[Tuple[str, List[str], List[str]]]
    # (tuples of Typename, AsnNode)
    _typeAssignments = None    # type: List[Tuple[str, AsnNode]]


588
# def CreateBoolean(newModule, lineNo, xmlBooleanNode):
589
def CreateBoolean(newModule: Module, lineNo: int, _: Any) -> AsnBool:
590
591
592
593
594
    return AsnBool(
        asnFilename=newModule._asnFilename,
        lineno=lineNo)


595
596
597
598
U = TypeVar('U', int, float)


def GetRange(newModule: Module, lineNo: int, nodeWithMinAndMax: Element, valueType: Type[U]) -> Tuple[U, U]:
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
    try:
        mmin = GetAttr(nodeWithMinAndMax, "Min")
        # rangel = ( mmin == "MIN" ) and -2147483648L or valueType(mmin)
        if mmin == "MIN":
            utility.panic("You missed a range specification, or used MIN/MAX (line %s)" % lineNo)  # pragma: no cover
        rangel = valueType(mmin)
        mmax = GetAttr(nodeWithMinAndMax, "Max")
        # rangeh = ( mmax == "MAX" ) and 2147483647L or valueType(mmax)
        if mmax == "MAX":
            utility.panic("You missed a range specification, or used MIN/MAX (line %s)" % lineNo)  # pragma: no cover
        rangeh = valueType(mmax)
    except:  # pragma: no cover
        descr = {int: "integer", float: "floating point"}  # pragma: no cover
        utility.panic("Expecting %s value ranges (%s, %s)" %  # pragma: no cover
                      (descr[valueType], newModule._asnFilename, lineNo))  # pragma: no cover
614
    return (rangel, rangeh)
615
616


617
def CreateInteger(newModule: Module, lineNo: int, xmlIntegerNode: Element) -> AsnInt:
618
619
620
621
622
623
    return AsnInt(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlIntegerNode, int))


624
def CreateReal(newModule: Module, lineNo: int, xmlRealNode: Element) -> AsnReal:
625
626
627
628
629
630
    return AsnReal(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlRealNode, float))


631
def CreateEnumerated(newModule: Module, lineNo: int, xmlEnumeratedNode: Element) -> AsnEnumerated:
632
633
634
635
636
637
638
    # bSetIntValue = True
    # if GetAttr(xmlEnumeratedNode, "ValuesAutoCalculated") == "True":
    #    bSetIntValue = False
    return AsnEnumerated(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        members=VisitAll(
639
            xmlEnumeratedNode, "EnumValue",
640
641
642
643
644
645
646
            # lambda x: [GetAttr(x, "StringValue"), GetAttr(x, "IntValue"), GetAttr(x, "EnumID")]))
            #  old code: used to check the ValuesAutoCalculated and use None for the integer values
            # lambda x: [GetAttr(x, "StringValue"), bSetIntValue and GetAttr(x, "IntValue") or None]))
            lambda x: [GetAttr(x, "StringValue"), GetAttr(x, "IntValue")]))


# def CreateBitString(newModule, lineNo, xmlBitString):
647
def CreateBitString(_, __, ___):  # type: ignore
648
    utility.panic("BitString type is not supported by the toolchain. "  # pragma: no cover
649
650
651
                  "Please use SEQUENCE OF BOOLEAN")  # pragma: no cover


652
def CreateOctetString(newModule: Module, lineNo: int, xmlOctetString: Element) -> AsnOctetString:
653
654
655
656
657
658
    return AsnOctetString(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlOctetString, int))


659
def CreateIA5String(newModule: Module, lineNo: int, xmlIA5StringNode: Element) -> AsnAsciiString:
660
661
662
663
664
665
666
667
668
    # utility.panic("IA5Strings are supported by ASN1SCC, but are not supported yet " # pragma: no cover
    #               "by the toolchain. Please use OCTET STRING") # pragma: no cover
    # return CreateOctetString(newModule, lineNo, xmlIA5StringNode)
    return AsnAsciiString(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlIA5StringNode, int))


669
def CreateNumericString(newModule: Module, lineNo: int, xmlNumericStringNode: Element) -> AsnOctetString:
670
671
672
    return CreateOctetString(newModule, lineNo, xmlNumericStringNode)  # pragma: no cover


673
674
def getIntOrFloatOrNone(d: str) -> Union[int, float, None]:
    i = f = None
675
    try:
676
677
        i = int(d)
        return i
678
679
    except:
        try:
680
681
            f = float(d)
            return f
682
        except:
683
684
685
            return None


686
def CreateReference(newModule: Module, lineNo: int, xmlReferenceNode: Element) -> AsnMetaType:
687
688
689
690
    return AsnMetaType(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        containedType=GetAttr(xmlReferenceNode, "ReferencedTypeName"),
691
692
        Min=getIntOrFloatOrNone(GetAttr(xmlReferenceNode, "Min")),
        Max=getIntOrFloatOrNone(GetAttr(xmlReferenceNode, "Max")))
693
694


695
696
697
698
V = TypeVar('V', AsnSequenceOf, AsnSetOf)


def CommonSetSeqOf(newModule: Module, lineNo: int, xmlSequenceOfNode: Element, classToCreate: Type[V]) -> V:
699
700
701
702
    xmlType = GetChild(xmlSequenceOfNode, "Type")
    if xmlType is None:
        utility.panic("CommonSetSeqOf: No child under SequenceOfType (%s, %s)" %  # pragma: no cover
                      (newModule._asnFilename, lineNo))  # pragma: no cover
703
    if len(xmlType._children) == 0:  # pylint: disable=len-as-condition
704
705
706
707
708
709
710
711
712
713
714
715
716
        utility.panic("CommonSetSeqOf: No children for Type (%s, %s)" %  # pragma: no cover
                      (newModule._asnFilename, lineNo))  # pragma: no cover
    if xmlType._children[0]._name == "ReferenceType":
        contained = GetAttr(xmlType._children[0], "ReferencedTypeName")
    else:
        contained = GenericFactory(newModule, xmlType)
    return classToCreate(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlSequenceOfNode, int),
        containedType=contained)


717
def CreateSequenceOf(newModule: Module, lineNo: int, xmlSequenceOfNode: Element) -> AsnSequenceOf:
718
719
720
    return CommonSetSeqOf(newModule, lineNo, xmlSequenceOfNode, AsnSequenceOf)


721
def CreateSetOf(newModule: Module, lineNo: int, xmlSetOfNode: Element) -> AsnSetOf:
722
723
724
    return CommonSetSeqOf(newModule, lineNo, xmlSetOfNode, AsnSetOf)


725
726
727
728
729
730
731
732
733
W = TypeVar('W', AsnSequence, AsnSet, AsnChoice)


def CommonSeqSetChoice(
        newModule: Module,
        lineNo: int,
        xmlSequenceNode: Element,
        classToCreate: Type[W],
        childTypeName: str) -> W:
734
735
736
737
738
739
740
741
742
    # Bug fixed in ASN1SCC, this check is no longer needed
    # if len(xmlSequenceNode._children) == 0:
    #     utility.panic("CommonSeqSetChoice: No children under Sequence/Choice/SetType (%s, %s)" %  # pragma: no cover
    #           (newModule._asnFilename, lineNo))  # pragma: no cover

    myMembers = []
    for x in xmlSequenceNode._children:
        if x._name == childTypeName:
            opti = GetAttr(x, "Optional")
743
744
            bAlwaysPresent = GetAttr(x, "bAlwaysPresent")
            bAlwaysAbsent = GetAttr(x, "bAlwaysAbsent")
745
            if opti and opti == "True":
746
                utility.warn("OPTIONAL attribute ignored by A/B mappers (for field contained in %s,%s)" % (newModule._asnFilename, lineNo))
747
            enumID = GetAttr(x, "EnumID")
748
            myMembers.append([GetAttr(x, "VarName"), GenericFactory(newModule, GetChild(x, "Type"))])
749
            myMembers[-1].append(enumID)
750
751
            for flag in [opti, bAlwaysPresent, bAlwaysAbsent]:
                myMembers[-1].append(flag == "True")
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
    for tup in myMembers:
        if isinstance(tup[1], AsnMetaType):
            asnMetaMember = AsnMetaMember(
                asnFilename=tup[1]._asnFilename,
                containedType=tup[1]._containedType,
                lineno=tup[1]._lineno,
                Min=tup[1]._Min,
                Max=tup[1]._Max)
            tup[1] = asnMetaMember

    return classToCreate(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        members=myMembers)


768
def CreateSequence(newModule: Module, lineNo: int, xmlSequenceNode: Element) -> AsnSequence:
769
770
771
772
773
    return CommonSeqSetChoice(
        newModule, lineNo, xmlSequenceNode,
        AsnSequence, "SequenceOrSetChild")


774
def CreateSet(newModule: Module, lineNo: int, xmlSetNode: Element) -> AsnSet:
775
776
777
778
779
    return CommonSeqSetChoice(
        newModule, lineNo, xmlSetNode,
        AsnSet, "SequenceOrSetChild")


780
def CreateChoice(newModule: Module, lineNo: int, xmlChoiceNode: Element) -> AsnChoice:
781
782
783
784
785
    return CommonSeqSetChoice(
        newModule, lineNo, xmlChoiceNode,
        AsnChoice, "ChoiceChild")


786
def GenericFactory(newModule: Module, xmlType: Element) -> AsnNode:
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
    Factories = {
        "BooleanType": CreateBoolean,
        "IntegerType": CreateInteger,
        "RealType": CreateReal,
        "EnumeratedType": CreateEnumerated,
        "BitStringType": CreateBitString,
        "OctetStringType": CreateOctetString,
        "IA5StringType": CreateIA5String,
        "NumericStringType": CreateNumericString,
        "ReferenceType": CreateReference,
        "SequenceOfType": CreateSequenceOf,
        "SetOfType": CreateSetOf,
        "SequenceType": CreateSequence,
        "SetType": CreateSet,
        "ChoiceType": CreateChoice
802
    }  # type: Dict[str, Callable[[Module, int, Element], AsnNode]]
803
804
805
    lineNo = GetAttr(xmlType, "Line")
    global g_lineno
    g_lineno = lineNo
806
    if len(xmlType._children) == 0:  # pylint: disable=len-as-condition
807
808
809
810
811
812
        utility.panic("GenericFactory: No children for Type (%s, %s)" %  # pragma: no cover
                      (newModule._asnFilename, lineNo))  # pragma: no cover
    xmlContainedType = xmlType._children[0]
    if xmlContainedType._name not in list(Factories.keys()):
        utility.panic("Unsupported XML type node: '%s' (%s, %s)" %  # pragma: no cover
                      (xmlContainedType._name, newModule._asnFilename, lineNo))  # pragma: no cover
813
814
    maker = Factories[xmlContainedType._name]
    return maker(newModule, lineNo, xmlContainedType)
815
816


817
def VisitTypeAssignment(newModule: Module, xmlTypeAssignment: Element) -> Tuple[str, AsnNode]:
818
819
820
821
822
823
824
825
    xmlType = GetChild(xmlTypeAssignment, "Type")
    if xmlType is None:
        utility.panic("VisitTypeAssignment: No child under TypeAssignment")  # pragma: no cover
    return (
        GetAttr(xmlTypeAssignment, "Name"),
        GenericFactory(newModule, xmlType))


826
def VisitAsn1Module(xmlAsn1File: Element, xmlModule: Element, modules: List[Module]) -> None:  # pylint: disable=invalid-sequence-index
827
828
829
830
    newModule = Module()
    newModule._id = GetAttr(xmlModule, "ID")
    newModule._asnFilename = GetAttr(xmlAsn1File, "FileName")
    newModule._exportedTypes = VisitAll(
831
        GetChild(xmlModule, "ExportedTypes"), "ExportedType",
832
833
834
        lambda x: GetAttr(x, "Name"))

    newModule._exportedVariables = VisitAll(
835
        GetChild(xmlModule, "ExportedVariables"), "ExportedVariable",
836
837
838
        lambda x: GetAttr(x, "Name"))

    newModule._importedModules = VisitAll(
839
840
        GetChild(xmlModule, "ImportedModules"), "ImportedModule",
        lambda x: (
841
            GetAttr(x, "ID"),
842
843
844
845
            VisitAll(GetChild(x, "ImportedTypes"), "ImportedType",
                     lambda y: GetAttr(y, "Name")),
            VisitAll(GetChild(x, "ImportedVariables"), "ImportedVariable",
                     lambda y: GetAttr(y, "Name")),
846
847
848
849
        )
    )

    newModule._typeAssignments = VisitAll(
850
        GetChild(xmlModule, "TypeAssignments"), "TypeAssignment",
851
852
853
854
        lambda x: VisitTypeAssignment(newModule, x))

    g_typesOfFile.setdefault(newModule._asnFilename, [])
    g_typesOfFile[newModule._asnFilename].extend(
855
        [x for x, _ in newModule._typeAssignments])
856
857
858

    g_astOfFile.setdefault(newModule._asnFilename, [])
    g_astOfFile[newModule._asnFilename].extend(
859
        [y for _, y in newModule._typeAssignments])
860
861
862
863

    modules.append(newModule)


864
def ParseASN1SCC_AST(filename: str) -> None:
865
    parser = xml.sax.make_parser([])
866
867
868
869
870
871
872
873
874
    handler = InputFormatXMLHandler()
    parser.setContentHandler(handler)
    # parser.setFeature("http://xml.org/sax/features/validation", True)
    parser.parse(filename)

    if len(handler._root._children) != 1 or handler._root._children[0]._name != "ASN1AST":
        utility.panic("You must use an XML file that contains one ASN1AST node")  # pragma: no cover

    # Travel("", handler._roots[0])
875
    modules = []  # type: List[Module]
876
    VisitAll(
877
878
879
        handler._root._children[0], "Asn1File",
        lambda x: VisitAll(x, "Asn1Module",
                           lambda y: VisitAsn1Module(x, y, modules)))
880
881
882
883

    global g_xmlASTrootNode
    g_xmlASTrootNode = handler._root

884
885
886
    g_names.clear()
    g_checkedSoFarForKeywords.clear()
    g_leafTypeDict.clear()
887
888
889
890
891
892

    for m in modules:
        # print "Module", m._id
        for typeName, typeData in m._typeAssignments:
            # print "Type:", typeName
            g_names[typeName] = typeData
893
            g_modules.setdefault(m._id, []).append(typeName)
894
895
896
897
898
899
900
901
    g_leafTypeDict.update(VerifyAndFixAST())

    for nodeTypename in list(g_names.keys()):
        if nodeTypename not in g_checkedSoFarForKeywords:
            g_checkedSoFarForKeywords[nodeTypename] = 1
            CheckForInvalidKeywords(nodeTypename)


902
def SimpleCleaner(x: str) -> str:
903
904
905
    return re.sub(r'[^a-zA-Z0-9_]', '_', x)


906
def PrintType(f: IO[Any], xmlType: Element, indent: str, nameCleaner: Callable[[str], str]) -> None:
907
    if len(xmlType._children) == 0:  # pylint: disable=len-as-condition
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
        utility.panic("AST inconsistency: xmlType._children == 0\nContact ESA")  # pragma: no cover
    realType = xmlType._children[0]
    if realType._name == "BooleanType":
        f.write('BOOLEAN')
    elif realType._name == "IntegerType":
        f.write('INTEGER')
        mmin = GetAttr(realType, "Min")
        mmax = GetAttr(realType, "Max")
        f.write(' (%s .. %s)' % (mmin, mmax))
    elif realType._name == "RealType":
        f.write('REAL')
        mmin = GetAttr(realType, "Min")
        mmax = GetAttr(realType, "Max")
        f.write(' (%s .. %s)' % (mmin, mmax))
    elif realType._name == "BitStringType":
        utility.panic("BIT STRINGs are not supported, use SEQUENCE OF BOOLEAN")  # pragma: no cover
    elif realType._name == "OctetStringType" or realType._name == "IA5StringType" or realType._name == "NumericStringType":
        f.write('OCTET STRING')
        mmin = GetAttr(realType, "Min")
        mmax = GetAttr(realType, "Max")
        f.write(' (SIZE (%s .. %s))' % (mmin, mmax))
    elif realType._name == "ReferenceType":
        f.write(nameCleaner(GetAttr(realType, "ReferencedTypeName")))
    elif realType._name == "EnumeratedType":
        f.write('ENUMERATED {\n')
        options = []
934

935
        def addNewOption(x: Any) -> None:
936
937
            options.append(x)
        VisitAll(realType, "EnumValue", addNewOption)
938
        if options:
939
940
941
942
943
944
945
946
947
            f.write(indent + '    ' + nameCleaner(GetAttr(options[0], "StringValue")) + "(" + GetAttr(options[0], "IntValue") + ")")
            for otherOptions in options[1:]:
                f.write(',\n' + indent + '    ' + nameCleaner(GetAttr(otherOptions, "StringValue")) + "(" + GetAttr(otherOptions, "IntValue") + ")")
        f.write('\n' + indent + '}')
    elif realType._name == "SequenceType" or realType._name == "SetType":
        if realType._name == "SequenceType":
            f.write('SEQUENCE {\n')
        else:
            f.write('SET {\n')
948
        if len(realType._children) > 0:  # pylint: disable=len-as-condition
949
950
            f.write(indent + '    ' + nameCleaner(GetAttr(realType._children[0], "VarName")) + "\t")
            firstChildOptional = GetAttr(realType._children[0], "Optional") == "True"
951
            if len(realType._children[0]._children) == 0:  # pylint: disable=len-as-condition
952
953
954
955
956
957
958
                utility.panic("AST inconsistency: len(realType._children[0]._children) = 0\nContact ESA")  # pragma: no cover
            PrintType(f, realType._children[0]._children[0], indent + "    ", nameCleaner)  # the contained type of the first child
            if firstChildOptional:
                f.write(' OPTIONAL')
            for sequenceOrSetChild in realType._children[1:]:
                f.write(",\n" + indent + '    ' + nameCleaner(GetAttr(sequenceOrSetChild, "VarName")) + "\t")
                childOptional = GetAttr(sequenceOrSetChild, "Optional") == "True"
959
                if len(sequenceOrSetChild._children) == 0:  # pylint: disable=len-as-condition
960
961
962
963
964
965
966
967
968
                    utility.panic("AST inconsistency: len(sequenceOrSetChild._children) = 0\nContact ESA")  # pragma: no cover
                PrintType(f, sequenceOrSetChild._children[0], indent + "    ", nameCleaner)  # the contained type
                if childOptional:
                    f.write(' OPTIONAL')
#       else:
#           utility.panic("AST inconsistency: len(realType._children)=0\nContact ESA")  # pragma: no cover
        f.write('\n' + indent + '}')
    elif realType._name == "ChoiceType":
        f.write('CHOICE {\n')
969
        if len(realType._children) > 0:  # pylint: disable=len-as-condition
970
            f.write(indent + '    ' + nameCleaner(GetAttr(realType._children[0], "VarName")) + "\t")
971
            if len(realType._children[0]._children) == 0:  # pylint: disable=len-as-condition
972
973
974
975
                utility.panic("AST inconsistency: len(realType._children[0]._children) = 0\nContact ESA")  # pragma: no cover
            PrintType(f, realType._children[0]._children[0], indent + "    ", nameCleaner)  # the contained type of the first child
            for choiceChild in realType._children[1:]:
                f.write(",\n" + indent + '    ' + nameCleaner(GetAttr(choiceChild, "VarName")) + "\t")
976
                if len(choiceChild._children) == 0:  # pylint: disable=len-as-condition
977
978
979
980
981
982
983
984
985
986
                    utility.panic("AST inconsistency: len(choiceChild._children) = 0\nContact ESA")  # pragma: no cover
                PrintType(f, choiceChild._children[0], indent + "    ", nameCleaner)  # the contained type
        else:
            utility.panic("AST inconsistency: len(realType._children)=0\nContact ESA")  # pragma: no cover
        f.write('\n' + indent + '}')
    elif realType._name == "SequenceOfType":
        f.write('SEQUENCE')
        mmin = GetAttr(realType, "Min")
        mmax = GetAttr(realType, "Max")
        f.write(' (SIZE (%s .. %s)) OF ' % (mmin, mmax))
987
        if len(realType._children) > 0:  # pylint: disable=len-as-condition
988
989
990
991
992
993
994
995
            PrintType(f, realType._children[0], indent + "    ", nameCleaner)  # the contained type
        else:
            utility.panic("AST inconsistency: len(realType._children)=0\nContact ESA")  # pragma: no cover
    elif realType._name == "SetOfType":
        f.write('SET')
        mmin = GetAttr(realType, "Min")
        mmax = GetAttr(realType, "Max")
        f.write(' (SIZE (%s .. %s)) OF ' % (mmin, mmax))
996
        if len(realType._children) > 0:  # pylint: disable=len-as-condition
997
998
999
            PrintType(f, realType._children[0], indent + "    ", nameCleaner)  # the contained type
        else:
            utility.panic("AST inconsistency: len(realType._children)=0\nContact ESA")  # pragma: no cover
1000
    else:
1001
1002
1003
        utility.panic("AST inconsistency: Unknown type (%s)\nContact ESA" % realType._name)  # pragma: no cover


1004
def PrintGrammarFromAST(f: IO[Any], nameCleaner: Callable[[str], str] = SimpleCleaner) -> None:
1005
1006
    ourtypeAssignments = []
    VisitAll(
1007
1008
1009
        g_xmlASTrootNode._children[0], "Asn1File",
        lambda x: VisitAll(x, "TypeAssignment",
                           lambda y: ourtypeAssignments.append((x, y))))
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020

    for a, t in ourtypeAssignments:
        f.write("-- " + GetAttr(a, "FileName") + "\n%s ::= " % nameCleaner(GetAttr(t, "Name")))
        typeChild = GetChild(t, "Type")
        if typeChild:
            PrintType(f, typeChild, '', nameCleaner)
            f.write('\n\n')
        else:
            utility.panic("AST inconsistency: typeChild is None\nContact ESA")  # pragma: no cover


1021
def PrintGrammarFromASTtoStdOut() -> None:
1022
1023
1024
1025
    # Starting from the xmlASTrootNode, recurse and print the ASN.1 grammar
    PrintGrammarFromAST(sys.stdout)


1026
def test_xml() -> None:
1027
1028
1029
1030
1031
1032
1033
1034
1035
    if len(sys.argv) != 2 or not os.path.isfile(sys.argv[1]):
        sys.stderr.write("Missing or invalid path provided!\n")
        sys.exit(1)

    ParseASN1SCC_AST(sys.argv[1])
    Dump()
    print("\nRe-created grammar:\n\n")
    PrintGrammarFromASTtoStdOut()

1036

1037
1038
1039
1040
1041
1042
1043
if __name__ == "__main__":
    if "-testXML" in sys.argv:
        sys.argv.remove("-testXML")
        test_xml()
    elif "-testASN1" in sys.argv:
        sys.argv.remove("-testASN1")
        test_asn1()
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
1044
1045

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4