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