• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# This file is part of pyasn1 software.
3#
4# Copyright (c) 2005-2018, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pyasn1/license.html
6#
7import sys
8
9from pyasn1 import error
10from pyasn1.type import tag
11from pyasn1.type import tagmap
12
13__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType',
14           'NamedTypes']
15
16try:
17    any
18
19except NameError:
20    any = lambda x: bool(filter(bool, x))
21
22
23class NamedType(object):
24    """Create named field object for a constructed ASN.1 type.
25
26    The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type.
27
28    |NamedType| objects are immutable and duck-type Python :class:`tuple` objects
29    holding *name* and *asn1Object* components.
30
31    Parameters
32    ----------
33    name: :py:class:`str`
34        Field name
35
36    asn1Object:
37        ASN.1 type object
38    """
39    isOptional = False
40    isDefaulted = False
41
42    def __init__(self, name, asn1Object, openType=None):
43        self.__name = name
44        self.__type = asn1Object
45        self.__nameAndType = name, asn1Object
46        self.__openType = openType
47
48    def __repr__(self):
49        representation = '%s=%r' % (self.name, self.asn1Object)
50
51        if self.openType:
52            representation += ' openType: %r' % self.openType
53
54        return '<%s object at 0x%x type %s>' % (self.__class__.__name__, id(self), representation)
55
56    def __eq__(self, other):
57        return self.__nameAndType == other
58
59    def __ne__(self, other):
60        return self.__nameAndType != other
61
62    def __lt__(self, other):
63        return self.__nameAndType < other
64
65    def __le__(self, other):
66        return self.__nameAndType <= other
67
68    def __gt__(self, other):
69        return self.__nameAndType > other
70
71    def __ge__(self, other):
72        return self.__nameAndType >= other
73
74    def __hash__(self):
75        return hash(self.__nameAndType)
76
77    def __getitem__(self, idx):
78        return self.__nameAndType[idx]
79
80    def __iter__(self):
81        return iter(self.__nameAndType)
82
83    @property
84    def name(self):
85        return self.__name
86
87    @property
88    def asn1Object(self):
89        return self.__type
90
91    @property
92    def openType(self):
93        return self.__openType
94
95    # Backward compatibility
96
97    def getName(self):
98        return self.name
99
100    def getType(self):
101        return self.asn1Object
102
103
104class OptionalNamedType(NamedType):
105    __doc__ = NamedType.__doc__
106
107    isOptional = True
108
109
110class DefaultedNamedType(NamedType):
111    __doc__ = NamedType.__doc__
112
113    isDefaulted = True
114
115
116class NamedTypes(object):
117    """Create a collection of named fields for a constructed ASN.1 type.
118
119    The NamedTypes object represents a collection of named fields of a constructed ASN.1 type.
120
121    *NamedTypes* objects are immutable and duck-type Python :class:`dict` objects
122    holding *name* as keys and ASN.1 type object as values.
123
124    Parameters
125    ----------
126    *namedTypes: :class:`~pyasn1.type.namedtype.NamedType`
127
128    Examples
129    --------
130
131    .. code-block:: python
132
133        class Description(Sequence):
134            '''
135            ASN.1 specification:
136
137            Description ::= SEQUENCE {
138                surname    IA5String,
139                first-name IA5String OPTIONAL,
140                age        INTEGER DEFAULT 40
141            }
142            '''
143            componentType = NamedTypes(
144                NamedType('surname', IA5String()),
145                OptionalNamedType('first-name', IA5String()),
146                DefaultedNamedType('age', Integer(40))
147            )
148
149        descr = Description()
150        descr['surname'] = 'Smith'
151        descr['first-name'] = 'John'
152    """
153    def __init__(self, *namedTypes, **kwargs):
154        self.__namedTypes = namedTypes
155        self.__namedTypesLen = len(self.__namedTypes)
156        self.__minTagSet = self.__computeMinTagSet()
157        self.__nameToPosMap = self.__computeNameToPosMap()
158        self.__tagToPosMap = self.__computeTagToPosMap()
159        self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {}
160        self.__uniqueTagMap = self.__computeTagMaps(unique=True)
161        self.__nonUniqueTagMap = self.__computeTagMaps(unique=False)
162        self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes
163                                           if namedType.isDefaulted or namedType.isOptional])
164        self.__hasOpenTypes = any([True for namedType in self.__namedTypes
165                                   if namedType.openType])
166
167        self.__requiredComponents = frozenset(
168                [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted]
169            )
170        self.__keys = frozenset([namedType.name for namedType in self.__namedTypes])
171        self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes])
172        self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes])
173
174    def __repr__(self):
175        representation = ', '.join(['%r' % x for x in self.__namedTypes])
176        return '<%s object at 0x%x types %s>' % (self.__class__.__name__, id(self), representation)
177
178    def __eq__(self, other):
179        return self.__namedTypes == other
180
181    def __ne__(self, other):
182        return self.__namedTypes != other
183
184    def __lt__(self, other):
185        return self.__namedTypes < other
186
187    def __le__(self, other):
188        return self.__namedTypes <= other
189
190    def __gt__(self, other):
191        return self.__namedTypes > other
192
193    def __ge__(self, other):
194        return self.__namedTypes >= other
195
196    def __hash__(self):
197        return hash(self.__namedTypes)
198
199    def __getitem__(self, idx):
200        try:
201            return self.__namedTypes[idx]
202
203        except TypeError:
204            return self.__namedTypes[self.__nameToPosMap[idx]]
205
206    def __contains__(self, key):
207        return key in self.__nameToPosMap
208
209    def __iter__(self):
210        return (x[0] for x in self.__namedTypes)
211
212    if sys.version_info[0] <= 2:
213        def __nonzero__(self):
214            return self.__namedTypesLen > 0
215    else:
216        def __bool__(self):
217            return self.__namedTypesLen > 0
218
219    def __len__(self):
220        return self.__namedTypesLen
221
222    # Python dict protocol
223
224    def values(self):
225        return self.__values
226
227    def keys(self):
228        return self.__keys
229
230    def items(self):
231        return self.__items
232
233    def clone(self):
234        return self.__class__(*self.__namedTypes)
235
236    class PostponedError(object):
237        def __init__(self, errorMsg):
238            self.__errorMsg = errorMsg
239
240        def __getitem__(self, item):
241            raise  error.PyAsn1Error(self.__errorMsg)
242
243    def __computeTagToPosMap(self):
244        tagToPosMap = {}
245        for idx, namedType in enumerate(self.__namedTypes):
246            tagMap = namedType.asn1Object.tagMap
247            if isinstance(tagMap, NamedTypes.PostponedError):
248                return tagMap
249            if not tagMap:
250                continue
251            for _tagSet in tagMap.presentTypes:
252                if _tagSet in tagToPosMap:
253                    return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType))
254                tagToPosMap[_tagSet] = idx
255
256        return tagToPosMap
257
258    def __computeNameToPosMap(self):
259        nameToPosMap = {}
260        for idx, namedType in enumerate(self.__namedTypes):
261            if namedType.name in nameToPosMap:
262                return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType))
263            nameToPosMap[namedType.name] = idx
264
265        return nameToPosMap
266
267    def __computeAmbiguousTypes(self):
268        ambigiousTypes = {}
269        partialAmbigiousTypes = ()
270        for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))):
271            if namedType.isOptional or namedType.isDefaulted:
272                partialAmbigiousTypes = (namedType,) + partialAmbigiousTypes
273            else:
274                partialAmbigiousTypes = (namedType,)
275            if len(partialAmbigiousTypes) == len(self.__namedTypes):
276                ambigiousTypes[idx] = self
277            else:
278                ambigiousTypes[idx] = NamedTypes(*partialAmbigiousTypes, **dict(terminal=True))
279        return ambigiousTypes
280
281    def getTypeByPosition(self, idx):
282        """Return ASN.1 type object by its position in fields set.
283
284        Parameters
285        ----------
286        idx: :py:class:`int`
287            Field index
288
289        Returns
290        -------
291        :
292            ASN.1 type
293
294        Raises
295        ------
296        : :class:`~pyasn1.error.PyAsn1Error`
297            If given position is out of fields range
298        """
299        try:
300            return self.__namedTypes[idx].asn1Object
301
302        except IndexError:
303            raise error.PyAsn1Error('Type position out of range')
304
305    def getPositionByType(self, tagSet):
306        """Return field position by its ASN.1 type.
307
308        Parameters
309        ----------
310        tagSet: :class:`~pysnmp.type.tag.TagSet`
311            ASN.1 tag set distinguishing one ASN.1 type from others.
312
313        Returns
314        -------
315        : :py:class:`int`
316            ASN.1 type position in fields set
317
318        Raises
319        ------
320        : :class:`~pyasn1.error.PyAsn1Error`
321            If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes*
322        """
323        try:
324            return self.__tagToPosMap[tagSet]
325
326        except KeyError:
327            raise error.PyAsn1Error('Type %s not found' % (tagSet,))
328
329    def getNameByPosition(self, idx):
330        """Return field name by its position in fields set.
331
332        Parameters
333        ----------
334        idx: :py:class:`idx`
335            Field index
336
337        Returns
338        -------
339        : :py:class:`str`
340            Field name
341
342        Raises
343        ------
344        : :class:`~pyasn1.error.PyAsn1Error`
345            If given field name is not present in callee *NamedTypes*
346        """
347        try:
348            return self.__namedTypes[idx].name
349
350        except IndexError:
351            raise error.PyAsn1Error('Type position out of range')
352
353    def getPositionByName(self, name):
354        """Return field position by filed name.
355
356        Parameters
357        ----------
358        name: :py:class:`str`
359            Field name
360
361        Returns
362        -------
363        : :py:class:`int`
364            Field position in fields set
365
366        Raises
367        ------
368        : :class:`~pyasn1.error.PyAsn1Error`
369            If *name* is not present or not unique within callee *NamedTypes*
370        """
371        try:
372            return self.__nameToPosMap[name]
373
374        except KeyError:
375            raise error.PyAsn1Error('Name %s not found' % (name,))
376
377    def getTagMapNearPosition(self, idx):
378        """Return ASN.1 types that are allowed at or past given field position.
379
380        Some ASN.1 serialisation allow for skipping optional and defaulted fields.
381        Some constructed ASN.1 types allow reordering of the fields. When recovering
382        such objects it may be important to know which types can possibly be
383        present at any given position in the field sets.
384
385        Parameters
386        ----------
387        idx: :py:class:`int`
388            Field index
389
390        Returns
391        -------
392        : :class:`~pyasn1.type.tagmap.TagMap`
393            Map if ASN.1 types allowed at given field position
394
395        Raises
396        ------
397        : :class:`~pyasn1.error.PyAsn1Error`
398            If given position is out of fields range
399        """
400        try:
401            return self.__ambiguousTypes[idx].tagMap
402
403        except KeyError:
404            raise error.PyAsn1Error('Type position out of range')
405
406    def getPositionNearType(self, tagSet, idx):
407        """Return the closest field position where given ASN.1 type is allowed.
408
409        Some ASN.1 serialisation allow for skipping optional and defaulted fields.
410        Some constructed ASN.1 types allow reordering of the fields. When recovering
411        such objects it may be important to know at which field position, in field set,
412        given *tagSet* is allowed at or past *idx* position.
413
414        Parameters
415        ----------
416        tagSet: :class:`~pyasn1.type.tag.TagSet`
417           ASN.1 type which field position to look up
418
419        idx: :py:class:`int`
420            Field position at or past which to perform ASN.1 type look up
421
422        Returns
423        -------
424        : :py:class:`int`
425            Field position in fields set
426
427        Raises
428        ------
429        : :class:`~pyasn1.error.PyAsn1Error`
430            If *tagSet* is not present or not unique within callee *NamedTypes*
431            or *idx* is out of fields range
432        """
433        try:
434            return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet)
435
436        except KeyError:
437            raise error.PyAsn1Error('Type position out of range')
438
439    def __computeMinTagSet(self):
440        minTagSet = None
441        for namedType in self.__namedTypes:
442            asn1Object = namedType.asn1Object
443
444            try:
445                tagSet = asn1Object.minTagSet
446
447            except AttributeError:
448                tagSet = asn1Object.tagSet
449
450            if minTagSet is None or tagSet < minTagSet:
451                minTagSet = tagSet
452
453        return minTagSet or tag.TagSet()
454
455    @property
456    def minTagSet(self):
457        """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*.
458
459        Some ASN.1 types/serialisation protocols require ASN.1 types to be
460        arranged based on their numerical tag value. The *minTagSet* property
461        returns that.
462
463        Returns
464        -------
465        : :class:`~pyasn1.type.tagset.TagSet`
466            Minimal TagSet among ASN.1 types in callee *NamedTypes*
467        """
468        return self.__minTagSet
469
470    def __computeTagMaps(self, unique):
471        presentTypes = {}
472        skipTypes = {}
473        defaultType = None
474        for namedType in self.__namedTypes:
475            tagMap = namedType.asn1Object.tagMap
476            if isinstance(tagMap, NamedTypes.PostponedError):
477                return tagMap
478            for tagSet in tagMap:
479                if unique and tagSet in presentTypes:
480                    return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self))
481                presentTypes[tagSet] = namedType.asn1Object
482            skipTypes.update(tagMap.skipTypes)
483
484            if defaultType is None:
485                defaultType = tagMap.defaultType
486            elif tagMap.defaultType is not None:
487                return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,))
488
489        return tagmap.TagMap(presentTypes, skipTypes, defaultType)
490
491    @property
492    def tagMap(self):
493        """Return a *TagMap* object from tags and types recursively.
494
495        Return a :class:`~pyasn1.type.tagmap.TagMap` object by
496        combining tags from *TagMap* objects of children types and
497        associating them with their immediate child type.
498
499        Example
500        -------
501        .. code-block:: python
502
503           OuterType ::= CHOICE {
504               innerType INTEGER
505           }
506
507        Calling *.tagMap* on *OuterType* will yield a map like this:
508
509        .. code-block:: python
510
511           Integer.tagSet -> Choice
512        """
513        return self.__nonUniqueTagMap
514
515    @property
516    def tagMapUnique(self):
517        """Return a *TagMap* object from unique tags and types recursively.
518
519        Return a :class:`~pyasn1.type.tagmap.TagMap` object by
520        combining tags from *TagMap* objects of children types and
521        associating them with their immediate child type.
522
523        Example
524        -------
525        .. code-block:: python
526
527           OuterType ::= CHOICE {
528               innerType INTEGER
529           }
530
531        Calling *.tagMapUnique* on *OuterType* will yield a map like this:
532
533        .. code-block:: python
534
535           Integer.tagSet -> Choice
536
537        Note
538        ----
539
540        Duplicate *TagSet* objects found in the tree of children
541        types would cause error.
542        """
543        return self.__uniqueTagMap
544
545    @property
546    def hasOptionalOrDefault(self):
547        return self.__hasOptionalOrDefault
548
549    @property
550    def hasOpenTypes(self):
551        return self.__hasOpenTypes
552
553    @property
554    def namedTypes(self):
555        return tuple(self.__namedTypes)
556
557    @property
558    def requiredComponents(self):
559        return self.__requiredComponents
560