blob: 994f1c99b3964daaac95fb84152bae1665402acc [file] [log] [blame]
#
# 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.compat import calling
from pyasn1.type import constraint
from pyasn1.type import tag
from pyasn1.type import tagmap
__all__ = ['Asn1Item', 'Asn1Type', 'SimpleAsn1Type',
'ConstructedAsn1Type']
class Asn1Item(object):
@classmethod
def getTypeId(cls, increment=1):
try:
Asn1Item._typeCounter += increment
except AttributeError:
Asn1Item._typeCounter = increment
return Asn1Item._typeCounter
class Asn1Type(Asn1Item):
"""Base class for all classes representing ASN.1 types.
In the user code, |ASN.1| class is normally used only for telling
ASN.1 objects from others.
Note
----
For as long as ASN.1 is concerned, a way to compare ASN.1 types
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
"""
#: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing
#: ASN.1 tag(s) associated with |ASN.1| type.
tagSet = tag.TagSet()
#: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
#: object imposing constraints on initialization values.
subtypeSpec = constraint.ConstraintsIntersection()
# Disambiguation ASN.1 types identification
typeId = None
def __init__(self, **kwargs):
readOnly = {
'tagSet': self.tagSet,
'subtypeSpec': self.subtypeSpec
}
readOnly.update(kwargs)
self.__dict__.update(readOnly)
self._readOnly = readOnly
def __setattr__(self, name, value):
if name[0] != '_' and name in self._readOnly:
raise error.PyAsn1Error('read-only instance attribute "%s"' % name)
self.__dict__[name] = value
def __str__(self):
return self.prettyPrint()
@property
def readOnly(self):
return self._readOnly
@property
def effectiveTagSet(self):
"""For |ASN.1| type is equivalent to *tagSet*
"""
return self.tagSet # used by untagged types
@property
def tagMap(self):
"""Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping ASN.1 tags to ASN.1 objects within callee object.
"""
return tagmap.TagMap({self.tagSet: self})
def isSameTypeWith(self, other, matchTags=True, matchConstraints=True):
"""Examine |ASN.1| type for equality with other ASN.1 type.
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
out ASN.1 types comparison.
Python class inheritance relationship is NOT considered.
Parameters
----------
other: a pyasn1 type object
Class instance representing ASN.1 type.
Returns
-------
: :class:`bool`
:obj:`True` if *other* is |ASN.1| type,
:obj:`False` otherwise.
"""
return (self is other or
(not matchTags or self.tagSet == other.tagSet) and
(not matchConstraints or self.subtypeSpec == other.subtypeSpec))
def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True):
"""Examine |ASN.1| type for subtype relationship with other ASN.1 type.
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
out ASN.1 types comparison.
Python class inheritance relationship is NOT considered.
Parameters
----------
other: a pyasn1 type object
Class instance representing ASN.1 type.
Returns
-------
: :class:`bool`
:obj:`True` if *other* is a subtype of |ASN.1| type,
:obj:`False` otherwise.
"""
return (not matchTags or
(self.tagSet.isSuperTagSetOf(other.tagSet)) and
(not matchConstraints or self.subtypeSpec.isSuperTypeOf(other.subtypeSpec)))
@staticmethod
def isNoValue(*values):
for value in values:
if value is not noValue:
return False
return True
def prettyPrint(self, scope=0):
raise NotImplementedError()
# backward compatibility
def getTagSet(self):
return self.tagSet
def getEffectiveTagSet(self):
return self.effectiveTagSet
def getTagMap(self):
return self.tagMap
def getSubtypeSpec(self):
return self.subtypeSpec
# backward compatibility
def hasValue(self):
return self.isValue
# Backward compatibility
Asn1ItemBase = Asn1Type
class NoValue(object):
"""Create a singleton instance of NoValue class.
The *NoValue* sentinel object represents an instance of ASN.1 schema
object as opposed to ASN.1 value object.
Only ASN.1 schema-related operations can be performed on ASN.1
schema objects.
Warning
-------
Any operation attempted on the *noValue* object will raise the
*PyAsn1Error* exception.
"""
skipMethods = set(
('__slots__',
# attributes
'__getattribute__',
'__getattr__',
'__setattr__',
'__delattr__',
# class instance
'__class__',
'__init__',
'__del__',
'__new__',
'__repr__',
'__qualname__',
'__objclass__',
'im_class',
'__sizeof__',
# pickle protocol
'__reduce__',
'__reduce_ex__',
'__getnewargs__',
'__getinitargs__',
'__getstate__',
'__setstate__')
)
_instance = None
def __new__(cls):
if cls._instance is None:
def getPlug(name):
def plug(self, *args, **kw):
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % name)
return plug
op_names = [name
for typ in (str, int, list, dict)
for name in dir(typ)
if (name not in cls.skipMethods and
name.startswith('__') and
name.endswith('__') and
calling.callable(getattr(typ, name)))]
for name in set(op_names):
setattr(cls, name, getPlug(name))
cls._instance = object.__new__(cls)
return cls._instance
def __getattr__(self, attr):
if attr in self.skipMethods:
raise AttributeError('Attribute %s not present' % attr)
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr)
def __repr__(self):
return '<%s object>' % self.__class__.__name__
noValue = NoValue()
class SimpleAsn1Type(Asn1Type):
"""Base class for all simple classes representing ASN.1 types.
ASN.1 distinguishes types by their ability to hold other objects.
Scalar types are known as *simple* in ASN.1.
In the user code, |ASN.1| class is normally used only for telling
ASN.1 objects from others.
Note
----
For as long as ASN.1 is concerned, a way to compare ASN.1 types
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
"""
#: Default payload value
defaultValue = noValue
def __init__(self, value=noValue, **kwargs):
Asn1Type.__init__(self, **kwargs)
if value is noValue:
value = self.defaultValue
else:
value = self.prettyIn(value)
try:
self.subtypeSpec(value)
except error.PyAsn1Error:
exType, exValue, exTb = sys.exc_info()
raise exType('%s at %s' % (exValue, self.__class__.__name__))
self._value = value
def __repr__(self):
representation = '%s %s object' % (
self.__class__.__name__, self.isValue and 'value' or 'schema')
for attr, value in self.readOnly.items():
if value:
representation += ', %s %s' % (attr, value)
if self.isValue:
value = self.prettyPrint()
if len(value) > 32:
value = value[:16] + '...' + value[-16:]
representation += ', payload [%s]' % value
return '<%s>' % representation
def __eq__(self, other):
return self is other and True or self._value == other
def __ne__(self, other):
return self._value != other
def __lt__(self, other):
return self._value < other
def __le__(self, other):
return self._value <= other
def __gt__(self, other):
return self._value > other
def __ge__(self, other):
return self._value >= other
if sys.version_info[0] <= 2:
def __nonzero__(self):
return self._value and True or False
else:
def __bool__(self):
return self._value and True or False
def __hash__(self):
return hash(self._value)
@property
def isValue(self):
"""Indicate that |ASN.1| object represents ASN.1 value.
If *isValue* is :obj:`False` then this object represents just
ASN.1 schema.
If *isValue* is :obj:`True` then, in addition to its ASN.1 schema
features, this object can also be used like a Python built-in object
(e.g. :class:`int`, :class:`str`, :class:`dict` etc.).
Returns
-------
: :class:`bool`
:obj:`False` if object represents just ASN.1 schema.
:obj:`True` if object represents ASN.1 schema and can be used as a normal value.
Note
----
There is an important distinction between PyASN1 schema and value objects.
The PyASN1 schema objects can only participate in ASN.1 schema-related
operations (e.g. defining or testing the structure of the data). Most
obvious uses of ASN.1 schema is to guide serialisation codecs whilst
encoding/decoding serialised ASN.1 contents.
The PyASN1 value objects can **additionally** participate in many operations
involving regular Python objects (e.g. arithmetic, comprehension etc).
"""
return self._value is not noValue
def clone(self, value=noValue, **kwargs):
"""Create a modified version of |ASN.1| schema or value object.
The `clone()` method accepts the same set arguments as |ASN.1|
class takes on instantiation except that all arguments
of the `clone()` method are optional.
Whatever arguments are supplied, they are used to create a copy
of `self` taking precedence over the ones used to instantiate `self`.
Note
----
Due to the immutable nature of the |ASN.1| object, if no arguments
are supplied, no new |ASN.1| object will be created and `self` will
be returned instead.
"""
if value is noValue:
if not kwargs:
return self
value = self._value
initializers = self.readOnly.copy()
initializers.update(kwargs)
return self.__class__(value, **initializers)
def subtype(self, value=noValue, **kwargs):
"""Create a specialization of |ASN.1| schema or value object.
The subtype relationship between ASN.1 types has no correlation with
subtype relationship between Python types. ASN.1 type is mainly identified
by its tag(s) (:py:class:`~pyasn1.type.tag.TagSet`) and value range
constraints (:py:class:`~pyasn1.type.constraint.ConstraintsIntersection`).
These ASN.1 type properties are implemented as |ASN.1| attributes.
The `subtype()` method accepts the same set arguments as |ASN.1|
class takes on instantiation except that all parameters
of the `subtype()` method are optional.
With the exception of the arguments described below, the rest of
supplied arguments they are used to create a copy of `self` taking
precedence over the ones used to instantiate `self`.
The following arguments to `subtype()` create a ASN.1 subtype out of
|ASN.1| type:
Other Parameters
----------------
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
Implicitly apply given ASN.1 tag object to `self`'s
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
new object's ASN.1 tag(s).
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
Explicitly apply given ASN.1 tag object to `self`'s
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
new object's ASN.1 tag(s).
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
Add ASN.1 constraints object to one of the `self`'s, then
use the result as new object's ASN.1 constraints.
Returns
-------
:
new instance of |ASN.1| schema or value object
Note
----
Due to the immutable nature of the |ASN.1| object, if no arguments
are supplied, no new |ASN.1| object will be created and `self` will
be returned instead.
"""
if value is noValue:
if not kwargs:
return self
value = self._value
initializers = self.readOnly.copy()
implicitTag = kwargs.pop('implicitTag', None)
if implicitTag is not None:
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
explicitTag = kwargs.pop('explicitTag', None)
if explicitTag is not None:
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
for arg, option in kwargs.items():
initializers[arg] += option
return self.__class__(value, **initializers)
def prettyIn(self, value):
return value
def prettyOut(self, value):
return str(value)
def prettyPrint(self, scope=0):
return self.prettyOut(self._value)
def prettyPrintType(self, scope=0):
return '%s -> %s' % (self.tagSet, self.__class__.__name__)
# Backward compatibility
AbstractSimpleAsn1Item = SimpleAsn1Type
#
# Constructed types:
# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice
# * ASN1 types and values are represened by Python class instances
# * Value initialization is made for defaulted components only
# * Primary method of component addressing is by-position. Data model for base
# type is Python sequence. Additional type-specific addressing methods
# may be implemented for particular types.
# * SequenceOf and SetOf types do not implement any additional methods
# * Sequence, Set and Choice types also implement by-identifier addressing
# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing
# * Sequence and Set types may include optional and defaulted
# components
# * Constructed types hold a reference to component types used for value
# verification and ordering.
# * Component type is a scalar type for SequenceOf/SetOf types and a list
# of types for Sequence/Set/Choice.
#
class ConstructedAsn1Type(Asn1Type):
"""Base class for all constructed classes representing ASN.1 types.
ASN.1 distinguishes types by their ability to hold other objects.
Those "nesting" types are known as *constructed* in ASN.1.
In the user code, |ASN.1| class is normally used only for telling
ASN.1 objects from others.
Note
----
For as long as ASN.1 is concerned, a way to compare ASN.1 types
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
"""
#: If :obj:`True`, requires exact component type matching,
#: otherwise subtype relation is only enforced
strictConstraints = False
componentType = None
# backward compatibility, unused
sizeSpec = constraint.ConstraintsIntersection()
def __init__(self, **kwargs):
readOnly = {
'componentType': self.componentType,
# backward compatibility, unused
'sizeSpec': self.sizeSpec
}
# backward compatibility: preserve legacy sizeSpec support
kwargs = self._moveSizeSpec(**kwargs)
readOnly.update(kwargs)
Asn1Type.__init__(self, **readOnly)
def _moveSizeSpec(self, **kwargs):
# backward compatibility, unused
sizeSpec = kwargs.pop('sizeSpec', self.sizeSpec)
if sizeSpec:
subtypeSpec = kwargs.pop('subtypeSpec', self.subtypeSpec)
if subtypeSpec:
subtypeSpec = sizeSpec
else:
subtypeSpec += sizeSpec
kwargs['subtypeSpec'] = subtypeSpec
return kwargs
def __repr__(self):
representation = '%s %s object' % (
self.__class__.__name__, self.isValue and 'value' or 'schema'
)
for attr, value in self.readOnly.items():
if value is not noValue:
representation += ', %s=%r' % (attr, value)
if self.isValue and self.components:
representation += ', payload [%s]' % ', '.join(
[repr(x) for x in self.components])
return '<%s>' % representation
def __eq__(self, other):
return self is other or self.components == other
def __ne__(self, other):
return self.components != other
def __lt__(self, other):
return self.components < other
def __le__(self, other):
return self.components <= other
def __gt__(self, other):
return self.components > other
def __ge__(self, other):
return self.components >= other
if sys.version_info[0] <= 2:
def __nonzero__(self):
return bool(self.components)
else:
def __bool__(self):
return bool(self.components)
@property
def components(self):
raise error.PyAsn1Error('Method not implemented')
def _cloneComponentValues(self, myClone, cloneValueFlag):
pass
def clone(self, **kwargs):
"""Create a modified version of |ASN.1| schema object.
The `clone()` method accepts the same set arguments as |ASN.1|
class takes on instantiation except that all arguments
of the `clone()` method are optional.
Whatever arguments are supplied, they are used to create a copy
of `self` taking precedence over the ones used to instantiate `self`.
Possible values of `self` are never copied over thus `clone()` can
only create a new schema object.
Returns
-------
:
new instance of |ASN.1| type/value
Note
----
Due to the mutable nature of the |ASN.1| object, even if no arguments
are supplied, a new |ASN.1| object will be created and returned.
"""
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
initializers = self.readOnly.copy()
initializers.update(kwargs)
clone = self.__class__(**initializers)
if cloneValueFlag:
self._cloneComponentValues(clone, cloneValueFlag)
return clone
def subtype(self, **kwargs):
"""Create a specialization of |ASN.1| schema object.
The `subtype()` method accepts the same set arguments as |ASN.1|
class takes on instantiation except that all parameters
of the `subtype()` method are optional.
With the exception of the arguments described below, the rest of
supplied arguments they are used to create a copy of `self` taking
precedence over the ones used to instantiate `self`.
The following arguments to `subtype()` create a ASN.1 subtype out of
|ASN.1| type.
Other Parameters
----------------
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
Implicitly apply given ASN.1 tag object to `self`'s
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
new object's ASN.1 tag(s).
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
Explicitly apply given ASN.1 tag object to `self`'s
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
new object's ASN.1 tag(s).
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
Add ASN.1 constraints object to one of the `self`'s, then
use the result as new object's ASN.1 constraints.
Returns
-------
:
new instance of |ASN.1| type/value
Note
----
Due to the mutable nature of the |ASN.1| object, even if no arguments
are supplied, a new |ASN.1| object will be created and returned.
"""
initializers = self.readOnly.copy()
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
implicitTag = kwargs.pop('implicitTag', None)
if implicitTag is not None:
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
explicitTag = kwargs.pop('explicitTag', None)
if explicitTag is not None:
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
for arg, option in kwargs.items():
initializers[arg] += option
clone = self.__class__(**initializers)
if cloneValueFlag:
self._cloneComponentValues(clone, cloneValueFlag)
return clone
def getComponentByPosition(self, idx):
raise error.PyAsn1Error('Method not implemented')
def setComponentByPosition(self, idx, value, verifyConstraints=True):
raise error.PyAsn1Error('Method not implemented')
def setComponents(self, *args, **kwargs):
for idx, value in enumerate(args):
self[idx] = value
for k in kwargs:
self[k] = kwargs[k]
return self
# backward compatibility
def setDefaultComponents(self):
pass
def getComponentType(self):
return self.componentType
# backward compatibility, unused
def verifySizeSpec(self):
self.subtypeSpec(self)
# Backward compatibility
AbstractConstructedAsn1Item = ConstructedAsn1Type