--- /dev/null
+# Python bytecode
+__pycache__/
+*.pyc
+
+# Proprietary test data
+source.asn1
--- /dev/null
+Copyright (c) 2013, Schneider Electric Buildings AB
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Schneider Electric Buildings AB nor the
+ names of contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Schneider Electric Buildings AB does not make any claims concerning code
+generated by the asn1ate library.
--- /dev/null
+asn1ate -- ASN.1 translation library.
+Copyright 2013 Schneider Electric Buildings AB
+
+Introduction
+------------
+
+``asn1ate`` is a Python library for translating ASN.1 into other forms.
+It is intended for code generation from formal ASN.1 definitions, and a
+code generator for ``pyasn1`` is included.
+
+``asn1ate`` is released under a 3-clause BSD license. For details, see
+LICENSE.txt.
+
+
+Caveat
+------
+
+This is very much an alpha-quality prototype. Things that need doing:
+
+* Regression test suite
+* HACK/TODO/BUGs need to be fixed
+* ASN.1 grammar is very incomplete and incorrect in some places
+* Improve parser error handling/reporting
+
+
+Usage
+-----
+
+The immediate use of ``asn1ate`` is to generate ``pyasn1`` definitions from
+ASN.1 definitions. The command to do this is::
+
+ $ python .../asn1ate/pyasn1gen.py source.asn1
+
+It will print the ``pyasn1`` equivalent of ``source.asn1`` to stdout.
+
+
+Dependencies
+------------
+
+The only third-party dependency is ``pyparsing``.
+
+Although ``asn1ate`` was initially developed on Python 3.2, it has been tested
+with Python 2.7.3 and should port to older Python versions easily.
+
+
+Design notes
+------------
+
+The ``asn1ate`` package is designed along the same lines as a compiler with a
+driver, a parser, a semantic model and a convention for code generators.
+
+* ``parser.py`` -- a tokenizing parser for ASN.1 per X.680. It currently
+ recognizes a naive sub-set of X.680
+* ``sema.py`` -- a semantic ASN.1 object model, which can be constructed from
+ the AST generated by ``parser.py``
+* ``support/pygen.py`` -- a support library for generating Python code.
+* ``pyasn1gen.py`` -- a code generator to transform a semantic model into
+ ``pyasn1`` syntax. This can be used as a script in which case it will dump
+ output to stdout.
+
+The ASN.1 parser is very ad-hoc, I've experimented with the grammar until I
+found something that accepted our proprietary ASN.1 definition. It's based on
+``pyparsing`` but sets up parse actions to build an annotated AST. Every node of
+interest is annotated with a string denoting its type, e.g. ``Identifier``,
+``TypeAssignment``, etc. I've tried to stay with token types as named in X.680,
+but added custom ones or suppressed others, as necessary to get the AST in a
+useful shape.
+
+Annotated tokens are represented by a simple class containing the type name and
+a list of children (called ``elements``) which may be annotated tokens, lists or
+simple values. This gives a very discoverable tree structure, but there are
+probably cleaner AST representations we could use. Patches welcome.
+
+``asn1ate.sema`` is an object model that represents ASN.1 constructs. It
+describes everything from type assignments to default values and tags, but still
+only the parts of ASN.1 we happen to use here. Most of the logic revolves around
+transforming the AST produced by ``asn1ate.parser`` into a more semantic model
+with proper Python objects.
+
+Codegen is designed to be extensible. In-house we have a set of code generators
+to build an entire protocol stack based on an ASN.1 source, but ``asn1ate`` only
+includes the generally useful one, ``asn1ate.pyasn1gen``.
+
+The most notable members of ``asn1ate.support`` are probably the
+``PythonWriter`` and ``PythonFragment`` classes, which simplify generation of
+correctly indented Python code.
--- /dev/null
+# Copyright (c) 2013, Schneider Electric Buildings AB
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Schneider Electric Buildings AB nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import re
+from copy import copy
+from pyparsing import Keyword, Literal, Word, OneOrMore, Combine, Regex, Forward, Optional, Group, Suppress, delimitedList, cStyleComment, nums, alphanums, empty, srange
+
+
+__all__ = ['parse_asn1', 'AnnotatedToken']
+
+
+def parse_asn1(asn1_payload):
+ """ Parse a string containing an ASN.1 module definition
+ and return a syntax tree in the form of a list of
+ AnnotatedToken objects.
+ """
+ grammar = _build_asn1_grammar()
+ parse_result = grammar.parseString(asn1_payload)
+ parse_tree = parse_result.asList()
+ return parse_tree
+
+
+def print_parse_tree(node, indent=1):
+ """ Debugging aid. Dumps a parse tree as returned
+ from parse_asn1 to stdout in indented tree form.
+ """
+ def indented_print(msg):
+ print(' ' * indent + msg)
+
+ if type(node) is AnnotatedToken:
+ # tagged token
+ tag, values = node.ty, node.elements
+ indented_print('%s:' % tag)
+ print_parse_tree(values, indent + 1)
+ elif type(node) is list:
+ # token list
+ for token in node:
+ print_parse_tree(token, indent + 1)
+ else:
+ # token
+ indented_print(str(node))
+
+
+class AnnotatedToken(object):
+ """ A simple data structure to keep track of a token's
+ type, identified by a string, and its children.
+ Children may be other annotated tokens, lists or simple
+ strings.
+ """
+ def __init__(self, token_type, elements):
+ self.ty = token_type
+ self.elements = elements
+
+ def __str__(self):
+ return 'T(%s)%s' % (self.ty, self.elements)
+
+ __repr__ = __str__
+
+
+def _build_asn1_grammar():
+ def build_identifier(prefix_pattern):
+ identifier_suffix = Optional(Word(srange('[-0-9a-zA-Z]')))
+ identifier = Combine(Word(srange(prefix_pattern), exact=1) + identifier_suffix) # todo: more rigorous? trailing hyphens and -- forbidden
+ return identifier
+
+ def braced_list(element_rule):
+ return Suppress('{') + Group(delimitedList(element_rule)) + Suppress('}')
+
+ def annotate(name):
+ def annotation(t):
+ return AnnotatedToken(name, t.asList())
+
+ return annotation
+
+ # Reserved words
+ DEFINITIONS = Keyword('DEFINITIONS')
+ BEGIN = Keyword('BEGIN')
+ END = Keyword('END')
+ OPTIONAL = Keyword('OPTIONAL')
+ DEFAULT = Keyword('DEFAULT')
+ TRUE = Keyword('TRUE')
+ FALSE = Keyword('FALSE')
+ UNIVERSAL = Keyword('UNIVERSAL')
+ APPLICATION = Keyword('APPLICATION')
+ PRIVATE = Keyword('PRIVATE')
+ MIN = Keyword('MIN')
+ MAX = Keyword('MAX')
+ IMPLICIT = Keyword('IMPLICIT')
+ EXPLICIT = Keyword('EXPLICIT')
+ EXPLICIT_TAGS = Keyword('EXPLICIT TAGS')
+ IMPLICIT_TAGS = Keyword('IMPLICIT TAGS')
+ AUTOMATIC_TAGS = Keyword('AUTOMATIC TAGS')
+ EXTENSIBILITY_IMPLIED = Keyword('EXTENSIBILITY IMPLIED')
+
+ # Built-in types
+ SEQUENCE = Keyword('SEQUENCE')
+ SEQUENCE_OF = Keyword('SEQUENCE OF')
+ CHOICE = Keyword('CHOICE')
+ ENUMERATED = Keyword('ENUMERATED')
+ BIT_STRING = Keyword('BIT STRING')
+ BOOLEAN = Keyword('BOOLEAN')
+ REAL = Keyword('REAL')
+ OCTET_STRING = Keyword('OCTET STRING')
+ CHARACTER_STRING = Keyword('CHARACTER STRING')
+ NULL = Keyword('NULL')
+ INTEGER = Keyword('INTEGER')
+
+ # Restricted string types
+ BMPString = Keyword('BMPString')
+ GeneralString = Keyword('GeneralString')
+ GraphicString = Keyword('GraphicString')
+ IA5String = Keyword('IA5String')
+ ISO646String = Keyword('ISO646String')
+ NumericString = Keyword('NumericString')
+ PrintableString = Keyword('PrintableString')
+ TeletexString = Keyword('TeletexString')
+ T61String = Keyword('T61String')
+ UniversalString = Keyword('UniversalString')
+ UTF8String = Keyword('UTF8String')
+ VideotexString = Keyword('VideotexString')
+ VisibleString = Keyword('VisibleString')
+
+ # Literals
+ number = Word(nums)
+ signed_number = Combine(Optional('-') + number) # todo: consider defined values from 18.1
+ bstring = Literal('\'') + Regex('[01]+') + Literal('\'B')
+ hstring = Literal('\'') + Regex('[0-9A-F]+') + Literal('\'H')
+
+ # Comments
+ hyphen_comment = Regex(r"--[\s\S]*?(--|$)", flags=re.MULTILINE)
+ comment = hyphen_comment | cStyleComment
+
+ # identifier
+ identifier = build_identifier('[a-z]')
+
+ # references
+ # these are duplicated to force unique token annotations
+ valuereference = build_identifier('[a-z]')
+ typereference = build_identifier('[A-Z]')
+ module_reference = build_identifier('[A-Z]')
+
+ # values
+ # BUG: These are badly specified and cause the grammar to break if used generally.
+ # todo: consider more literals from 16.9
+ real_value = Regex(r'-?\d+(\.\d*)?') # todo: this doesn't really follow the spec
+ boolean_value = TRUE | FALSE
+ bitstring_value = bstring | hstring # todo: consider more forms from 21.9
+ integer_value = signed_number
+ null_value = NULL
+
+ builtin_value = boolean_value | bitstring_value | real_value | integer_value | null_value
+ defined_value = valuereference # todo: more options from 13.1
+ value = builtin_value | defined_value
+
+ # tags
+ class_ = UNIVERSAL | APPLICATION | PRIVATE
+ class_number = number # todo: consider defined values from 30.1
+ tag = Suppress('[') + Optional(class_) + class_number + Suppress(']')
+ tag_default = EXPLICIT_TAGS | IMPLICIT_TAGS | AUTOMATIC_TAGS | empty
+
+ # extensions
+ extension_default = EXTENSIBILITY_IMPLIED | empty
+
+ # types
+ defined_type = Unique(typereference) # todo: consider other defined types from 13.1
+ referenced_type = Unique(defined_type) # todo: consider other ref:d types from 16.3
+ named_type = Forward() # this can only be full defined once we have all types defined.
+ type_ = Forward() # this can only be full defined once we have all types defined.
+
+ # constraints
+ # todo: consider the full subtype and general constraint syntax described in 45.*
+ # but for now, just implement a simple integer value range.
+ value_range_min = (signed_number | MIN)
+ value_range_max = (signed_number | MAX)
+ value_range_constraint = value_range_min + Suppress('..') + value_range_max
+ constraint = Suppress('(') + value_range_constraint + Suppress(')') # todo: consider exception spec from 45.6
+
+ # BUG: identifier should not be Optional here,
+ # but our ASN.1 interpreter supports unnamed members,
+ # and we use them.
+ # todo: consider COMPONENTS OF from 24.1
+ component_optional = OPTIONAL
+ component_default = DEFAULT + value
+ component_type = Optional(identifier) + type_ + Optional(component_optional | component_default)
+ tagged_type = tag + Optional(IMPLICIT | EXPLICIT) + type_
+
+ named_number_value = Suppress('(') + signed_number + Suppress(')')
+ named_number = identifier + named_number_value
+ enumeration = named_number | identifier
+
+ # todo: consider extension and exception syntax from 24.1
+ sequence_type = SEQUENCE + braced_list(component_type)
+ sequenceof_type = SEQUENCE_OF + (type_ | named_type)
+ choice_type = CHOICE + braced_list(named_type)
+ enumerated_type = ENUMERATED + braced_list(enumeration)
+ bitstring_type = BIT_STRING + braced_list(named_number)
+ plain_integer_type = INTEGER
+ restricted_integer_type = INTEGER + braced_list(named_number)
+ boolean_type = BOOLEAN
+ real_type = REAL
+ null_type = NULL
+ octetstring_type = OCTET_STRING
+ unrestricted_characterstring_type = CHARACTER_STRING
+ restricted_characterstring_type = BMPString | GeneralString | \
+ GraphicString | IA5String | \
+ ISO646String | NumericString | \
+ PrintableString | TeletexString | \
+ T61String | UniversalString | \
+ UTF8String | VideotexString | VisibleString
+ characterstring_type = restricted_characterstring_type | unrestricted_characterstring_type
+
+ # todo: consider other builtins from 16.2
+ simple_type = (boolean_type | null_type | octetstring_type | characterstring_type | real_type | plain_integer_type) + Optional(constraint)
+ constructed_type = choice_type | sequence_type
+ value_list_type = restricted_integer_type | enumerated_type
+ builtin_type = tagged_type | simple_type | constructed_type | sequenceof_type | value_list_type | bitstring_type
+
+ type_ << (builtin_type | referenced_type)
+ named_type << (identifier + type_)
+
+ # BUG: Trailing semi-colon is not allowed by standard grammar, but our ASN.1 interpreter accepts it
+ # and we happen to use it.
+ type_assignment = typereference + '::=' + type_ + Suppress(Optional(';'))
+ value_assignment = valuereference + type_ + '::=' + value
+
+ assignment = type_assignment | value_assignment
+ assignment_list = OneOrMore(assignment)
+
+ module_body = (assignment_list | empty)
+ module_defaults = Suppress(tag_default + extension_default) # we don't want these in the AST
+ module_definition = module_reference + DEFINITIONS + module_defaults + '::=' + BEGIN + module_body + END
+
+ module_definition.ignore(comment)
+
+ # Mark up the parse results with token tags
+ identifier.setParseAction(annotate('Identifier'))
+ named_number_value.setParseAction(annotate('Value'))
+ tag.setParseAction(annotate('Tag'))
+ class_.setParseAction(annotate('TagClass'))
+ class_number.setParseAction(annotate('TagClassNumber'))
+ type_.setParseAction(annotate('Type'))
+ simple_type.setParseAction(annotate('SimpleType'))
+ choice_type.setParseAction(annotate('ChoiceType'))
+ sequence_type.setParseAction(annotate('SequenceType'))
+ value_list_type.setParseAction(annotate('ValueListType'))
+ bitstring_type.setParseAction(annotate('BitStringType'))
+ referenced_type.setParseAction(annotate('ReferencedType'))
+ sequenceof_type.setParseAction(annotate('SequenceOfType'))
+ named_number.setParseAction(annotate('NamedValue'))
+ constraint.setParseAction(annotate('Constraint'))
+ component_type.setParseAction(annotate('ComponentType'))
+ tagged_type.setParseAction(annotate('TaggedType'))
+ named_type.setParseAction(annotate('NamedType'))
+ type_assignment.setParseAction(annotate('TypeAssignment'))
+ value_assignment.setParseAction(annotate('ValueAssignment'))
+ module_reference.setParseAction(annotate('ModuleReference'))
+ module_body.setParseAction(annotate('ModuleBody'))
+ module_definition.setParseAction(annotate('ModuleDefinition'))
+ component_optional.setParseAction(annotate('ComponentOptional'))
+ component_default.setParseAction(annotate('ComponentDefault'))
+
+ return module_definition
+
+
+def Unique(token):
+ """ Use to create a distinct name of a production
+ with the same form as another, e.g.
+ identifier = build_identifier('[a-z]')
+ valuereference = build_identifier('[a-z]')
+ We prefer:
+ identifier = build_identifier('[a-z]')
+ valuereference = Unique(identifier)
+ to avoid duplicating the details of the grammar.
+ This allows unique parse actions for productions
+ with the same underlying rules.
+ """
+ return copy(token)
--- /dev/null
+# Copyright (c) 2013, Schneider Electric Buildings AB
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Schneider Electric Buildings AB nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import print_function # Python 2 compatibility
+
+import sys
+from asn1ate import parser
+from asn1ate.support import pygen
+from asn1ate.sema import *
+
+
+class Pyasn1Backend(object):
+ """ Backend to generate pyasn1 declarations from semantic tree.
+ Generators are divided into declarations and expressions,
+ because types in pyasn1 can be declared either as class
+ definitions or inline, e.g.
+
+ # Foo ::= INTEGER
+ # Foo is a decl
+ class Foo(univ.Integer):
+ pass
+
+ # Seq ::= SEQUENCE {
+ # foo INTEGER
+ # }
+ # Seq is a decl,
+ # univ.Integer is an expr
+ class Seq(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('foo', univ.Integer())
+ )
+
+ Typically, declarations can contain other declarations
+ or expressions, expressions can only contain other expressions.
+ """
+ def __init__(self, sema_module, out_stream):
+ self.sema_module = sema_module
+ self.writer = pygen.PythonWriter(out_stream)
+
+ self.decl_generators = {
+ ChoiceType: self.decl_constructed_type,
+ SequenceType: self.decl_constructed_type,
+ TaggedType: self.decl_tagged_type,
+ SimpleType: self.decl_simple_type,
+ UserDefinedType: self.decl_userdefined_type,
+ ValueListType: self.decl_value_list_type,
+ BitStringType: self.decl_bitstring_type,
+ SequenceOfType: self.decl_sequenceof_type,
+ TypeAssignment: self.decl_type_assignment
+ }
+
+ self.expr_generators = {
+ TaggedType: self.expr_tagged_type,
+ SimpleType: self.expr_simple_type,
+ UserDefinedType: self.expr_userdefined_type,
+ ComponentType: self.expr_component_type,
+ NamedType: self.expr_named_type,
+ SequenceOfType: self.expr_sequenceof_type,
+ }
+
+ def generate_code(self):
+ self.writer.write_line('from pyasn1.type import univ, char, namedtype, namedval, tag, constraint')
+ self.writer.write_blanks(2)
+
+ declarations = topological_sort(self.sema_module.declarations)
+ for declaration in declarations:
+ self.writer.write_block(self.generate_decl(declaration))
+ self.writer.write_blanks(2)
+
+ def generate_expr(self, t):
+ generator = self.expr_generators[type(t)]
+ return generator(t)
+
+ def generate_decl(self, t):
+ generator = self.decl_generators[type(t)]
+ return generator(t)
+
+ def decl_type_assignment(self, assignment):
+ fragment = self.writer.get_fragment()
+
+ assigned_type, type_decl = assignment.type_name, assignment.type_decl
+
+ base_type = _translate_type(type_decl.type_name)
+ fragment.write_line('class %s(%s):' % (assigned_type, base_type))
+
+ fragment.push_indent()
+ fragment.write_block(self.generate_decl(type_decl))
+ fragment.pop_indent()
+
+ return str(fragment)
+
+ def expr_simple_type(self, t):
+ type_expr = _translate_type(t.type_name) + '()'
+ if t.constraint:
+ type_expr += '.subtype(subtypeSpec=constraint.ValueRangeConstraint(%s, %s))' % (t.constraint.min_value, t.constraint.max_value)
+
+ return type_expr
+
+ def decl_simple_type(self, t):
+ return 'pass'
+
+ def expr_userdefined_type(self, t):
+ return t.type_name + '()'
+
+ def decl_userdefined_type(self, t):
+ return 'pass'
+
+ def decl_constructed_type(self, t):
+ fragment = self.writer.get_fragment()
+
+ fragment.write_line('componentType = namedtype.NamedTypes(')
+
+ fragment.push_indent()
+ fragment.write_block(self.expr_component_types(t.components))
+ fragment.pop_indent()
+
+ fragment.write_line(')')
+
+ return str(fragment)
+
+ def expr_component_types(self, components):
+ fragment = self.writer.get_fragment()
+
+ component_exprs = []
+ for c in components:
+ component_exprs.append(self.generate_expr(c))
+
+ fragment.write_enumeration(component_exprs)
+
+ return str(fragment)
+
+ def expr_tagged_type(self, t):
+ tag_type = 'implicitTag' if t.implicit else 'explicitTag'
+ type_expr = self.generate_expr(t.type_decl)
+ type_expr += '.subtype(%s=%s)' % (tag_type, self.build_tag_expr(t))
+
+ return type_expr
+
+ def decl_tagged_type(self, t):
+ fragment = self.writer.get_fragment()
+
+ tag_type = 'tagImplicitly' if t.implicit else 'tagExplicitly'
+ base_type = _translate_type(t.type_decl.type_name)
+ fragment.write_line('tagSet = %s.tagSet.%s(%s)' % (base_type, tag_type, self.build_tag_expr(t)))
+ fragment.write_line(self.generate_decl(t.type_decl)) # possibly 'pass'. but that's OK in a decl
+
+ return str(fragment)
+
+ def build_tag_expr(self, tag_def):
+ context = _translate_tag_class(tag_def.class_name)
+
+ tagged_type_decl = self.sema_module.resolve_type_decl(tag_def.type_decl)
+ if isinstance(tagged_type_decl, ConstructedType):
+ tag_format = 'tag.tagFormatConstructed'
+ else:
+ tag_format = 'tag.tagFormatSimple'
+
+ return 'tag.Tag(%s, %s, %s)' % (context, tag_format, tag_def.class_number)
+
+ def expr_component_type(self, t):
+ if t.optional:
+ return "namedtype.OptionalNamedType('%s', %s)" % (t.identifier, self.generate_expr(t.type_decl))
+ elif t.default_value is not None:
+ type_expr = self.generate_expr(t.type_decl)
+ type_expr += '.subtype(value=%s)' % _translate_value(t.default_value)
+
+ return "namedtype.DefaultedNamedType('%s', %s)" % (t.identifier, type_expr)
+ else:
+ return "namedtype.NamedType('%s', %s)" % (t.identifier, self.generate_expr(t.type_decl))
+
+ def expr_named_type(self, t):
+ return "namedtype.NamedType('%s', %s)" % (t.identifier, self.generate_expr(t.type_decl))
+
+ def decl_value_list_type(self, t):
+ fragment = self.writer.get_fragment()
+
+ if t.named_values:
+ fragment.write_line('namedValues = namedval.NamedValues(')
+ fragment.push_indent()
+
+ named_values = list(map(lambda v: '(\'%s\', %s)' % (v.identifier, v.value), t.named_values))
+ fragment.write_enumeration(named_values)
+
+ fragment.pop_indent()
+ fragment.write_line(')')
+ else:
+ fragment.write_line('pass')
+
+ return str(fragment)
+
+ def decl_bitstring_type(self, t):
+ fragment = self.writer.get_fragment()
+
+ if t.named_bits:
+ fragment.write_line('namedValues = namedval.NamedValues(')
+ fragment.push_indent()
+
+ named_bit_list = list(map(lambda v: '(\'%s\', %s)' % (v.identifier, v.value), t.named_bits))
+ fragment.write_enumeration(named_bit_list)
+
+ fragment.pop_indent()
+ fragment.write_line(')')
+ else:
+ fragment.write_line('pass')
+
+ return str(fragment)
+
+ def expr_sequenceof_type(self, t):
+ return 'univ.SequenceOf(componentType=%s)' % self.generate_expr(t.type_decl)
+
+ def decl_sequenceof_type(self, t):
+ return 'componentType = %s' % self.generate_expr(t.type_decl)
+
+
+def generate_pyasn1(sema_module, out_stream):
+ return Pyasn1Backend(sema_module, out_stream).generate_code()
+
+
+# Translation tables from ASN.1 primitives to pyasn1 primitives
+_ASN1_TAG_CONTEXTS = {
+ 'APPLICATION': 'tag.tagClassApplication',
+ 'PRIVATE': 'tag.tagClassPrivate',
+ 'UNIVERSAL': 'tag.tagClassUniversal'
+}
+
+
+_ASN1_BUILTIN_VALUES = {
+ 'FALSE': '0',
+ 'TRUE': '1'
+}
+
+
+_ASN1_BUILTIN_TYPES = {
+ 'INTEGER': 'univ.Integer',
+ 'BOOLEAN': 'univ.Boolean',
+ 'NULL': 'univ.Null',
+ 'ENUMERATED': 'univ.Enumerated',
+ 'REAL': 'univ.Real',
+ 'BIT STRING': 'univ.BitString',
+ 'OCTET STRING': 'univ.OctetString',
+ 'CHOICE': 'univ.Choice',
+ 'SEQUENCE': 'univ.Sequence',
+ 'SEQUENCE OF': 'univ.SequenceOf',
+ 'UTF8String': 'char.UTF8String'
+}
+
+
+def _translate_type(type_name):
+ """ Translate ASN.1 built-in types to pyasn1 equivalents.
+ Non-builtins are not translated.
+ """
+ return _ASN1_BUILTIN_TYPES.get(type_name, type_name)
+
+
+def _translate_tag_class(tag_class):
+ """ Translate ASN.1 tag class names to pyasn1 equivalents.
+ Defaults to tag.tagClassContext if tag_class is not
+ recognized.
+ """
+ return _ASN1_TAG_CONTEXTS.get(tag_class, 'tag.tagClassContext')
+
+
+def _translate_value(value):
+ """ Translate ASN.1 built-in values to Python equivalents.
+ Unrecognized values are not translated.
+ """
+ return _ASN1_BUILTIN_VALUES.get(value, value)
+
+
+# Simplistic command-line driver
+def main(args):
+ with open(args[0]) as f:
+ asn1def = f.read()
+
+ parse_tree = parser.parse_asn1(asn1def)
+
+ modules = build_semantic_model(parse_tree)
+ if len(modules) > 1:
+ print('WARNING: More than one module generated to the same stream.', file=sys.stderr)
+
+ for module in modules:
+ print(pygen.auto_generated_header())
+ generate_pyasn1(module, sys.stdout)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
--- /dev/null
+# Copyright (c) 2013, Schneider Electric Buildings AB
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Schneider Electric Buildings AB nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from asn1ate import parser
+
+
+def build_semantic_model(parse_result):
+ """ Build a semantic model of the ASN.1 definition
+ from a syntax tree generated by asn1ate.parser.
+ """
+ root = []
+ for token in parse_result:
+ _assert_annotated_token(token)
+ root.append(_create_sema_node(token))
+
+ return root
+
+
+def topological_sort(decls):
+ """ Algorithm adapted from:
+ http://en.wikipedia.org/wiki/Topological_sorting.
+
+ Assumes decls is an iterable of items with two members:
+ - type_name
+ - imports() method that returns an iterable of type names
+ upon which the decl depends.
+ """
+ graph = dict((d.type_name, set(d.imports())) for d in decls)
+
+ def has_predecessor(node):
+ for predecessor in graph.keys():
+ if node in graph[predecessor]:
+ return True
+
+ return False
+
+ # Build a topological order of type names
+ topological_order = []
+ roots = [type_name for type_name in graph.keys() if not has_predecessor(type_name)]
+
+ while roots:
+ root = roots.pop()
+
+ # Remove the current node from the graph
+ # and collect all new roots (the nodes that
+ # were previously only referenced from n)
+ successors = graph.pop(root, set())
+ roots.extend(successor for successor in successors if not has_predecessor(successor))
+
+ topological_order.insert(0, root)
+
+ if graph:
+ raise Exception('Can\'t sort cyclic dependencies: %s' % graph)
+
+ # Sort the actual decls based on the topological order
+ return sorted(decls, key=lambda d: topological_order.index(d.type_name))
+
+
+class Module(object):
+ def __init__(self, elements):
+ module_reference, _, _, _, module_body, _ = elements
+ _assert_annotated_token(module_reference)
+ _assert_annotated_token(module_body)
+
+ self.name = module_reference.elements[0]
+ self.declarations = [_create_sema_node(token) for token in module_body.elements]
+ self._user_types = {}
+
+ def user_types(self):
+ if not self._user_types:
+ # Index all type assignments by name
+ for user_defined in self.declarations:
+ self._user_types[user_defined.type_name] = user_defined.type_decl
+
+ return self._user_types
+
+ def resolve_type_decl(self, type_decl):
+ """ Recursively resolve user-defined types to their
+ built-in declaration.
+ """
+ user_types = self.user_types()
+
+ if isinstance(type_decl, UserDefinedType):
+ return self.resolve_type_decl(user_types[type_decl.type_name])
+ else:
+ return type_decl
+
+
+ def __str__(self):
+ return '%s DEFINITIONS ::=\n' % self.name \
+ + 'BEGIN\n' \
+ + '\n'.join(map(str, self.declarations)) + '\n' \
+ + 'END\n'
+
+ __repr__ = __str__
+
+
+class TypeAssignment(object):
+ def __init__(self, elements):
+ assert(len(elements) == 3)
+ type_name, _, type_decl = elements
+ self.type_name = type_name
+ self.type_decl = _create_sema_node(type_decl)
+
+ def imports(self):
+ return self.type_decl.imports()
+
+ def __str__(self):
+ return '%s ::= %s' % (self.type_name, self.type_decl)
+
+ __repr__ = __str__
+
+
+class ConstructedType(object):
+ def __init__(self, elements):
+ kind, component_tokens = elements
+ self.type_name = kind
+ self.components = [_create_sema_node(token) for token in component_tokens]
+
+ def imports(self):
+ imports = []
+ for component in self.components:
+ imports.extend(component.imports())
+ return imports
+
+ def __str__(self):
+ component_type_list = ', '.join(map(str, self.components))
+ return '%s { %s }' % (self.type_name, component_type_list)
+
+ __repr__ = __str__
+
+
+class ChoiceType(ConstructedType):
+ def __init__(self, elements):
+ super(ChoiceType, self).__init__(elements)
+
+class SequenceType(ConstructedType):
+ def __init__(self, elements):
+ super(SequenceType, self).__init__(elements)
+
+class SequenceOfType(object):
+ def __init__(self, elements):
+ sequenceof, type_token = elements
+ self.type_name = sequenceof
+ self.type_decl = _create_sema_node(type_token)
+
+ def imports(self):
+ return self.type_decl.imports()
+
+ def __str__(self):
+ return '%s %s' % (self.type_name, self.type_decl)
+
+ __repr__ = __str__
+
+
+class TaggedType(object):
+ def __init__(self, elements):
+ self.class_name = None
+ self.class_number = None
+ self.implicit = False
+
+ tag_token = elements[0]
+ if type(elements[1]) is parser.AnnotatedToken:
+ type_token = elements[1]
+ else:
+ self.implicit = elements[1] == 'IMPLICIT'
+ type_token = elements[2]
+
+ for tag_element in tag_token.elements:
+ if tag_element.ty == 'TagClassNumber':
+ self.class_number = tag_element.elements[0]
+ elif tag_element.ty == 'TagClass':
+ self.class_name = tag_element.elements[0]
+ else:
+ assert False, 'Unknown tag element: %s' % tag_element
+
+ self.type_decl = _create_sema_node(type_token)
+
+ @property
+ def type_name(self):
+ return self.type_decl.type_name
+
+ def imports(self):
+ return self.type_decl.imports()
+
+ def __str__(self):
+ class_spec = []
+ if self.class_name:
+ class_spec.append(self.class_name)
+ class_spec.append(self.class_number)
+
+ result = '[%s] ' % ' '.join(class_spec)
+ if self.implicit:
+ result += 'IMPLICIT '
+
+ result += str(self.type_decl)
+
+ return result
+
+ __repr__ = __str__
+
+
+class SimpleType(object):
+ def __init__(self, elements):
+ self.constraint = None
+ self.type_name = elements[0]
+ if len(elements) > 1 and elements[1].ty == 'Constraint':
+ self.constraint = Constraint(elements[1].elements)
+
+ def imports(self):
+ return [self.type_name]
+
+ def __str__(self):
+ if self.constraint is None:
+ return self.type_name
+
+ return '%s %s' % (self.type_name, self.constraint)
+
+ __repr__ = __str__
+
+
+class UserDefinedType(object):
+ def __init__(self, elements):
+ self.type_name = elements[0]
+
+ def imports(self):
+ return [self.type_name]
+
+ def __str__(self):
+ return self.type_name
+
+ __repr__ = __str__
+
+
+class Constraint(object):
+ def __init__(self, elements):
+ min_value, max_value = elements
+ self.min_value = min_value
+ self.max_value = max_value
+
+ def __str__(self):
+ return '(%s..%s)' % (self.min_value, self.max_value)
+
+ __repr__ = __str__
+
+
+class ComponentType(object):
+ def __init__(self, elements):
+ first_token = elements[0]
+ if first_token.ty == 'Type':
+ # an unnamed member
+ type_token = first_token
+ self.identifier = _get_next_unnamed()
+ elements = elements[1:]
+ elif first_token.ty == 'Identifier':
+ # an identifier
+ self.identifier = first_token.elements[0]
+ type_token = elements[1]
+ elements = elements[2:]
+
+ self.optional = elements and elements[0].ty == 'ComponentOptional'
+
+ if elements and elements[0].ty == 'ComponentDefault':
+ default_spec = elements[0]
+ assert default_spec.elements[0] == 'DEFAULT'
+ self.default_value = default_spec.elements[1]
+ else:
+ self.default_value = None
+
+ self.type_decl = _create_sema_node(type_token)
+
+ def imports(self):
+ return self.type_decl.imports()
+
+ def __str__(self):
+ result = '%s %s' % (self.identifier, self.type_decl)
+ if self.optional:
+ result += ' OPTIONAL'
+
+ if not self.default_value is None:
+ result += ' DEFAULT ' + self.default_value
+
+ return result
+
+ __repr__ = __str__
+
+
+class NamedType(object):
+ def __init__(self, elements):
+ assert(elements[0].ty == 'Identifier')
+ assert(elements[1].ty == 'Type')
+ self.identifier = elements[0].elements[0]
+ self.type_decl = _create_sema_node(elements[1])
+
+ def imports(self):
+ return self.type_decl.imports()
+
+ def __str__(self):
+ return '%s %s' % (self.identifier, self.type_decl)
+
+ __repr__ = __str__
+
+
+class ValueListType(object):
+ def __init__(self, elements):
+ self.type_name = elements[0]
+ if len(elements) > 1:
+ self.named_values = [_create_sema_node(token) for token in elements[1]]
+ else:
+ self.named_values = None
+
+ def imports(self):
+ return []
+
+ def __str__(self):
+ if self.named_values:
+ named_value_list = ', '.join(map(str, self.named_values))
+ return '%s { %s }' % (self.type_name, named_value_list)
+ else:
+ return '%s' % self.type_name
+
+ __repr__ = __str__
+
+
+class BitStringType(object):
+ def __init__(self, elements):
+ self.type_name = elements[0]
+ if len(elements) > 1:
+ self.named_bits = [_create_sema_node(token) for token in elements[1]]
+ else:
+ self.named_bits = None
+
+ def imports(self):
+ return []
+
+ def __str__(self):
+ if self.named_bits:
+ named_bit_list = ', '.join(map(str, self.named_bits))
+ return '%s { %s }' % (self.type_name, named_bit_list)
+ else:
+ return '%s' % self.type_name
+
+ __repr__ = __str__
+
+
+class NamedValue(object):
+ def __init__(self, elements):
+ identifier_token, value_token = elements
+ self.identifier = identifier_token.elements[0]
+ self.value = value_token.elements[0]
+
+ def imports(self):
+ return []
+
+ def __str__(self):
+ return '%s (%s)' % (self.identifier, self.value)
+
+ __repr__ = __str__
+
+
+def _create_sema_node(token):
+ _assert_annotated_token(token)
+
+ if token.ty == 'ModuleDefinition':
+ return Module(token.elements)
+ elif token.ty == 'TypeAssignment':
+ return TypeAssignment(token.elements)
+ elif token.ty == 'ComponentType':
+ return ComponentType(token.elements)
+ elif token.ty == 'NamedType':
+ return NamedType(token.elements)
+ elif token.ty == 'ValueListType':
+ return ValueListType(token.elements)
+ elif token.ty == 'BitStringType':
+ return BitStringType(token.elements)
+ elif token.ty == 'NamedValue':
+ return NamedValue(token.elements)
+ elif token.ty == 'Type':
+ # Type tokens have a more specific type category
+ # embedded as their first element
+ return _create_sema_node(token.elements[0])
+ elif token.ty == 'SimpleType':
+ return SimpleType(token.elements)
+ elif token.ty == 'ReferencedType':
+ return UserDefinedType(token.elements)
+ elif token.ty == 'TaggedType':
+ return TaggedType(token.elements)
+ elif token.ty == 'SequenceType':
+ return SequenceType(token.elements)
+ elif token.ty == 'ChoiceType':
+ return ChoiceType(token.elements)
+ elif token.ty == 'SequenceOfType':
+ return SequenceOfType(token.elements)
+
+ raise Exception('Unknown token type: %s' % token.ty)
+
+
+def _assert_annotated_token(obj):
+ assert(type(obj) is parser.AnnotatedToken)
+
+
+# HACK: Generate unique names for unnamed members
+_unnamed_counter = 0
+def _get_next_unnamed():
+ global _unnamed_counter
+ _unnamed_counter += 1
+ return 'unnamed%d' % _unnamed_counter
--- /dev/null
+# Copyright (c) 2013, Schneider Electric Buildings AB
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Schneider Electric Buildings AB nor the
+# names of contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+try:
+ # Python 2
+ from cStringIO import StringIO
+except ImportError:
+ # Python 3
+ from io import StringIO
+
+from datetime import datetime
+
+
+def auto_generated_header():
+ return '# Auto-generated by asn1ate on %s' % datetime.now()
+
+
+class NullBackend(object):
+ """ Code generator to create an empty file.
+ Used to create __init__.py files.
+ """
+ def __init__(self, *args):
+ pass
+
+ def generate_code(self, *args):
+ pass
+
+
+class PythonWriter(object):
+ """ Indentation-aware text stream. """
+ def __init__(self, out_stream, indent_size=4):
+ self.out = out_stream
+ self.indent_size = indent_size
+ self.current_indent = 0
+
+ def push_indent(self):
+ self.current_indent += self.indent_size
+
+ def pop_indent(self):
+ self.current_indent -= self.indent_size
+
+ def write_line(self, line):
+ if line is not None:
+ line = self._indent(line) if line else line
+ self.out.write('%s\n' % line)
+
+ def write_blanks(self, count=1):
+ for i in range(0, count):
+ self.out.write('\n')
+
+ def write_block(self, block):
+ """ Reindents after every line break. """
+ block = block.rstrip()
+ for line in block.split('\n'):
+ self.write_line(line)
+
+ def write_enumeration(self, items):
+ self.write_block(',\n'.join(items))
+
+ def get_fragment(self):
+ return PythonFragment(self.indent_size)
+
+ def _indent(self, line):
+ return ' ' * self.current_indent + line
+
+
+class PythonFragment(PythonWriter):
+ """ A buffering python writer, useful for nested structures.
+ """
+ def __init__(self, indent_size=4):
+ self.buf = StringIO()
+ super(PythonFragment, self).__init__(self.buf, indent_size)
+
+ def __str__(self):
+ return self.buf.getvalue()