| # |
| # This file is part of pyasn1 software. |
| # |
| # Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com> |
| # License: http://snmplabs.com/pyasn1/license.html |
| # |
| import sys |
| |
| from pyasn1 import error |
| from pyasn1.type import tag |
| from pyasn1.type import tagmap |
| |
| __all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', |
| 'NamedTypes'] |
| |
| try: |
| any |
| |
| except NameError: |
| any = lambda x: bool(filter(bool, x)) |
| |
| |
| class NamedType(object): |
| """Create named field object for a constructed ASN.1 type. |
| |
| The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type. |
| |
| |NamedType| objects are immutable and duck-type Python :class:`tuple` objects |
| holding *name* and *asn1Object* components. |
| |
| Parameters |
| ---------- |
| name: :py:class:`str` |
| Field name |
| |
| asn1Object: |
| ASN.1 type object |
| """ |
| isOptional = False |
| isDefaulted = False |
| |
| def __init__(self, name, asn1Object, openType=None): |
| self.__name = name |
| self.__type = asn1Object |
| self.__nameAndType = name, asn1Object |
| self.__openType = openType |
| |
| def __repr__(self): |
| representation = '%s=%r' % (self.name, self.asn1Object) |
| |
| if self.openType: |
| representation += ', open type %r' % self.openType |
| |
| return '<%s object, type %s>' % ( |
| self.__class__.__name__, representation) |
| |
| def __eq__(self, other): |
| return self.__nameAndType == other |
| |
| def __ne__(self, other): |
| return self.__nameAndType != other |
| |
| def __lt__(self, other): |
| return self.__nameAndType < other |
| |
| def __le__(self, other): |
| return self.__nameAndType <= other |
| |
| def __gt__(self, other): |
| return self.__nameAndType > other |
| |
| def __ge__(self, other): |
| return self.__nameAndType >= other |
| |
| def __hash__(self): |
| return hash(self.__nameAndType) |
| |
| def __getitem__(self, idx): |
| return self.__nameAndType[idx] |
| |
| def __iter__(self): |
| return iter(self.__nameAndType) |
| |
| @property |
| def name(self): |
| return self.__name |
| |
| @property |
| def asn1Object(self): |
| return self.__type |
| |
| @property |
| def openType(self): |
| return self.__openType |
| |
| # Backward compatibility |
| |
| def getName(self): |
| return self.name |
| |
| def getType(self): |
| return self.asn1Object |
| |
| |
| class OptionalNamedType(NamedType): |
| __doc__ = NamedType.__doc__ |
| |
| isOptional = True |
| |
| |
| class DefaultedNamedType(NamedType): |
| __doc__ = NamedType.__doc__ |
| |
| isDefaulted = True |
| |
| |
| class NamedTypes(object): |
| """Create a collection of named fields for a constructed ASN.1 type. |
| |
| The NamedTypes object represents a collection of named fields of a constructed ASN.1 type. |
| |
| *NamedTypes* objects are immutable and duck-type Python :class:`dict` objects |
| holding *name* as keys and ASN.1 type object as values. |
| |
| Parameters |
| ---------- |
| *namedTypes: :class:`~pyasn1.type.namedtype.NamedType` |
| |
| Examples |
| -------- |
| |
| .. code-block:: python |
| |
| class Description(Sequence): |
| ''' |
| ASN.1 specification: |
| |
| Description ::= SEQUENCE { |
| surname IA5String, |
| first-name IA5String OPTIONAL, |
| age INTEGER DEFAULT 40 |
| } |
| ''' |
| componentType = NamedTypes( |
| NamedType('surname', IA5String()), |
| OptionalNamedType('first-name', IA5String()), |
| DefaultedNamedType('age', Integer(40)) |
| ) |
| |
| descr = Description() |
| descr['surname'] = 'Smith' |
| descr['first-name'] = 'John' |
| """ |
| def __init__(self, *namedTypes, **kwargs): |
| self.__namedTypes = namedTypes |
| self.__namedTypesLen = len(self.__namedTypes) |
| self.__minTagSet = self.__computeMinTagSet() |
| self.__nameToPosMap = self.__computeNameToPosMap() |
| self.__tagToPosMap = self.__computeTagToPosMap() |
| self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {} |
| self.__uniqueTagMap = self.__computeTagMaps(unique=True) |
| self.__nonUniqueTagMap = self.__computeTagMaps(unique=False) |
| self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes |
| if namedType.isDefaulted or namedType.isOptional]) |
| self.__hasOpenTypes = any([True for namedType in self.__namedTypes |
| if namedType.openType]) |
| |
| self.__requiredComponents = frozenset( |
| [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted] |
| ) |
| self.__keys = frozenset([namedType.name for namedType in self.__namedTypes]) |
| self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes]) |
| self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes]) |
| |
| def __repr__(self): |
| representation = ', '.join(['%r' % x for x in self.__namedTypes]) |
| return '<%s object, types %s>' % ( |
| self.__class__.__name__, representation) |
| |
| def __eq__(self, other): |
| return self.__namedTypes == other |
| |
| def __ne__(self, other): |
| return self.__namedTypes != other |
| |
| def __lt__(self, other): |
| return self.__namedTypes < other |
| |
| def __le__(self, other): |
| return self.__namedTypes <= other |
| |
| def __gt__(self, other): |
| return self.__namedTypes > other |
| |
| def __ge__(self, other): |
| return self.__namedTypes >= other |
| |
| def __hash__(self): |
| return hash(self.__namedTypes) |
| |
| def __getitem__(self, idx): |
| try: |
| return self.__namedTypes[idx] |
| |
| except TypeError: |
| return self.__namedTypes[self.__nameToPosMap[idx]] |
| |
| def __contains__(self, key): |
| return key in self.__nameToPosMap |
| |
| def __iter__(self): |
| return (x[0] for x in self.__namedTypes) |
| |
| if sys.version_info[0] <= 2: |
| def __nonzero__(self): |
| return self.__namedTypesLen > 0 |
| else: |
| def __bool__(self): |
| return self.__namedTypesLen > 0 |
| |
| def __len__(self): |
| return self.__namedTypesLen |
| |
| # Python dict protocol |
| |
| def values(self): |
| return self.__values |
| |
| def keys(self): |
| return self.__keys |
| |
| def items(self): |
| return self.__items |
| |
| def clone(self): |
| return self.__class__(*self.__namedTypes) |
| |
| class PostponedError(object): |
| def __init__(self, errorMsg): |
| self.__errorMsg = errorMsg |
| |
| def __getitem__(self, item): |
| raise error.PyAsn1Error(self.__errorMsg) |
| |
| def __computeTagToPosMap(self): |
| tagToPosMap = {} |
| for idx, namedType in enumerate(self.__namedTypes): |
| tagMap = namedType.asn1Object.tagMap |
| if isinstance(tagMap, NamedTypes.PostponedError): |
| return tagMap |
| if not tagMap: |
| continue |
| for _tagSet in tagMap.presentTypes: |
| if _tagSet in tagToPosMap: |
| return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType)) |
| tagToPosMap[_tagSet] = idx |
| |
| return tagToPosMap |
| |
| def __computeNameToPosMap(self): |
| nameToPosMap = {} |
| for idx, namedType in enumerate(self.__namedTypes): |
| if namedType.name in nameToPosMap: |
| return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType)) |
| nameToPosMap[namedType.name] = idx |
| |
| return nameToPosMap |
| |
| def __computeAmbiguousTypes(self): |
| ambiguousTypes = {} |
| partialAmbiguousTypes = () |
| for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))): |
| if namedType.isOptional or namedType.isDefaulted: |
| partialAmbiguousTypes = (namedType,) + partialAmbiguousTypes |
| else: |
| partialAmbiguousTypes = (namedType,) |
| if len(partialAmbiguousTypes) == len(self.__namedTypes): |
| ambiguousTypes[idx] = self |
| else: |
| ambiguousTypes[idx] = NamedTypes(*partialAmbiguousTypes, **dict(terminal=True)) |
| return ambiguousTypes |
| |
| def getTypeByPosition(self, idx): |
| """Return ASN.1 type object by its position in fields set. |
| |
| Parameters |
| ---------- |
| idx: :py:class:`int` |
| Field index |
| |
| Returns |
| ------- |
| : |
| ASN.1 type |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If given position is out of fields range |
| """ |
| try: |
| return self.__namedTypes[idx].asn1Object |
| |
| except IndexError: |
| raise error.PyAsn1Error('Type position out of range') |
| |
| def getPositionByType(self, tagSet): |
| """Return field position by its ASN.1 type. |
| |
| Parameters |
| ---------- |
| tagSet: :class:`~pysnmp.type.tag.TagSet` |
| ASN.1 tag set distinguishing one ASN.1 type from others. |
| |
| Returns |
| ------- |
| : :py:class:`int` |
| ASN.1 type position in fields set |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes* |
| """ |
| try: |
| return self.__tagToPosMap[tagSet] |
| |
| except KeyError: |
| raise error.PyAsn1Error('Type %s not found' % (tagSet,)) |
| |
| def getNameByPosition(self, idx): |
| """Return field name by its position in fields set. |
| |
| Parameters |
| ---------- |
| idx: :py:class:`idx` |
| Field index |
| |
| Returns |
| ------- |
| : :py:class:`str` |
| Field name |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If given field name is not present in callee *NamedTypes* |
| """ |
| try: |
| return self.__namedTypes[idx].name |
| |
| except IndexError: |
| raise error.PyAsn1Error('Type position out of range') |
| |
| def getPositionByName(self, name): |
| """Return field position by filed name. |
| |
| Parameters |
| ---------- |
| name: :py:class:`str` |
| Field name |
| |
| Returns |
| ------- |
| : :py:class:`int` |
| Field position in fields set |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If *name* is not present or not unique within callee *NamedTypes* |
| """ |
| try: |
| return self.__nameToPosMap[name] |
| |
| except KeyError: |
| raise error.PyAsn1Error('Name %s not found' % (name,)) |
| |
| def getTagMapNearPosition(self, idx): |
| """Return ASN.1 types that are allowed at or past given field position. |
| |
| Some ASN.1 serialisation allow for skipping optional and defaulted fields. |
| Some constructed ASN.1 types allow reordering of the fields. When recovering |
| such objects it may be important to know which types can possibly be |
| present at any given position in the field sets. |
| |
| Parameters |
| ---------- |
| idx: :py:class:`int` |
| Field index |
| |
| Returns |
| ------- |
| : :class:`~pyasn1.type.tagmap.TagMap` |
| Map if ASN.1 types allowed at given field position |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If given position is out of fields range |
| """ |
| try: |
| return self.__ambiguousTypes[idx].tagMap |
| |
| except KeyError: |
| raise error.PyAsn1Error('Type position out of range') |
| |
| def getPositionNearType(self, tagSet, idx): |
| """Return the closest field position where given ASN.1 type is allowed. |
| |
| Some ASN.1 serialisation allow for skipping optional and defaulted fields. |
| Some constructed ASN.1 types allow reordering of the fields. When recovering |
| such objects it may be important to know at which field position, in field set, |
| given *tagSet* is allowed at or past *idx* position. |
| |
| Parameters |
| ---------- |
| tagSet: :class:`~pyasn1.type.tag.TagSet` |
| ASN.1 type which field position to look up |
| |
| idx: :py:class:`int` |
| Field position at or past which to perform ASN.1 type look up |
| |
| Returns |
| ------- |
| : :py:class:`int` |
| Field position in fields set |
| |
| Raises |
| ------ |
| ~pyasn1.error.PyAsn1Error |
| If *tagSet* is not present or not unique within callee *NamedTypes* |
| or *idx* is out of fields range |
| """ |
| try: |
| return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet) |
| |
| except KeyError: |
| raise error.PyAsn1Error('Type position out of range') |
| |
| def __computeMinTagSet(self): |
| minTagSet = None |
| for namedType in self.__namedTypes: |
| asn1Object = namedType.asn1Object |
| |
| try: |
| tagSet = asn1Object.minTagSet |
| |
| except AttributeError: |
| tagSet = asn1Object.tagSet |
| |
| if minTagSet is None or tagSet < minTagSet: |
| minTagSet = tagSet |
| |
| return minTagSet or tag.TagSet() |
| |
| @property |
| def minTagSet(self): |
| """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*. |
| |
| Some ASN.1 types/serialisation protocols require ASN.1 types to be |
| arranged based on their numerical tag value. The *minTagSet* property |
| returns that. |
| |
| Returns |
| ------- |
| : :class:`~pyasn1.type.tagset.TagSet` |
| Minimal TagSet among ASN.1 types in callee *NamedTypes* |
| """ |
| return self.__minTagSet |
| |
| def __computeTagMaps(self, unique): |
| presentTypes = {} |
| skipTypes = {} |
| defaultType = None |
| for namedType in self.__namedTypes: |
| tagMap = namedType.asn1Object.tagMap |
| if isinstance(tagMap, NamedTypes.PostponedError): |
| return tagMap |
| for tagSet in tagMap: |
| if unique and tagSet in presentTypes: |
| return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self)) |
| presentTypes[tagSet] = namedType.asn1Object |
| skipTypes.update(tagMap.skipTypes) |
| |
| if defaultType is None: |
| defaultType = tagMap.defaultType |
| elif tagMap.defaultType is not None: |
| return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,)) |
| |
| return tagmap.TagMap(presentTypes, skipTypes, defaultType) |
| |
| @property |
| def tagMap(self): |
| """Return a *TagMap* object from tags and types recursively. |
| |
| Return a :class:`~pyasn1.type.tagmap.TagMap` object by |
| combining tags from *TagMap* objects of children types and |
| associating them with their immediate child type. |
| |
| Example |
| ------- |
| .. code-block:: python |
| |
| OuterType ::= CHOICE { |
| innerType INTEGER |
| } |
| |
| Calling *.tagMap* on *OuterType* will yield a map like this: |
| |
| .. code-block:: python |
| |
| Integer.tagSet -> Choice |
| """ |
| return self.__nonUniqueTagMap |
| |
| @property |
| def tagMapUnique(self): |
| """Return a *TagMap* object from unique tags and types recursively. |
| |
| Return a :class:`~pyasn1.type.tagmap.TagMap` object by |
| combining tags from *TagMap* objects of children types and |
| associating them with their immediate child type. |
| |
| Example |
| ------- |
| .. code-block:: python |
| |
| OuterType ::= CHOICE { |
| innerType INTEGER |
| } |
| |
| Calling *.tagMapUnique* on *OuterType* will yield a map like this: |
| |
| .. code-block:: python |
| |
| Integer.tagSet -> Choice |
| |
| Note |
| ---- |
| |
| Duplicate *TagSet* objects found in the tree of children |
| types would cause error. |
| """ |
| return self.__uniqueTagMap |
| |
| @property |
| def hasOptionalOrDefault(self): |
| return self.__hasOptionalOrDefault |
| |
| @property |
| def hasOpenTypes(self): |
| return self.__hasOpenTypes |
| |
| @property |
| def namedTypes(self): |
| return tuple(self.__namedTypes) |
| |
| @property |
| def requiredComponents(self): |
| return self.__requiredComponents |