#!/usr/bin/env python # -*- coding: utf-8 -*- """ ASN.1 Value Editor - Value Notation parser and converter This module contains the following functions: 1) fromValueNotationToPySide(varName, string) Parse a string containing an ASN.1 value expressed in GSER (also called ASN.1 Value Notation) and return a Python structure that is compatible with the widget of the ASN.1 Value Editor 2) toASN1ValueNotation(val) Does the reverse (from Qt widget to ASN.1 Value Notation/GSER) 3) valueNotationToSwig(gser, dest, sort, ASN1Swig, ASN1_AST, var=None) Parse a Value Notation string and fill a SWIG instance with the values Copyright (c) 2012-2015 European Space Agency Designed and implemented by Maxime Perrotin Contact: maxime.perrotin@esa.int License is LGPLv3 - Check the LICENSE file """ from pyparsing import(Word, QuotedString, Literal, Combine, Optional, oneOf, OneOrMore, srange, nums, nestedExpr, Forward, delimitedList, Keyword, ParseException) import string # Parser for ASN.1 Value Notation # Based on the ASN1Scc grammar BitStringLiteral = ('"' + Word("01") + '"B').setResultsName('BITSTRING') OctetStringLiteral=('"' + Word(string.hexdigits) + '"H').setResultsName('OCTETSTRING') # QuotedString's first parameter = quote type..+multiline to support \n => OK # Support embedded quotes as per GSER standard (""embedded quoted text"") StringLiteral = QuotedString('"', escQuote='""', multiline=True,).setResultsName('QUOTEDSTRING') TRUE = Literal("TRUE").setResultsName('TRUE') FALSE = Literal("FALSE").setResultsName('FALSE') NULL = Literal("NULL").setResultsName('NULL') COMMA = Literal(',').suppress() PLUS_INFINITY = Literal("PLUS-INFINITY").setResultsName('PLUS-INFINITY') MINUS_INFINITY = Literal("MINUS-INFINITY").setResultsName('MINUS-INFINITY') # EMPTY_LIST: LBRACKET = Literal('{').suppress() RBRACKET = Literal('}').suppress() # INTEGER INT = Combine(Optional(oneOf("+ -")) + (Word(srange("[1-9]"), nums)|Literal('0'))).setResultsName('INTEGER') # This one is correct: # a lowercase followed by a-zA-z and - #LID = Word(string.lowercase, string.letters+string.digits+'-') # Update MP 21/11/12: made it tolerant to uppercase for the first character LID = Word(string.letters, string.letters+string.digits+'-') identifier = LID.setResultsName('IDENTIFIER') # Value reference is in practice used to identify enumerated values valuereference = LID.setResultsName('VALUE_REF') # FloatingPointLiteral DOT = Literal('.') DOUBLE_DOT = Literal('..') # Exponent (e.g. e+10) Exponent = oneOf("e E") + Optional(oneOf("+ -")) + OneOrMore(Word(nums)) # Thefirst (INT + DOUBLE_DOT) is unclear (e.g. "5.."). FloatingPointLiteral = (INT + DOUBLE_DOT | Combine(INT + DOT + Optional(Word(nums)) + Optional(Exponent)) | INT).setResultsName('REAL1') # Numeric value2 (eg. { mantissa 3, base 4, exponent 5 }) MANTISSA = Literal('mantissa').suppress() BASE = Literal('base').suppress() EXPONENT = Literal('exponent').suppress() NUMERIC_VALUE2 = (nestedExpr('{', '}', MANTISSA + INT + COMMA + BASE + INT + COMMA + EXPONENT + INT)).setResultsName('REAL2') # Forward allows to use recursive constructs NAMED_VALUE_LIST = Forward() VALUE_LIST = Forward() choiceValue = Forward() # Support for "Any" value (using a star) AnyValueLiteral = Keyword('*').setResultsName('ANY') # ASN.1 Value Notation: only missing construct are OID references # Ambiguity: FloatingPointLiteral contains INT -> INT will never be parsed as REAL value = (BitStringLiteral | OctetStringLiteral | TRUE | FALSE | StringLiteral | NULL | PLUS_INFINITY | MINUS_INFINITY | NAMED_VALUE_LIST | VALUE_LIST | (valuereference ^ choiceValue) | (INT ^ FloatingPointLiteral) | NUMERIC_VALUE2 | AnyValueLiteral) # namedValue is used for SEQUENCE fields namedValue = identifier + value # ASN.1 CHOICE choiceValue << (identifier + ':' + value)#.setResultsName('CHOICE') # ASN.1 SEQUENCE NAMED_VALUE_LIST << LBRACKET + delimitedList(namedValue) + RBRACKET # ASN.1 SEQUENCE OF VALUE_LIST << LBRACKET + Optional(delimitedList(value)) + RBRACKET # Parse actions allow to modify the AST to be compliant with the ASN.1 Editor input NAMED_VALUE_LIST.setParseAction(lambda s, l, t: reduce(lambda a, b: a.update(b) or a, t, {})) # below: works only with Python 2.7+ #NAMED_VALUE_LIST.setParseAction(lambda s, l, t: {c:a[c] for a in t for c in a.iterkeys()}) VALUE_LIST.setParseAction(lambda s, l, t: [t.asList()]) def parseChoiceValue(s, l, t): ''' Parsing CHOICE ''' choice = t[0].replace('-', '_') return {'Choice': choice, choice: t[2]} choiceValue.setParseAction(parseChoiceValue) def parseValueReference(s, l, t): ''' Parsing ENUMERATED Id ''' value = t[0].replace('-', '_') return {'Enum': value} valuereference.setParseAction(parseValueReference) #choiceValue.setParseAction(lambda s, l, t: {'Choice': t[0].replace('-', '_'), t[0].replace('-', '_'): t[2]}) #valuereference.setParseAction(lambda s, l, t: {'Enum': t[0].replace('-', '_')}) namedValue.setParseAction(lambda s, l, t: {t[0].replace('-', '_'): t[1]}) FloatingPointLiteral.setParseAction(lambda s, l, t: float(t[0])) INT.setParseAction(lambda s, l, t: int(t[0])) TRUE.setParseAction(lambda s, l, t: True) FALSE.setParseAction(lambda s, l, t: False) AnyValueLiteral.setParseAction(lambda s, l, t: {'Any': t[0]}) OctetStringLiteral.setParseAction(lambda s, l, t: ''.join([chr(int(t[1][i:i+2], 16)) for i in range(0, len(t[1]), 2)])) # BitString Warning: will only work with series of 8 bits BitStringLiteral.setParseAction(lambda s, l, t: ''.join([chr(int(t[1][i:i+9], 2)) for i in range(0, len(t[1]), 2)])) def fromValueNotationToPySide(varName, string): ''' Return a variable compatible with the ASN.1 Editor from a GSER string''' try: return {varName: value.parseString(string, True)[0]} except ParseException as err: print '[fromValueNotationToPySide] Parsing error:', string raise def toASN1ValueNotation(val): ''' Create a GSER reprentation of the Python variable ''' result = '' if isinstance(val, dict): if 'Choice' in val: result += val['Choice'].replace('_', '-').strip() + ':' result += toASN1ValueNotation(val[val['Choice']]) elif 'Enum' in val: result += unicode(val['Enum']).replace('_', '-').strip() else: # SEQUENCE and SET result += ' { ' needsComa = False for seqElem in val: if needsComa: result += ', ' result += seqElem.replace('_', '-').strip() + ' ' result += toASN1ValueNotation(val[seqElem]) needsComa = True result += ' }' elif isinstance(val, list): # SEQUENCE OF or SET OF result += ' { ' needsComa = False for seqOfElem in val: if needsComa: result += ', ' result += toASN1ValueNotation(seqOfElem) needsComa = True result += ' }' elif isinstance(val, bool): # boolean are in upper case in ASN.1 result += str(val).upper() elif isinstance(val, basestring): # Strings: add quotes, and if string contain non-displayable chars, use repr() to get a visible string if all(c in string.printable for c in val): result = '"' + val + '"' else: result = '"' for c in val: result += '%02x' % ord(c) result += '"H' else: result = str(val) # INTEGER and REAL return result def valueNotationToSwig(gser, dest, sort, ASN1Swig, ASN1_AST, var=None): ''' Parse a GSER value and fill a SWIG variable with it Inputs: gser : input GSER string dest : output SWIG instance to be filled ASN1Swig : python module containing SWIG DV access sort: ASN1 typename, with dashes, no underscores ASN1_AST : AST generated by ASN1SCC var : optional already pyside-converted GSER string Outputs: none - "dest" is modified by this function ''' if var is None: var = value.parseString(gser, True)[0] def reach(field, orig, idx=True): ''' Helper: move swig pointer to the next field, and optionaly index (if idx=True) Inputs: field is a string with optional index (e.g. "a[0]") orig is the swig pointer idx: set to true if you want the index to be reached ''' split = field.strip(']').split('[') ptr = getattr(orig, split[0]) if split else orig if len(split) > 1 and idx: # SEQOF index ptr = ptr[int(split[1])] def rec(inp, outp, sort): ''' Recursively fill up the value ''' if sort.kind == 'ReferenceType': sort = ASN1_AST[sort.ReferencedTypeName] if isinstance(inp, list): # SEQUENCE OF # get the path to the sequence of _, params, path = outp.GetState() if path: path = path.strip('.').split('.') for i in range(len(inp)): outp.Reset() for each in path: reach(each, outp) # Follow the ASN.1 type in the AST from ASN1SCC rec(inp[i], outp[i], sort.type.type) if sort.type.Min != sort.type.Max: outp.Reset() for each in path: reach(each, outp) # The ASN1SCC AST only knows if the list has a fixed length outp.SetLength(len(inp)) elif isinstance(inp, (int, float, bool)): outp.Set(inp) elif isinstance(inp, dict): if 'Enum' in inp: # Get proper enum id from the ASN1SCC AST enum_id = sort.type.EnumValues[ inp['Enum'].replace('_', '-')].EnumID val = getattr(ASN1Swig.DV, enum_id) outp.Set(val) elif 'Choice' in inp: child_name = inp['Choice'] ch_ty = sort.type.Children[child_name.replace('_', '-')] enum_val = getattr(ASN1Swig.DV, ch_ty.EnumID) outp.kind.Set(enum_val) rec(inp[child_name], getattr(outp, child_name.replace('-', '_')), ch_ty.type) else: # SEQUENCE # get the path to the sequence _, params, path = outp.GetState() if path: path = path.strip('.').split('.') for field, data in inp.viewitems(): outp.Reset() for each in path: # Reach the path, including indexes reach(each, outp) # Then get the field itself reach(field.replace('-', '_'), outp) field_ty = sort.type.Children[field.replace('_', '-')] rec(data, outp, field_ty.type) # move back to original path outp.Reset() if len(path): reach(path[0], outp) else: # Unsupported type pass rec(var, dest, sort) if __name__ == '__main__': ''' Test application ''' import sys if len(sys.argv)==2: print fromValueNotationToPySide(sys.argv[1])