From ce5eb39f377eee8270cbd28f34a056a85e3526bb Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Sun, 8 Oct 2017 21:22:35 -0400 Subject: [PATCH] upgrade pyasn1 to 0.3.7 --- src/pyasn1/__init__.py | 3 +- src/pyasn1/codec/ber/decoder.py | 1323 +++++++---- src/pyasn1/codec/ber/encoder.py | 593 +++-- src/pyasn1/codec/ber/eoo.py | 19 +- src/pyasn1/codec/cer/decoder.py | 71 +- src/pyasn1/codec/cer/encoder.py | 263 ++- src/pyasn1/codec/der/decoder.py | 67 +- src/pyasn1/codec/der/encoder.py | 66 +- src/pyasn1/codec/native/__init__.py | 1 + src/pyasn1/codec/native/decoder.py | 194 ++ src/pyasn1/codec/native/encoder.py | 212 ++ src/pyasn1/compat/binary.py | 31 +- src/pyasn1/compat/calling.py | 20 + src/pyasn1/compat/dateandtime.py | 22 + src/pyasn1/compat/integer.py | 108 + src/pyasn1/compat/octets.py | 34 +- src/pyasn1/compat/string.py | 26 + src/pyasn1/debug.py | 99 +- src/pyasn1/error.py | 21 +- src/pyasn1/type/base.py | 707 ++++-- src/pyasn1/type/char.py | 382 +++- src/pyasn1/type/constraint.py | 174 +- src/pyasn1/type/error.py | 10 +- src/pyasn1/type/namedtype.py | 590 ++++- src/pyasn1/type/namedval.py | 211 +- src/pyasn1/type/tag.py | 356 ++- src/pyasn1/type/tagmap.py | 138 +- src/pyasn1/type/univ.py | 3265 ++++++++++++++++++++------- src/pyasn1/type/useful.py | 184 +- 29 files changed, 6957 insertions(+), 2233 deletions(-) create mode 100644 src/pyasn1/codec/native/__init__.py create mode 100644 src/pyasn1/codec/native/decoder.py create mode 100644 src/pyasn1/codec/native/encoder.py create mode 100644 src/pyasn1/compat/calling.py create mode 100644 src/pyasn1/compat/dateandtime.py create mode 100644 src/pyasn1/compat/integer.py create mode 100644 src/pyasn1/compat/string.py diff --git a/src/pyasn1/__init__.py b/src/pyasn1/__init__.py index 5f093000..c9218a04 100644 --- a/src/pyasn1/__init__.py +++ b/src/pyasn1/__init__.py @@ -1,8 +1,7 @@ import sys # http://www.python.org/dev/peps/pep-0396/ -__version__ = '0.1.9' +__version__ = '0.3.7' if sys.version_info[:2] < (2, 4): raise RuntimeError('PyASN1 requires Python 2.4 or later') - diff --git a/src/pyasn1/codec/ber/decoder.py b/src/pyasn1/codec/ber/decoder.py index 61bfbce7..ee3064f3 100644 --- a/src/pyasn1/codec/ber/decoder.py +++ b/src/pyasn1/codec/ber/decoder.py @@ -1,226 +1,285 @@ -# BER decoder -from pyasn1.type import tag, univ, char, useful, tagmap +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import base, tag, univ, char, useful, tagmap from pyasn1.codec.ber import eoo -from pyasn1.compat.octets import oct2int, isOctetsType +from pyasn1.compat.octets import oct2int, octs2ints, ints2octs, null +from pyasn1.compat.integer import from_bytes from pyasn1 import debug, error -class AbstractDecoder: +__all__ = ['decode'] + +noValue = base.noValue + + +class AbstractDecoder(object): protoComponent = None - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): raise error.PyAsn1Error('Decoder not implemented for %s' % (tagSet,)) - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): raise error.PyAsn1Error('Indefinite length mode decoder not implemented for %s' % (tagSet,)) + class AbstractSimpleDecoder(AbstractDecoder): - tagFormats = (tag.tagFormatSimple,) - def _createComponent(self, asn1Spec, tagSet, value=None): - if tagSet[0][1] not in self.tagFormats: - raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType())) + @staticmethod + def substrateCollector(asn1Object, substrate, length): + return substrate[:length], substrate[length:] + + def _createComponent(self, asn1Spec, tagSet, value=noValue): if asn1Spec is None: - return self.protoComponent.clone(value, tagSet) - elif value is None: + return self.protoComponent.clone(value, tagSet=tagSet) + elif value is noValue: return asn1Spec else: return asn1Spec.clone(value) - -class AbstractConstructedDecoder(AbstractDecoder): - tagFormats = (tag.tagFormatConstructed,) - def _createComponent(self, asn1Spec, tagSet, value=None): - if tagSet[0][1] not in self.tagFormats: - raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType())) - if asn1Spec is None: - return self.protoComponent.clone(tagSet) - else: - return asn1Spec.clone() - + + class ExplicitTagDecoder(AbstractSimpleDecoder): protoComponent = univ.Any('') - tagFormats = (tag.tagFormatConstructed,) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), - substrate, length - ) + self._createComponent(asn1Spec, tagSet, ''), + substrate, length + ) + head, tail = substrate[:length], substrate[length:] - value, _ = decodeFun(head, asn1Spec, tagSet, length) + + value, _ = decodeFun(head, asn1Spec, tagSet, length, **options) + return value, tail - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), - substrate, length - ) - value, substrate = decodeFun(substrate, asn1Spec, tagSet, length) - terminator, substrate = decodeFun(substrate, allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(terminator) and \ - terminator == eoo.endOfOctets: + self._createComponent(asn1Spec, tagSet, ''), + substrate, length + ) + + value, substrate = decodeFun(substrate, asn1Spec, tagSet, length, **options) + + eooMarker, substrate = decodeFun(substrate, allowEoo=True, **options) + + if eooMarker is eoo.endOfOctets: return value, substrate else: raise error.PyAsn1Error('Missing end-of-octets terminator') + explicitTagDecoder = ExplicitTagDecoder() + class IntegerDecoder(AbstractSimpleDecoder): protoComponent = univ.Integer(0) - precomputedValues = { - '\x00': 0, - '\x01': 1, - '\x02': 2, - '\x03': 3, - '\x04': 4, - '\x05': 5, - '\x06': 6, - '\x07': 7, - '\x08': 8, - '\x09': 9, - '\xff': -1, - '\xfe': -2, - '\xfd': -3, - '\xfc': -4, - '\xfb': -5 - } - - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, - state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + + if tagSet[0].tagFormat != tag.tagFormatSimple: + raise error.PyAsn1Error('Simple tag format expected') + head, tail = substrate[:length], substrate[length:] + if not head: return self._createComponent(asn1Spec, tagSet, 0), tail - if head in self.precomputedValues: - value = self.precomputedValues[head] - else: - firstOctet = oct2int(head[0]) - if firstOctet & 0x80: - value = -1 - else: - value = 0 - for octet in head: - value = value << 8 | oct2int(octet) + + value = from_bytes(head, signed=True) + return self._createComponent(asn1Spec, tagSet, value), tail + class BooleanDecoder(IntegerDecoder): protoComponent = univ.Boolean(0) - def _createComponent(self, asn1Spec, tagSet, value=None): + + def _createComponent(self, asn1Spec, tagSet, value=noValue): return IntegerDecoder._createComponent(self, asn1Spec, tagSet, value and 1 or 0) + class BitStringDecoder(AbstractSimpleDecoder): protoComponent = univ.BitString(()) - tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, - state, decodeFun, substrateFun): + supportConstructedForm = True + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): head, tail = substrate[:length], substrate[length:] - if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check? + if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? if not head: raise error.PyAsn1Error('Empty substrate') trailingBits = oct2int(head[0]) if trailingBits > 7: raise error.PyAsn1Error( 'Trailing bits overflow %s' % trailingBits - ) + ) head = head[1:] - lsb = p = 0; l = len(head)-1; b = [] - while p <= l: - if p == l: - lsb = trailingBits - j = 7 - o = oct2int(head[p]) - while j >= lsb: - b.append((o>>j)&0x01) - j = j - 1 - p = p + 1 - return self._createComponent(asn1Spec, tagSet, b), tail - r = self._createComponent(asn1Spec, tagSet, ()) - if substrateFun: - return substrateFun(r, substrate, length) - while head: - component, head = decodeFun(head, self.protoComponent) - r = r + component - return r, tail + value = self.protoComponent.fromOctetString(head, trailingBits) + return self._createComponent(asn1Spec, tagSet, value), tail + + if not self.supportConstructedForm: + raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) + + bitString = self._createComponent(asn1Spec, tagSet) - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet, '') if substrateFun: - return substrateFun(r, substrate, length) + return substrateFun(bitString, substrate, length) + + while head: + component, head = decodeFun(head, self.protoComponent, **options) + bitString += component + + return bitString, tail + + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + bitString = self._createComponent(asn1Spec, tagSet) + + if substrateFun: + return substrateFun(bitString, substrate, length) + while substrate: component, substrate = decodeFun(substrate, self.protoComponent, - allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + allowEoo=True, **options) + if component is eoo.endOfOctets: break - r = r + component + + bitString += component + else: - raise error.SubstrateUnderrunError( - 'No EOO seen before substrate ends' - ) - return r, substrate + raise error.SubstrateUnderrunError('No EOO seen before substrate ends') + + return bitString, substrate + class OctetStringDecoder(AbstractSimpleDecoder): protoComponent = univ.OctetString('') - tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, - state, decodeFun, substrateFun): - head, tail = substrate[:length], substrate[length:] - if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check? - return self._createComponent(asn1Spec, tagSet, head), tail - r = self._createComponent(asn1Spec, tagSet, '') - if substrateFun: - return substrateFun(r, substrate, length) - while head: - component, head = decodeFun(head, self.protoComponent) - r = r + component - return r, tail + supportConstructedForm = True + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + head, tail = substrate[:length], substrate[length:] - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet, '') if substrateFun: - return substrateFun(r, substrate, length) + return substrateFun(self._createComponent(asn1Spec, tagSet), + substrate, length) + + if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? + return self._createComponent(asn1Spec, tagSet, head), tail + + if not self.supportConstructedForm: + raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + + header = null + + while head: + component, head = decodeFun(head, self.protoComponent, + substrateFun=substrateFun, + **options) + header += component + + return self._createComponent(asn1Spec, tagSet, header), tail + + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if substrateFun and substrateFun is not self.substrateCollector: + asn1Object = self._createComponent(asn1Spec, tagSet) + return substrateFun(asn1Object, substrate, length) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + + header = null + while substrate: - component, substrate = decodeFun(substrate, self.protoComponent, - allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, + self.protoComponent, + substrateFun=substrateFun, + allowEoo=True, **options) + if component is eoo.endOfOctets: break - r = r + component + header += component else: raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' - ) - return r, substrate + ) + + return self._createComponent(asn1Spec, tagSet, header), substrate + class NullDecoder(AbstractSimpleDecoder): protoComponent = univ.Null('') - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + + if tagSet[0].tagFormat != tag.tagFormatSimple: + raise error.PyAsn1Error('Simple tag format expected') + head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) + + component = self._createComponent(asn1Spec, tagSet) + if head: raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length) - return r, tail + + return component, tail + class ObjectIdentifierDecoder(AbstractSimpleDecoder): protoComponent = univ.ObjectIdentifier(()) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, - state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if tagSet[0].tagFormat != tag.tagFormatSimple: + raise error.PyAsn1Error('Simple tag format expected') + head, tail = substrate[:length], substrate[length:] if not head: raise error.PyAsn1Error('Empty substrate') + head = octs2ints(head) + oid = () index = 0 substrateLen = len(head) while index < substrateLen: - subId = oct2int(head[index]) + subId = head[index] index += 1 if subId < 128: - oid = oid + (subId,) + oid += (subId,) elif subId > 128: # Construct subid from a number of octets nextSubId = subId @@ -231,36 +290,46 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): raise error.SubstrateUnderrunError( 'Short substrate for sub-OID past %s' % (oid,) ) - nextSubId = oct2int(head[index]) + nextSubId = head[index] index += 1 - oid = oid + ((subId << 7) + nextSubId,) + oid += ((subId << 7) + nextSubId,) elif subId == 128: # ASN.1 spec forbids leading zeros (0x80) in OID # encoding, tolerating it opens a vulnerability. See # http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf # page 7 raise error.PyAsn1Error('Invalid octet 0x80 in OID encoding') - + # Decode two leading arcs if 0 <= oid[0] <= 39: oid = (0,) + oid elif 40 <= oid[0] <= 79: - oid = (1, oid[0]-40) + oid[1:] + oid = (1, oid[0] - 40) + oid[1:] elif oid[0] >= 80: - oid = (2, oid[0]-80) + oid[1:] + oid = (2, oid[0] - 80) + oid[1:] else: raise error.PyAsn1Error('Malformed first OID octet: %s' % head[0]) return self._createComponent(asn1Spec, tagSet, oid), tail + class RealDecoder(AbstractSimpleDecoder): protoComponent = univ.Real() - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if tagSet[0].tagFormat != tag.tagFormatSimple: + raise error.PyAsn1Error('Simple tag format expected') + head, tail = substrate[:length], substrate[length:] + if not head: return self._createComponent(asn1Spec, tagSet, 0.0), tail - fo = oct2int(head[0]); head = head[1:] + + fo = oct2int(head[0]) + head = head[1:] if fo & 0x80: # binary encoding if not head: raise error.PyAsn1Error("Incomplete floating-point value") @@ -272,26 +341,26 @@ class RealDecoder(AbstractSimpleDecoder): if not eo or not head: raise error.PyAsn1Error('Real exponent screwed') e = oct2int(eo[0]) & 0x80 and -1 or 0 - while eo: # exponent + while eo: # exponent e <<= 8 e |= oct2int(eo[0]) eo = eo[1:] - b = fo >> 4 & 0x03 # base bits + b = fo >> 4 & 0x03 # base bits if b > 2: raise error.PyAsn1Error('Illegal Real base') - if b == 1: # encbase = 8 + if b == 1: # encbase = 8 e *= 3 - elif b == 2: # encbase = 16 + elif b == 2: # encbase = 16 e *= 4 p = 0 while head: # value p <<= 8 p |= oct2int(head[0]) head = head[1:] - if fo & 0x40: # sign bit + if fo & 0x40: # sign bit p = -p sf = fo >> 2 & 0x03 # scale bits - p *= 2**sf + p *= 2 ** sf value = (p, 2, e) elif fo & 0x40: # infinite value value = fo & 0x01 and '-inf' or 'inf' @@ -308,248 +377,479 @@ class RealDecoder(AbstractSimpleDecoder): else: raise error.SubstrateUnderrunError( 'Unknown NR (tag %s)' % fo - ) + ) except ValueError: raise error.SubstrateUnderrunError( 'Bad character Real syntax' - ) + ) else: raise error.SubstrateUnderrunError( 'Unknown encoding (tag %s)' % fo - ) + ) return self._createComponent(asn1Spec, tagSet, value), tail - -class SequenceDecoder(AbstractConstructedDecoder): + + +class AbstractConstructedDecoder(AbstractDecoder): + protoComponent = None + + +class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): + protoRecordComponent = None + protoSequenceComponent = None + + def _getComponentTagMap(self, asn1Object, idx): + raise NotImplementedError() + + def _getComponentPositionByType(self, asn1Object, tagSet, idx): + raise NotImplementedError() + + def _decodeComponents(self, substrate, tagSet=None, decodeFun=None, **options): + components = [] + componentTypes = set() + while substrate: + component, substrate = decodeFun(substrate, **options) + if component is eoo.endOfOctets: + break + components.append(component) + componentTypes.add(component.tagSet) + + # Now we have to guess is it SEQUENCE/SET or SEQUENCE OF/SET OF + # The heuristics is: + # * 0-1 component -> likely SEQUENCE OF/SET OF + # * 1+ components of the same type -> likely SEQUENCE OF/SET OF + # * otherwise -> likely SEQUENCE/SET + if len(components) > 1 or len(componentTypes) > 1: + protoComponent = self.protoRecordComponent + else: + protoComponent = self.protoSequenceComponent + + asn1Object = protoComponent.clone( + # construct tagSet from base tag from prototype ASN.1 object + # and additional tags recovered from the substrate + tagSet=tag.TagSet(protoComponent.tagSet.baseTag, *tagSet.superTags) + ) + + for idx, component in enumerate(components): + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + return asn1Object, substrate + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if tagSet[0].tagFormat != tag.tagFormatConstructed: + raise error.PyAsn1Error('Constructed tag format expected') + + head, tail = substrate[:length], substrate[length:] + + if substrateFun is not None: + if asn1Spec is not None: + asn1Object = asn1Spec.clone() + elif self.protoComponent is not None: + asn1Object = self.protoComponent.clone(tagSet=tagSet) + else: + asn1Object = self.protoRecordComponent, self.protoSequenceComponent + + return substrateFun(asn1Object, substrate, length) + + if asn1Spec is None: + asn1Object, trailing = self._decodeComponents( + head, tagSet=tagSet, decodeFun=decodeFun, **options + ) + if trailing: + raise error.PyAsn1Error('Unused trailing %d octets encountered' % len(trailing)) + return asn1Object, tail + + asn1Object = asn1Spec.clone() + + if asn1Object.typeId in (univ.Sequence.typeId, univ.Set.typeId): + + namedTypes = asn1Object.componentType + + isSetType = asn1Object.typeId == univ.Set.typeId + isDeterministic = not isSetType and not namedTypes.hasOptionalOrDefault + + seenIndices = set() + idx = 0 + while head: + if not namedTypes: + asn1Spec = None + elif isSetType: + asn1Spec = namedTypes.tagMapUnique + else: + try: + if isDeterministic: + asn1Spec = namedTypes[idx].asn1Object + elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted: + asn1Spec = namedTypes.getTagMapNearPosition(idx) + else: + asn1Spec = namedTypes[idx].asn1Object + except IndexError: + raise error.PyAsn1Error( + 'Excessive components decoded at %r' % (asn1Object,) + ) + + component, head = decodeFun(head, asn1Spec, **options) + + if not isDeterministic and namedTypes: + if isSetType: + idx = namedTypes.getPositionByType(component.effectiveTagSet) + elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted: + idx = namedTypes.getPositionNearType(component.effectiveTagSet, idx) + + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + seenIndices.add(idx) + idx += 1 + + if namedTypes: + if not namedTypes.requiredComponents.issubset(seenIndices): + raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) + else: + asn1Object.verifySizeSpec() + + else: + asn1Spec = asn1Object.componentType + idx = 0 + while head: + component, head = decodeFun(head, asn1Spec, **options) + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + idx += 1 + + asn1Object.verifySizeSpec() + + return asn1Object, tail + + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if tagSet[0].tagFormat != tag.tagFormatConstructed: + raise error.PyAsn1Error('Constructed tag format expected') + + if substrateFun is not None: + if asn1Spec is not None: + asn1Object = asn1Spec.clone() + elif self.protoComponent is not None: + asn1Object = self.protoComponent.clone(tagSet=tagSet) + else: + asn1Object = self.protoRecordComponent, self.protoSequenceComponent + + return substrateFun(asn1Object, substrate, length) + + if asn1Spec is None: + return self._decodeComponents( + substrate, tagSet=tagSet, decodeFun=decodeFun, allowEoo=True, **options + ) + + asn1Object = asn1Spec.clone() + + if asn1Object.typeId in (univ.Sequence.typeId, univ.Set.typeId): + + namedTypes = asn1Object.componentType + + isSetType = asn1Object.typeId == univ.Set.typeId + isDeterministic = not isSetType and not namedTypes.hasOptionalOrDefault + + seenIndices = set() + idx = 0 + while substrate: + if len(namedTypes) <= idx: + asn1Spec = None + elif isSetType: + asn1Spec = namedTypes.tagMapUnique + else: + try: + if isDeterministic: + asn1Spec = namedTypes[idx].asn1Object + elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted: + asn1Spec = namedTypes.getTagMapNearPosition(idx) + else: + asn1Spec = namedTypes[idx].asn1Object + except IndexError: + raise error.PyAsn1Error( + 'Excessive components decoded at %r' % (asn1Object,) + ) + + component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True, **options) + if component is eoo.endOfOctets: + break + + if not isDeterministic and namedTypes: + if isSetType: + idx = namedTypes.getPositionByType(component.effectiveTagSet) + elif namedTypes[idx].isOptional or namedTypes[idx].isDefaulted: + idx = namedTypes.getPositionNearType(component.effectiveTagSet, idx) + + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + seenIndices.add(idx) + idx += 1 + + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + + if namedTypes: + if not namedTypes.requiredComponents.issubset(seenIndices): + raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) + else: + asn1Object.verifySizeSpec() + + else: + asn1Spec = asn1Object.componentType + idx = 0 + while substrate: + component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True, **options) + if component is eoo.endOfOctets: + break + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + idx += 1 + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + asn1Object.verifySizeSpec() + + return asn1Object, substrate + + +class SequenceOrSequenceOfDecoder(UniversalConstructedTypeDecoder): + protoRecordComponent = univ.Sequence() + protoSequenceComponent = univ.SequenceOf() + + +class SequenceDecoder(SequenceOrSequenceOfDecoder): protoComponent = univ.Sequence() - def _getComponentTagMap(self, r, idx): - try: - return r.getComponentTagMapNearPosition(idx) - except error.PyAsn1Error: - return - def _getComponentPositionByType(self, r, t, idx): - return r.getComponentPositionNearType(t, idx) - - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) - idx = 0 - if substrateFun: - return substrateFun(r, substrate, length) - while head: - asn1Spec = self._getComponentTagMap(r, idx) - component, head = decodeFun(head, asn1Spec) - idx = self._getComponentPositionByType( - r, component.getEffectiveTagSet(), idx - ) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - r.setDefaultComponents() - r.verifySizeSpec() - return r, tail - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) - if substrateFun: - return substrateFun(r, substrate, length) - idx = 0 - while substrate: - asn1Spec = self._getComponentTagMap(r, idx) - component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: - break - idx = self._getComponentPositionByType( - r, component.getEffectiveTagSet(), idx - ) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - else: - raise error.SubstrateUnderrunError( - 'No EOO seen before substrate ends' - ) - r.setDefaultComponents() - r.verifySizeSpec() - return r, substrate +class SequenceOfDecoder(SequenceOrSequenceOfDecoder): + protoComponent = univ.SequenceOf() -class SequenceOfDecoder(AbstractConstructedDecoder): - protoComponent = univ.SequenceOf() - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) - if substrateFun: - return substrateFun(r, substrate, length) - asn1Spec = r.getComponentType() - idx = 0 - while head: - component, head = decodeFun(head, asn1Spec) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - r.verifySizeSpec() - return r, tail - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) - if substrateFun: - return substrateFun(r, substrate, length) - asn1Spec = r.getComponentType() - idx = 0 - while substrate: - component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: - break - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - else: - raise error.SubstrateUnderrunError( - 'No EOO seen before substrate ends' - ) - r.verifySizeSpec() - return r, substrate +class SetOrSetOfDecoder(UniversalConstructedTypeDecoder): + protoRecordComponent = univ.Set() + protoSequenceComponent = univ.SetOf() -class SetDecoder(SequenceDecoder): + +class SetDecoder(SetOrSetOfDecoder): protoComponent = univ.Set() - def _getComponentTagMap(self, r, idx): - return r.getComponentTagMap() - def _getComponentPositionByType(self, r, t, idx): - nextIdx = r.getComponentPositionByType(t) - if nextIdx is None: - return idx - else: - return nextIdx - -class SetOfDecoder(SequenceOfDecoder): + + +class SetOfDecoder(SetOrSetOfDecoder): protoComponent = univ.SetOf() - + + class ChoiceDecoder(AbstractConstructedDecoder): protoComponent = univ.Choice() - tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) - if substrateFun: - return substrateFun(r, substrate, length) - if r.getTagSet() == tagSet: # explicitly tagged Choice - component, head = decodeFun( - head, r.getComponentTagMap() - ) - else: - component, head = decodeFun( - head, r.getComponentTagMap(), tagSet, length, state - ) - if isinstance(component, univ.Choice): - effectiveTagSet = component.getEffectiveTagSet() - else: - effectiveTagSet = component.getTagSet() - r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None) - return r, tail - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + head, tail = substrate[:length], substrate[length:] + if asn1Spec is None: + asn1Object = self.protoComponent.clone(tagSet=tagSet) + else: + asn1Object = asn1Spec.clone() if substrateFun: - return substrateFun(r, substrate, length) - if r.getTagSet() == tagSet: # explicitly tagged Choice - component, substrate = decodeFun(substrate, r.getComponentTagMap()) + return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice + component, head = decodeFun( + head, asn1Object.componentTagMap, **options + ) + else: + component, head = decodeFun( + head, asn1Object.componentTagMap, + tagSet, length, state, **options + ) + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( + effectiveTagSet, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False, + innerFlag=False + ) + return asn1Object, tail + + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if asn1Spec is None: + asn1Object = self.protoComponent.clone(tagSet=tagSet) + else: + asn1Object = asn1Spec.clone() + if substrateFun: + return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice + component, substrate = decodeFun( + substrate, asn1Object.componentType.tagMapUnique, **options + ) # eat up EOO marker - eooMarker, substrate = decodeFun(substrate, allowEoo=True) - if not eoo.endOfOctets.isSameTypeWith(eooMarker) or \ - eooMarker != eoo.endOfOctets: + eooMarker, substrate = decodeFun( + substrate, allowEoo=True, **options + ) + if eooMarker is not eoo.endOfOctets: raise error.PyAsn1Error('No EOO seen before substrate ends') else: - component, substrate= decodeFun( - substrate, r.getComponentTagMap(), tagSet, length, state + component, substrate = decodeFun( + substrate, asn1Object.componentType.tagMapUnique, + tagSet, length, state, **options ) - if isinstance(component, univ.Choice): - effectiveTagSet = component.getEffectiveTagSet() - else: - effectiveTagSet = component.getTagSet() - r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None) - return r, substrate + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( + effectiveTagSet, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False, + innerFlag=False + ) + return asn1Object, substrate + class AnyDecoder(AbstractSimpleDecoder): protoComponent = univ.Any() - tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - if asn1Spec is None or \ - asn1Spec is not None and tagSet != asn1Spec.getTagSet(): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if asn1Spec is None or asn1Spec is not None and tagSet != asn1Spec.tagSet: + fullSubstrate = options['fullSubstrate'] + # untagged Any container, recover inner header substrate - length = length + len(fullSubstrate) - len(substrate) + length += len(fullSubstrate) - len(substrate) substrate = fullSubstrate + if substrateFun: return substrateFun(self._createComponent(asn1Spec, tagSet), substrate, length) + head, tail = substrate[:length], substrate[length:] + return self._createComponent(asn1Spec, tagSet, value=head), tail - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - if asn1Spec is not None and tagSet == asn1Spec.getTagSet(): + def indefLenValueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): + if asn1Spec is not None and tagSet == asn1Spec.tagSet: # tagged Any type -- consume header substrate - header = '' + header = null else: + fullSubstrate = options['fullSubstrate'] + # untagged Any, recover header substrate header = fullSubstrate[:-len(substrate)] - r = self._createComponent(asn1Spec, tagSet, header) - # Any components do not inherit initial tag asn1Spec = self.protoComponent - - if substrateFun: - return substrateFun(r, substrate, length) + + if substrateFun and substrateFun is not self.substrateCollector: + asn1Object = self._createComponent(asn1Spec, tagSet) + return substrateFun(asn1Object, header + substrate, length + len(header)) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + while substrate: - component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, asn1Spec, + substrateFun=substrateFun, + allowEoo=True, **options) + if component is eoo.endOfOctets: break - r = r + component + header += component else: raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' - ) - return r, substrate + ) + if substrateFun: + return header, substrate + else: + return self._createComponent(asn1Spec, tagSet, header), substrate + # character string types class UTF8StringDecoder(OctetStringDecoder): protoComponent = char.UTF8String() + + class NumericStringDecoder(OctetStringDecoder): protoComponent = char.NumericString() + + class PrintableStringDecoder(OctetStringDecoder): protoComponent = char.PrintableString() + + class TeletexStringDecoder(OctetStringDecoder): protoComponent = char.TeletexString() + + class VideotexStringDecoder(OctetStringDecoder): protoComponent = char.VideotexString() + + class IA5StringDecoder(OctetStringDecoder): protoComponent = char.IA5String() + + class GraphicStringDecoder(OctetStringDecoder): protoComponent = char.GraphicString() + + class VisibleStringDecoder(OctetStringDecoder): protoComponent = char.VisibleString() + + class GeneralStringDecoder(OctetStringDecoder): protoComponent = char.GeneralString() + + class UniversalStringDecoder(OctetStringDecoder): protoComponent = char.UniversalString() + + class BMPStringDecoder(OctetStringDecoder): protoComponent = char.BMPString() + # "useful" types class ObjectDescriptorDecoder(OctetStringDecoder): protoComponent = useful.ObjectDescriptor() + + class GeneralizedTimeDecoder(OctetStringDecoder): protoComponent = useful.GeneralizedTime() + + class UTCTimeDecoder(OctetStringDecoder): protoComponent = useful.UTCTime() + tagMap = { univ.Integer.tagSet: IntegerDecoder(), univ.Boolean.tagSet: BooleanDecoder(), @@ -559,9 +859,9 @@ tagMap = { univ.ObjectIdentifier.tagSet: ObjectIdentifierDecoder(), univ.Enumerated.tagSet: IntegerDecoder(), univ.Real.tagSet: RealDecoder(), - univ.Sequence.tagSet: SequenceDecoder(), # conflicts with SequenceOf - univ.Set.tagSet: SetDecoder(), # conflicts with SetOf - univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any + univ.Sequence.tagSet: SequenceOrSequenceOfDecoder(), # conflicts with SequenceOf + univ.Set.tagSet: SetOrSetOfDecoder(), # conflicts with SetOf + univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any # character string types char.UTF8String.tagSet: UTF8StringDecoder(), char.NumericString.tagSet: NumericStringDecoder(), @@ -590,126 +890,167 @@ typeMap = { univ.Any.typeId: AnyDecoder() } -( stDecodeTag, stDecodeLength, stGetValueDecoder, stGetValueDecoderByAsn1Spec, - stGetValueDecoderByTag, stTryAsExplicitTag, stDecodeValue, - stDumpRawValue, stErrorCondition, stStop ) = [x for x in range(10)] +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + if typeDecoder.protoComponent is not None: + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder -class Decoder: + +(stDecodeTag, + stDecodeLength, + stGetValueDecoder, + stGetValueDecoderByAsn1Spec, + stGetValueDecoderByTag, + stTryAsExplicitTag, + stDecodeValue, + stDumpRawValue, + stErrorCondition, + stStop) = [x for x in range(10)] + + +class Decoder(object): defaultErrorState = stErrorCondition -# defaultErrorState = stDumpRawValue + # defaultErrorState = stDumpRawValue defaultRawDecoder = AnyDecoder() supportIndefLength = True + + # noinspection PyDefaultArgument def __init__(self, tagMap, typeMap={}): self.__tagMap = tagMap self.__typeMap = typeMap # Tag & TagSet objects caches self.__tagCache = {} self.__tagSetCache = {} - - def __call__(self, substrate, asn1Spec=None, tagSet=None, - length=None, state=stDecodeTag, recursiveFlag=1, - substrateFun=None, allowEoo=False): + self.__eooSentinel = ints2octs((0, 0)) + + def __call__(self, substrate, asn1Spec=None, + tagSet=None, length=None, state=stDecodeTag, + decodeFun=None, substrateFun=None, + **options): + if debug.logger & debug.flagDecoder: - debug.logger('decoder called at scope %s with state %d, working with up to %d octets of substrate: %s' % (debug.scope, state, len(substrate), debug.hexdump(substrate))) + logger = debug.logger + else: + logger = None + + if logger: + logger('decoder called at scope %s with state %d, working with up to %d octets of substrate: %s' % (debug.scope, state, len(substrate), debug.hexdump(substrate))) + + allowEoo = options.pop('allowEoo', False) + + # Look for end-of-octets sentinel + if allowEoo and self.supportIndefLength: + if substrate[:2] == self.__eooSentinel: + if logger: + logger('end-of-octets sentinel found') + return eoo.endOfOctets, substrate[2:] + + value = noValue + + tagMap = self.__tagMap + typeMap = self.__typeMap + tagCache = self.__tagCache + tagSetCache = self.__tagSetCache + fullSubstrate = substrate - while state != stStop: - if state == stDecodeTag: + + while state is not stStop: + if state is stDecodeTag: if not substrate: raise error.SubstrateUnderrunError( 'Short octet stream on tag decoding' - ) - if not isOctetsType(substrate) and \ - not isinstance(substrate, univ.OctetString): - raise error.PyAsn1Error('Bad octet stream type') + ) # Decode tag + isShortTag = True firstOctet = substrate[0] substrate = substrate[1:] - if firstOctet in self.__tagCache: - lastTag = self.__tagCache[firstOctet] - else: - t = oct2int(firstOctet) - # Look for end-of-octets sentinel - if t == 0: - if substrate and oct2int(substrate[0]) == 0: - if allowEoo and self.supportIndefLength: - debug.logger and debug.logger & debug.flagDecoder and debug.logger('end-of-octets sentinel found') - value, substrate = eoo.endOfOctets, substrate[1:] - state = stStop - continue - else: - raise error.PyAsn1Error('Unexpected end-of-contents sentinel') - else: - raise error.PyAsn1Error('Zero tag encountered') - tagClass = t&0xC0 - tagFormat = t&0x20 - tagId = t&0x1F + try: + lastTag = tagCache[firstOctet] + except KeyError: + integerTag = oct2int(firstOctet) + tagClass = integerTag & 0xC0 + tagFormat = integerTag & 0x20 + tagId = integerTag & 0x1F if tagId == 0x1F: + isShortTag = False + lengthOctetIdx = 0 tagId = 0 - while 1: - if not substrate: - raise error.SubstrateUnderrunError( - 'Short octet stream on long tag decoding' - ) - t = oct2int(substrate[0]) - tagId = tagId << 7 | (t&0x7F) - substrate = substrate[1:] - if not t&0x80: - break + try: + while True: + integerTag = oct2int(substrate[lengthOctetIdx]) + lengthOctetIdx += 1 + tagId <<= 7 + tagId |= (integerTag & 0x7F) + if not integerTag & 0x80: + break + substrate = substrate[lengthOctetIdx:] + except IndexError: + raise error.SubstrateUnderrunError( + 'Short octet stream on long tag decoding' + ) lastTag = tag.Tag( tagClass=tagClass, tagFormat=tagFormat, tagId=tagId ) - if tagId < 31: + if isShortTag: # cache short tags - self.__tagCache[firstOctet] = lastTag + tagCache[firstOctet] = lastTag if tagSet is None: - if firstOctet in self.__tagSetCache: - tagSet = self.__tagSetCache[firstOctet] + if isShortTag: + try: + tagSet = tagSetCache[firstOctet] + except KeyError: + # base tag not recovered + tagSet = tag.TagSet((), lastTag) + tagSetCache[firstOctet] = tagSet else: - # base tag not recovered tagSet = tag.TagSet((), lastTag) - if firstOctet in self.__tagCache: - self.__tagSetCache[firstOctet] = tagSet else: tagSet = lastTag + tagSet state = stDecodeLength - debug.logger and debug.logger & debug.flagDecoder and debug.logger('tag decoded into %s, decoding length' % tagSet) - if state == stDecodeLength: + if logger: + logger('tag decoded into %s, decoding length' % tagSet) + if state is stDecodeLength: # Decode length if not substrate: raise error.SubstrateUnderrunError( 'Short octet stream on length decoding' ) - firstOctet = oct2int(substrate[0]) - if firstOctet == 128: + firstOctet = oct2int(substrate[0]) + if firstOctet < 128: size = 1 - length = -1 - elif firstOctet < 128: - length, size = firstOctet, 1 - else: + length = firstOctet + elif firstOctet > 128: size = firstOctet & 0x7F # encoded in size bytes - length = 0 - lengthString = substrate[1:size+1] + encodedLength = octs2ints(substrate[1:size + 1]) # missing check on maximum size, which shouldn't be a # problem, we can handle more than is possible - if len(lengthString) != size: + if len(encodedLength) != size: raise error.SubstrateUnderrunError( - '%s<%s at %s' % - (size, len(lengthString), tagSet) - ) - for char in lengthString: - length = (length << 8) | oct2int(char) - size = size + 1 - substrate = substrate[size:] - if length != -1 and len(substrate) < length: - raise error.SubstrateUnderrunError( - '%d-octet short' % (length - len(substrate)) + '%s<%s at %s' % (size, len(encodedLength), tagSet) ) - if length == -1 and not self.supportIndefLength: - error.PyAsn1Error('Indefinite length encoding not supported by this codec') + length = 0 + for lengthOctet in encodedLength: + length <<= 8 + length |= lengthOctet + size += 1 + else: + size = 1 + length = -1 + + substrate = substrate[size:] + if length == -1: + if not self.supportIndefLength: + raise error.PyAsn1Error('Indefinite length encoding not supported by this codec') + else: + if len(substrate) < length: + raise error.SubstrateUnderrunError('%d-octet short' % (length - len(substrate))) state = stGetValueDecoder - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value length decoded into %d, payload substrate is: %s' % (length, debug.hexdump(length == -1 and substrate or substrate[:length]))) - if state == stGetValueDecoder: + if logger: + logger('value length decoded into %d, payload substrate is: %s' % (length, debug.hexdump(length == -1 and substrate or substrate[:length]))) + if state is stGetValueDecoder: if asn1Spec is None: state = stGetValueDecoderByTag else: @@ -729,112 +1070,150 @@ class Decoder: # in an incremental, tag-by-tag fashion (this is the case of # EXPLICIT tag which is most basic). Outermost tag comes first # from the wire. - # - if state == stGetValueDecoderByTag: - if tagSet in self.__tagMap: - concreteDecoder = self.__tagMap[tagSet] - else: + # + if state is stGetValueDecoderByTag: + try: + concreteDecoder = tagMap[tagSet] + except KeyError: concreteDecoder = None if concreteDecoder: state = stDecodeValue else: - _k = tagSet[:1] - if _k in self.__tagMap: - concreteDecoder = self.__tagMap[_k] - else: + try: + concreteDecoder = tagMap[tagSet[:1]] + except KeyError: concreteDecoder = None if concreteDecoder: state = stDecodeValue else: state = stTryAsExplicitTag - if debug.logger and debug.logger & debug.flagDecoder: - debug.logger('codec %s chosen by a built-in type, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as explicit tag')) + if logger: + logger('codec %s chosen by a built-in type, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state is stDecodeValue and 'value' or 'as explicit tag')) debug.scope.push(concreteDecoder is None and '?' or concreteDecoder.protoComponent.__class__.__name__) - if state == stGetValueDecoderByAsn1Spec: - if isinstance(asn1Spec, (dict, tagmap.TagMap)): - if tagSet in asn1Spec: - __chosenSpec = asn1Spec[tagSet] - else: - __chosenSpec = None - if debug.logger and debug.logger & debug.flagDecoder: - debug.logger('candidate ASN.1 spec is a map of:') - for t, v in asn1Spec.getPosMap().items(): - debug.logger(' %s -> %s' % (t, v.__class__.__name__)) - if asn1Spec.getNegMap(): - debug.logger('but neither of: ') - for t, v in asn1Spec.getNegMap().items(): - debug.logger(' %s -> %s' % (t, v.__class__.__name__)) - debug.logger('new candidate ASN.1 spec is %s, chosen by %s' % (__chosenSpec is None and '' or __chosenSpec.prettyPrintType(), tagSet)) + if state is stGetValueDecoderByAsn1Spec: + if asn1Spec.__class__ is tagmap.TagMap: + try: + chosenSpec = asn1Spec[tagSet] + except KeyError: + chosenSpec = None + if logger: + logger('candidate ASN.1 spec is a map of:') + for firstOctet, v in asn1Spec.presentTypes.items(): + logger(' %s -> %s' % (firstOctet, v.__class__.__name__)) + if asn1Spec.skipTypes: + logger('but neither of: ') + for firstOctet, v in asn1Spec.skipTypes.items(): + logger(' %s -> %s' % (firstOctet, v.__class__.__name__)) + logger('new candidate ASN.1 spec is %s, chosen by %s' % (chosenSpec is None and '' or chosenSpec.prettyPrintType(), tagSet)) + elif tagSet == asn1Spec.tagSet or tagSet in asn1Spec.tagMap: + chosenSpec = asn1Spec + if logger: + logger('candidate ASN.1 spec is %s' % asn1Spec.__class__.__name__) else: - __chosenSpec = asn1Spec - debug.logger and debug.logger & debug.flagDecoder and debug.logger('candidate ASN.1 spec is %s' % asn1Spec.__class__.__name__) - if __chosenSpec is not None and ( - tagSet == __chosenSpec.getTagSet() or \ - tagSet in __chosenSpec.getTagMap() - ): - # use base type for codec lookup to recover untagged types - baseTagSet = __chosenSpec.baseTagSet - if __chosenSpec.typeId is not None and \ - __chosenSpec.typeId in self.__typeMap: - # ambiguous type - concreteDecoder = self.__typeMap[__chosenSpec.typeId] - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen for an ambiguous type by type ID %s' % (__chosenSpec.typeId,)) - elif baseTagSet in self.__tagMap: - # base type or tagged subtype - concreteDecoder = self.__tagMap[baseTagSet] - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen by base %s' % (baseTagSet,)) - else: - concreteDecoder = None + chosenSpec = None + + if chosenSpec is not None: + try: + # ambiguous type or just faster codec lookup + concreteDecoder = typeMap[chosenSpec.typeId] + if logger: + logger('value decoder chosen for an ambiguous type by type ID %s' % (chosenSpec.typeId,)) + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(chosenSpec.tagSet.baseTag, chosenSpec.tagSet.baseTag) + try: + # base type or tagged subtype + concreteDecoder = tagMap[baseTagSet] + if logger: + logger('value decoder chosen by base %s' % (baseTagSet,)) + except KeyError: + concreteDecoder = None if concreteDecoder: - asn1Spec = __chosenSpec + asn1Spec = chosenSpec state = stDecodeValue else: state = stTryAsExplicitTag else: concreteDecoder = None state = stTryAsExplicitTag - if debug.logger and debug.logger & debug.flagDecoder: - debug.logger('codec %s chosen by ASN.1 spec, decoding %s' % (state == stDecodeValue and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as explicit tag')) - debug.scope.push(__chosenSpec is None and '?' or __chosenSpec.__class__.__name__) - if state == stTryAsExplicitTag: - if tagSet and \ - tagSet[0][1] == tag.tagFormatConstructed and \ - tagSet[0][0] != tag.tagClassUniversal: + if logger: + logger('codec %s chosen by ASN.1 spec, decoding %s' % (state is stDecodeValue and concreteDecoder.__class__.__name__ or "", state is stDecodeValue and 'value' or 'as explicit tag')) + debug.scope.push(chosenSpec is None and '?' or chosenSpec.__class__.__name__) + if state is stDecodeValue: + if not options.get('recursiveFlag', True) and not substrateFun: # deprecate this + substrateFun = lambda a, b, c: (a, b[:c]) + + options.update(fullSubstrate=fullSubstrate) + + if length == -1: # indef length + value, substrate = concreteDecoder.indefLenValueDecoder( + substrate, asn1Spec, + tagSet, length, stGetValueDecoder, + self, substrateFun, + **options + ) + else: + value, substrate = concreteDecoder.valueDecoder( + substrate, asn1Spec, + tagSet, length, stGetValueDecoder, + self, substrateFun, + **options + ) + if logger: + logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, isinstance(value, base.Asn1Item) and value.prettyPrint() or value, substrate and debug.hexdump(substrate) or '')) + state = stStop + break + if state is stTryAsExplicitTag: + if tagSet and tagSet[0].tagFormat == tag.tagFormatConstructed and tagSet[0].tagClass != tag.tagClassUniversal: # Assume explicit tagging concreteDecoder = explicitTagDecoder state = stDecodeValue - else: + else: concreteDecoder = None state = self.defaultErrorState - debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as failure')) - if state == stDumpRawValue: + if logger: + logger('codec %s chosen, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state is stDecodeValue and 'value' or 'as failure')) + if state is stDumpRawValue: concreteDecoder = self.defaultRawDecoder - debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding value' % concreteDecoder.__class__.__name__) + if logger: + logger('codec %s chosen, decoding value' % concreteDecoder.__class__.__name__) state = stDecodeValue - if state == stDecodeValue: - if recursiveFlag == 0 and not substrateFun: # legacy - substrateFun = lambda a,b,c: (a,b[:c]) - if length == -1: # indef length - value, substrate = concreteDecoder.indefLenValueDecoder( - fullSubstrate, substrate, asn1Spec, tagSet, length, - stGetValueDecoder, self, substrateFun - ) - else: - value, substrate = concreteDecoder.valueDecoder( - fullSubstrate, substrate, asn1Spec, tagSet, length, - stGetValueDecoder, self, substrateFun - ) - state = stStop - debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, value.prettyPrint(), substrate and debug.hexdump(substrate) or '')) - if state == stErrorCondition: + if state is stErrorCondition: raise error.PyAsn1Error( - '%s not in asn1Spec: %s' % (tagSet, asn1Spec) + '%s not in asn1Spec: %r' % (tagSet, asn1Spec) ) - if debug.logger and debug.logger & debug.flagDecoder: + if logger: debug.scope.pop() - debug.logger('decoder left scope %s, call completed' % debug.scope) + logger('decoder left scope %s, call completed' % debug.scope) return value, substrate - + + +#: Turns BER octet stream into an ASN.1 object. +#: +#: Takes BER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: BER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from BER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, typeMap) # XXX diff --git a/src/pyasn1/codec/ber/encoder.py b/src/pyasn1/codec/ber/encoder.py index 0fb4ae71..4ed4c3b7 100644 --- a/src/pyasn1/codec/ber/encoder.py +++ b/src/pyasn1/codec/ber/encoder.py @@ -1,228 +1,265 @@ -# BER encoder -from pyasn1.type import base, tag, univ, char, useful +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import tag, univ, char, useful from pyasn1.codec.ber import eoo from pyasn1.compat.octets import int2oct, oct2int, ints2octs, null, str2octs +from pyasn1.compat.integer import to_bytes from pyasn1 import debug, error -class Error(Exception): pass +__all__ = ['encode'] -class AbstractItemEncoder: + +class AbstractItemEncoder(object): supportIndefLenMode = 1 - def encodeTag(self, t, isConstructed): - tagClass, tagFormat, tagId = t.asTuple() # this is a hotspot - v = tagClass | tagFormat + + # An outcome of otherwise legit call `encodeFun(eoo.endOfOctets)` + eooIntegerSubstrate = (0, 0) + eooOctetsSubstrate = ints2octs(eooIntegerSubstrate) + + # noinspection PyMethodMayBeStatic + def encodeTag(self, singleTag, isConstructed): + tagClass, tagFormat, tagId = singleTag + encodedTag = tagClass | tagFormat if isConstructed: - v = v|tag.tagFormatConstructed + encodedTag |= tag.tagFormatConstructed if tagId < 31: - return int2oct(v|tagId) + return encodedTag | tagId, else: - s = int2oct(tagId&0x7f) - tagId = tagId >> 7 + substrate = tagId & 0x7f, + tagId >>= 7 while tagId: - s = int2oct(0x80|(tagId&0x7f)) + s - tagId = tagId >> 7 - return int2oct(v|0x1F) + s + substrate = (0x80 | (tagId & 0x7f),) + substrate + tagId >>= 7 + return (encodedTag | 0x1F,) + substrate def encodeLength(self, length, defMode): if not defMode and self.supportIndefLenMode: - return int2oct(0x80) + return (0x80,) if length < 0x80: - return int2oct(length) + return length, else: - substrate = null + substrate = () while length: - substrate = int2oct(length&0xff) + substrate - length = length >> 8 + substrate = (length & 0xff,) + substrate + length >>= 8 substrateLen = len(substrate) if substrateLen > 126: - raise Error('Length octets overflow (%d)' % substrateLen) - return int2oct(0x80 | substrateLen) + substrate + raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen) + return (0x80 | substrateLen,) + substrate - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - raise Error('Not implemented') + def encodeValue(self, value, encodeFun, **options): + raise error.PyAsn1Error('Not implemented') - def _encodeEndOfOctets(self, encodeFun, defMode): - if defMode or not self.supportIndefLenMode: - return null - else: - return encodeFun(eoo.endOfOctets, defMode) - - def encode(self, encodeFun, value, defMode, maxChunkSize): - substrate, isConstructed = self.encodeValue( - encodeFun, value, defMode, maxChunkSize + def encode(self, value, encodeFun, **options): + + tagSet = value.tagSet + + # untagged item? + if not tagSet: + substrate, isConstructed, isOctets = self.encodeValue( + value, encodeFun, **options ) - tagSet = value.getTagSet() - if tagSet: - if not isConstructed: # primitive form implies definite mode - defMode = 1 - return self.encodeTag( - tagSet[-1], isConstructed - ) + self.encodeLength( - len(substrate), defMode - ) + substrate + self._encodeEndOfOctets(encodeFun, defMode) - else: - return substrate # untagged value + return substrate + + defMode = options.get('defMode', True) + + for idx, singleTag in enumerate(tagSet.superTags): + + defModeOverride = defMode + + # base tag? + if not idx: + substrate, isConstructed, isOctets = self.encodeValue( + value, encodeFun, **options + ) + + if options.get('ifNotEmpty', False) and not substrate: + return substrate + + # primitive form implies definite mode + if not isConstructed: + defModeOverride = True + + header = self.encodeTag(singleTag, isConstructed) + header += self.encodeLength(len(substrate), defModeOverride) + + if isOctets: + substrate = ints2octs(header) + substrate + + if not defModeOverride: + substrate += self.eooOctetsSubstrate + + else: + substrate = header + substrate + + if not defModeOverride: + substrate += self.eooIntegerSubstrate + + if not isOctets: + substrate = ints2octs(substrate) + + return substrate + class EndOfOctetsEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return null, 0 + def encodeValue(self, value, encodeFun, **options): + return null, False, True -class ExplicitlyTaggedItemEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if isinstance(value, base.AbstractConstructedAsn1Item): - value = value.clone(tagSet=value.getTagSet()[:-1], - cloneValueFlag=1) - else: - value = value.clone(tagSet=value.getTagSet()[:-1]) - return encodeFun(value, defMode, maxChunkSize), 1 - -explicitlyTaggedItemEncoder = ExplicitlyTaggedItemEncoder() class BooleanEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 - _true = ints2octs((1,)) - _false = ints2octs((0,)) - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return value and self._true or self._false, 0 + supportIndefLenMode = False + + def encodeValue(self, value, encodeFun, **options): + return value and (1,) or (0,), False, False + class IntegerEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 + supportIndefLenMode = False supportCompactZero = False - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if value == 0: # shortcut for zero value + + def encodeValue(self, value, encodeFun, **options): + if value == 0: + # de-facto way to encode zero if self.supportCompactZero: - # this seems to be a correct way for encoding zeros - return null, 0 + return (), False, False else: - # this seems to be a widespread way for encoding zeros - return ints2octs((0,)), 0 - octets = [] - value = int(value) # to save on ops on asn1 type - while 1: - octets.insert(0, value & 0xff) - if value == 0 or value == -1: - break - value = value >> 8 - if value == 0 and octets[0] & 0x80: - octets.insert(0, 0) - while len(octets) > 1 and \ - (octets[0] == 0 and octets[1] & 0x80 == 0 or \ - octets[0] == 0xff and octets[1] & 0x80 != 0): - del octets[0] - return ints2octs(octets), 0 + return (0,), False, False + + return to_bytes(int(value), signed=True), False, True + class BitStringEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if not maxChunkSize or len(value) <= maxChunkSize*8: - out_len = (len(value) + 7) // 8 - out_list = out_len * [0] - j = 7 - i = -1 - for val in value: - j += 1 - if j == 8: - i += 1 - j = 0 - out_list[i] = out_list[i] | val << (7-j) - return int2oct(7-j) + ints2octs(out_list), 0 + def encodeValue(self, value, encodeFun, **options): + valueLength = len(value) + if valueLength % 8: + alignedValue = value << (8 - valueLength % 8) else: - pos = 0; substrate = null - while 1: - # count in octets - v = value.clone(value[pos*8:pos*8+maxChunkSize*8]) - if not v: - break - substrate = substrate + encodeFun(v, defMode, maxChunkSize) - pos = pos + maxChunkSize - return substrate, 1 + alignedValue = value + + maxChunkSize = options.get('maxChunkSize', 0) + if not maxChunkSize or len(alignedValue) <= maxChunkSize * 8: + substrate = alignedValue.asOctets() + return int2oct(len(substrate) * 8 - valueLength) + substrate, False, True + + # strip off explicit tags + alignedValue = alignedValue.clone( + tagSet=tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + ) + + stop = 0 + substrate = null + while stop < valueLength: + start = stop + stop = min(start + maxChunkSize * 8, valueLength) + substrate += encodeFun(alignedValue[start:stop], **options) + + return substrate, True, True + class OctetStringEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + def encodeValue(self, value, encodeFun, **options): + maxChunkSize = options.get('maxChunkSize', 0) if not maxChunkSize or len(value) <= maxChunkSize: - return value.asOctets(), 0 + return value.asOctets(), False, True + else: - pos = 0; substrate = null - while 1: - v = value.clone(value[pos:pos+maxChunkSize]) - if not v: + # will strip off explicit tags + baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + + pos = 0 + substrate = null + while True: + chunk = value.clone(value[pos:pos + maxChunkSize], + tagSet=baseTagSet) + if not chunk: break - substrate = substrate + encodeFun(v, defMode, maxChunkSize) - pos = pos + maxChunkSize - return substrate, 1 + substrate += encodeFun(chunk, **options) + pos += maxChunkSize + + return substrate, True, True + class NullEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return null, 0 + supportIndefLenMode = False + + def encodeValue(self, value, encodeFun, **options): + return null, False, True + class ObjectIdentifierEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 - precomputedValues = { - (1, 3, 6, 1, 2): (43, 6, 1, 2), - (1, 3, 6, 1, 4): (43, 6, 1, 4) - } - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + supportIndefLenMode = False + + def encodeValue(self, value, encodeFun, **options): oid = value.asTuple() - if oid[:5] in self.precomputedValues: - octets = self.precomputedValues[oid[:5]] - oid = oid[5:] - else: - if len(oid) < 2: - raise error.PyAsn1Error('Short OID %s' % (value,)) - octets = () + # Build the first pair + try: + first = oid[0] + second = oid[1] - # Build the first twos - if oid[0] == 0 and 0 <= oid[1] <= 39: - oid = (oid[1],) + oid[2:] - elif oid[0] == 1 and 0 <= oid[1] <= 39: - oid = (oid[1] + 40,) + oid[2:] - elif oid[0] == 2: - oid = (oid[1] + 80,) + oid[2:] + except IndexError: + raise error.PyAsn1Error('Short OID %s' % (value,)) + + if 0 <= second <= 39: + if first == 1: + oid = (second + 40,) + oid[2:] + elif first == 0: + oid = (second,) + oid[2:] + elif first == 2: + oid = (second + 80,) + oid[2:] else: - raise error.PyAsn1Error( - 'Impossible initial arcs %s at %s' % (oid[:2], value) - ) + raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) + elif first == 2: + oid = (second + 80,) + oid[2:] + else: + raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) + + octets = () # Cycle through subIds - for subId in oid: - if subId > -1 and subId < 128: + for subOid in oid: + if 0 <= subOid <= 127: # Optimize for the common case - octets = octets + (subId & 0x7f,) - elif subId < 0: - raise error.PyAsn1Error( - 'Negative OID arc %s at %s' % (subId, value) - ) - else: + octets += (subOid,) + elif subOid > 127: # Pack large Sub-Object IDs - res = (subId & 0x7f,) - subId = subId >> 7 - while subId > 0: - res = (0x80 | (subId & 0x7f),) + res - subId = subId >> 7 + res = (subOid & 0x7f,) + subOid >>= 7 + while subOid: + res = (0x80 | (subOid & 0x7f),) + res + subOid >>= 7 # Add packed Sub-Object ID to resulted Object ID octets += res + else: + raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value)) + + return octets, False, False - return ints2octs(octets), 0 class RealEncoder(AbstractItemEncoder): supportIndefLenMode = 0 - binEncBase = 2 # set to None to choose encoding base automatically - def _dropFloatingPoint(self, m, encbase, e): + binEncBase = 2 # set to None to choose encoding base automatically + + @staticmethod + def _dropFloatingPoint(m, encbase, e): ms, es = 1, 1 if m < 0: ms = -1 # mantissa sign if e < 0: es = -1 # exponenta sign - m *= ms + m *= ms if encbase == 8: - m = m*2**(abs(e) % 3 * es) + m *= 2 ** (abs(e) % 3 * es) e = abs(e) // 3 * es elif encbase == 16: - m = m*2**(abs(e) % 4 * es) + m *= 2 ** (abs(e) % 4 * es) e = abs(e) // 4 * es - while 1: + while True: if int(m) != m: m *= encbase e -= 1 @@ -232,41 +269,43 @@ class RealEncoder(AbstractItemEncoder): def _chooseEncBase(self, value): m, b, e = value - base = [2, 8, 16] - if value.binEncBase in base: + encBase = [2, 8, 16] + if value.binEncBase in encBase: return self._dropFloatingPoint(m, value.binEncBase, e) - elif self.binEncBase in base: + elif self.binEncBase in encBase: return self._dropFloatingPoint(m, self.binEncBase, e) # auto choosing base 2/8/16 mantissa = [m, m, m] exponenta = [e, e, e] - encbase = 2 + sign = 1 + encbase = 2 e = float('inf') for i in range(3): - sign, mantissa[i], base[i], exponenta[i] = \ - self._dropFloatingPoint(mantissa[i], base[i], exponenta[i]) - if abs(exponenta[i]) < abs(e) or \ - (abs(exponenta[i]) == abs(e) and mantissa[i] < m): + (sign, + mantissa[i], + encBase[i], + exponenta[i]) = self._dropFloatingPoint(mantissa[i], encBase[i], exponenta[i]) + if abs(exponenta[i]) < abs(e) or (abs(exponenta[i]) == abs(e) and mantissa[i] < m): e = exponenta[i] m = int(mantissa[i]) - encbase = base[i] + encbase = encBase[i] return sign, m, encbase, e - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if value.isPlusInfinity(): - return int2oct(0x40), 0 - if value.isMinusInfinity(): - return int2oct(0x41), 0 + def encodeValue(self, value, encodeFun, **options): + if value.isPlusInf: + return (0x40,), False, False + if value.isMinusInf: + return (0x41,), False, False m, b, e = value if not m: - return null, 0 + return null, False, True if b == 10: - return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), 0 + return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), False, True elif b == 2: - fo = 0x80 # binary encoding + fo = 0x80 # binary encoding ms, m, encbase, e = self._chooseEncBase(value) - if ms < 0: # mantissa sign - fo = fo | 0x40 # sign bit + if ms < 0: # mantissa sign + fo |= 0x40 # sign bit # exponenta & mantissa normalization if encbase == 2: while m & 0x1 == 0: @@ -277,24 +316,24 @@ class RealEncoder(AbstractItemEncoder): m >>= 3 e += 1 fo |= 0x10 - else: # encbase = 16 + else: # encbase = 16 while m & 0xf == 0: m >>= 4 e += 1 fo |= 0x20 - sf = 0 # scale factor + sf = 0 # scale factor while m & 0x1 == 0: m >>= 1 sf += 1 if sf > 3: - raise error.PyAsn1Error('Scale factor overflow') # bug if raised + raise error.PyAsn1Error('Scale factor overflow') # bug if raised fo |= sf << 2 eo = null if e == 0 or e == -1: - eo = int2oct(e&0xff) - else: + eo = int2oct(e & 0xff) + else: while e not in (0, -1): - eo = int2oct(e&0xff) + eo + eo = int2oct(e & 0xff) + eo e >>= 8 if e == 0 and eo and oct2int(eo[0]) & 0x80: eo = int2oct(0) + eo @@ -311,51 +350,57 @@ class RealEncoder(AbstractItemEncoder): fo |= 2 else: fo |= 3 - eo = int2oct(n&0xff) + eo + eo = int2oct(n & 0xff) + eo po = null while m: - po = int2oct(m&0xff) + po + po = int2oct(m & 0xff) + po m >>= 8 substrate = int2oct(fo) + eo + po - return substrate, 0 + return substrate, False, True else: raise error.PyAsn1Error('Prohibited Real base %s' % b) + class SequenceEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - value.setDefaultComponents() + def encodeValue(self, value, encodeFun, **options): value.verifySizeSpec() - substrate = null; idx = len(value) + + namedTypes = value.componentType + substrate = null + + idx = len(value) while idx > 0: - idx = idx - 1 - if value[idx] is None: # Optional component - continue - component = value.getDefaultComponentByPosition(idx) - if component is not None and component == value[idx]: - continue - substrate = encodeFun( - value[idx], defMode, maxChunkSize - ) + substrate - return substrate, 1 + idx -= 1 + if namedTypes: + if namedTypes[idx].isOptional and not value[idx].isValue: + continue + if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: + continue + substrate = encodeFun(value[idx], **options) + substrate + + return substrate, True, True + class SequenceOfEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + def encodeValue(self, value, encodeFun, **options): value.verifySizeSpec() - substrate = null; idx = len(value) + substrate = null + idx = len(value) while idx > 0: - idx = idx - 1 - substrate = encodeFun( - value[idx], defMode, maxChunkSize - ) + substrate - return substrate, 1 + idx -= 1 + substrate = encodeFun(value[idx], **options) + substrate + return substrate, True, True + class ChoiceEncoder(AbstractItemEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return encodeFun(value.getComponent(), defMode, maxChunkSize), 1 + def encodeValue(self, value, encodeFun, **options): + return encodeFun(value.getComponent(), **options), True, True + class AnyEncoder(OctetStringEncoder): - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return value.asOctets(), defMode == 0 + def encodeValue(self, value, encodeFun, **options): + return value.asOctets(), not options.get('defMode', True), True + tagMap = { eoo.endOfOctets.tagSet: EndOfOctetsEncoder(), @@ -386,48 +431,118 @@ tagMap = { # useful types useful.ObjectDescriptor.tagSet: OctetStringEncoder(), useful.GeneralizedTime.tagSet: OctetStringEncoder(), - useful.UTCTime.tagSet: OctetStringEncoder() - } + useful.UTCTime.tagSet: OctetStringEncoder() +} -# Type-to-codec map for ambiguous ASN.1 types +# Put in ambiguous & non-ambiguous types for faster codec lookup typeMap = { + univ.Boolean.typeId: BooleanEncoder(), + univ.Integer.typeId: IntegerEncoder(), + univ.BitString.typeId: BitStringEncoder(), + univ.OctetString.typeId: OctetStringEncoder(), + univ.Null.typeId: NullEncoder(), + univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(), + univ.Enumerated.typeId: IntegerEncoder(), + univ.Real.typeId: RealEncoder(), + # Sequence & Set have same tags as SequenceOf & SetOf univ.Set.typeId: SequenceEncoder(), univ.SetOf.typeId: SequenceOfEncoder(), univ.Sequence.typeId: SequenceEncoder(), univ.SequenceOf.typeId: SequenceOfEncoder(), univ.Choice.typeId: ChoiceEncoder(), - univ.Any.typeId: AnyEncoder() - } + univ.Any.typeId: AnyEncoder(), + # character string types + char.UTF8String.typeId: OctetStringEncoder(), + char.NumericString.typeId: OctetStringEncoder(), + char.PrintableString.typeId: OctetStringEncoder(), + char.TeletexString.typeId: OctetStringEncoder(), + char.VideotexString.typeId: OctetStringEncoder(), + char.IA5String.typeId: OctetStringEncoder(), + char.GraphicString.typeId: OctetStringEncoder(), + char.VisibleString.typeId: OctetStringEncoder(), + char.GeneralString.typeId: OctetStringEncoder(), + char.UniversalString.typeId: OctetStringEncoder(), + char.BMPString.typeId: OctetStringEncoder(), + # useful types + useful.ObjectDescriptor.typeId: OctetStringEncoder(), + useful.GeneralizedTime.typeId: OctetStringEncoder(), + useful.UTCTime.typeId: OctetStringEncoder() +} -class Encoder: - supportIndefLength = True + +class Encoder(object): + fixedDefLengthMode = None + fixedChunkSize = None + + # noinspection PyDefaultArgument def __init__(self, tagMap, typeMap={}): self.__tagMap = tagMap self.__typeMap = typeMap - def __call__(self, value, defMode=True, maxChunkSize=0): - if not defMode and not self.supportIndefLength: - raise error.PyAsn1Error('Indefinite length encoding not supported by this codec') - debug.logger & debug.flagEncoder and debug.logger('encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % (not defMode and 'in' or '', maxChunkSize, value.prettyPrintType(), value.prettyPrint())) - tagSet = value.getTagSet() - if len(tagSet) > 1: - concreteEncoder = explicitlyTaggedItemEncoder + def __call__(self, value, **options): + + if debug.logger & debug.flagEncoder: + logger = debug.logger else: - if value.typeId is not None and value.typeId in self.__typeMap: - concreteEncoder = self.__typeMap[value.typeId] - elif tagSet in self.__tagMap: - concreteEncoder = self.__tagMap[tagSet] - else: - tagSet = value.baseTagSet - if tagSet in self.__tagMap: - concreteEncoder = self.__tagMap[tagSet] - else: - raise Error('No encoder for %s' % (value,)) - debug.logger & debug.flagEncoder and debug.logger('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet)) - substrate = concreteEncoder.encode( - self, value, defMode, maxChunkSize - ) - debug.logger & debug.flagEncoder and debug.logger('built %s octets of substrate: %s\nencoder completed' % (len(substrate), debug.hexdump(substrate))) + logger = None + + if logger: + logger('encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % (not options.get('defMode', True) and 'in' or '', options.get('maxChunkSize', 0), value.prettyPrintType(), value.prettyPrint())) + + if self.fixedDefLengthMode is not None: + options.update(defMode=self.fixedDefLengthMode) + + if self.fixedChunkSize is not None: + options.update(maxChunkSize=self.fixedChunkSize) + + tagSet = value.tagSet + + try: + concreteEncoder = self.__typeMap[value.typeId] + + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + + try: + concreteEncoder = self.__tagMap[baseTagSet] + + except KeyError: + raise error.PyAsn1Error('No encoder for %s' % (value,)) + + if logger: + logger('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet)) + + substrate = concreteEncoder.encode(value, self, **options) + + if logger: + logger('codec %s built %s octets of substrate: %s\nencoder completed' % (concreteEncoder, len(substrate), debug.hexdump(substrate))) + return substrate +#: Turns ASN.1 object into BER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a BER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/ber/eoo.py b/src/pyasn1/codec/ber/eoo.py index 379be199..28e33c5a 100644 --- a/src/pyasn1/codec/ber/eoo.py +++ b/src/pyasn1/codec/ber/eoo.py @@ -1,8 +1,25 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import base, tag + class EndOfOctets(base.AbstractSimpleAsn1Item): defaultValue = 0 tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00) - ) + ) + + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = object.__new__(cls, *args, **kwargs) + + return cls._instance + + endOfOctets = EndOfOctets() diff --git a/src/pyasn1/codec/cer/decoder.py b/src/pyasn1/codec/cer/decoder.py index 1770cd87..5e3e8bf2 100644 --- a/src/pyasn1/codec/cer/decoder.py +++ b/src/pyasn1/codec/cer/decoder.py @@ -1,13 +1,24 @@ -# CER decoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.codec.ber import decoder from pyasn1.compat.octets import oct2int from pyasn1 import error +__all__ = ['decode'] + + class BooleanDecoder(decoder.AbstractSimpleDecoder): protoComponent = univ.Boolean(0) - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, - state, decodeFun, substrateFun): + + def valueDecoder(self, substrate, asn1Spec, + tagSet=None, length=None, state=None, + decodeFun=None, substrateFun=None, + **options): head, tail = substrate[:length], substrate[length:] if not head or length != 1: raise error.PyAsn1Error('Not single-octet Boolean payload') @@ -23,13 +34,57 @@ class BooleanDecoder(decoder.AbstractSimpleDecoder): raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte) return self._createComponent(asn1Spec, tagSet, value), tail +# TODO: prohibit non-canonical encoding +BitStringDecoder = decoder.BitStringDecoder +OctetStringDecoder = decoder.OctetStringDecoder +RealDecoder = decoder.RealDecoder + tagMap = decoder.tagMap.copy() -tagMap.update({ - univ.Boolean.tagSet: BooleanDecoder() - }) +tagMap.update( + {univ.Boolean.tagSet: BooleanDecoder(), + univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: OctetStringDecoder(), + univ.Real.tagSet: RealDecoder()} +) -typeMap = decoder.typeMap +typeMap = decoder.typeMap.copy() -class Decoder(decoder.Decoder): pass +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + if typeDecoder.protoComponent is not None: + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder + +class Decoder(decoder.Decoder): + pass + + +#: Turns CER octet stream into an ASN.1 object. +#: +#: Takes CER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: CER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from CER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, decoder.typeMap) diff --git a/src/pyasn1/codec/cer/encoder.py b/src/pyasn1/codec/cer/encoder.py index 61ce8a15..4700de0a 100644 --- a/src/pyasn1/codec/cer/encoder.py +++ b/src/pyasn1/codec/cer/encoder.py @@ -1,130 +1,221 @@ -# CER encoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.type import useful from pyasn1.codec.ber import encoder -from pyasn1.compat.octets import int2oct, str2octs, null +from pyasn1.compat.octets import str2octs, null from pyasn1 import error +__all__ = ['encode'] + + class BooleanEncoder(encoder.IntegerEncoder): - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - if client == 0: - substrate = int2oct(0) + def encodeValue(self, value, encodeFun, **options): + if value == 0: + substrate = (0,) else: - substrate = int2oct(255) - return substrate, 0 + substrate = (255,) + return substrate, False, False -class BitStringEncoder(encoder.BitStringEncoder): - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - return encoder.BitStringEncoder.encodeValue( - self, encodeFun, client, defMode, 1000 - ) - -class OctetStringEncoder(encoder.OctetStringEncoder): - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - return encoder.OctetStringEncoder.encodeValue( - self, encodeFun, client, defMode, 1000 - ) class RealEncoder(encoder.RealEncoder): def _chooseEncBase(self, value): m, b, e = value return self._dropFloatingPoint(m, b, e) + # specialized GeneralStringEncoder here -class GeneralizedTimeEncoder(OctetStringEncoder): - zchar = str2octs('Z') - pluschar = str2octs('+') - minuschar = str2octs('-') - zero = str2octs('0') - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - octets = client.asOctets() -# This breaks too many existing data items -# if '.' not in octets: -# raise error.PyAsn1Error('Format must include fraction of second: %r' % octets) - if len(octets) < 15: - raise error.PyAsn1Error('Bad UTC time length: %r' % octets) +class TimeEncoderMixIn(object): + zchar, = str2octs('Z') + pluschar, = str2octs('+') + minuschar, = str2octs('-') + commachar, = str2octs(',') + minLength = 12 + maxLength = 19 + + def encodeValue(self, value, encodeFun, **options): + # Encoding constraints: + # - minutes are mandatory, seconds are optional + # - subseconds must NOT be zero + # - no hanging fraction dot + # - time in UTC (Z) + # - only dot is allowed for fractions + + octets = value.asOctets() + + if not self.minLength < len(octets) < self.maxLength: + raise error.PyAsn1Error('Length constraint violated: %r' % value) + if self.pluschar in octets or self.minuschar in octets: raise error.PyAsn1Error('Must be UTC time: %r' % octets) - if octets[-1] != self.zchar[0]: - raise error.PyAsn1Error('Missing timezone specifier: %r' % octets) + + if octets[-1] != self.zchar: + raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % octets) + + if self.commachar in octets: + raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value) + + options.update(maxChunkSize=1000) + return encoder.OctetStringEncoder.encodeValue( - self, encodeFun, client, defMode, 1000 + self, value, encodeFun, **options ) -class UTCTimeEncoder(encoder.OctetStringEncoder): - zchar = str2octs('Z') - pluschar = str2octs('+') - minuschar = str2octs('-') - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - octets = client.asOctets() - if self.pluschar in octets or self.minuschar in octets: - raise error.PyAsn1Error('Must be UTC time: %r' % octets) - if octets and octets[-1] != self.zchar[0]: - client = client.clone(octets + self.zchar) - if len(client) != 13: - raise error.PyAsn1Error('Bad UTC time length: %r' % client) - return encoder.OctetStringEncoder.encodeValue( - self, encodeFun, client, defMode, 1000 - ) + +class GeneralizedTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): + minLength = 12 + maxLength = 19 + + +class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): + minLength = 10 + maxLength = 14 + class SetOfEncoder(encoder.SequenceOfEncoder): - def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - if isinstance(client, univ.SequenceAndSetBase): - client.setDefaultComponents() - client.verifySizeSpec() - substrate = null; idx = len(client) - # This is certainly a hack but how else do I distinguish SetOf - # from Set if they have the same tags&constraints? - if isinstance(client, univ.SequenceAndSetBase): - # Set + @staticmethod + def _sortComponents(components): + # sort by tags regardless of the Choice value (static sort) + return sorted(components, key=lambda x: isinstance(x, univ.Choice) and x.minTagSet or x.tagSet) + + def encodeValue(self, value, encodeFun, **options): + value.verifySizeSpec() + substrate = null + idx = len(value) + if value.typeId == univ.Set.typeId: + namedTypes = value.componentType comps = [] + compsMap = {} while idx > 0: - idx = idx - 1 - if client[idx] is None: # Optional component - continue - if client.getDefaultComponentByPosition(idx) == client[idx]: - continue - comps.append(client[idx]) - comps.sort(key=lambda x: isinstance(x, univ.Choice) and \ - x.getMinTagSet() or x.getTagSet()) - for c in comps: - substrate += encodeFun(c, defMode, maxChunkSize) + idx -= 1 + if namedTypes: + if namedTypes[idx].isOptional and not value[idx].isValue: + continue + if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: + continue + + comps.append(value[idx]) + compsMap[id(value[idx])] = namedTypes and namedTypes[idx].isOptional + + for comp in self._sortComponents(comps): + options.update(ifNotEmpty=compsMap[id(comp)]) + substrate += encodeFun(comp, **options) else: - # SetOf - compSubs = [] - while idx > 0: - idx = idx - 1 - compSubs.append( - encodeFun(client[idx], defMode, maxChunkSize) - ) - compSubs.sort() # perhaps padding's not needed - substrate = null - for compSub in compSubs: - substrate += compSub - return substrate, 1 + components = [encodeFun(x, **options) for x in value] + + # sort by serialized and padded components + if len(components) > 1: + zero = str2octs('\x00') + maxLen = max(map(len, components)) + paddedComponents = [ + (x.ljust(maxLen, zero), x) for x in components + ] + paddedComponents.sort(key=lambda x: x[0]) + + components = [x[1] for x in paddedComponents] + + substrate = null.join(components) + + return substrate, True, True + + +class SequenceEncoder(encoder.SequenceEncoder): + def encodeValue(self, value, encodeFun, **options): + value.verifySizeSpec() + + namedTypes = value.componentType + substrate = null + + idx = len(value) + while idx > 0: + idx -= 1 + if namedTypes: + if namedTypes[idx].isOptional and not value[idx].isValue: + continue + if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: + continue + + options.update(ifNotEmpty=namedTypes and namedTypes[idx].isOptional) + + substrate = encodeFun(value[idx], **options) + substrate + + return substrate, True, True + + +class SequenceOfEncoder(encoder.SequenceOfEncoder): + def encodeValue(self, value, encodeFun, **options): + substrate = null + idx = len(value) + + if options.get('ifNotEmpty', False) and not idx: + return substrate, True, True + + value.verifySizeSpec() + while idx > 0: + idx -= 1 + substrate = encodeFun(value[idx], **options) + substrate + return substrate, True, True + tagMap = encoder.tagMap.copy() tagMap.update({ univ.Boolean.tagSet: BooleanEncoder(), - univ.BitString.tagSet: BitStringEncoder(), - univ.OctetString.tagSet: OctetStringEncoder(), univ.Real.tagSet: RealEncoder(), useful.GeneralizedTime.tagSet: GeneralizedTimeEncoder(), useful.UTCTime.tagSet: UTCTimeEncoder(), - univ.SetOf().tagSet: SetOfEncoder() # conflcts with Set + # Sequence & Set have same tags as SequenceOf & SetOf + univ.SetOf.tagSet: SetOfEncoder(), + univ.Sequence.typeId: SequenceEncoder() }) typeMap = encoder.typeMap.copy() typeMap.update({ + univ.Boolean.typeId: BooleanEncoder(), + univ.Real.typeId: RealEncoder(), + useful.GeneralizedTime.typeId: GeneralizedTimeEncoder(), + useful.UTCTime.typeId: UTCTimeEncoder(), + # Sequence & Set have same tags as SequenceOf & SetOf univ.Set.typeId: SetOfEncoder(), - univ.SetOf.typeId: SetOfEncoder() + univ.SetOf.typeId: SetOfEncoder(), + univ.Sequence.typeId: SequenceEncoder(), + univ.SequenceOf.typeId: SequenceOfEncoder() }) -class Encoder(encoder.Encoder): - def __call__(self, client, defMode=False, maxChunkSize=0): - return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) +class Encoder(encoder.Encoder): + fixedDefLengthMode = False + fixedChunkSize = 1000 + +#: Turns ASN.1 object into CER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a CER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) # EncoderFactory queries class instance and builds a map of tags -> encoders diff --git a/src/pyasn1/codec/der/decoder.py b/src/pyasn1/codec/der/decoder.py index ea58d6d7..77b1cec5 100644 --- a/src/pyasn1/codec/der/decoder.py +++ b/src/pyasn1/codec/der/decoder.py @@ -1,9 +1,70 @@ -# DER decoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import univ from pyasn1.codec.cer import decoder -tagMap = decoder.tagMap -typeMap = decoder.typeMap +__all__ = ['decode'] + + +class BitStringDecoder(decoder.BitStringDecoder): + supportConstructedForm = False + + +class OctetStringDecoder(decoder.OctetStringDecoder): + supportConstructedForm = False + +# TODO: prohibit non-canonical encoding +RealDecoder = decoder.RealDecoder + +tagMap = decoder.tagMap.copy() +tagMap.update( + {univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: OctetStringDecoder(), + univ.Real.tagSet: RealDecoder()} +) + +typeMap = decoder.typeMap.copy() + +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + if typeDecoder.protoComponent is not None: + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder + + class Decoder(decoder.Decoder): supportIndefLength = False + +#: Turns DER octet stream into an ASN.1 object. +#: +#: Takes DER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: DER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from DER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/der/encoder.py b/src/pyasn1/codec/der/encoder.py index 7f55eeb9..d2992a96 100644 --- a/src/pyasn1/codec/der/encoder.py +++ b/src/pyasn1/codec/der/encoder.py @@ -1,32 +1,62 @@ -# DER encoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.codec.cer import encoder -from pyasn1 import error + +__all__ = ['encode'] + class SetOfEncoder(encoder.SetOfEncoder): - def _cmpSetComponents(self, c1, c2): - tagSet1 = isinstance(c1, univ.Choice) and \ - c1.getEffectiveTagSet() or c1.getTagSet() - tagSet2 = isinstance(c2, univ.Choice) and \ - c2.getEffectiveTagSet() or c2.getTagSet() - return cmp(tagSet1, tagSet2) + @staticmethod + def _sortComponents(components): + # sort by tags depending on the actual Choice value (dynamic sort) + return sorted(components, key=lambda x: isinstance(x, univ.Choice) and x.getComponent().tagSet or x.tagSet) tagMap = encoder.tagMap.copy() tagMap.update({ - # Overload CER encoders with BER ones (a bit hackerish XXX) - univ.BitString.tagSet: encoder.encoder.BitStringEncoder(), - univ.OctetString.tagSet: encoder.encoder.OctetStringEncoder(), # Set & SetOf have same tags - univ.SetOf().tagSet: SetOfEncoder() + univ.SetOf.tagSet: SetOfEncoder() +}) + +typeMap = encoder.typeMap.copy() +typeMap.update({ + # Set & SetOf have same tags + univ.Set.typeId: SetOfEncoder(), + univ.SetOf.typeId: SetOfEncoder() }) -typeMap = encoder.typeMap class Encoder(encoder.Encoder): - supportIndefLength = False - def __call__(self, client, defMode=True, maxChunkSize=0): - if not defMode: - raise error.PyAsn1Error('DER forbids indefinite length mode') - return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) + fixedDefLengthMode = True + fixedChunkSize = 0 +#: Turns ASN.1 object into DER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a DER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/native/__init__.py b/src/pyasn1/codec/native/__init__.py new file mode 100644 index 00000000..8c3066b2 --- /dev/null +++ b/src/pyasn1/codec/native/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/src/pyasn1/codec/native/decoder.py b/src/pyasn1/codec/native/decoder.py new file mode 100644 index 00000000..70b22a85 --- /dev/null +++ b/src/pyasn1/codec/native/decoder.py @@ -0,0 +1,194 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import base, univ, char, useful, tag +from pyasn1 import debug, error + +__all__ = ['decode'] + + +class AbstractScalarDecoder(object): + def __call__(self, pyObject, asn1Spec, decodeFun=None, **options): + return asn1Spec.clone(pyObject) + + +class BitStringDecoder(AbstractScalarDecoder): + def __call__(self, pyObject, asn1Spec, decodeFun=None, **options): + return asn1Spec.clone(univ.BitString.fromBinaryString(pyObject)) + + +class SequenceOrSetDecoder(object): + def __call__(self, pyObject, asn1Spec, decodeFun=None, **options): + asn1Value = asn1Spec.clone() + + componentsTypes = asn1Spec.componentType + + for field in asn1Value: + if field in pyObject: + asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options) + + return asn1Value + + +class SequenceOfOrSetOfDecoder(object): + def __call__(self, pyObject, asn1Spec, decodeFun=None, **options): + asn1Value = asn1Spec.clone() + + for pyValue in pyObject: + asn1Value.append(decodeFun(pyValue, asn1Spec.componentType), **options) + + return asn1Value + + +class ChoiceDecoder(object): + def __call__(self, pyObject, asn1Spec, decodeFun=None, **options): + asn1Value = asn1Spec.clone() + + componentsTypes = asn1Spec.componentType + + for field in pyObject: + if field in componentsTypes: + asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options) + break + + return asn1Value + + +tagMap = { + univ.Integer.tagSet: AbstractScalarDecoder(), + univ.Boolean.tagSet: AbstractScalarDecoder(), + univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: AbstractScalarDecoder(), + univ.Null.tagSet: AbstractScalarDecoder(), + univ.ObjectIdentifier.tagSet: AbstractScalarDecoder(), + univ.Enumerated.tagSet: AbstractScalarDecoder(), + univ.Real.tagSet: AbstractScalarDecoder(), + univ.Sequence.tagSet: SequenceOrSetDecoder(), # conflicts with SequenceOf + univ.Set.tagSet: SequenceOrSetDecoder(), # conflicts with SetOf + univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any + # character string types + char.UTF8String.tagSet: AbstractScalarDecoder(), + char.NumericString.tagSet: AbstractScalarDecoder(), + char.PrintableString.tagSet: AbstractScalarDecoder(), + char.TeletexString.tagSet: AbstractScalarDecoder(), + char.VideotexString.tagSet: AbstractScalarDecoder(), + char.IA5String.tagSet: AbstractScalarDecoder(), + char.GraphicString.tagSet: AbstractScalarDecoder(), + char.VisibleString.tagSet: AbstractScalarDecoder(), + char.GeneralString.tagSet: AbstractScalarDecoder(), + char.UniversalString.tagSet: AbstractScalarDecoder(), + char.BMPString.tagSet: AbstractScalarDecoder(), + # useful types + useful.ObjectDescriptor.tagSet: AbstractScalarDecoder(), + useful.GeneralizedTime.tagSet: AbstractScalarDecoder(), + useful.UTCTime.tagSet: AbstractScalarDecoder() +} + +# Put in ambiguous & non-ambiguous types for faster codec lookup +typeMap = { + univ.Integer.typeId: AbstractScalarDecoder(), + univ.Boolean.typeId: AbstractScalarDecoder(), + univ.BitString.typeId: BitStringDecoder(), + univ.OctetString.typeId: AbstractScalarDecoder(), + univ.Null.typeId: AbstractScalarDecoder(), + univ.ObjectIdentifier.typeId: AbstractScalarDecoder(), + univ.Enumerated.typeId: AbstractScalarDecoder(), + univ.Real.typeId: AbstractScalarDecoder(), + # ambiguous base types + univ.Set.typeId: SequenceOrSetDecoder(), + univ.SetOf.typeId: SequenceOfOrSetOfDecoder(), + univ.Sequence.typeId: SequenceOrSetDecoder(), + univ.SequenceOf.typeId: SequenceOfOrSetOfDecoder(), + univ.Choice.typeId: ChoiceDecoder(), + univ.Any.typeId: AbstractScalarDecoder(), + # character string types + char.UTF8String.typeId: AbstractScalarDecoder(), + char.NumericString.typeId: AbstractScalarDecoder(), + char.PrintableString.typeId: AbstractScalarDecoder(), + char.TeletexString.typeId: AbstractScalarDecoder(), + char.VideotexString.typeId: AbstractScalarDecoder(), + char.IA5String.typeId: AbstractScalarDecoder(), + char.GraphicString.typeId: AbstractScalarDecoder(), + char.VisibleString.typeId: AbstractScalarDecoder(), + char.GeneralString.typeId: AbstractScalarDecoder(), + char.UniversalString.typeId: AbstractScalarDecoder(), + char.BMPString.typeId: AbstractScalarDecoder(), + # useful types + useful.ObjectDescriptor.typeId: AbstractScalarDecoder(), + useful.GeneralizedTime.typeId: AbstractScalarDecoder(), + useful.UTCTime.typeId: AbstractScalarDecoder() +} + + +class Decoder(object): + + # noinspection PyDefaultArgument + def __init__(self, tagMap, typeMap): + self.__tagMap = tagMap + self.__typeMap = typeMap + + def __call__(self, pyObject, asn1Spec, **options): + if debug.logger & debug.flagDecoder: + logger = debug.logger + else: + logger = None + if logger: + debug.scope.push(type(pyObject).__name__) + logger('decoder called at scope %s, working with type %s' % (debug.scope, type(pyObject).__name__)) + + if asn1Spec is None or not isinstance(asn1Spec, base.Asn1Item): + raise error.PyAsn1Error('asn1Spec is not valid (should be an instance of an ASN.1 Item, not %s)' % asn1Spec.__class__.__name__) + + try: + valueDecoder = self.__typeMap[asn1Spec.typeId] + + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(asn1Spec.tagSet.baseTag, asn1Spec.tagSet.baseTag) + + try: + valueDecoder = self.__tagMap[baseTagSet] + except KeyError: + raise error.PyAsn1Error('Unknown ASN.1 tag %s' % asn1Spec.tagSet) + + if logger: + logger('calling decoder %s on Python type %s <%s>' % (type(valueDecoder).__name__, type(pyObject).__name__, repr(pyObject))) + + value = valueDecoder(pyObject, asn1Spec, self, **options) + + if logger: + logger('decoder %s produced ASN.1 type %s <%s>' % (type(valueDecoder).__name__, type(value).__name__, repr(value))) + debug.scope.pop() + + return value + + +#: Turns Python objects of built-in types into ASN.1 objects. +#: +#: Takes Python objects of built-in types and turns them into a tree of +#: ASN.1 objects (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: pyObject: :py:class:`object` +#: A scalar or nested Python objects +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. It is required +#: for successful interpretation of Python objects mapping into their ASN.1 +#: representations. +#: +#: Returns +#: ------- +#: : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A scalar or constructed pyasn1 object +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors +decode = Decoder(tagMap, typeMap) diff --git a/src/pyasn1/codec/native/encoder.py b/src/pyasn1/codec/native/encoder.py new file mode 100644 index 00000000..3d23d606 --- /dev/null +++ b/src/pyasn1/codec/native/encoder.py @@ -0,0 +1,212 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +try: + from collections import OrderedDict + +except ImportError: + OrderedDict = dict + +from pyasn1.type import base, univ, tag, char, useful +from pyasn1 import debug, error + +__all__ = ['encode'] + + +class AbstractItemEncoder(object): + def encode(self, value, encodeFun, **options): + raise error.PyAsn1Error('Not implemented') + + +class BooleanEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return bool(value) + + +class IntegerEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return int(value) + + +class BitStringEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return str(value) + + +class OctetStringEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return value.asOctets() + + +class TextStringEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return value.prettyPrint() + + +class NullEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return None + + +class ObjectIdentifierEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return str(value) + + +class RealEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return float(value) + + +class SetEncoder(AbstractItemEncoder): + protoDict = dict + + def encode(self, value, encodeFun, **options): + value.verifySizeSpec() + + namedTypes = value.componentType + substrate = self.protoDict() + + for idx, (key, subValue) in enumerate(value.items()): + if namedTypes and namedTypes[idx].isOptional and not value[idx].isValue: + continue + substrate[key] = encodeFun(subValue, **options) + return substrate + + +class SequenceEncoder(SetEncoder): + protoDict = OrderedDict + + +class SequenceOfEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + value.verifySizeSpec() + return [encodeFun(x, **options) for x in value] + + +class ChoiceEncoder(SequenceEncoder): + pass + + +class AnyEncoder(AbstractItemEncoder): + def encode(self, value, encodeFun, **options): + return value.asOctets() + + +tagMap = { + univ.Boolean.tagSet: BooleanEncoder(), + univ.Integer.tagSet: IntegerEncoder(), + univ.BitString.tagSet: BitStringEncoder(), + univ.OctetString.tagSet: OctetStringEncoder(), + univ.Null.tagSet: NullEncoder(), + univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(), + univ.Enumerated.tagSet: IntegerEncoder(), + univ.Real.tagSet: RealEncoder(), + # Sequence & Set have same tags as SequenceOf & SetOf + univ.SequenceOf.tagSet: SequenceOfEncoder(), + univ.SetOf.tagSet: SequenceOfEncoder(), + univ.Choice.tagSet: ChoiceEncoder(), + # character string types + char.UTF8String.tagSet: TextStringEncoder(), + char.NumericString.tagSet: TextStringEncoder(), + char.PrintableString.tagSet: TextStringEncoder(), + char.TeletexString.tagSet: TextStringEncoder(), + char.VideotexString.tagSet: TextStringEncoder(), + char.IA5String.tagSet: TextStringEncoder(), + char.GraphicString.tagSet: TextStringEncoder(), + char.VisibleString.tagSet: TextStringEncoder(), + char.GeneralString.tagSet: TextStringEncoder(), + char.UniversalString.tagSet: TextStringEncoder(), + char.BMPString.tagSet: TextStringEncoder(), + # useful types + useful.ObjectDescriptor.tagSet: OctetStringEncoder(), + useful.GeneralizedTime.tagSet: OctetStringEncoder(), + useful.UTCTime.tagSet: OctetStringEncoder() +} + +# Type-to-codec map for ambiguous ASN.1 types +typeMap = { + univ.Set.typeId: SetEncoder(), + univ.SetOf.typeId: SequenceOfEncoder(), + univ.Sequence.typeId: SequenceEncoder(), + univ.SequenceOf.typeId: SequenceOfEncoder(), + univ.Choice.typeId: ChoiceEncoder(), + univ.Any.typeId: AnyEncoder() +} + + +class Encoder(object): + + # noinspection PyDefaultArgument + def __init__(self, tagMap, typeMap={}): + self.__tagMap = tagMap + self.__typeMap = typeMap + + def __call__(self, value, **options): + if not isinstance(value, base.Asn1Item): + raise error.PyAsn1Error('value is not valid (should be an instance of an ASN.1 Item)') + + if debug.logger & debug.flagEncoder: + logger = debug.logger + else: + logger = None + + if logger: + debug.scope.push(type(value).__name__) + logger('encoder called for type %s <%s>' % (type(value).__name__, value.prettyPrint())) + + tagSet = value.tagSet + + try: + concreteEncoder = self.__typeMap[value.typeId] + + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + + try: + concreteEncoder = self.__tagMap[baseTagSet] + + except KeyError: + raise error.PyAsn1Error('No encoder for %s' % (value,)) + + if logger: + logger('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet)) + + pyObject = concreteEncoder.encode(value, self, **options) + + if logger: + logger('encoder %s produced: %s' % (type(concreteEncoder).__name__, repr(pyObject))) + debug.scope.pop() + + return pyObject + + +#: Turns ASN.1 object into a Python built-in type object(s). +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a Python built-in type or a tree +#: of those. +#: +#: One exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict` +#: can be produced (whenever available) to preserve ordering of the components +#: in ASN.1 SEQUENCE. +#: +#: Parameters +#: ---------- +# asn1Value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: pyasn1 object to encode (or a tree of them) +#: +#: Returns +#: ------- +#: : :py:class:`object` +#: Python built-in type instance (or a tree of them) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors +encode = Encoder(tagMap, typeMap) diff --git a/src/pyasn1/compat/binary.py b/src/pyasn1/compat/binary.py index b38932af..86f6e5d0 100644 --- a/src/pyasn1/compat/binary.py +++ b/src/pyasn1/compat/binary.py @@ -1,10 +1,33 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from sys import version_info if version_info[0:2] < (2, 6): - def bin(x): - if x <= 1: - return '0b'+str(x) + def bin(value): + bitstring = [] + + if value > 0: + prefix = '0b' + elif value < 0: + prefix = '-0b' + value = abs(value) else: - return bin(x>>1) + str(x&1) + prefix = '0b0' + + while value: + if value & 1 == 1: + bitstring.append('1') + else: + bitstring.append('0') + + value >>= 1 + + bitstring.reverse() + + return prefix + ''.join(bitstring) else: bin = bin diff --git a/src/pyasn1/compat/calling.py b/src/pyasn1/compat/calling.py new file mode 100644 index 00000000..fde25d80 --- /dev/null +++ b/src/pyasn1/compat/calling.py @@ -0,0 +1,20 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from sys import version_info + +__all__ = ['callable'] + + +if (2, 7) < version_info[:2] < (3, 2): + import collections + + def callable(x): + return isinstance(x, collections.Callable) + +else: + + callable = callable diff --git a/src/pyasn1/compat/dateandtime.py b/src/pyasn1/compat/dateandtime.py new file mode 100644 index 00000000..646b9e85 --- /dev/null +++ b/src/pyasn1/compat/dateandtime.py @@ -0,0 +1,22 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from sys import version_info +from datetime import datetime +import time + +__all__ = ['strptime'] + + +if version_info[:2] <= (2, 4): + + def strptime(text, dateFormat): + return datetime(*(time.strptime(text, dateFormat)[0:6])) + +else: + + def strptime(text, dateFormat): + return datetime.strptime(text, dateFormat) diff --git a/src/pyasn1/compat/integer.py b/src/pyasn1/compat/integer.py new file mode 100644 index 00000000..0c426a2e --- /dev/null +++ b/src/pyasn1/compat/integer.py @@ -0,0 +1,108 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys +try: + import platform + implementation = platform.python_implementation() + +except (ImportError, AttributeError): + implementation = 'CPython' + +from pyasn1.compat.octets import oct2int, null, ensureString + +if sys.version_info[0:2] < (3, 2) or implementation != 'CPython': + from binascii import a2b_hex, b2a_hex + + if sys.version_info[0] > 2: + long = int + + def from_bytes(octets, signed=False): + if not octets: + return 0 + + value = long(b2a_hex(ensureString(octets)), 16) + + if signed and oct2int(octets[0]) & 0x80: + return value - (1 << len(octets) * 8) + + return value + + def to_bytes(value, signed=False, length=0): + if value < 0: + if signed: + bits = bitLength(value) + + # two's complement form + maxValue = 1 << bits + valueToEncode = (value + maxValue) % maxValue + + else: + raise OverflowError('can\'t convert negative int to unsigned') + elif value == 0 and length == 0: + return null + else: + bits = 0 + valueToEncode = value + + hexValue = hex(valueToEncode)[2:] + if hexValue.endswith('L'): + hexValue = hexValue[:-1] + + if len(hexValue) & 1: + hexValue = '0' + hexValue + + # padding may be needed for two's complement encoding + if value != valueToEncode or length: + hexLength = len(hexValue) * 4 + + padLength = max(length, bits) + + if padLength > hexLength: + hexValue = '00' * ((padLength - hexLength - 1) // 8 + 1) + hexValue + elif length and hexLength - length > 7: + raise OverflowError('int too big to convert') + + firstOctet = int(hexValue[:2], 16) + + if signed: + if firstOctet & 0x80: + if value >= 0: + hexValue = '00' + hexValue + elif value < 0: + hexValue = 'ff' + hexValue + + octets_value = a2b_hex(hexValue) + + return octets_value + + def bitLength(number): + # bits in unsigned number + hexValue = hex(abs(number)) + bits = len(hexValue) - 2 + if hexValue.endswith('L'): + bits -= 1 + if bits & 1: + bits += 1 + bits *= 4 + # TODO: strip lhs zeros + return bits + +else: + + def from_bytes(octets, signed=False): + return int.from_bytes(bytes(octets), 'big', signed=signed) + + def to_bytes(value, signed=False, length=0): + length = max(value.bit_length(), length) + + if signed and length % 8 == 0: + length += 1 + + return value.to_bytes(length // 8 + (length % 8 and 1 or 0), 'big', signed=signed) + + def bitLength(number): + return int(number).bit_length() diff --git a/src/pyasn1/compat/octets.py b/src/pyasn1/compat/octets.py index e8127370..f8d47089 100644 --- a/src/pyasn1/compat/octets.py +++ b/src/pyasn1/compat/octets.py @@ -1,22 +1,46 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from sys import version_info if version_info[0] <= 2: int2oct = chr - ints2octs = lambda s: ''.join([ int2oct(x) for x in s ]) + # noinspection PyPep8 + ints2octs = lambda s: ''.join([int2oct(x) for x in s]) null = '' oct2int = ord - octs2ints = lambda s: [ oct2int(x) for x in s ] + # TODO: refactor to return a sequence of ints + # noinspection PyPep8 + octs2ints = lambda s: [oct2int(x) for x in s] + # noinspection PyPep8 str2octs = lambda x: x + # noinspection PyPep8 octs2str = lambda x: x + # noinspection PyPep8 isOctetsType = lambda s: isinstance(s, str) + # noinspection PyPep8 isStringType = lambda s: isinstance(s, (str, unicode)) + # noinspection PyPep8 + ensureString = str else: ints2octs = bytes + # noinspection PyPep8 int2oct = lambda x: ints2octs((x,)) null = ints2octs() + # noinspection PyPep8 oct2int = lambda x: x - octs2ints = lambda s: [ x for x in s ] - str2octs = lambda x: x.encode() - octs2str = lambda x: x.decode() + # noinspection PyPep8 + octs2ints = lambda x: x + # noinspection PyPep8 + str2octs = lambda x: x.encode('iso-8859-1') + # noinspection PyPep8 + octs2str = lambda x: x.decode('iso-8859-1') + # noinspection PyPep8 isOctetsType = lambda s: isinstance(s, bytes) + # noinspection PyPep8 isStringType = lambda s: isinstance(s, str) + # noinspection PyPep8 + ensureString = bytes diff --git a/src/pyasn1/compat/string.py b/src/pyasn1/compat/string.py new file mode 100644 index 00000000..24e64b68 --- /dev/null +++ b/src/pyasn1/compat/string.py @@ -0,0 +1,26 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from sys import version_info + +if version_info[:2] <= (2, 5): + + def partition(string, sep): + try: + a, c = string.split(sep, 1) + + except ValueError: + a, b, c = string, '', '' + + else: + b = sep + + return a, b, c + +else: + + def partition(string, sep): + return string.partition(sep) diff --git a/src/pyasn1/debug.py b/src/pyasn1/debug.py index 9b69886c..24ac5ce0 100644 --- a/src/pyasn1/debug.py +++ b/src/pyasn1/debug.py @@ -1,79 +1,103 @@ -import time +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# import logging from pyasn1.compat.octets import octs2ints from pyasn1 import error from pyasn1 import __version__ -flagNone = 0x0000 -flagEncoder = 0x0001 -flagDecoder = 0x0002 -flagAll = 0xffff +__all__ = ['Debug', 'setLogger', 'hexdump'] + +flagNone = 0x0000 +flagEncoder = 0x0001 +flagDecoder = 0x0002 +flagAll = 0xffff flagMap = { 'encoder': flagEncoder, 'decoder': flagDecoder, 'all': flagAll - } +} -class Printer: + +class Printer(object): + # noinspection PyShadowingNames def __init__(self, logger=None, handler=None, formatter=None): if logger is None: logger = logging.getLogger('pyasn1') + logger.setLevel(logging.DEBUG) + if handler is None: handler = logging.StreamHandler() + if formatter is None: formatter = logging.Formatter('%(asctime)s %(name)s: %(message)s') + handler.setFormatter(formatter) handler.setLevel(logging.DEBUG) logger.addHandler(handler) + self.__logger = logger - def __call__(self, msg): self.__logger.debug(msg) - def __str__(self): return '' + def __call__(self, msg): + self.__logger.debug(msg) + + def __str__(self): + return '' + if hasattr(logging, 'NullHandler'): NullHandler = logging.NullHandler + else: # Python 2.6 and older class NullHandler(logging.Handler): def emit(self, record): pass -class Debug: - defaultPrinter = None + +class Debug(object): + defaultPrinter = Printer() + def __init__(self, *flags, **options): self._flags = flagNone - if options.get('printer') is not None: - self._printer = options.get('printer') - elif self.defaultPrinter is not None: - self._printer = self.defaultPrinter - if 'loggerName' in options: + + if 'loggerName' in options: # route our logs to parent logger self._printer = Printer( logger=logging.getLogger(options['loggerName']), handler=NullHandler() ) + + elif 'printer' in options: + self._printer = options.get('printer') + else: - self._printer = Printer() - self('running pyasn1 version %s' % __version__) - for f in flags: - inverse = f and f[0] in ('!', '~') + self._printer = self.defaultPrinter + + self._printer('running pyasn1 %s, debug flags %s' % (__version__, ', '.join(flags))) + + for flag in flags: + inverse = flag and flag[0] in ('!', '~') if inverse: - f = f[1:] + flag = flag[1:] try: if inverse: - self._flags &= ~flagMap[f] + self._flags &= ~flagMap[flag] else: - self._flags |= flagMap[f] + self._flags |= flagMap[flag] except KeyError: - raise error.PyAsn1Error('bad debug flag %s' % f) - - self('debug category \'%s\' %s' % (f, inverse and 'disabled' or 'enabled')) + raise error.PyAsn1Error('bad debug flag %s' % flag) + + self._printer("debug category '%s' %s" % (flag, inverse and 'disabled' or 'enabled')) def __str__(self): return 'logger %s, flags %x' % (self._printer, self._flags) - + def __call__(self, msg): self._printer(msg) @@ -83,19 +107,27 @@ class Debug: def __rand__(self, flag): return flag & self._flags + logger = 0 -def setLogger(l): + +def setLogger(userLogger): global logger - logger = l + + if userLogger: + logger = userLogger + else: + logger = 0 + def hexdump(octets): return ' '.join( - [ '%s%.2X' % (n%16 == 0 and ('\n%.5d: ' % n) or '', x) - for n,x in zip(range(len(octets)), octs2ints(octets)) ] - ) + ['%s%.2X' % (n % 16 == 0 and ('\n%.5d: ' % n) or '', x) + for n, x in zip(range(len(octets)), octs2ints(octets))] + ) -class Scope: + +class Scope(object): def __init__(self): self._list = [] @@ -107,4 +139,5 @@ class Scope: def pop(self): return self._list.pop() + scope = Scope() diff --git a/src/pyasn1/error.py b/src/pyasn1/error.py index 716406ff..85308557 100644 --- a/src/pyasn1/error.py +++ b/src/pyasn1/error.py @@ -1,3 +1,18 @@ -class PyAsn1Error(Exception): pass -class ValueConstraintError(PyAsn1Error): pass -class SubstrateUnderrunError(PyAsn1Error): pass +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# + + +class PyAsn1Error(Exception): + pass + + +class ValueConstraintError(PyAsn1Error): + pass + + +class SubstrateUnderrunError(PyAsn1Error): + pass diff --git a/src/pyasn1/type/base.py b/src/pyasn1/type/base.py index 72920a9d..1b54d8d2 100644 --- a/src/pyasn1/type/base.py +++ b/src/pyasn1/type/base.py @@ -1,151 +1,393 @@ -# Base classes for ASN.1 types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# import sys from pyasn1.type import constraint, tagmap, tag +from pyasn1.compat import calling from pyasn1 import error -class Asn1Item: pass +__all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', 'AbstractConstructedAsn1Item'] + + +class Asn1Item(object): + @classmethod + def getTypeId(cls, increment=1): + try: + Asn1Item._typeCounter += increment + except AttributeError: + Asn1Item._typeCounter = increment + return Asn1Item._typeCounter + class Asn1ItemBase(Asn1Item): - # Set of tags for this ASN.1 type + #: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing + #: ASN.1 tag(s) associated with |ASN.1| type. tagSet = tag.TagSet() - - # A list of constraint.Constraint instances for checking values + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on initialization values. subtypeSpec = constraint.ConstraintsIntersection() - # Used for ambiguous ASN.1 types identification + # Disambiguation ASN.1 types identification typeId = None - - def __init__(self, tagSet=None, subtypeSpec=None): - if tagSet is None: - self._tagSet = self.tagSet - else: - self._tagSet = tagSet - if subtypeSpec is None: - self._subtypeSpec = self.subtypeSpec - else: - self._subtypeSpec = subtypeSpec - def _verifySubtypeSpec(self, value, idx=None): - try: - self._subtypeSpec(value, idx) - except error.PyAsn1Error: - c, i, t = sys.exc_info() - raise c('%s at %s' % (i, self.__class__.__name__)) - - def getSubtypeSpec(self): return self._subtypeSpec - - def getTagSet(self): return self._tagSet - def getEffectiveTagSet(self): return self._tagSet # used by untagged types - def getTagMap(self): return tagmap.TagMap({self._tagSet: self}) - + 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 + + @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): - return self is other or \ - (not matchTags or \ - self._tagSet == other.getTagSet()) and \ - (not matchConstraints or \ - self._subtypeSpec==other.getSubtypeSpec()) + """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. + + No Python inheritance relationship between PyASN1 objects is considered. + + Parameters + ---------- + other: a pyasn1 type object + Class instance representing ASN.1 type. + + Returns + ------- + : :class:`bool` + :class:`True` if *other* is |ASN.1| type, + :class:`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): - """Returns true if argument is a ASN1 subtype of ourselves""" - return (not matchTags or \ - self._tagSet.isSuperTagSetOf(other.getTagSet())) and \ - (not matchConstraints or \ - (self._subtypeSpec.isSuperTypeOf(other.getSubtypeSpec()))) - -class NoValue: - def __getattr__(self, attr): - raise error.PyAsn1Error('No value for %s()' % attr) - def __getitem__(self, i): - raise error.PyAsn1Error('No value') - def __repr__(self): return '%s()' % self.__class__.__name__ - -noValue = NoValue() - -# Base class for "simple" ASN.1 objects. These are immutable. -class AbstractSimpleAsn1Item(Asn1ItemBase): - defaultValue = noValue - def __init__(self, value=None, tagSet=None, subtypeSpec=None): - Asn1ItemBase.__init__(self, tagSet, subtypeSpec) - if value is None or value is noValue: - value = self.defaultValue - if value is None or value is noValue: - self.__hashedValue = value = noValue - else: - value = self.prettyIn(value) - self._verifySubtypeSpec(value) - self.__hashedValue = hash(value) - self._value = value - self._len = None + """Examine |ASN.1| type for subtype relationship with other ASN.1 type. - def __repr__(self): - r = [] - if self._value is not self.defaultValue: - r.append(self.prettyOut(self._value)) - if self._tagSet is not self.tagSet: - r.append('tagSet=%r' % (self._tagSet,)) - if self._subtypeSpec is not self.subtypeSpec: - r.append('subtypeSpec=%r' % (self._subtypeSpec,)) - return '%s(%s)' % (self.__class__.__name__, ', '.join(r)) + ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints + (:py:mod:`~pyasn1.type.constraint`) are examined when carrying + out ASN.1 types comparison. - def __str__(self): return str(self._value) - 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 bool(self._value) - else: - def __bool__(self): return bool(self._value) - def __hash__(self): - return self.__hashedValue is noValue and hash(noValue) or self.__hashedValue + No Python inheritance relationship between PyASN1 objects is considered. + + + Parameters + ---------- + other: a pyasn1 type object + Class instance representing ASN.1 type. + + Returns + ------- + : :class:`bool` + :class:`True` if *other* is a subtype of |ASN.1| type, + :class:`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 None and value is not noValue: + return False + return True + + # 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 def hasValue(self): - return not isinstance(self._value, NoValue) + return self.isValue - def clone(self, value=None, tagSet=None, subtypeSpec=None): - if value is None and tagSet is None and subtypeSpec is None: - return self - if value is None: - value = self._value - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - return self.__class__(value, tagSet, subtypeSpec) - def subtype(self, value=None, implicitTag=None, explicitTag=None, - subtypeSpec=None): - if value is None: +class NoValue(object): + """Create a singleton instance of NoValue class. + + NoValue object can be used as an initializer on PyASN1 type class + instantiation to represent ASN.1 type rather than ASN.1 data value. + + No operations other than type comparison can be performed on + a PyASN1 type object. + """ + skipMethods = ('__getattribute__', '__getattr__', '__setattr__', '__delattr__', + '__class__', '__init__', '__del__', '__new__', '__repr__', + '__qualname__', '__objclass__', 'im_class', '__sizeof__') + + _instance = None + + def __new__(cls): + if cls._instance is None: + def getPlug(name): + def plug(self, *args, **kw): + raise error.PyAsn1Error('Uninitialized ASN.1 value ("%s" attribute looked up)' % 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('No value for "%s"' % attr) + + def __repr__(self): + return '%s()' % self.__class__.__name__ + +noValue = NoValue() + + +# Base class for "simple" ASN.1 objects. These are immutable. +class AbstractSimpleAsn1Item(Asn1ItemBase): + #: Default payload value + defaultValue = noValue + + def __init__(self, value=noValue, **kwargs): + Asn1ItemBase.__init__(self, **kwargs) + if value is noValue or value is None: + 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 = [] + if self._value is not self.defaultValue: + representation.append(self.prettyOut(self._value)) + if self.tagSet is not self.__class__.tagSet: + representation.append('tagSet=%r' % (self.tagSet,)) + if self.subtypeSpec is not self.__class__.subtypeSpec: + representation.append('subtypeSpec=%r' % (self.subtypeSpec,)) + return '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) + + def __str__(self): + return str(self._value) + + 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 if |ASN.1| object represents ASN.1 type or ASN.1 value. + + In other words, if *isValue* is `True`, then the ASN.1 object is + initialized. + + Returns + ------- + : :class:`bool` + :class:`True` if object represents ASN.1 value and type, + :class:`False` if object represents just ASN.1 type. + + Note + ---- + There is an important distinction between PyASN1 type and value objects. + The PyASN1 type objects can only participate in ASN.1 type + operations (subtyping, comparison etc) and serve as a + blueprint for serialization codecs to resolve ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + """ + return self._value is not noValue + + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + if value is noValue or value is None: + if not kwargs: + return self + value = self._value + + initilaizers = self.readOnly.copy() + initilaizers.update(kwargs) + + return self.__class__(value, **initilaizers) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller'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 caller, then + use the result as new object's ASN.1 constraints. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + if value is noValue or value is None: + if not kwargs: + return self + + value = self._value + + initializers = self.readOnly.copy() + + implicitTag = kwargs.pop('implicitTag', None) if implicitTag is not None: - tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: - tagSet = self._tagSet.tagExplicitly(explicitTag) - else: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - else: - subtypeSpec = subtypeSpec + self._subtypeSpec - return self.__class__(value, tagSet, subtypeSpec) + initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag) - def prettyIn(self, value): return value - def prettyOut(self, value): return str(value) + 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): - if self.hasValue(): + """Provide human-friendly printable object representation. + + Returns + ------- + : :class:`str` + human-friendly type and/or value representation. + """ + if self.isValue: return self.prettyOut(self._value) else: return '' # XXX Compatibility stub - def prettyPrinter(self, scope=0): return self.prettyPrint(scope) - + def prettyPrinter(self, scope=0): + return self.prettyPrint(scope) + + # noinspection PyUnusedLocal def prettyPrintType(self, scope=0): - return '%s -> %s' % (self.getTagSet(), self.__class__.__name__) + return '%s -> %s' % (self.tagSet, self.__class__.__name__) # # Constructed types: @@ -166,113 +408,194 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): # of types for Sequence/Set/Choice. # +def setupComponent(): + """Returns a sentinel value. + + Indicates to a constructed type to set up its inner component so that it + can be referred to. This is useful in situation when you want to populate + descendants of a constructed type what requires being able to refer to + their parent types along the way. + + Example + ------- + + >>> constructed['record'] = setupComponent() + >>> constructed['record']['scalar'] = 42 + """ + return noValue + + class AbstractConstructedAsn1Item(Asn1ItemBase): + + #: If `True`, requires exact component type matching, + #: otherwise subtype relation is only enforced + strictConstraints = False + componentType = None - sizeSpec = constraint.ConstraintsIntersection() - def __init__(self, componentType=None, tagSet=None, - subtypeSpec=None, sizeSpec=None): - Asn1ItemBase.__init__(self, tagSet, subtypeSpec) - if componentType is None: - self._componentType = self.componentType - else: - self._componentType = componentType - if sizeSpec is None: - self._sizeSpec = self.sizeSpec - else: - self._sizeSpec = sizeSpec + sizeSpec = None + + def __init__(self, **kwargs): + readOnly = { + 'componentType': self.componentType, + 'sizeSpec': self.sizeSpec + } + readOnly.update(kwargs) + + Asn1ItemBase.__init__(self, **readOnly) + self._componentValues = [] - self._componentValuesSet = 0 def __repr__(self): - r = [] - if self._componentType is not self.componentType: - r.append('componentType=%r' % (self._componentType,)) - if self._tagSet is not self.tagSet: - r.append('tagSet=%r' % (self._tagSet,)) - if self._subtypeSpec is not self.subtypeSpec: - r.append('subtypeSpec=%r' % (self._subtypeSpec,)) - r = '%s(%s)' % (self.__class__.__name__, ', '.join(r)) + representation = [] + if self.componentType is not self.__class__.componentType: + representation.append('componentType=%r' % (self.componentType,)) + if self.tagSet is not self.__class__.tagSet: + representation.append('tagSet=%r' % (self.tagSet,)) + if self.subtypeSpec is not self.__class__.subtypeSpec: + representation.append('subtypeSpec=%r' % (self.subtypeSpec,)) + representation = '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) if self._componentValues: - r += '.setComponents(%s)' % ', '.join([repr(x) for x in self._componentValues]) - return r + for idx, component in enumerate(self._componentValues): + if component is None or component is noValue: + continue + representation += '.setComponentByPosition(%d, %s)' % (idx, repr(component)) + return representation def __eq__(self, other): return self is other and True or self._componentValues == other - def __ne__(self, other): return self._componentValues != other - def __lt__(self, other): return self._componentValues < other - def __le__(self, other): return self._componentValues <= other - def __gt__(self, other): return self._componentValues > other - def __ge__(self, other): return self._componentValues >= other + + def __ne__(self, other): + return self._componentValues != other + + def __lt__(self, other): + return self._componentValues < other + + def __le__(self, other): + return self._componentValues <= other + + def __gt__(self, other): + return self._componentValues > other + + def __ge__(self, other): + return self._componentValues >= other + if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._componentValues) + def __nonzero__(self): + return self._componentValues and True or False else: - def __bool__(self): return bool(self._componentValues) + def __bool__(self): + return self._componentValues and True or False - def getComponentTagMap(self): - raise error.PyAsn1Error('Method not implemented') + def _cloneComponentValues(self, myClone, cloneValueFlag): + pass - def _cloneComponentValues(self, myClone, cloneValueFlag): pass + def clone(self, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 size constraint(s) + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + cloneValueFlag = kwargs.pop('cloneValueFlag', False) + + initilaizers = self.readOnly.copy() + initilaizers.update(kwargs) + + clone = self.__class__(**initilaizers) - def clone(self, tagSet=None, subtypeSpec=None, sizeSpec=None, - cloneValueFlag=None): - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - if sizeSpec is None: - sizeSpec = self._sizeSpec - r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) if cloneValueFlag: - self._cloneComponentValues(r, cloneValueFlag) - return r + self._cloneComponentValues(clone, cloneValueFlag) - def subtype(self, implicitTag=None, explicitTag=None, subtypeSpec=None, - sizeSpec=None, cloneValueFlag=None): + return clone + + def subtype(self, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 size constraint(s) + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + + initializers = self.readOnly.copy() + + cloneValueFlag = kwargs.pop('cloneValueFlag', False) + + implicitTag = kwargs.pop('implicitTag', None) if implicitTag is not None: - tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: - tagSet = self._tagSet.tagExplicitly(explicitTag) - else: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if sizeSpec is None: - sizeSpec = self._sizeSpec - else: - sizeSpec = sizeSpec + self._sizeSpec - r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) + 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(r, cloneValueFlag) - return r + self._cloneComponentValues(clone, cloneValueFlag) - def _verifyComponent(self, idx, value): pass + return clone - def verifySizeSpec(self): self._sizeSpec(self) + def verifySizeSpec(self): + self.sizeSpec(self) 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 in range(len(args)): - self[idx] = args[idx] + for idx, value in enumerate(args): + self[idx] = value for k in kwargs: self[k] = kwargs[k] return self - def getComponentType(self): return self._componentType + def __len__(self): + return len(self._componentValues) - def setDefaultComponents(self): pass - - def __getitem__(self, idx): return self.getComponentByPosition(idx) - def __setitem__(self, idx, value): self.setComponentByPosition(idx, value) - - def __len__(self): return len(self._componentValues) - def clear(self): self._componentValues = [] - self._componentValuesSet = 0 + # backward compatibility + + def setDefaultComponents(self): + pass + + def getComponentType(self): + return self.componentType diff --git a/src/pyasn1/type/char.py b/src/pyasn1/type/char.py index af49ab3e..60f9d978 100644 --- a/src/pyasn1/type/char.py +++ b/src/pyasn1/type/char.py @@ -1,64 +1,374 @@ -# ASN.1 "character string" types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys from pyasn1.type import univ, tag +from pyasn1 import error -class NumericString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + +__all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString', + 'IA5String', 'GraphicString', 'VisibleString', 'ISO646String', + 'GeneralString', 'UniversalString', 'BMPString', 'UTF8String'] + +NoValue = univ.NoValue +noValue = univ.noValue + + +class AbstractCharacterString(univ.OctetString): + """Creates |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python 2 :class:`unicode` or Python 3 :class:`str`. + When used in octet-stream context, |ASN.1| type assumes "|encoding|" encoding. + + Parameters + ---------- + value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object + unicode object (Python 2) or string (Python 3), alternatively string + (Python 2) or bytes (Python 3) representing octet-stream of serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) or + :class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + + if sys.version_info[0] <= 2: + def __str__(self): + try: + return self._value.encode(self.encoding) + except UnicodeEncodeError: + raise error.PyAsn1Error( + "Can't encode string '%s' with codec %s" % (self._value, self.encoding) + ) + + def __unicode__(self): + return unicode(self._value) + + def prettyIn(self, value): + try: + if isinstance(value, unicode): + return value + elif isinstance(value, str): + return value.decode(self.encoding) + elif isinstance(value, (tuple, list)): + return self.prettyIn(''.join([chr(x) for x in value])) + elif isinstance(value, univ.OctetString): + return value.asOctets().decode(self.encoding) + else: + return unicode(value) + + except (UnicodeDecodeError, LookupError): + raise error.PyAsn1Error( + "Can't decode string '%s' with codec %s" % (value, self.encoding) + ) + + def asOctets(self, padding=True): + return str(self) + + def asNumbers(self, padding=True): + return tuple([ord(x) for x in str(self)]) + + else: + def __str__(self): + return str(self._value) + + def __bytes__(self): + try: + return self._value.encode(self.encoding) + except UnicodeEncodeError: + raise error.PyAsn1Error( + "Can't encode string '%s' with codec %s" % (self._value, self.encoding) + ) + + def prettyIn(self, value): + try: + if isinstance(value, str): + return value + elif isinstance(value, bytes): + return value.decode(self.encoding) + elif isinstance(value, (tuple, list)): + return self.prettyIn(bytes(value)) + elif isinstance(value, univ.OctetString): + return value.asOctets().decode(self.encoding) + else: + return str(value) + + except (UnicodeDecodeError, LookupError): + raise error.PyAsn1Error( + "Can't decode string '%s' with codec %s" % (value, self.encoding) + ) + + def asOctets(self, padding=True): + return bytes(self) + + def asNumbers(self, padding=True): + return tuple(bytes(self)) + + def prettyOut(self, value): + return value + + def __reversed__(self): + return reversed(self._value) + + def clone(self, value=noValue, **kwargs): + """Creates a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object + unicode object (Python 2) or string (Python 3), alternatively string + (Python 2) or bytes (Python 3) representing octet-stream of serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or + :py:class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + return univ.OctetString.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Creates a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object + unicode object (Python 2) or string (Python 3), alternatively string + (Python 2) or bytes (Python 3) representing octet-stream of serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller'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` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or + :py:class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + return univ.OctetString.subtype(self, value, **kwargs) + +class NumericString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18) - ) + ) + encoding = 'us-ascii' -class PrintableString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class PrintableString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19) - ) + ) + encoding = 'us-ascii' -class TeletexString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class TeletexString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20) - ) + ) + encoding = 'iso-8859-1' -class T61String(TeletexString): pass + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() -class VideotexString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + +class T61String(TeletexString): + __doc__ = TeletexString.__doc__ + + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class VideotexString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21) - ) + ) + encoding = 'iso-8859-1' -class IA5String(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class IA5String(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22) - ) + ) + encoding = 'us-ascii' -class GraphicString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class GraphicString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25) - ) + ) + encoding = 'iso-8859-1' -class VisibleString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class VisibleString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26) - ) + ) + encoding = 'us-ascii' -class ISO646String(VisibleString): pass + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() -class GeneralString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + +class ISO646String(VisibleString): + __doc__ = VisibleString.__doc__ + + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + +class GeneralString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27) - ) + ) + encoding = 'iso-8859-1' -class UniversalString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class UniversalString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28) - ) + ) encoding = "utf-32-be" -class BMPString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class BMPString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30) - ) + ) encoding = "utf-16-be" -class UTF8String(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class UTF8String(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) - ) + ) encoding = "utf-8" + + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() diff --git a/src/pyasn1/type/constraint.py b/src/pyasn1/type/constraint.py index 66873937..35bb0e24 100644 --- a/src/pyasn1/type/constraint.py +++ b/src/pyasn1/type/constraint.py @@ -1,86 +1,124 @@ # -# ASN.1 subtype constraints classes. +# This file is part of pyasn1 software. # -# Constraints are relatively rare, but every ASN1 object -# is doing checks all the time for whether they have any -# constraints and whether they are applicable to the object. +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html # -# What we're going to do is define objects/functions that -# can be called unconditionally if they are present, and that -# are simply not present if there are no constraints. -# -# Original concept and code by Mike C. Fletcher. +# Original concept and code by Mike C. Fletcher. # import sys from pyasn1.type import error -class AbstractConstraint: +__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 'ValueRangeConstraint', + 'ValueSizeConstraint', 'PermittedAlphabetConstraint', 'InnerTypeConstraint', + 'ConstraintsExclusion', 'ConstraintsIntersection', 'ConstraintsUnion'] + + +class AbstractConstraint(object): """Abstract base-class for constraint objects Constraints should be stored in a simple sequence in the - namespace of their client Asn1Item sub-classes. + namespace of their client Asn1Item sub-classes in cases + when ASN.1 constraint is define. """ + def __init__(self, *values): - self._valueMap = {} + self._valueMap = set() self._setValues(values) - self.__hashedValues = None + self.__hash = hash((self.__class__.__name__, self._values)) + def __call__(self, value, idx=None): + if not self._values: + return + try: self._testValue(value, idx) + except error.ValueConstraintError: raise error.ValueConstraintError( - '%s failed at: \"%s\"' % (self, sys.exc_info()[1]) + '%s failed at: %r' % (self, sys.exc_info()[1]) ) + def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join([repr(x) for x in self._values]) ) + def __eq__(self, other): return self is other and True or self._values == other - def __ne__(self, other): return self._values != other - def __lt__(self, other): return self._values < other - def __le__(self, other): return self._values <= other - def __gt__(self, other): return self._values > other - def __ge__(self, other): return self._values >= other + + def __ne__(self, other): + return self._values != other + + def __lt__(self, other): + return self._values < other + + def __le__(self, other): + return self._values <= other + + def __gt__(self, other): + return self._values > other + + def __ge__(self, other): + return self._values >= other + if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._values) + def __nonzero__(self): + return self._values and True or False else: - def __bool__(self): return bool(self._values) + def __bool__(self): + return self._values and True or False def __hash__(self): - if self.__hashedValues is None: - self.__hashedValues = hash((self.__class__.__name__, self._values)) - return self.__hashedValues + return self.__hash + + def _setValues(self, values): + self._values = values - def _setValues(self, values): self._values = values def _testValue(self, value, idx): raise error.ValueConstraintError(value) # Constraints derivation logic - def getValueMap(self): return self._valueMap + def getValueMap(self): + return self._valueMap + def isSuperTypeOf(self, otherConstraint): - return self in otherConstraint.getValueMap() or \ - otherConstraint is self or otherConstraint == self + # TODO: fix possible comparison of set vs scalars here + return (otherConstraint is self or + not self._values or + otherConstraint == self or + self in otherConstraint.getValueMap()) + def isSubTypeOf(self, otherConstraint): - return otherConstraint in self._valueMap or \ - otherConstraint is self or otherConstraint == self + return (otherConstraint is self or + not self or + otherConstraint == self or + otherConstraint in self._valueMap) class SingleValueConstraint(AbstractConstraint): """Value must be part of defined values constraint""" + + def _setValues(self, values): + self._values = values + self._set = set(values) + def _testValue(self, value, idx): - # XXX index vals for performance? - if value not in self._values: + if value not in self._set: raise error.ValueConstraintError(value) + class ContainedSubtypeConstraint(AbstractConstraint): """Value must satisfy all of defined set of constraints""" + def _testValue(self, value, idx): for c in self._values: c(value, idx) + class ValueRangeConstraint(AbstractConstraint): """Value must be within start and stop values (inclusive)""" + def _testValue(self, value, idx): if value < self.start or value > self.stop: raise error.ValueConstraintError(value) @@ -89,7 +127,7 @@ class ValueRangeConstraint(AbstractConstraint): if len(values) != 2: raise error.PyAsn1Error( '%s: bad constraint values' % (self.__class__.__name__,) - ) + ) self.start, self.stop = values if self.start > self.stop: raise error.PyAsn1Error( @@ -99,28 +137,31 @@ class ValueRangeConstraint(AbstractConstraint): ) ) AbstractConstraint._setValues(self, values) - + + class ValueSizeConstraint(ValueRangeConstraint): """len(value) must be within start and stop values (inclusive)""" + def _testValue(self, value, idx): - l = len(value) - if l < self.start or l > self.stop: + valueSize = len(value) + if valueSize < self.start or valueSize > self.stop: raise error.ValueConstraintError(value) + class PermittedAlphabetConstraint(SingleValueConstraint): def _setValues(self, values): - self._values = () - for v in values: - self._values = self._values + tuple(v) + self._values = values + self._set = set(values) def _testValue(self, value, idx): - for v in value: - if v not in self._values: - raise error.ValueConstraintError(value) + if not self._set.issuperset(value): + raise error.ValueConstraintError(value) -# This is a bit kludgy, meaning two op modes within a single constraing + +# This is a bit kludgy, meaning two op modes within a single constraint class InnerTypeConstraint(AbstractConstraint): """Value must satisfy type and presense constraints""" + def _testValue(self, value, idx): if self.__singleTypeConstraint: self.__singleTypeConstraint(value) @@ -128,7 +169,7 @@ class InnerTypeConstraint(AbstractConstraint): if idx not in self.__multipleTypeConstraint: raise error.ValueConstraintError(value) constraint, status = self.__multipleTypeConstraint[idx] - if status == 'ABSENT': # XXX presense is not checked! + if status == 'ABSENT': # XXX presense is not checked! raise error.ValueConstraintError(value) constraint(value) @@ -142,10 +183,12 @@ class InnerTypeConstraint(AbstractConstraint): self.__singleTypeConstraint = v AbstractConstraint._setValues(self, values) -# Boolean ops on constraints + +# Boolean ops on constraints class ConstraintsExclusion(AbstractConstraint): """Value must not fit the single constraint""" + def _testValue(self, value, idx): try: self._values[0](value, idx) @@ -159,42 +202,57 @@ class ConstraintsExclusion(AbstractConstraint): raise error.PyAsn1Error('Single constraint expected') AbstractConstraint._setValues(self, values) + class AbstractConstraintSet(AbstractConstraint): """Value must not satisfy the single constraint""" - def __getitem__(self, idx): return self._values[idx] - def __add__(self, value): return self.__class__(self, value) - def __radd__(self, value): return self.__class__(self, value) + def __getitem__(self, idx): + return self._values[idx] - def __len__(self): return len(self._values) + def __iter__(self): + return iter(self._values) + + def __add__(self, value): + return self.__class__(*(self._values + (value,))) + + def __radd__(self, value): + return self.__class__(*((value,) + self._values)) + + def __len__(self): + return len(self._values) # Constraints inclusion in sets - + def _setValues(self, values): self._values = values - for v in values: - self._valueMap[v] = 1 - self._valueMap.update(v.getValueMap()) + for constraint in values: + if constraint: + self._valueMap.add(constraint) + self._valueMap.update(constraint.getValueMap()) + class ConstraintsIntersection(AbstractConstraintSet): """Value must satisfy all constraints""" + def _testValue(self, value, idx): - for v in self._values: - v(value, idx) + for constraint in self._values: + constraint(value, idx) + class ConstraintsUnion(AbstractConstraintSet): """Value must satisfy at least one constraint""" + def _testValue(self, value, idx): - for v in self._values: + for constraint in self._values: try: - v(value, idx) + constraint(value, idx) except error.ValueConstraintError: pass else: return raise error.ValueConstraintError( 'all of %s failed for \"%s\"' % (self._values, value) - ) + ) # XXX # add tests for type check diff --git a/src/pyasn1/type/error.py b/src/pyasn1/type/error.py index 3e684844..cbfa276a 100644 --- a/src/pyasn1/type/error.py +++ b/src/pyasn1/type/error.py @@ -1,3 +1,11 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.error import PyAsn1Error -class ValueConstraintError(PyAsn1Error): pass + +class ValueConstraintError(PyAsn1Error): + pass diff --git a/src/pyasn1/type/namedtype.py b/src/pyasn1/type/namedtype.py index aca42828..7a51f186 100644 --- a/src/pyasn1/type/namedtype.py +++ b/src/pyasn1/type/namedtype.py @@ -1,149 +1,511 @@ -# NamedType specification for constructed types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# import sys -from pyasn1.type import tagmap +from pyasn1.type import tag, tagmap from pyasn1 import error -class NamedType: - isOptional = 0 - isDefaulted = 0 - def __init__(self, name, t): - self.__name = name; self.__type = t - def __repr__(self): return '%s(%r, %r)' % ( - self.__class__.__name__, self.__name, self.__type - ) - def __eq__(self, other): return tuple(self) == tuple(other) - def __ne__(self, other): return tuple(self) != tuple(other) - def __lt__(self, other): return tuple(self) < tuple(other) - def __le__(self, other): return tuple(self) <= tuple(other) - def __gt__(self, other): return tuple(self) > tuple(other) - def __ge__(self, other): return tuple(self) >= tuple(other) - def __hash__(self): return hash(tuple(self)) - - def getType(self): return self.__type - def getName(self): return self.__name +__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 'NamedTypes'] + + +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): + self.__name = name + self.__type = asn1Object + self.__nameAndType = name, asn1Object + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.__name, self.__type) + + 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): - if idx == 0: return self.__name - if idx == 1: return self.__type - raise IndexError() + 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 + + # Backward compatibility + + def getName(self): + return self.name + + def getType(self): + return self.asn1Object + + class OptionalNamedType(NamedType): - isOptional = 1 + __doc__ = NamedType.__doc__ + + isOptional = True + + class DefaultedNamedType(NamedType): - isDefaulted = 1 - -class NamedTypes: - def __init__(self, *namedTypes): + __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` + """ + def __init__(self, *namedTypes, **kwargs): self.__namedTypes = namedTypes self.__namedTypesLen = len(self.__namedTypes) - self.__minTagSet = None - self.__tagToPosIdx = {}; self.__nameToPosIdx = {} - self.__tagMap = { False: None, True: None } - self.__ambigiousTypes = {} + 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 = bool([True for namedType in self.__namedTypes + if namedType.isDefaulted or namedType.isOptional]) + 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): return '%s(%s)' % ( - self.__class__.__name__, - ', '.join([ repr(x) for x in self.__namedTypes ]) + self.__class__.__name__, ', '.join([repr(x) for x in self.__namedTypes]) ) - def __eq__(self, other): return tuple(self) == tuple(other) - def __ne__(self, other): return tuple(self) != tuple(other) - def __lt__(self, other): return tuple(self) < tuple(other) - def __le__(self, other): return tuple(self) <= tuple(other) - def __gt__(self, other): return tuple(self) > tuple(other) - def __ge__(self, other): return tuple(self) >= tuple(other) - def __hash__(self): return hash(tuple(self)) - - def __getitem__(self, idx): return self.__namedTypes[idx] + + 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 bool(self.__namedTypesLen) + def __nonzero__(self): + return self.__namedTypesLen > 0 else: - def __bool__(self): return bool(self.__namedTypesLen) - def __len__(self): return self.__namedTypesLen - - def clone(self): return self.__class__(*self.__namedTypes) - - def getTypeByPosition(self, idx): - if idx < 0 or idx >= self.__namedTypesLen: - raise error.PyAsn1Error('Type position out of range') - else: - return self.__namedTypes[idx].getType() + def __bool__(self): + return self.__namedTypesLen > 0 - def getPositionByType(self, tagSet): - if not self.__tagToPosIdx: - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - tagMap = self.__namedTypes[idx].getType().getTagMap() - for t in tagMap.getPosMap(): - if t in self.__tagToPosIdx: - raise error.PyAsn1Error('Duplicate type %s' % (t,)) - self.__tagToPosIdx[t] = idx + 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): + ambigiousTypes = {} + partialAmbigiousTypes = () + for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))): + if namedType.isOptional or namedType.isDefaulted: + partialAmbigiousTypes = (namedType,) + partialAmbigiousTypes + else: + partialAmbigiousTypes = (namedType,) + if len(partialAmbigiousTypes) == len(self.__namedTypes): + ambigiousTypes[idx] = self + else: + ambigiousTypes[idx] = NamedTypes(*partialAmbigiousTypes, **dict(terminal=True)) + return ambigiousTypes + + 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 + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If given position is out of fields range + """ try: - return self.__tagToPosIdx[tagSet] - except KeyError: - raise error.PyAsn1Error('Type %s not found' % (tagSet,)) - - def getNameByPosition(self, idx): - try: - return self.__namedTypes[idx].getName() + return self.__namedTypes[idx].asn1Object + except IndexError: raise error.PyAsn1Error('Type position out of range') - def getPositionByName(self, name): - if not self.__nameToPosIdx: - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - n = self.__namedTypes[idx].getName() - if n in self.__nameToPosIdx: - raise error.PyAsn1Error('Duplicate name %s' % (n,)) - self.__nameToPosIdx[n] = idx + + 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 + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes* + """ try: - return self.__nameToPosIdx[name] + 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 + ------ + : :class:`~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 + ------ + : :class:`~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 __buildAmbigiousTagMap(self): - ambigiousTypes = () - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - t = self.__namedTypes[idx] - if t.isOptional or t.isDefaulted: - ambigiousTypes = (t, ) + ambigiousTypes - else: - ambigiousTypes = (t, ) - self.__ambigiousTypes[idx] = NamedTypes(*ambigiousTypes) - def getTagMapNearPosition(self, idx): - if not self.__ambigiousTypes: self.__buildAmbigiousTagMap() + """Return ASN.1 types that are allowed at or past given field position. + + Some ASN.1 serialization 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 + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If given position is out of fields range + """ try: - return self.__ambigiousTypes[idx].getTagMap() + return self.__ambiguousTypes[idx].tagMap + except KeyError: raise error.PyAsn1Error('Type position out of range') def getPositionNearType(self, tagSet, idx): - if not self.__ambigiousTypes: self.__buildAmbigiousTagMap() + """Return the closest field position where given ASN.1 type is allowed. + + Some ASN.1 serialization 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 + ------ + : :class:`~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.__ambigiousTypes[idx].getPositionByType(tagSet) + return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet) + except KeyError: raise error.PyAsn1Error('Type position out of range') - def genMinTagSet(self): - if self.__minTagSet is None: - for t in self.__namedTypes: - __type = t.getType() - tagSet = getattr(__type,'getMinTagSet',__type.getTagSet)() - if self.__minTagSet is None or tagSet < self.__minTagSet: - self.__minTagSet = tagSet + 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/serialization 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 getTagMap(self, uniq=False): - if self.__tagMap[uniq] is None: - tagMap = tagmap.TagMap() - for nt in self.__namedTypes: - tagMap = tagMap.clone( - nt.getType(), nt.getType().getTagMap(), uniq - ) - self.__tagMap[uniq] = tagMap - return self.__tagMap[uniq] + + 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 namedTypes(self): + return iter(self.__namedTypes) + + @property + def requiredComponents(self): + return self.__requiredComponents diff --git a/src/pyasn1/type/namedval.py b/src/pyasn1/type/namedval.py index 676cb934..008ddc04 100644 --- a/src/pyasn1/type/namedval.py +++ b/src/pyasn1/type/namedval.py @@ -1,58 +1,181 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# # ASN.1 named integers +# from pyasn1 import error -__all__ = [ 'NamedValues' ] +__all__ = ['NamedValues'] + + +class NamedValues(object): + """Create named values object. + + The |NamedValues| object represents a collection of string names + associated with numeric IDs. These objects are used for giving + names to otherwise numerical values. + + |NamedValues| objects are immutable and duck-type Python + :class:`dict` object mapping ID to name and vice-versa. + + Parameters + ---------- + + \*args: variable number of two-element :py:class:`tuple` + \*\*kwargs: keyword parameters of: + + name: :py:class:`str` + Value name + + value: :py:class:`int` + A numerical value + + Examples + -------- + + >>> nv = namedval.NamedValues('a', 'b', ('c', 0), d=1) + >>> nv + >>> {'c': 0, 'd': 1, 'a': 2, 'b': 3} + >>> nv[0] + 'c' + >>> nv['a'] + 2 + """ + def __init__(self, *args, **kwargs): + self.__names = {} + self.__numbers = {} + + anonymousNames = [] + + for namedValue in args: + if isinstance(namedValue, (tuple, list)): + try: + name, number = namedValue + + except ValueError: + raise error.PyAsn1Error('Not a proper attribute-value pair %r' % (namedValue,)) -class NamedValues: - def __init__(self, *namedValues): - self.nameToValIdx = {}; self.valToNameIdx = {} - self.namedValues = () - automaticVal = 1 - for namedValue in namedValues: - if isinstance(namedValue, tuple): - name, val = namedValue else: - name = namedValue - val = automaticVal - if name in self.nameToValIdx: + anonymousNames.append(namedValue) + continue + + if name in self.__names: raise error.PyAsn1Error('Duplicate name %s' % (name,)) - self.nameToValIdx[name] = val - if val in self.valToNameIdx: - raise error.PyAsn1Error('Duplicate value %s=%s' % (name, val)) - self.valToNameIdx[val] = name - self.namedValues = self.namedValues + ((name, val),) - automaticVal = automaticVal + 1 + + if number in self.__numbers: + raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number)) + + self.__names[name] = number + self.__numbers[number] = name + + for name, number in kwargs.items(): + if name in self.__names: + raise error.PyAsn1Error('Duplicate name %s' % (name,)) + + if number in self.__numbers: + raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number)) + + self.__names[name] = number + self.__numbers[number] = name + + if anonymousNames: + + number = self.__numbers and max(self.__numbers) + 1 or 0 + + for name in anonymousNames: + + if name in self.__names: + raise error.PyAsn1Error('Duplicate name %s' % (name,)) + + self.__names[name] = number + self.__numbers[number] = name + + number += 1 def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(x) for x in self.namedValues])) + return '%s(%r)' % (self.__class__.__name__, tuple(self.items())) - def __str__(self): return str(self.namedValues) + def __str__(self): + return str(self.items()) - def __eq__(self, other): return tuple(self) == tuple(other) - def __ne__(self, other): return tuple(self) != tuple(other) - def __lt__(self, other): return tuple(self) < tuple(other) - def __le__(self, other): return tuple(self) <= tuple(other) - def __gt__(self, other): return tuple(self) > tuple(other) - def __ge__(self, other): return tuple(self) >= tuple(other) - def __hash__(self): return hash(tuple(self)) - - def getName(self, value): - if value in self.valToNameIdx: - return self.valToNameIdx[value] + def __eq__(self, other): + return dict(self) == other - def getValue(self, name): - if name in self.nameToValIdx: - return self.nameToValIdx[name] - - def __getitem__(self, i): return self.namedValues[i] - def __len__(self): return len(self.namedValues) + def __ne__(self, other): + return dict(self) != other + + def __lt__(self, other): + return dict(self) < other + + def __le__(self, other): + return dict(self) <= other + + def __gt__(self, other): + return dict(self) > other + + def __ge__(self, other): + return dict(self) >= other + + def __hash__(self): + return hash(self.items()) + + # Python dict protocol (read-only) + + def __getitem__(self, key): + try: + return self.__numbers[key] + + except KeyError: + return self.__names[key] + + def __len__(self): + return len(self.__names) + + def __contains__(self, key): + return key in self.__names or key in self.__numbers + + def __iter__(self): + return iter(self.__names) + + def values(self): + return iter(self.__numbers) + + def keys(self): + return iter(self.__names) + + def items(self): + for name in self.__names: + yield name, self.__names[name] + + # support merging def __add__(self, namedValues): - return self.__class__(*self.namedValues + namedValues) - def __radd__(self, namedValues): - return self.__class__(*namedValues + tuple(self)) - - def clone(self, *namedValues): - return self.__class__(*tuple(self) + namedValues) + return self.__class__(*tuple(self.items()) + tuple(namedValues.items())) -# XXX clone/subtype? + # XXX clone/subtype? + + def clone(self, *args, **kwargs): + new = self.__class__(*args, **kwargs) + return self + new + + # legacy protocol + + def getName(self, value): + if value in self.__numbers: + return self.__numbers[value] + + def getValue(self, name): + if name in self.__names: + return self.__names[name] + + def getValues(self, *names): + try: + return [self.__names[name] for name in names] + + except KeyError: + raise error.PyAsn1Error( + 'Unknown bit identifier(s): %s' % (set(names).difference(self.__names),) + ) diff --git a/src/pyasn1/type/tag.py b/src/pyasn1/type/tag.py index 7471a9b1..6c0e2c17 100644 --- a/src/pyasn1/type/tag.py +++ b/src/pyasn1/type/tag.py @@ -1,128 +1,318 @@ -# ASN.1 types tags -from operator import getitem +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1 import error +__all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext', + 'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed', + 'tagCategoryImplicit', 'tagCategoryExplicit', 'tagCategoryUntagged', + 'Tag', 'TagSet'] + +#: Identifier for ASN.1 class UNIVERSAL tagClassUniversal = 0x00 + +#: Identifier for ASN.1 class APPLICATION tagClassApplication = 0x40 + +#: Identifier for ASN.1 class context-specific tagClassContext = 0x80 + +#: Identifier for ASN.1 class private tagClassPrivate = 0xC0 +#: Identifier for "simple" ASN.1 structure (e.g. scalar) tagFormatSimple = 0x00 + +#: Identifier for "constructed" ASN.1 structure (e.g. may have inner components) tagFormatConstructed = 0x20 tagCategoryImplicit = 0x01 tagCategoryExplicit = 0x02 tagCategoryUntagged = 0x04 -class Tag: + +class Tag(object): + """Create ASN.1 tag + + Represents ASN.1 tag that can be attached to a ASN.1 type to make + types distinguishable from each other. + + *Tag* objects are immutable and duck-type Python :class:`tuple` objects + holding three integer components of a tag. + + Parameters + ---------- + tagClass: :py:class:`int` + Tag *class* value + + tagFormat: :py:class:`int` + Tag *format* value + + tagId: :py:class:`int` + Tag ID value + """ def __init__(self, tagClass, tagFormat, tagId): if tagId < 0: - raise error.PyAsn1Error( - 'Negative tag ID (%s) not allowed' % (tagId,) - ) - self.__tag = (tagClass, tagFormat, tagId) - self.uniq = (tagClass, tagId) - self.__hashedUniqTag = hash(self.uniq) + raise error.PyAsn1Error('Negative tag ID (%s) not allowed' % tagId) + self.__tagClass = tagClass + self.__tagFormat = tagFormat + self.__tagId = tagId + self.__tagClassId = tagClass, tagId + self.__hash = hash(self.__tagClassId) def __str__(self): - return '[%s:%s:%s]' % self.__tag + return '[%s:%s:%s]' % (self.__tagClass, self.__tagFormat, self.__tagId) def __repr__(self): return '%s(tagClass=%s, tagFormat=%s, tagId=%s)' % ( - (self.__class__.__name__,) + self.__tag - ) - # These is really a hotspot -- expose public "uniq" attribute to save on - # function calls - def __eq__(self, other): return self.uniq == other.uniq - def __ne__(self, other): return self.uniq != other.uniq - def __lt__(self, other): return self.uniq < other.uniq - def __le__(self, other): return self.uniq <= other.uniq - def __gt__(self, other): return self.uniq > other.uniq - def __ge__(self, other): return self.uniq >= other.uniq - def __hash__(self): return self.__hashedUniqTag - def __getitem__(self, idx): return self.__tag[idx] + (self.__class__.__name__, self.__tagClass, self.__tagFormat, self.__tagId) + ) + + def __eq__(self, other): + return self.__tagClassId == other + + def __ne__(self, other): + return self.__tagClassId != other + + def __lt__(self, other): + return self.__tagClassId < other + + def __le__(self, other): + return self.__tagClassId <= other + + def __gt__(self, other): + return self.__tagClassId > other + + def __ge__(self, other): + return self.__tagClassId >= other + + def __hash__(self): + return self.__hash + + def __getitem__(self, idx): + if idx == 0: + return self.__tagClass + elif idx == 1: + return self.__tagFormat + elif idx == 2: + return self.__tagId + else: + raise IndexError() + + def __iter__(self): + yield self.__tagClass + yield self.__tagFormat + yield self.__tagId + def __and__(self, otherTag): - (tagClass, tagFormat, tagId) = otherTag - return self.__class__( - self.__tag&tagClass, self.__tag&tagFormat, self.__tag&tagId - ) + return self.__class__(self.__tagClass & otherTag.tagClass, + self.__tagFormat & otherTag.tagFormat, + self.__tagId & otherTag.tagId) + def __or__(self, otherTag): - (tagClass, tagFormat, tagId) = otherTag - return self.__class__( - self.__tag[0]|tagClass, - self.__tag[1]|tagFormat, - self.__tag[2]|tagId - ) - def asTuple(self): return self.__tag # __getitem__() is slow - -class TagSet: + return self.__class__(self.__tagClass | otherTag.tagClass, + self.__tagFormat | otherTag.tagFormat, + self.__tagId | otherTag.tagId) + + @property + def tagClass(self): + """ASN.1 tag class + + Returns + ------- + : :py:class:`int` + Tag class + """ + return self.__tagClass + + @property + def tagFormat(self): + """ASN.1 tag format + + Returns + ------- + : :py:class:`int` + Tag format + """ + return self.__tagFormat + + @property + def tagId(self): + """ASN.1 tag ID + + Returns + ------- + : :py:class:`int` + Tag ID + """ + return self.__tagId + + +class TagSet(object): + """Create a collection of ASN.1 tags + + Represents a combination of :class:`~pyasn1.type.tag.Tag` objects + that can be attached to a ASN.1 type to make types distinguishable + from each other. + + *TagSet* objects are immutable and duck-type Python :class:`tuple` objects + holding arbitrary number of :class:`~pyasn1.type.tag.Tag` objects. + + Parameters + ---------- + baseTag: :class:`~pyasn1.type.tag.Tag` + Base *Tag* object. This tag survives IMPLICIT tagging. + + *superTags: :class:`~pyasn1.type.tag.Tag` + Additional *Tag* objects taking part in subtyping. + """ def __init__(self, baseTag=(), *superTags): self.__baseTag = baseTag self.__superTags = superTags - self.__hashedSuperTags = hash(superTags) - _uniq = () - for t in superTags: - _uniq = _uniq + t.uniq - self.uniq = _uniq + self.__superTagsClassId = tuple( + [(superTag.tagClass, superTag.tagId) for superTag in superTags] + ) self.__lenOfSuperTags = len(superTags) + self.__hash = hash(self.__superTagsClassId) def __str__(self): return self.__superTags and '+'.join([str(x) for x in self.__superTags]) or '[untagged]' - + def __repr__(self): return '%s(%s)' % ( - self.__class__.__name__, - '(), ' + ', '.join([repr(x) for x in self.__superTags]) - ) + self.__class__.__name__, '(), ' + ', '.join([repr(x) for x in self.__superTags]) + ) def __add__(self, superTag): - return self.__class__( - self.__baseTag, *self.__superTags + (superTag,) - ) + return self.__class__(self.__baseTag, *self.__superTags + (superTag,)) + def __radd__(self, superTag): - return self.__class__( - self.__baseTag, *(superTag,) + self.__superTags - ) + return self.__class__(self.__baseTag, *(superTag,) + self.__superTags) + + def __getitem__(self, i): + if i.__class__ is slice: + return self.__class__(self.__baseTag, *self.__superTags[i]) + else: + return self.__superTags[i] + + def __eq__(self, other): + return self.__superTagsClassId == other + + def __ne__(self, other): + return self.__superTagsClassId != other + + def __lt__(self, other): + return self.__superTagsClassId < other + + def __le__(self, other): + return self.__superTagsClassId <= other + + def __gt__(self, other): + return self.__superTagsClassId > other + + def __ge__(self, other): + return self.__superTagsClassId >= other + + def __hash__(self): + return self.__hash + + def __len__(self): + return self.__lenOfSuperTags + + @property + def baseTag(self): + """Return base ASN.1 tag + + Returns + ------- + : :class:`~pyasn1.type.tag.Tag` + Base tag of this *TagSet* + """ + return self.__baseTag + + @property + def superTags(self): + """Return ASN.1 tags + + Returns + ------- + : :py:class:`tuple` + Tuple of :class:`~pyasn1.type.tag.Tag` objects that this *TagSet* contains + """ + return self.__superTags def tagExplicitly(self, superTag): - tagClass, tagFormat, tagId = superTag - if tagClass == tagClassUniversal: - raise error.PyAsn1Error( - 'Can\'t tag with UNIVERSAL-class tag' - ) - if tagFormat != tagFormatConstructed: - superTag = Tag(tagClass, tagFormatConstructed, tagId) + """Return explicitly tagged *TagSet* + + Create a new *TagSet* representing callee *TagSet* explicitly tagged + with passed tag(s). With explicit tagging mode, new tags are appended + to existing tag(s). + + Parameters + ---------- + superTag: :class:`~pyasn1.type.tag.Tag` + *Tag* object to tag this *TagSet* + + Returns + ------- + : :class:`~pyasn1.type.tag.TagSet` + New *TagSet* object + """ + if superTag.tagClass == tagClassUniversal: + raise error.PyAsn1Error("Can't tag with UNIVERSAL class tag") + if superTag.tagFormat != tagFormatConstructed: + superTag = Tag(superTag.tagClass, tagFormatConstructed, superTag.tagId) return self + superTag def tagImplicitly(self, superTag): - tagClass, tagFormat, tagId = superTag + """Return implicitly tagged *TagSet* + + Create a new *TagSet* representing callee *TagSet* implicitly tagged + with passed tag(s). With implicit tagging mode, new tag(s) replace the + last existing tag. + + Parameters + ---------- + superTag: :class:`~pyasn1.type.tag.Tag` + *Tag* object to tag this *TagSet* + + Returns + ------- + : :class:`~pyasn1.type.tag.TagSet` + New *TagSet* object + """ if self.__superTags: - superTag = Tag(tagClass, self.__superTags[-1][1], tagId) + superTag = Tag(superTag.tagClass, self.__superTags[-1].tagFormat, superTag.tagId) return self[:-1] + superTag - def getBaseTag(self): return self.__baseTag - def __getitem__(self, idx): - if isinstance(idx, slice): - return self.__class__( - self.__baseTag, *getitem(self.__superTags, idx) - ) - return self.__superTags[idx] - def __eq__(self, other): return self.uniq == other.uniq - def __ne__(self, other): return self.uniq != other.uniq - def __lt__(self, other): return self.uniq < other.uniq - def __le__(self, other): return self.uniq <= other.uniq - def __gt__(self, other): return self.uniq > other.uniq - def __ge__(self, other): return self.uniq >= other.uniq - def __hash__(self): return self.__hashedSuperTags - def __len__(self): return self.__lenOfSuperTags def isSuperTagSetOf(self, tagSet): + """Test type relationship against given *TagSet* + + The callee is considered to be a supertype of given *TagSet* + tag-wise if all tags in *TagSet* are present in the callee and + they are in the same order. + + Parameters + ---------- + tagSet: :class:`~pyasn1.type.tag.TagSet` + *TagSet* object to evaluate against the callee + + Returns + ------- + : :py:class:`bool` + `True` if callee is a supertype of *tagSet* + """ if len(tagSet) < self.__lenOfSuperTags: - return - idx = self.__lenOfSuperTags - 1 - while idx >= 0: - if self.__superTags[idx] != tagSet[idx]: - return - idx = idx - 1 - return 1 - -def initTagSet(tag): return TagSet(tag, tag) + return False + return self.__superTags == tagSet[:self.__lenOfSuperTags] + + # Backward compatibility + + def getBaseTag(self): + return self.__baseTag + +def initTagSet(tag): + return TagSet(tag, tag) diff --git a/src/pyasn1/type/tagmap.py b/src/pyasn1/type/tagmap.py index feb91ae3..32b74473 100644 --- a/src/pyasn1/type/tagmap.py +++ b/src/pyasn1/type/tagmap.py @@ -1,66 +1,102 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1 import error -class TagMap: - def __init__(self, posMap={}, negMap={}, defType=None): - self.__posMap = posMap.copy() - self.__negMap = negMap.copy() - self.__defType = defType - +__all__ = ['TagMap'] + + +class TagMap(object): + """Map *TagSet* objects to ASN.1 types + + Create an object mapping *TagSet* object to ASN.1 type. + + *TagMap* objects are immutable and duck-type read-only Python + :class:`dict` objects holding *TagSet* objects as keys and ASN.1 + type objects as values. + + Parameters + ---------- + presentTypes: :py:class:`dict` + Map of :class:`~pyasn1.type.tag.TagSet` to ASN.1 objects considered + as being unconditionally present in the *TagMap*. + + skipTypes: :py:class:`dict` + A collection of :class:`~pyasn1.type.tag.TagSet` objects considered + as absent in the *TagMap* even when *defaultType* is present. + + defaultType: ASN.1 type object + An ASN.1 type object callee *TagMap* returns for any *TagSet* key not present + in *presentTypes* (unless given key is present in *skipTypes*). + """ + def __init__(self, presentTypes=None, skipTypes=None, defaultType=None): + self.__presentTypes = presentTypes or {} + self.__skipTypes = skipTypes or {} + self.__defaultType = defaultType + def __contains__(self, tagSet): - return tagSet in self.__posMap or \ - self.__defType is not None and tagSet not in self.__negMap + return (tagSet in self.__presentTypes or + self.__defaultType is not None and tagSet not in self.__skipTypes) def __getitem__(self, tagSet): - if tagSet in self.__posMap: - return self.__posMap[tagSet] - elif tagSet in self.__negMap: - raise error.PyAsn1Error('Key in negative map') - elif self.__defType is not None: - return self.__defType - else: - raise KeyError() + try: + return self.__presentTypes[tagSet] + except KeyError: + if self.__defaultType is None: + raise KeyError() + elif tagSet in self.__skipTypes: + raise error.PyAsn1Error('Key in negative map') + else: + return self.__defaultType + + def __iter__(self): + return iter(self.__presentTypes) def __repr__(self): s = self.__class__.__name__ + '(' - if self.__posMap: - s = s + 'posMap=%r, ' % (self.__posMap,) - if self.__negMap: - s = s + 'negMap=%r, ' % (self.__negMap,) - if self.__defType is not None: - s = s + 'defType=%r' % (self.__defType,) + if self.__presentTypes: + s += 'presentTypes=%r, ' % (self.__presentTypes,) + if self.__skipTypes: + s += 'skipTypes=%r, ' % (self.__skipTypes,) + if self.__defaultType is not None: + s += 'defaultType=%r' % (self.__defaultType,) return s + ')' def __str__(self): - s = self.__class__.__name__ + ':\n' - if self.__posMap: - s = s + 'posMap:\n%s, ' % ',\n '.join([ x.prettyPrintType() for x in self.__posMap.values()]) - if self.__negMap: - s = s + 'negMap:\n%s, ' % ',\n '.join([ x.prettyPrintType() for x in self.__negMap.values()]) - if self.__defType is not None: - s = s + 'defType:\n%s, ' % self.__defType.prettyPrintType() + s = self.__class__.__name__ + ': ' + if self.__presentTypes: + s += 'presentTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__presentTypes.values()]) + if self.__skipTypes: + s += 'skipTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__skipTypes.values()]) + if self.__defaultType is not None: + s += 'defaultType: %s, ' % self.__defaultType.prettyPrintType() return s - def clone(self, parentType, tagMap, uniq=False): - if self.__defType is not None and tagMap.getDef() is not None: - raise error.PyAsn1Error('Duplicate default value at %s' % (self,)) - if tagMap.getDef() is not None: - defType = tagMap.getDef() - else: - defType = self.__defType - - posMap = self.__posMap.copy() - for k in tagMap.getPosMap(): - if uniq and k in posMap: - raise error.PyAsn1Error('Duplicate positive key %s' % (k,)) - posMap[k] = parentType + @property + def presentTypes(self): + """Return *TagSet* to ASN.1 type map present in callee *TagMap*""" + return self.__presentTypes - negMap = self.__negMap.copy() - negMap.update(tagMap.getNegMap()) - - return self.__class__( - posMap, negMap, defType, - ) + @property + def skipTypes(self): + """Return *TagSet* collection unconditionally absent in callee *TagMap*""" + return self.__skipTypes - def getPosMap(self): return self.__posMap.copy() - def getNegMap(self): return self.__negMap.copy() - def getDef(self): return self.__defType + @property + def defaultType(self): + """Return default ASN.1 type being returned for any missing *TagSet*""" + return self.__defaultType + + # Backward compatibility + + def getPosMap(self): + return self.presentTypes + + def getNegMap(self): + return self.skipTypes + + def getDef(self): + return self.defaultType diff --git a/src/pyasn1/type/univ.py b/src/pyasn1/type/univ.py index 4ed640f2..a90c6483 100644 --- a/src/pyasn1/type/univ.py +++ b/src/pyasn1/type/univ.py @@ -1,423 +1,1063 @@ -# ASN.1 "universal" data types -import operator, sys, math +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys +import math from pyasn1.type import base, tag, constraint, namedtype, namedval, tagmap from pyasn1.codec.ber import eoo -from pyasn1.compat import octets +from pyasn1.compat import octets, integer, binary from pyasn1 import error +NoValue = base.NoValue +noValue = NoValue() + +__all__ = ['Integer', 'Boolean', 'BitString', 'OctetString', 'Null', + 'ObjectIdentifier', 'Real', 'Enumerated', 'SequenceOfAndSetOfBase', 'SequenceOf', + 'SetOf', 'SequenceAndSetBase', 'Sequence', 'Set', 'Choice', 'Any', + 'NoValue', 'noValue'] + # "Simple" ASN.1 types (yet incomplete) + class Integer(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Python integer or string literal or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x02) - ) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers namedValues = namedval.NamedValues() - def __init__(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if namedValues is None: - self.__namedValues = self.namedValues - else: - self.__namedValues = namedValues - base.AbstractSimpleAsn1Item.__init__( - self, value, tagSet, subtypeSpec - ) + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def __init__(self, value=noValue, **kwargs): + if 'namedValues' not in kwargs: + kwargs['namedValues'] = self.namedValues + + base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) def __repr__(self): - if self.__namedValues is not self.namedValues: - return '%s, %r)' % (base.AbstractSimpleAsn1Item.__repr__(self)[:-1], self.__namedValues) + if self.namedValues is not self.__class__.namedValues: + return '%s, %r)' % (base.AbstractSimpleAsn1Item.__repr__(self)[:-1], self.namedValues) else: return base.AbstractSimpleAsn1Item.__repr__(self) - def __and__(self, value): return self.clone(self._value & value) - def __rand__(self, value): return self.clone(value & self._value) - def __or__(self, value): return self.clone(self._value | value) - def __ror__(self, value): return self.clone(value | self._value) - def __xor__(self, value): return self.clone(self._value ^ value) - def __rxor__(self, value): return self.clone(value ^ self._value) - def __lshift__(self, value): return self.clone(self._value << value) - def __rshift__(self, value): return self.clone(self._value >> value) + def __and__(self, value): + return self.clone(self._value & value) - def __add__(self, value): return self.clone(self._value + value) - def __radd__(self, value): return self.clone(value + self._value) - def __sub__(self, value): return self.clone(self._value - value) - def __rsub__(self, value): return self.clone(value - self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self.clone(value * self._value) - def __mod__(self, value): return self.clone(self._value % value) - def __rmod__(self, value): return self.clone(value % self._value) - def __pow__(self, value, modulo=None): return self.clone(pow(self._value, value, modulo)) - def __rpow__(self, value): return self.clone(pow(value, self._value)) + def __rand__(self, value): + return self.clone(value & self._value) + + def __or__(self, value): + return self.clone(self._value | value) + + def __ror__(self, value): + return self.clone(value | self._value) + + def __xor__(self, value): + return self.clone(self._value ^ value) + + def __rxor__(self, value): + return self.clone(value ^ self._value) + + def __lshift__(self, value): + return self.clone(self._value << value) + + def __rshift__(self, value): + return self.clone(self._value >> value) + + def __add__(self, value): + return self.clone(self._value + value) + + def __radd__(self, value): + return self.clone(value + self._value) + + def __sub__(self, value): + return self.clone(self._value - value) + + def __rsub__(self, value): + return self.clone(value - self._value) + + def __mul__(self, value): + return self.clone(self._value * value) + + def __rmul__(self, value): + return self.clone(value * self._value) + + def __mod__(self, value): + return self.clone(self._value % value) + + def __rmod__(self, value): + return self.clone(value % self._value) + + def __pow__(self, value, modulo=None): + return self.clone(pow(self._value, value, modulo)) + + def __rpow__(self, value): + return self.clone(pow(value, self._value)) + + def __floordiv__(self, value): + return self.clone(self._value // value) + + def __rfloordiv__(self, value): + return self.clone(value // self._value) if sys.version_info[0] <= 2: - def __div__(self, value): return self.clone(self._value // value) - def __rdiv__(self, value): return self.clone(value // self._value) + def __div__(self, value): + if isinstance(value, float): + return Real(self._value / value) + else: + return self.clone(self._value / value) + + def __rdiv__(self, value): + if isinstance(value, float): + return Real(value / self._value) + else: + return self.clone(value / self._value) else: - def __truediv__(self, value): return self.clone(self._value / value) - def __rtruediv__(self, value): return self.clone(value / self._value) - def __divmod__(self, value): return self.clone(self._value // value) - def __rdivmod__(self, value): return self.clone(value // self._value) + def __truediv__(self, value): + return Real(self._value / value) + + def __rtruediv__(self, value): + return Real(value / self._value) + + def __divmod__(self, value): + return self.clone(divmod(self._value, value)) + + def __rdivmod__(self, value): + return self.clone(divmod(value, self._value)) __hash__ = base.AbstractSimpleAsn1Item.__hash__ - def __int__(self): return int(self._value) + def __int__(self): + return int(self._value) + if sys.version_info[0] <= 2: - def __long__(self): return long(self._value) - def __float__(self): return float(self._value) - def __abs__(self): return self.clone(abs(self._value)) - def __index__(self): return int(self._value) - def __pos__(self): return self.clone(+self._value) - def __neg__(self): return self.clone(-self._value) - def __invert__(self): return self.clone(~self._value) + def __long__(self): + return long(self._value) + + def __float__(self): + return float(self._value) + + def __abs__(self): + return self.clone(abs(self._value)) + + def __index__(self): + return int(self._value) + + def __pos__(self): + return self.clone(+self._value) + + def __neg__(self): + return self.clone(-self._value) + + def __invert__(self): + return self.clone(~self._value) + def __round__(self, n=0): r = round(self._value, n) if n: return self.clone(r) else: return r - def __floor__(self): return math.floor(self._value) - def __ceil__(self): return math.ceil(self._value) - if sys.version_info[0:2] > (2, 5): - def __trunc__(self): return self.clone(math.trunc(self._value)) - def __lt__(self, value): return self._value < value - def __le__(self, value): return self._value <= value - def __eq__(self, value): return self._value == value - def __ne__(self, value): return self._value != value - def __gt__(self, value): return self._value > value - def __ge__(self, value): return self._value >= value + def __floor__(self): + return math.floor(self._value) + + def __ceil__(self): + return math.ceil(self._value) + + if sys.version_info[0:2] > (2, 5): + def __trunc__(self): + return self.clone(math.trunc(self._value)) + + def __lt__(self, value): + return self._value < value + + def __le__(self, value): + return self._value <= value + + def __eq__(self, value): + return self._value == value + + def __ne__(self, value): + return self._value != value + + def __gt__(self, value): + return self._value > value + + def __ge__(self, value): + return self._value >= value def prettyIn(self, value): - if not isinstance(value, str): - try: - return int(value) - except: - raise error.PyAsn1Error( - 'Can\'t coerce %r into integer: %s' % (value, sys.exc_info()[1]) - ) - r = self.__namedValues.getValue(value) - if r is not None: - return r try: return int(value) - except: - raise error.PyAsn1Error( - 'Can\'t coerce %r into integer: %s' % (value, sys.exc_info()[1]) + + except ValueError: + try: + return self.namedValues[value] + + except KeyError: + raise error.PyAsn1Error( + 'Can\'t coerce %r into integer: %s' % (value, sys.exc_info()[1]) ) def prettyOut(self, value): - r = self.__namedValues.getName(value) - return r is None and str(value) or repr(r) + try: + return repr(self.namedValues[value]) - def getNamedValues(self): return self.__namedValues + except KeyError: + return str(value) - def clone(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if value is None and tagSet is None and subtypeSpec is None \ - and namedValues is None: - return self - if value is None: - value = self._value - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - if namedValues is None: - namedValues = self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing symbolic aliases for numbers to use instead of inheriting from caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller'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 caller, then + use the result as new object's ASN.1 constraints. + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Add given object representing symbolic aliases for numbers + to one of the caller, then use the result as new object's + named numbers. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) + + # backward compatibility + + def getNamedValues(self): + return self.namedValues - def subtype(self, value=None, implicitTag=None, explicitTag=None, - subtypeSpec=None, namedValues=None): - if value is None: - value = self._value - if implicitTag is not None: - tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: - tagSet = self._tagSet.tagExplicitly(explicitTag) - else: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if namedValues is None: - namedValues = self.__namedValues - else: - namedValues = namedValues + self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) class Boolean(Integer): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = Integer.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x01), - ) - subtypeSpec = Integer.subtypeSpec+constraint.SingleValueConstraint(0,1) - namedValues = Integer.namedValues.clone(('False', 0), ('True', 1)) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = Integer.subtypeSpec + constraint.SingleValueConstraint(0, 1) + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers + namedValues = namedval.NamedValues(('False', 0), ('True', 1)) + + # Optimization for faster codec lookup + typeId = Integer.getTypeId() + class BitString(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type both Python :class:`tuple` (as a tuple + of bits) and :class:`int` objects. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Python integer or string literal representing binary or hexadecimal + number or sequence of integer bits or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x03) - ) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers namedValues = namedval.NamedValues() - def __init__(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if namedValues is None: - self.__namedValues = self.namedValues - else: - self.__namedValues = namedValues - base.AbstractSimpleAsn1Item.__init__( - self, value, tagSet, subtypeSpec - ) - def clone(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if value is None and tagSet is None and subtypeSpec is None \ - and namedValues is None: + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + defaultBinValue = defaultHexValue = noValue + + if sys.version_info[0] < 3: + SizedIntegerBase = long + else: + SizedIntegerBase = int + + class SizedInteger(SizedIntegerBase): + bitLength = leadingZeroBits = None + + def setBitLength(self, bitLength): + self.bitLength = bitLength + self.leadingZeroBits = max(bitLength - integer.bitLength(self), 0) return self - if value is None: - value = self._value - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - if namedValues is None: - namedValues = self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) - def subtype(self, value=None, implicitTag=None, explicitTag=None, - subtypeSpec=None, namedValues=None): - if value is None: - value = self._value - if implicitTag is not None: - tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: - tagSet = self._tagSet.tagExplicitly(explicitTag) - else: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if namedValues is None: - namedValues = self.__namedValues - else: - namedValues = namedValues + self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + def __len__(self): + if self.bitLength is None: + self.setBitLength(integer.bitLength(self)) - def __str__(self): return str(tuple(self)) + return self.bitLength + + def __init__(self, value=noValue, **kwargs): + if value is noValue or value is None: + if kwargs: + try: + value = self.fromBinaryString(kwargs.pop('binValue')) + + except KeyError: + pass + + try: + value = self.fromHexString(kwargs.pop('hexValue')) + + except KeyError: + pass + + if value is noValue or value is None: + if self.defaultBinValue is not noValue: + value = self.fromBinaryString(self.defaultBinValue) + + elif self.defaultHexValue is not noValue: + value = self.fromHexString(self.defaultHexValue) + + if 'namedValues' not in kwargs: + kwargs['namedValues'] = self.namedValues + + base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) + + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Class instance representing BitString type enumerations + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller'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 caller, then + use the result as new object's ASN.1 constraints. + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Add given object representing symbolic aliases for numbers + to one of the caller, then use the result as new object's + named numbers. + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) + + def __str__(self): + return self.asBinary() + + def __eq__(self, other): + other = self.prettyIn(other) + return self is other or self._value == other and len(self._value) == len(other) + + def __ne__(self, other): + other = self.prettyIn(other) + return self._value != other or len(self._value) != len(other) + + def __lt__(self, other): + other = self.prettyIn(other) + return len(self._value) < len(other) or len(self._value) == len(other) and self._value < other + + def __le__(self, other): + other = self.prettyIn(other) + return len(self._value) <= len(other) or len(self._value) == len(other) and self._value <= other + + def __gt__(self, other): + other = self.prettyIn(other) + return len(self._value) > len(other) or len(self._value) == len(other) and self._value > other + + def __ge__(self, other): + other = self.prettyIn(other) + return len(self._value) >= len(other) or len(self._value) == len(other) and self._value >= other # Immutable sequence object protocol def __len__(self): - if self._len is None: - self._len = len(self._value) - return self._len - def __getitem__(self, i): - if isinstance(i, slice): - return self.clone(operator.getitem(self._value, i)) - else: - return self._value[i] + return len(self._value) - def __add__(self, value): return self.clone(self._value + value) - def __radd__(self, value): return self.clone(value + self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self * value + def __getitem__(self, i): + if i.__class__ is slice: + return self.clone([self[x] for x in range(*i.indices(len(self)))]) + else: + length = len(self._value) - 1 + if i > length or i < 0: + raise IndexError('bit index out of range') + return (self._value >> (length - i)) & 1 + + def __iter__(self): + length = len(self._value) + while length: + length -= 1 + yield (self._value >> length) & 1 + + def __reversed__(self): + return reversed(tuple(self)) + + # arithmetic operators + + def __add__(self, value): + value = self.prettyIn(value) + return self.clone(self.SizedInteger(self._value << len(value) | value).setBitLength(len(self._value) + len(value))) + + def __radd__(self, value): + value = self.prettyIn(value) + return self.clone(self.SizedInteger(value << len(self._value) | self._value).setBitLength(len(self._value) + len(value))) + + def __mul__(self, value): + bitString = self._value + while value > 1: + bitString <<= len(self._value) + bitString |= self._value + value -= 1 + return self.clone(bitString) + + def __rmul__(self, value): + return self * value + + def __lshift__(self, count): + return self.clone(self.SizedInteger(self._value << count).setBitLength(len(self._value) + count)) + + def __rshift__(self, count): + return self.clone(self.SizedInteger(self._value >> count).setBitLength(max(0, len(self._value) - count))) + + def __int__(self): + return self._value + + def __float__(self): + return float(self._value) + + if sys.version_info[0] < 3: + def __long__(self): + return self._value + + def asNumbers(self): + """Get |ASN.1| value as a sequence of 8-bit integers. + + If |ASN.1| object length is not a multiple of 8, result + will be left-padded with zeros. + """ + return tuple(octets.octs2ints(self.asOctets())) + + def asOctets(self): + """Get |ASN.1| value as a sequence of octets. + + If |ASN.1| object length is not a multiple of 8, result + will be left-padded with zeros. + """ + return integer.to_bytes(self._value, length=len(self)) + + def asInteger(self): + """Get |ASN.1| value as a single integer value. + """ + return self._value + + def asBinary(self): + """Get |ASN.1| value as a text string of bits. + """ + binString = binary.bin(self._value)[2:] + return '0' * (len(self._value) - len(binString)) + binString + + @classmethod + def fromHexString(cls, value): + """Create a |ASN.1| object initialized from the hex string. + + Parameters + ---------- + value: :class:`str` + Text string like 'DEADBEEF' + """ + try: + return cls.SizedInteger(value, 16).setBitLength(len(value) * 4) + + except ValueError: + raise error.PyAsn1Error('%s.fromHexString() error: %s' % (cls.__name__, sys.exc_info()[1])) + + @classmethod + def fromBinaryString(cls, value): + """Create a |ASN.1| object initialized from a string of '0' and '1'. + + Parameters + ---------- + value: :class:`str` + Text string like '1010111' + """ + try: + return cls.SizedInteger(value or '0', 2).setBitLength(len(value)) + + except ValueError: + raise error.PyAsn1Error('%s.fromBinaryString() error: %s' % (cls.__name__, sys.exc_info()[1])) + + @classmethod + def fromOctetString(cls, value, padding=0): + """Create a |ASN.1| object initialized from a string. + + Parameters + ---------- + value: :class:`str` (Py2) or :class:`bytes` (Py3) + Text string like '\\\\x01\\\\xff' (Py2) or b'\\\\x01\\\\xff' (Py3) + """ + return cls(cls.SizedInteger(integer.from_bytes(value) >> padding).setBitLength(len(value) * 8 - padding)) def prettyIn(self, value): - r = [] - if not value: - return () - elif isinstance(value, str): - if value[0] == '\'': + if octets.isStringType(value): + if not value: + return self.SizedInteger(0).setBitLength(0) + + elif value[0] == '\'': # "'1011'B" -- ASN.1 schema representation (deprecated) if value[-2:] == '\'B': - for v in value[1:-2]: - if v == '0': - r.append(0) - elif v == '1': - r.append(1) - else: - raise error.PyAsn1Error( - 'Non-binary BIT STRING initializer %s' % (v,) - ) - return tuple(r) + return self.fromBinaryString(value[1:-2]) elif value[-2:] == '\'H': - for v in value[1:-2]: - i = 4 - v = int(v, 16) - while i: - i = i - 1 - r.append((v>>i)&0x01) - return tuple(r) + return self.fromHexString(value[1:-2]) else: raise error.PyAsn1Error( 'Bad BIT STRING value notation %s' % (value,) - ) - else: - for i in value.split(','): - j = self.__namedValues.getValue(i) - if j is None: - raise error.PyAsn1Error( - 'Unknown bit identifier \'%s\'' % (i,) - ) - if j >= len(r): - r.extend([0]*(j-len(r)+1)) - r[j] = 1 - return tuple(r) + ) + + elif self.namedValues and not value.isdigit(): # named bits like 'Urgent, Active' + names = [x.strip() for x in value.split(',')] + + try: + + bitPositions = [self.namedValues[name] for name in names] + + except KeyError: + raise error.PyAsn1Error('unknown bit name(s) in %r' % (names,)) + + rightmostPosition = max(bitPositions) + + number = 0 + for bitPosition in bitPositions: + number |= 1 << (rightmostPosition - bitPosition) + + return self.SizedInteger(number).setBitLength(rightmostPosition + 1) + + elif value.startswith('0x'): + return self.fromHexString(value[2:]) + + elif value.startswith('0b'): + return self.fromBinaryString(value[2:]) + + else: # assume plain binary string like '1011' + return self.fromBinaryString(value) + elif isinstance(value, (tuple, list)): - r = tuple(value) - for b in r: - if b and b != 1: - raise error.PyAsn1Error( - 'Non-binary BitString initializer \'%s\'' % (r,) - ) - return r - elif isinstance(value, BitString): - return tuple(value) + return self.fromBinaryString(''.join([b and '1' or '0' for b in value])) + + elif isinstance(value, (self.SizedInteger, BitString)): + return self.SizedInteger(value).setBitLength(len(value)) + + elif isinstance(value, intTypes): + return self.SizedInteger(value) + else: raise error.PyAsn1Error( 'Bad BitString initializer type \'%s\'' % (value,) - ) + ) def prettyOut(self, value): - return '\"\'%s\'B\"' % ''.join([str(x) for x in value]) + return '\'%s\'' % str(self) + try: + # noinspection PyStatementEffect all + except NameError: # Python 2.4 + # noinspection PyShadowingBuiltins def all(iterable): for element in iterable: if not element: return False return True -class OctetString(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x04) - ) - defaultBinValue = defaultHexValue = base.noValue - encoding = 'us-ascii' - def __init__(self, value=None, tagSet=None, subtypeSpec=None, - encoding=None, binValue=None, hexValue=None): - if encoding is None: - self._encoding = self.encoding - else: - self._encoding = encoding - if binValue is not None: - value = self.fromBinaryString(binValue) - if hexValue is not None: - value = self.fromHexString(hexValue) - if value is None or value is base.noValue: - value = self.defaultHexValue - if value is None or value is base.noValue: - value = self.defaultBinValue - self.__asNumbersCache = None - base.AbstractSimpleAsn1Item.__init__(self, value, tagSet, subtypeSpec) - def clone(self, value=None, tagSet=None, subtypeSpec=None, - encoding=None, binValue=None, hexValue=None): - if value is None and tagSet is None and subtypeSpec is None and \ - encoding is None and binValue is None and hexValue is None: - return self - if value is None and binValue is None and hexValue is None: - value = self._value - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - if encoding is None: - encoding = self._encoding - return self.__class__( - value, tagSet, subtypeSpec, encoding, binValue, hexValue - ) - +class OctetString(base.AbstractSimpleAsn1Item): + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python 2 :class:`str` or Python 3 :class:`bytes`. + When used in Unicode context, |ASN.1| type assumes "|encoding|" serialization. + + Parameters + ---------- + value : :class:`str`, :class:`bytes` or |ASN.1| object + string (Python 2) or bytes (Python 3), alternatively unicode object + (Python 2) or string (Python 3) representing character string to be + serialized into octets (note `encoding` parameter) or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) or + :class:`str` (Python 3) the payload when |ASN.1| object is used + in text string context. + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x04) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + defaultBinValue = defaultHexValue = noValue + encoding = 'iso-8859-1' + + def __init__(self, value=noValue, **kwargs): + if kwargs: + if value is noValue or value is None: + try: + value = self.fromBinaryString(kwargs.pop('binValue')) + + except KeyError: + pass + + try: + value = self.fromHexString(kwargs.pop('hexValue')) + + except KeyError: + pass + + if value is noValue or value is None: + if self.defaultBinValue is not noValue: + value = self.fromBinaryString(self.defaultBinValue) + + elif self.defaultHexValue is not noValue: + value = self.fromHexString(self.defaultHexValue) + + if 'encoding' not in kwargs: + kwargs['encoding'] = self.encoding + + base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) + + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`str`, :class:`bytes` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) + or :class:`str` (Python 3) the payload when |ASN.1| + object is used in string context. + + binValue: :py:class:`str` + Binary string initializer. Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer. Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`str`, :class:`bytes` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to |ASN.1| object tag set + :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 |ASN.1| object tag set + :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 caller, then + use the result as new object's ASN.1 constraints. + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) + or :class:`str` (Python 3) the payload when *OctetString* + object is used in string context. + + binValue: :py:class:`str` + Binary string initializer. Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer. Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) + if sys.version_info[0] <= 2: def prettyIn(self, value): if isinstance(value, str): return value elif isinstance(value, unicode): try: - return value.encode(self._encoding) + return value.encode(self.encoding) except (LookupError, UnicodeEncodeError): raise error.PyAsn1Error( - 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self._encoding) + "Can't encode string '%s' with codec %s" % (value, self.encoding) ) elif isinstance(value, (tuple, list)): try: - return ''.join([ chr(x) for x in value ]) + return ''.join([chr(x) for x in value]) except ValueError: raise error.PyAsn1Error( - 'Bad OctetString initializer \'%s\'' % (value,) - ) + 'Bad %s initializer \'%s\'' % (self.__class__.__name__, value) + ) else: return str(value) + + def __str__(self): + return str(self._value) + + def __unicode__(self): + try: + return self._value.decode(self.encoding) + + except UnicodeDecodeError: + raise error.PyAsn1Error( + "Can't decode string '%s' with codec %s" % (self._value, self.encoding) + ) + + def asOctets(self): + return str(self._value) + + def asNumbers(self): + return tuple([ord(x) for x in self._value]) + else: def prettyIn(self, value): if isinstance(value, bytes): return value elif isinstance(value, str): try: - return value.encode(self._encoding) + return value.encode(self.encoding) except UnicodeEncodeError: raise error.PyAsn1Error( - 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self._encoding) + 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self.encoding) ) - elif isinstance(value, OctetString): + elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way return value.asOctets() - elif isinstance(value, (tuple, list, map)): - try: - return bytes(value) - except ValueError: - raise error.PyAsn1Error( - 'Bad OctetString initializer \'%s\'' % (value,) - ) + elif isinstance(value, base.AbstractSimpleAsn1Item): # this mostly targets Integer objects + return self.prettyIn(str(value)) + elif isinstance(value, (tuple, list)): + return self.prettyIn(bytes(value)) else: - try: - return str(value).encode(self._encoding) - except UnicodeEncodeError: - raise error.PyAsn1Error( - 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self._encoding) - ) - + return bytes(value) - def fromBinaryString(self, value): - bitNo = 8; byte = 0; r = () - for v in value: - if bitNo: - bitNo = bitNo - 1 - else: - bitNo = 7 - r = r + (byte,) - byte = 0 - if v == '0': - v = 0 - elif v == '1': - v = 1 - else: + def __str__(self): + try: + return self._value.decode(self.encoding) + + except UnicodeDecodeError: raise error.PyAsn1Error( - 'Non-binary OCTET STRING initializer %s' % (v,) - ) - byte = byte | (v << bitNo) - return octets.ints2octs(r + (byte,)) - - def fromHexString(self, value): - r = p = () - for v in value: - if p: - r = r + (int(p+v, 16),) - p = () - else: - p = v - if p: - r = r + (int(p+'0', 16),) - return octets.ints2octs(r) + 'Can\'t decode string \'%s\' with \'%s\' codec at \'%s\'' % (self._value, self.encoding, self.__class__.__name__) + ) + + def __bytes__(self): + return bytes(self._value) + + def asOctets(self): + return bytes(self._value) + + def asNumbers(self): + return tuple(self._value) def prettyOut(self, value): if sys.version_info[0] <= 2: - numbers = tuple(( ord(x) for x in value )) + numbers = tuple((ord(x) for x in value)) else: numbers = tuple(value) - if all(x >= 32 and x <= 126 for x in numbers): - return str(value) + for x in numbers: + if x < 32 or x > 126: + return '0x' + ''.join(('%.2x' % x for x in numbers)) else: - return '0x' + ''.join(( '%.2x' % x for x in numbers )) + try: + return value.decode(self.encoding) + + except UnicodeDecodeError: + raise error.PyAsn1Error( + "Can't decode string '%s' with '%s' codec at '%s'" % (value, self.encoding, self.__class__.__name__) + ) + + @staticmethod + def fromBinaryString(value): + """Create a |ASN.1| object initialized from a string of '0' and '1'. + + Parameters + ---------- + value: :class:`str` + Text string like '1010111' + """ + bitNo = 8 + byte = 0 + r = [] + for v in value: + if bitNo: + bitNo -= 1 + else: + bitNo = 7 + r.append(byte) + byte = 0 + if v in ('0', '1'): + v = int(v) + else: + raise error.PyAsn1Error( + 'Non-binary OCTET STRING initializer %s' % (v,) + ) + byte |= v << bitNo + + r.append(byte) + + return octets.ints2octs(r) + + @staticmethod + def fromHexString(value): + """Create a |ASN.1| object initialized from the hex string. + + Parameters + ---------- + value: :class:`str` + Text string like 'DEADBEEF' + """ + r = [] + p = [] + for v in value: + if p: + r.append(int(p + v, 16)) + p = None + else: + p = v + if p: + r.append(int(p + '0', 16)) + + return octets.ints2octs(r) def __repr__(self): r = [] @@ -429,60 +1069,138 @@ class OctetString(base.AbstractSimpleAsn1Item): break if not doHex: r.append('%r' % (self._value,)) - if self._tagSet is not self.tagSet: - r.append('tagSet=%r' % (self._tagSet,)) - if self._subtypeSpec is not self.subtypeSpec: - r.append('subtypeSpec=%r' % (self._subtypeSpec,)) - if self.encoding is not self._encoding: - r.append('encoding=%r' % (self._encoding,)) + if self.tagSet is not self.__class__.tagSet: + r.append('tagSet=%r' % (self.tagSet,)) + if self.subtypeSpec is not self.__class__.subtypeSpec: + r.append('subtypeSpec=%r' % (self.subtypeSpec,)) + if self.encoding is not self.__class__.encoding: + r.append('encoding=%r' % (self.encoding,)) if doHex: - r.append('hexValue=%r' % ''.join([ '%.2x' % x for x in self.asNumbers() ])) + r.append('hexValue=%r' % ''.join(['%.2x' % x for x in self.asNumbers()])) return '%s(%s)' % (self.__class__.__name__, ', '.join(r)) - - if sys.version_info[0] <= 2: - def __str__(self): return str(self._value) - def __unicode__(self): - return self._value.decode(self._encoding, 'ignore') - def asOctets(self): return self._value - def asNumbers(self): - if self.__asNumbersCache is None: - self.__asNumbersCache = tuple([ ord(x) for x in self._value ]) - return self.__asNumbersCache - else: - def __str__(self): return self._value.decode(self._encoding, 'ignore') - def __bytes__(self): return self._value - def asOctets(self): return self._value - def asNumbers(self): - if self.__asNumbersCache is None: - self.__asNumbersCache = tuple(self._value) - return self.__asNumbersCache - + # Immutable sequence object protocol - + def __len__(self): - if self._len is None: - self._len = len(self._value) - return self._len + return len(self._value) + def __getitem__(self, i): - if isinstance(i, slice): - return self.clone(operator.getitem(self._value, i)) + if i.__class__ is slice: + return self.clone(self._value[i]) else: return self._value[i] - def __add__(self, value): return self.clone(self._value + self.prettyIn(value)) - def __radd__(self, value): return self.clone(self.prettyIn(value) + self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self * value - def __int__(self): return int(self._value) - def __float__(self): return float(self._value) - + def __iter__(self): + return iter(self._value) + + def __contains__(self, value): + return value in self._value + + def __add__(self, value): + return self.clone(self._value + self.prettyIn(value)) + + def __radd__(self, value): + return self.clone(self.prettyIn(value) + self._value) + + def __mul__(self, value): + return self.clone(self._value * value) + + def __rmul__(self, value): + return self * value + + def __int__(self): + return int(self._value) + + def __float__(self): + return float(self._value) + + def __reversed__(self): + return reversed(self._value) + + class Null(OctetString): + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`str` objects (always empty). + + Parameters + ---------- + value : :class:`str` or :py:class:`~pyasn1.type.univ.Null` object + Python empty string literal or *Null* class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ defaultValue = ''.encode() # This is tightly constrained - tagSet = baseTagSet = tag.initTagSet( + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x05) - ) - subtypeSpec = OctetString.subtypeSpec+constraint.SingleValueConstraint(''.encode()) - + ) + subtypeSpec = OctetString.subtypeSpec + constraint.SingleValueConstraint(octets.str2octs('')) + + # Optimization for faster codec lookup + typeId = OctetString.getTypeId() + + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : :py:class:`~pyasn1.type.univ.Null` + new instance of NULL type/value + """ + return OctetString.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + Returns + ------- + : :py:class:`~pyasn1.type.univ.Null` + new instance of NULL type/value + """ + return OctetString.subtype(self, value, **kwargs) + + if sys.version_info[0] <= 2: intTypes = (int, long) else: @@ -490,81 +1208,157 @@ else: numericTypes = intTypes + (float,) -class ObjectIdentifier(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x06) - ) - def __add__(self, other): return self.clone(self._value + other) - def __radd__(self, other): return self.clone(other + self._value) - def asTuple(self): return self._value - +class ObjectIdentifier(base.AbstractSimpleAsn1Item): + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`tuple` objects (tuple of non-negative integers). + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Python sequence of :class:`int` or string literal or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x06) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def __add__(self, other): + return self.clone(self._value + other) + + def __radd__(self, other): + return self.clone(other + self._value) + + def asTuple(self): + return self._value + # Sequence object protocol - + def __len__(self): - if self._len is None: - self._len = len(self._value) - return self._len + return len(self._value) + def __getitem__(self, i): - if isinstance(i, slice): - return self.clone( - operator.getitem(self._value, i) - ) + if i.__class__ is slice: + return self.clone(self._value[i]) else: return self._value[i] - def __str__(self): return self.prettyPrint() + def __iter__(self): + return iter(self._value) + + def __contains__(self, value): + return value in self._value + + def __str__(self): + return self.prettyPrint() + def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.prettyPrint()) - def index(self, suboid): return self._value.index(suboid) + def index(self, suboid): + return self._value.index(suboid) - def isPrefixOf(self, value): - """Returns true if argument OID resides deeper in the OID tree""" + def isPrefixOf(self, other): + """Indicate if this |ASN.1| object is a prefix of other |ASN.1| object. + + Parameters + ---------- + other: |ASN.1| object + |ASN.1| object + + Returns + ------- + : :class:`bool` + :class:`True` if this |ASN.1| object is a parent (e.g. prefix) of the other |ASN.1| object + or :class:`False` otherwise. + """ l = len(self) - if l <= len(value): - if self._value[:l] == value[:l]: - return 1 - return 0 + if l <= len(other): + if self._value[:l] == other[:l]: + return True + return False def prettyIn(self, value): - """Dotted -> tuple of numerics OID converter""" - if isinstance(value, tuple): - pass - elif isinstance(value, ObjectIdentifier): - return tuple(value) + if isinstance(value, ObjectIdentifier): + return tuple(value) elif octets.isStringType(value): - r = [] - for element in [ x for x in value.split('.') if x != '' ]: - try: - r.append(int(element, 0)) - except ValueError: - raise error.PyAsn1Error( - 'Malformed Object ID %s at %s: %s' % - (str(value), self.__class__.__name__, sys.exc_info()[1]) - ) - value = tuple(r) - else: + if '-' in value: + raise error.PyAsn1Error( + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) try: - value = tuple(value) - except TypeError: + return tuple([int(subOid) for subOid in value.split('.') if subOid]) + except ValueError: raise error.PyAsn1Error( - 'Malformed Object ID %s at %s: %s' % - (str(value), self.__class__.__name__,sys.exc_info()[1]) - ) + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) + + try: + tupleOfInts = tuple([int(subOid) for subOid in value if subOid >= 0]) + + except (ValueError, TypeError): + raise error.PyAsn1Error( + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) + + if len(tupleOfInts) == len(value): + return tupleOfInts + + raise error.PyAsn1Error('Malformed Object ID %s at %s' % (value, self.__class__.__name__)) + + def prettyOut(self, value): + return '.'.join([str(x) for x in value]) - for x in value: - if not isinstance(x, intTypes) or x < 0: - raise error.PyAsn1Error( - 'Invalid sub-ID in %s at %s' % (value, self.__class__.__name__) - ) - - return value - def prettyOut(self, value): return '.'.join([ str(x) for x in value ]) - class Real(base.AbstractSimpleAsn1Item): - binEncBase = None # binEncBase = 16 is recommended for large numbers + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`float` objects. + Additionally, |ASN.1| objects behave like a :class:`tuple` in which case its + elements are mantissa, base and exponent. + + Parameters + ---------- + value: :class:`tuple`, :class:`float` or |ASN.1| object + Python sequence of :class:`int` (representing mantissa, base and + exponent) or float instance or *Real* class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + + """ + binEncBase = None # binEncBase = 16 is recommended for large numbers + try: _plusInf = float('inf') _minusInf = float('-inf') @@ -574,15 +1368,84 @@ class Real(base.AbstractSimpleAsn1Item): _plusInf = _minusInf = None _inf = () - tagSet = baseTagSet = tag.initTagSet( + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x09) - ) + ) - def __normalizeBase10(self, value): + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def clone(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`float` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.clone(self, value, **kwargs) + + def subtype(self, value=noValue, **kwargs): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`float` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller'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 caller'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` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.subtype(self, value, **kwargs) + + @staticmethod + def __normalizeBase10(value): m, b, e = value while m and m % 10 == 0: - m = m / 10 - e = e + 1 + m /= 10 + e += 1 return m, b, e def prettyIn(self, value): @@ -592,19 +1455,19 @@ class Real(base.AbstractSimpleAsn1Item): not isinstance(value[2], intTypes): raise error.PyAsn1Error('Lame Real value syntax: %s' % (value,)) if isinstance(value[0], float) and \ - self._inf and value[0] in self._inf: + self._inf and value[0] in self._inf: return value[0] if value[1] not in (2, 10): raise error.PyAsn1Error( 'Prohibited base for Real value: %s' % (value[1],) - ) + ) if value[1] == 10: value = self.__normalizeBase10(value) return value elif isinstance(value, intTypes): return self.__normalizeBase10((value, 10, 0)) - elif isinstance(value, (str, float)): - if isinstance(value, str): + elif isinstance(value, float) or octets.isStringType(value): + if octets.isStringType(value): try: value = float(value) except ValueError: @@ -616,15 +1479,15 @@ class Real(base.AbstractSimpleAsn1Item): else: e = 0 while int(value) != value: - value = value * 10 - e = e - 1 + value *= 10 + e -= 1 return self.__normalizeBase10((int(value), 10, e)) elif isinstance(value, Real): return tuple(value) raise error.PyAsn1Error( 'Bad real value syntax: %s' % (value,) - ) - + ) + def prettyOut(self, value): if value in self._inf: return '\'%s\'' % value @@ -632,40 +1495,103 @@ class Real(base.AbstractSimpleAsn1Item): return str(value) def prettyPrint(self, scope=0): - if self.isInfinity(): + if self.isInf: return self.prettyOut(self._value) else: - return str(float(self)) + try: + return str(float(self)) - def isPlusInfinity(self): return self._value == self._plusInf - def isMinusInfinity(self): return self._value == self._minusInf - def isInfinity(self): return self._value in self._inf - - def __str__(self): return str(float(self)) - - def __add__(self, value): return self.clone(float(self) + value) - def __radd__(self, value): return self + value - def __mul__(self, value): return self.clone(float(self) * value) - def __rmul__(self, value): return self * value - def __sub__(self, value): return self.clone(float(self) - value) - def __rsub__(self, value): return self.clone(value - float(self)) - def __mod__(self, value): return self.clone(float(self) % value) - def __rmod__(self, value): return self.clone(value % float(self)) - def __pow__(self, value, modulo=None): return self.clone(pow(float(self), value, modulo)) - def __rpow__(self, value): return self.clone(pow(value, float(self))) + except OverflowError: + return '' + + @property + def isPlusInf(self): + """Indicate PLUS-INFINITY object value + + Returns + ------- + : :class:`bool` + :class:`True` if calling object represents plus infinity + or :class:`False` otherwise. + + """ + return self._value == self._plusInf + + @property + def isMinusInf(self): + """Indicate MINUS-INFINITY object value + + Returns + ------- + : :class:`bool` + :class:`True` if calling object represents minus infinity + or :class:`False` otherwise. + """ + return self._value == self._minusInf + + @property + def isInf(self): + return self._value in self._inf + + def __str__(self): + return str(float(self)) + + def __add__(self, value): + return self.clone(float(self) + value) + + def __radd__(self, value): + return self + value + + def __mul__(self, value): + return self.clone(float(self) * value) + + def __rmul__(self, value): + return self * value + + def __sub__(self, value): + return self.clone(float(self) - value) + + def __rsub__(self, value): + return self.clone(value - float(self)) + + def __mod__(self, value): + return self.clone(float(self) % value) + + def __rmod__(self, value): + return self.clone(value % float(self)) + + def __pow__(self, value, modulo=None): + return self.clone(pow(float(self), value, modulo)) + + def __rpow__(self, value): + return self.clone(pow(value, float(self))) if sys.version_info[0] <= 2: - def __div__(self, value): return self.clone(float(self) / value) - def __rdiv__(self, value): return self.clone(value / float(self)) + def __div__(self, value): + return self.clone(float(self) / value) + + def __rdiv__(self, value): + return self.clone(value / float(self)) else: - def __truediv__(self, value): return self.clone(float(self) / value) - def __rtruediv__(self, value): return self.clone(value / float(self)) - def __divmod__(self, value): return self.clone(float(self) // value) - def __rdivmod__(self, value): return self.clone(value // float(self)) + def __truediv__(self, value): + return self.clone(float(self) / value) + + def __rtruediv__(self, value): + return self.clone(value / float(self)) + + def __divmod__(self, value): + return self.clone(float(self) // value) + + def __rdivmod__(self, value): + return self.clone(value // float(self)) + + def __int__(self): + return int(float(self)) - def __int__(self): return int(float(self)) if sys.version_info[0] <= 2: - def __long__(self): return long(float(self)) + def __long__(self): + return long(float(self)) + def __float__(self): if self._value in self._inf: return self._value @@ -673,31 +1599,58 @@ class Real(base.AbstractSimpleAsn1Item): return float( self._value[0] * pow(self._value[1], self._value[2]) ) - def __abs__(self): return self.clone(abs(float(self))) - def __pos__(self): return self.clone(+float(self)) - def __neg__(self): return self.clone(-float(self)) + + def __abs__(self): + return self.clone(abs(float(self))) + + def __pos__(self): + return self.clone(+float(self)) + + def __neg__(self): + return self.clone(-float(self)) + def __round__(self, n=0): r = round(float(self), n) if n: return self.clone(r) else: return r - def __floor__(self): return self.clone(math.floor(float(self))) - def __ceil__(self): return self.clone(math.ceil(float(self))) - if sys.version_info[0:2] > (2, 5): - def __trunc__(self): return self.clone(math.trunc(float(self))) - def __lt__(self, value): return float(self) < value - def __le__(self, value): return float(self) <= value - def __eq__(self, value): return float(self) == value - def __ne__(self, value): return float(self) != value - def __gt__(self, value): return float(self) > value - def __ge__(self, value): return float(self) >= value + def __floor__(self): + return self.clone(math.floor(float(self))) + + def __ceil__(self): + return self.clone(math.ceil(float(self))) + + if sys.version_info[0:2] > (2, 5): + def __trunc__(self): + return self.clone(math.trunc(float(self))) + + def __lt__(self, value): + return float(self) < value + + def __le__(self, value): + return float(self) <= value + + def __eq__(self, value): + return float(self) == value + + def __ne__(self, value): + return float(self) != value + + def __gt__(self, value): + return float(self) > value + + def __ge__(self, value): + return float(self) >= value if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(float(self)) + def __nonzero__(self): + return bool(float(self)) else: - def __bool__(self): return bool(float(self)) + def __bool__(self): + return bool(float(self)) + __hash__ = base.AbstractSimpleAsn1Item.__hash__ def __getitem__(self, idx): @@ -705,421 +1658,1132 @@ class Real(base.AbstractSimpleAsn1Item): raise error.PyAsn1Error('Invalid infinite value operation') else: return self._value[idx] - + + # compatibility stubs + + def isPlusInfinity(self): + return self.isPlusInf + + def isMinusInfinity(self): + return self.isMinusInf + + def isInfinity(self): + return self.isInf + + class Enumerated(Integer): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = Integer.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x0A) - ) + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = Integer.getTypeId() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers + namedValues = namedval.NamedValues() + # "Structured" ASN.1 types -class SetOf(base.AbstractConstructedAsn1Item): - componentType = None - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) - ) - typeId = 1 - strictConstraints = False +class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): + """Create |ASN.1| type. - def _cloneComponentValues(self, myClone, cloneValueFlag): - idx = 0; l = len(self._componentValues) - while idx < l: - c = self._componentValues[idx] - if c is not None: - if isinstance(c, base.AbstractConstructedAsn1Item): - myClone.setComponentByPosition( - idx, c.clone(cloneValueFlag=cloneValueFlag) - ) - else: - myClone.setComponentByPosition(idx, c.clone()) - idx = idx + 1 - - def _verifyComponent(self, idx, value): - t = self._componentType - if t is None: - return - if not t.isSameTypeWith(value,matchConstraints=self.strictConstraints): - raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, t)) - if self.strictConstraints and \ - not t.isSuperTypeOf(value, matchTags=False): - raise error.PyAsn1Error('Component value is constraints-incompatible: %r vs %r' % (value, t)) + |ASN.1| objects are mutable and duck-type Python :class:`list` objects. - def getComponentByPosition(self, idx): return self._componentValues[idx] - def setComponentByPosition(self, idx, value=None, verifyConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if value is None: - if self._componentValues[idx] is None: - if self._componentType is None: - raise error.PyAsn1Error('Component type not defined') - self._componentValues[idx] = self._componentType.clone() - self._componentValuesSet = self._componentValuesSet + 1 - return self - elif not isinstance(value, base.Asn1Item): - if self._componentType is None: - raise error.PyAsn1Error('Component type not defined') - if isinstance(self._componentType, base.AbstractSimpleAsn1Item): - value = self._componentType.clone(value=value) - else: - raise error.PyAsn1Error('Instance value required') - if verifyConstraints: - if self._componentType is not None: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - if self._componentValues[idx] is None: - self._componentValuesSet = self._componentValuesSet + 1 - self._componentValues[idx] = value - return self + Parameters + ---------- + componentType : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A pyasn1 object representing ASN.1 type allowed within |ASN.1| type - def getComponentTagMap(self): - if self._componentType is not None: - return self._componentType.getTagMap() + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) - def prettyPrint(self, scope=0): - scope = scope + 1 - r = self.__class__.__name__ + ':\n' - for idx in range(len(self._componentValues)): - r = r + ' '*scope - if self._componentValues[idx] is None: - r = r + '' - else: - r = r + self._componentValues[idx].prettyPrint(scope) - return r + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) - def prettyPrintType(self, scope=0): - scope = scope + 1 - r = '%s -> %s {\n' % (self.getTagSet(), self.__class__.__name__) - if self._componentType is not None: - r = r + ' '*scope - r = r + self._componentType.prettyPrintType(scope) - return r + '\n' + ' '*(scope-1) + '}' + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing collection size constraint + """ -class SequenceOf(SetOf): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) - ) - typeId = 2 + def __init__(self, *args, **kwargs): + # support positional params for backward compatibility + if args: + for key, value in zip(('componentType', 'tagSet', + 'subtypeSpec', 'sizeSpec'), args): + if key in kwargs: + raise error.PyAsn1Error('Conflicting positional and keyword params!') + kwargs['componentType'] = value -class SequenceAndSetBase(base.AbstractConstructedAsn1Item): - componentType = namedtype.NamedTypes() - strictConstraints = False - def __init__(self, componentType=None, tagSet=None, - subtypeSpec=None, sizeSpec=None): - if componentType is None: - componentType = self.componentType - base.AbstractConstructedAsn1Item.__init__( - self, componentType.clone(), tagSet, subtypeSpec, sizeSpec - ) - self._componentTypeLen = len(self._componentType) + base.AbstractConstructedAsn1Item.__init__(self, **kwargs) + + # Python list protocol def __getitem__(self, idx): - if isinstance(idx, str): - return self.getComponentByName(idx) - else: - return base.AbstractConstructedAsn1Item.__getitem__(self, idx) + try: + return self.getComponentByPosition(idx) + + except error.PyAsn1Error: + raise IndexError(sys.exc_info()[1]) def __setitem__(self, idx, value): - if isinstance(idx, str): - self.setComponentByName(idx, value) - else: - base.AbstractConstructedAsn1Item.__setitem__(self, idx, value) - + try: + self.setComponentByPosition(idx, value) + + except error.PyAsn1Error: + raise IndexError(sys.exc_info()[1]) + + def clear(self): + self._componentValues = [] + + def append(self, value): + self[len(self)] = value + + def count(self, value): + return self._componentValues.count(value) + + def extend(self, values): + for value in values: + self.append(value) + + def index(self, value, start=0, stop=None): + if stop is None: + stop = len(self) + try: + return self._componentValues.index(value, start, stop) + + except error.PyAsn1Error: + raise ValueError(sys.exc_info()[1]) + + def reverse(self): + self._componentValues.reverse() + + def sort(self, key=None, reverse=False): + self._componentValues.sort(key=key, reverse=reverse) + + def __iter__(self): + return iter(self._componentValues) + def _cloneComponentValues(self, myClone, cloneValueFlag): - idx = 0; l = len(self._componentValues) - while idx < l: - c = self._componentValues[idx] - if c is not None: - if isinstance(c, base.AbstractConstructedAsn1Item): + for idx, componentValue in enumerate(self._componentValues): + if componentValue is not noValue: + if isinstance(componentValue, base.AbstractConstructedAsn1Item): myClone.setComponentByPosition( - idx, c.clone(cloneValueFlag=cloneValueFlag) - ) + idx, componentValue.clone(cloneValueFlag=cloneValueFlag) + ) else: - myClone.setComponentByPosition(idx, c.clone()) - idx = idx + 1 - - def _verifyComponent(self, idx, value): - if idx >= self._componentTypeLen: - raise error.PyAsn1Error( - 'Component type error out of range' - ) - t = self._componentType[idx].getType() - if not t.isSameTypeWith(value,matchConstraints=self.strictConstraints): - raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, t)) - if self.strictConstraints and \ - not t.isSuperTypeOf(value, matchTags=False): - raise error.PyAsn1Error('Component value is constraints-incompatible: %r vs %r' % (value, t)) - - def getComponentByName(self, name): - return self.getComponentByPosition( - self._componentType.getPositionByName(name) - ) - def setComponentByName(self, name, value=None, verifyConstraints=True): - return self.setComponentByPosition( - self._componentType.getPositionByName(name),value,verifyConstraints - ) + myClone.setComponentByPosition(idx, componentValue.clone()) def getComponentByPosition(self, idx): + """Return |ASN.1| type component value by position. + + Equivalent to Python sequence subscription operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to an existing + component or to N+1 component (if *componentType* is set). In the latter + case a new component type gets instantiated and appended to the |ASN.1| + sequence. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a pyasn1 object + """ try: return self._componentValues[idx] except IndexError: - if idx < self._componentTypeLen: - return - raise - def setComponentByPosition(self, idx, value=None, + self.setComponentByPosition(idx) + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, verifyConstraints=True, - exactTypes=False, matchTags=True, matchConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if value is None: - if self._componentValues[idx] is None: - self._componentValues[idx] = self._componentType.getTypeByPosition(idx).clone() - self._componentValuesSet = self._componentValuesSet + 1 - return self + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`) + or list.append() (when idx == len(self)). + + Parameters + ---------- + idx: :class:`int` + Component index (zero-based). Must either refer to existing + component or to N+1 component. In the latter case a new component + type gets instantiated (if *componentType* is set, or given ASN.1 + object is taken otherwise) and appended to the |ASN.1| sequence. + + value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A Python value to initialize |ASN.1| component with (if *componentType* is set) + or ASN.1 value object to assign to |ASN.1| component. + + verifyConstraints: :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + + Raises + ------ + IndexError: + When idx > len(self) + """ + if value is None: # backward compatibility + value = noValue + + componentType = self.componentType + + try: + currentValue = self._componentValues[idx] + except IndexError: + currentValue = noValue + + if len(self._componentValues) < idx: + raise error.PyAsn1Error('Component index out of range') + + if value is noValue: + if componentType is not None: + value = componentType.clone() + elif currentValue is noValue: + raise error.PyAsn1Error('Component type not defined') elif not isinstance(value, base.Asn1Item): - t = self._componentType.getTypeByPosition(idx) - if isinstance(t, base.AbstractSimpleAsn1Item): - value = t.clone(value=value) + if componentType is not None and isinstance(componentType, base.AbstractSimpleAsn1Item): + value = componentType.clone(value=value) + elif currentValue is not noValue and isinstance(currentValue, base.AbstractSimpleAsn1Item): + value = currentValue.clone(value=value) else: - raise error.PyAsn1Error('Instance value required') - if verifyConstraints: - if self._componentTypeLen: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - if self._componentValues[idx] is None: - self._componentValuesSet = self._componentValuesSet + 1 - self._componentValues[idx] = value + raise error.PyAsn1Error('Non-ASN.1 value %r and undefined component type at %r' % (value, self)) + elif componentType is not None: + if self.strictConstraints: + if not componentType.isSameTypeWith(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + else: + if not componentType.isSuperTypeOf(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + + if verifyConstraints and value.isValue: + try: + self.subtypeSpec(value, idx) + + except error.PyAsn1Error: + exType, exValue, exTb = sys.exc_info() + raise exType('%s at %s' % (exValue, self.__class__.__name__)) + + if currentValue is noValue: + self._componentValues.append(value) + else: + self._componentValues[idx] = value + return self - def getNameByPosition(self, idx): - if self._componentTypeLen: - return self._componentType.getNameByPosition(idx) + @property + def componentTagMap(self): + if self.componentType is not None: + return self.componentType.tagMap - def getDefaultComponentByPosition(self, idx): - if self._componentTypeLen and self._componentType[idx].isDefaulted: - return self._componentType[idx].getType() + def prettyPrint(self, scope=0): + scope += 1 + representation = self.__class__.__name__ + ':\n' + for idx, componentValue in enumerate(self._componentValues): + representation += ' ' * scope + if (componentValue is noValue and + self.componentType is not None): + representation += '' + else: + representation += componentValue.prettyPrint(scope) + return representation + + def prettyPrintType(self, scope=0): + scope += 1 + representation = '%s -> %s {\n' % (self.tagSet, self.__class__.__name__) + if self.componentType is not None: + representation += ' ' * scope + representation += self.componentType.prettyPrintType(scope) + return representation + '\n' + ' ' * (scope - 1) + '}' + + + @property + def isValue(self): + """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + + In other words, if *isValue* is `True`, then the ASN.1 object is + initialized. + + For the purpose of this check, empty |ASN.1| object is considered + as initialized. + + Returns + ------- + : :class:`bool` + :class:`True` if object represents ASN.1 value and type, + :class:`False` if object represents just ASN.1 type. + + Note + ---- + There is an important distinction between PyASN1 type and value objects. + The PyASN1 type objects can only participate in ASN.1 type + operations (subtyping, comparison etc) and serve as a + blueprint for serialization codecs to resolve ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + """ + for componentValue in self._componentValues: + if not componentValue.isValue: + return False + + return True + + +class SequenceOf(SequenceOfAndSetOfBase): + __doc__ = SequenceOfAndSetOfBase.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) + ) + + #: Default :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = None + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceOfAndSetOfBase.getTypeId() + + +class SetOf(SequenceOfAndSetOfBase): + __doc__ = SequenceOfAndSetOfBase.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ) + + #: Default :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = None + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceOfAndSetOfBase.getTypeId() + + +class SequenceAndSetBase(base.AbstractConstructedAsn1Item): + """Create |ASN.1| type. + + |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. + + Parameters + ---------- + componentType: :py:class:`~pyasn1.type.namedtype.NamedType` + Object holding named ASN.1 types allowed within this collection + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing collection size constraint + """ + #: Default :py:class:`~pyasn1.type.namedtype.NamedTypes` + #: object representing named ASN.1 types allowed within |ASN.1| type + componentType = namedtype.NamedTypes() + + + class DynamicNames(object): + """Fields names/positions mapping for component-less objects""" + def __init__(self): + self._keyToIdxMap = {} + self._idxToKeyMap = {} + + def __len__(self): + return len(self._keyToIdxMap) + + def __contains__(self, item): + return item in self._keyToIdxMap or item in self._idxToKeyMap + + def __iter__(self): + return (self._idxToKeyMap[idx] for idx in range(len(self._idxToKeyMap))) + + def __getitem__(self, item): + try: + return self._keyToIdxMap[item] + + except KeyError: + return self._idxToKeyMap[item] + + def getNameByPosition(self, idx): + try: + return self._idxToKeyMap[idx] + + except KeyError: + raise error.PyAsn1Error('Type position out of range') + + def getPositionByName(self, name): + try: + return self._keyToIdxMap[name] + + except KeyError: + raise error.PyAsn1Error('Name %s not found' % (name,)) + + def addField(self, idx): + self._keyToIdxMap['field-%d' % idx] = idx + self._idxToKeyMap[idx] = 'field-%d' % idx + + + def __init__(self, **kwargs): + base.AbstractConstructedAsn1Item.__init__(self, **kwargs) + self._componentTypeLen = len(self.componentType) + self._dynamicNames = self._componentTypeLen or self.DynamicNames() + + def __getitem__(self, idx): + if octets.isStringType(idx): + try: + return self.getComponentByName(idx) + + except error.PyAsn1Error: + # duck-typing dict + raise KeyError(sys.exc_info()[1]) + + else: + try: + return self.getComponentByPosition(idx) + + except error.PyAsn1Error: + # duck-typing list + raise IndexError(sys.exc_info()[1]) + + def __setitem__(self, idx, value): + if octets.isStringType(idx): + try: + self.setComponentByName(idx, value) + + except error.PyAsn1Error: + # duck-typing dict + raise KeyError(sys.exc_info()[1]) + + else: + try: + self.setComponentByPosition(idx, value) + + except error.PyAsn1Error: + # duck-typing list + raise IndexError(sys.exc_info()[1]) + + def __contains__(self, key): + if self._componentTypeLen: + return key in self.componentType + else: + return key in self._dynamicNames + + def __iter__(self): + return iter(self.componentType or self._dynamicNames) + + # Python dict protocol + + def values(self): + for idx in range(self._componentTypeLen or len(self._dynamicNames)): + yield self[idx] + + def keys(self): + return iter(self) + + def items(self): + for idx in range(self._componentTypeLen or len(self._dynamicNames)): + if self._componentTypeLen: + yield self.componentType[idx].name, self[idx] + else: + yield self._dynamicNames[idx], self[idx] + + def update(self, *iterValue, **mappingValue): + for k, v in iterValue: + self[k] = v + for k in mappingValue: + self[k] = mappingValue[k] + + def clear(self): + self._componentValues = [] + self._dynamicNames = self.DynamicNames() + + def _cloneComponentValues(self, myClone, cloneValueFlag): + for idx, componentValue in enumerate(self._componentValues): + if componentValue is not noValue: + if isinstance(componentValue, base.AbstractConstructedAsn1Item): + myClone.setComponentByPosition( + idx, componentValue.clone(cloneValueFlag=cloneValueFlag) + ) + else: + myClone.setComponentByPosition(idx, componentValue.clone()) + + def getComponentByName(self, name): + """Returns |ASN.1| type component by name. + + Equivalent to Python :class:`dict` subscription operation (e.g. `[]`). + + Parameters + ---------- + name : :class:`str` + |ASN.1| type component name + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + Instantiate |ASN.1| component type or return existing component value + """ + if self._componentTypeLen: + idx = self.componentType.getPositionByName(name) + else: + try: + idx = self._dynamicNames.getPositionByName(name) + + except KeyError: + raise error.PyAsn1Error('Name %s not found' % (name,)) + + return self.getComponentByPosition(idx) + + def setComponentByName(self, name, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by name. + + Equivalent to Python :class:`dict` item assignment operation (e.g. `[]`). + + Parameters + ---------- + name: :class:`str` + |ASN.1| type component name + + value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A Python value to initialize |ASN.1| component with (if *componentType* is set) + or ASN.1 value object to assign to |ASN.1| component. + + verifyConstraints: :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ + if self._componentTypeLen: + idx = self.componentType.getPositionByName(name) + else: + try: + idx = self._dynamicNames.getPositionByName(name) + + except KeyError: + raise error.PyAsn1Error('Name %s not found' % (name,)) + + return self.setComponentByPosition( + idx, value, verifyConstraints, matchTags, matchConstraints + ) + + def getComponentByPosition(self, idx): + """Returns |ASN.1| type component by index. + + Equivalent to Python sequence subscription operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to an existing + component or (if *componentType* is set) new ASN.1 type object gets + instantiated. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a PyASN1 object + """ + try: + componentValue = self._componentValues[idx] + except IndexError: + componentValue = noValue + + if componentValue is noValue: + self.setComponentByPosition(idx) + + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to existing + component (if *componentType* is set) or to N+1 component + otherwise. In the latter case a new component of given ASN.1 + type gets instantiated and appended to |ASN.1| sequence. + + value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A Python value to initialize |ASN.1| component with (if *componentType* is set) + or ASN.1 value object to assign to |ASN.1| component. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ + if value is None: # backward compatibility + value = noValue + + componentType = self.componentType + componentTypeLen = self._componentTypeLen + + try: + currentValue = self._componentValues[idx] + except IndexError: + currentValue = noValue + if componentTypeLen: + if componentTypeLen < idx: + raise error.PyAsn1Error('component index out of range') + self._componentValues = [noValue] * componentTypeLen + + if value is noValue: + if componentTypeLen: + value = componentType.getTypeByPosition(idx).clone() + elif currentValue is noValue: + raise error.PyAsn1Error('Component type not defined') + elif not isinstance(value, base.Asn1Item): + if componentTypeLen: + subComponentType = componentType.getTypeByPosition(idx) + if isinstance(subComponentType, base.AbstractSimpleAsn1Item): + value = subComponentType.clone(value=value) + else: + raise error.PyAsn1Error('%s can cast only scalar values' % componentType.__class__.__name__) + elif currentValue is not noValue and isinstance(currentValue, base.AbstractSimpleAsn1Item): + value = currentValue.clone(value=value) + else: + raise error.PyAsn1Error('%s undefined component type' % componentType.__class__.__name__) + elif (matchTags or matchConstraints) and componentTypeLen: + subComponentType = componentType.getTypeByPosition(idx) + if subComponentType is not noValue: + if self.strictConstraints: + if not subComponentType.isSameTypeWith(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + else: + if not subComponentType.isSuperTypeOf(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + + if verifyConstraints and value.isValue: + try: + self.subtypeSpec(value, idx) + + except error.PyAsn1Error: + exType, exValue, exTb = sys.exc_info() + raise exType('%s at %s' % (exValue, self.__class__.__name__)) + + if componentTypeLen or idx in self._dynamicNames: + self._componentValues[idx] = value + elif len(self._componentValues) == idx: + self._componentValues.append(value) + self._dynamicNames.addField(idx) + else: + raise error.PyAsn1Error('Component index out of range') + + return self + + @property + def isValue(self): + """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + + In other words, if *isValue* is `True`, then the ASN.1 object is + initialized. + + For the purpose of check, the *OPTIONAL* and *DEFAULT* fields are + unconditionally considered as initialized. + + Returns + ------- + : :class:`bool` + :class:`True` if object represents ASN.1 value and type, + :class:`False` if object represents just ASN.1 type. + + Note + ---- + There is an important distinction between PyASN1 type and value objects. + The PyASN1 type objects can only participate in ASN.1 type + operations (subtyping, comparison etc) and serve as a + blueprint for serialization codecs to resolve ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + """ + componentType = self.componentType + + if componentType: + for idx, subComponentType in enumerate(componentType.namedTypes): + if subComponentType.isDefaulted or subComponentType.isOptional: + continue + if (not self._componentValues or + not self._componentValues[idx].isValue): + return False + + else: + for componentValue in self._componentValues: + if not componentValue.isValue: + return False + + return True + + def prettyPrint(self, scope=0): + """Return an object representation string. + + Returns + ------- + : :class:`str` + Human-friendly object representation. + """ + scope += 1 + representation = self.__class__.__name__ + ':\n' + for idx, componentValue in enumerate(self._componentValues): + if componentValue is not noValue: + representation += ' ' * scope + if self.componentType: + representation += self.componentType.getNameByPosition(idx) + else: + representation += self._dynamicNames.getNameByPosition(idx) + representation = '%s=%s\n' % ( + representation, componentValue.prettyPrint(scope) + ) + return representation + + def prettyPrintType(self, scope=0): + scope += 1 + representation = '%s -> %s {\n' % (self.tagSet, self.__class__.__name__) + for idx, componentType in enumerate(self.componentType.values() or self._componentValues): + representation += ' ' * scope + if self.componentType: + representation += '"%s"' % self.componentType.getNameByPosition(idx) + else: + representation += '"%s"' % self._dynamicNames.getNameByPosition(idx) + representation = '%s = %s\n' % ( + representation, componentType.prettyPrintType(scope) + ) + return representation + '\n' + ' ' * (scope - 1) + '}' + + # backward compatibility + + def setDefaultComponents(self): + return self def getComponentType(self): if self._componentTypeLen: - return self._componentType - - def setDefaultComponents(self): - if self._componentTypeLen == self._componentValuesSet: - return - idx = self._componentTypeLen - while idx: - idx = idx - 1 - if self._componentType[idx].isDefaulted: - if self.getComponentByPosition(idx) is None: - self.setComponentByPosition(idx) - elif not self._componentType[idx].isOptional: - if self.getComponentByPosition(idx) is None: - raise error.PyAsn1Error( - 'Uninitialized component #%s at %r' % (idx, self) - ) + return self.componentType - def prettyPrint(self, scope=0): - scope = scope + 1 - r = self.__class__.__name__ + ':\n' - for idx in range(len(self._componentValues)): - if self._componentValues[idx] is not None: - r = r + ' '*scope - componentType = self.getComponentType() - if componentType is None: - r = r + '' - else: - r = r + componentType.getNameByPosition(idx) - r = '%s=%s\n' % ( - r, self._componentValues[idx].prettyPrint(scope) - ) - return r + def getNameByPosition(self, idx): + if self._componentTypeLen: + return self.componentType[idx].name - def prettyPrintType(self, scope=0): - scope = scope + 1 - r = '%s -> %s {\n' % (self.getTagSet(), self.__class__.__name__) - for idx in range(len(self.componentType)): - r = r + ' '*scope - r = r + '"%s"' % self.componentType.getNameByPosition(idx) - r = '%s = %s\n' % ( - r, self._componentType.getTypeByPosition(idx).prettyPrintType(scope) - ) - return r + '\n' + ' '*(scope-1) + '}' class Sequence(SequenceAndSetBase): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = SequenceAndSetBase.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) - ) - typeId = 3 + ) + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object imposing size constraint on |ASN.1| objects + componentType = namedtype.NamedTypes() + + # Disambiguation ASN.1 types identification + typeId = SequenceAndSetBase.getTypeId() + + # backward compatibility def getComponentTagMapNearPosition(self, idx): - if self._componentType: - return self._componentType.getTagMapNearPosition(idx) - + if self.componentType: + return self.componentType.getTagMapNearPosition(idx) + def getComponentPositionNearType(self, tagSet, idx): - if self._componentType: - return self._componentType.getPositionNearType(tagSet, idx) + if self.componentType: + return self.componentType.getPositionNearType(tagSet, idx) else: return idx - -class Set(SequenceAndSetBase): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) - ) - typeId = 4 - def getComponent(self, innerFlag=0): return self - - def getComponentByType(self, tagSet, innerFlag=0): - c = self.getComponentByPosition( - self._componentType.getPositionByType(tagSet) - ) - if innerFlag and isinstance(c, Set): + +class Set(SequenceAndSetBase): + __doc__ = SequenceAndSetBase.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ) + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = namedtype.NamedTypes() + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceAndSetBase.getTypeId() + + def getComponent(self, innerFlag=False): + return self + + def getComponentByType(self, tagSet, innerFlag=False): + """Returns |ASN.1| type component by ASN.1 tag. + + Parameters + ---------- + tagSet : :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tags to identify one of + |ASN.1| object component + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a pyasn1 object + """ + component = self.getComponentByPosition( + self.componentType.getPositionByType(tagSet) + ) + if innerFlag and isinstance(component, Set): # get inner component by inner tagSet - return c.getComponent(1) + return component.getComponent(innerFlag=True) else: # get outer component by inner tagSet - return c - - def setComponentByType(self, tagSet, value=None, innerFlag=0, - verifyConstraints=True): - idx = self._componentType.getPositionByType(tagSet) - t = self._componentType.getTypeByPosition(idx) + return component + + def setComponentByType(self, tagSet, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True, + innerFlag=False): + """Assign |ASN.1| type component by ASN.1 tag. + + Parameters + ---------- + tagSet : :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tags to identify one of + |ASN.1| object component + + value : :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A Python value to initialize |ASN.1| component with (if *componentType* is set) + or ASN.1 value object to assign to |ASN.1| component. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + innerFlag: :class:`bool` + If `True`, search for matching *tagSet* recursively. + + Returns + ------- + self + """ + idx = self.componentType.getPositionByType(tagSet) + if innerFlag: # set inner component by inner tagSet - if t.getTagSet(): + componentType = self.componentType.getTypeByPosition(idx) + + if componentType.tagSet: return self.setComponentByPosition( - idx, value, verifyConstraints + idx, value, verifyConstraints, matchTags, matchConstraints ) else: - t = self.setComponentByPosition(idx).getComponentByPosition(idx) - return t.setComponentByType( - tagSet, value, innerFlag, verifyConstraints + componentType = self.getComponentByPosition(idx) + return componentType.setComponentByType( + tagSet, value, verifyConstraints, matchTags, matchConstraints, innerFlag=innerFlag ) else: # set outer component by inner tagSet return self.setComponentByPosition( - idx, value, verifyConstraints + idx, value, verifyConstraints, matchTags, matchConstraints ) - - def getComponentTagMap(self): - if self._componentType: - return self._componentType.getTagMap(True) - def getComponentPositionByType(self, tagSet): - if self._componentType: - return self._componentType.getPositionByType(tagSet) + @property + def componentTagMap(self): + if self.componentType: + return self.componentType.tagMapUnique + class Choice(Set): - tagSet = baseTagSet = tag.TagSet() # untagged + __doc__ = Set.__doc__ + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.TagSet() # untagged + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = namedtype.NamedTypes() + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects sizeSpec = constraint.ConstraintsIntersection( constraint.ValueSizeConstraint(1, 1) - ) - typeId = 5 + ) + + # Disambiguation ASN.1 types identification + typeId = Set.getTypeId() + _currentIdx = None def __eq__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] == other return NotImplemented + def __ne__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] != other return NotImplemented + def __lt__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] < other return NotImplemented + def __le__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] <= other return NotImplemented + def __gt__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] > other return NotImplemented + def __ge__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] >= other return NotImplemented - if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._componentValues) - else: - def __bool__(self): return bool(self._componentValues) - def __len__(self): return self._currentIdx is not None and 1 or 0 - + if sys.version_info[0] <= 2: + def __nonzero__(self): + return self._componentValues and True or False + else: + def __bool__(self): + return self._componentValues and True or False + + def __len__(self): + return self._currentIdx is not None and 1 or 0 + + def __contains__(self, key): + if self._currentIdx is None: + return False + return key == self.componentType[self._currentIdx].getName() + + def __iter__(self): + if self._currentIdx is None: + raise StopIteration + yield self.componentType[self._currentIdx].getName() + + # Python dict protocol + + def values(self): + if self._currentIdx is not None: + yield self._componentValues[self._currentIdx] + + def keys(self): + if self._currentIdx is not None: + yield self.componentType[self._currentIdx].getName() + + def items(self): + if self._currentIdx is not None: + yield self.componentType[self._currentIdx].getName(), self[self._currentIdx] + def verifySizeSpec(self): if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') - else: - self._sizeSpec(' ') def _cloneComponentValues(self, myClone, cloneValueFlag): try: - c = self.getComponent() + component = self.getComponent() except error.PyAsn1Error: pass else: - if isinstance(c, Choice): - tagSet = c.getEffectiveTagSet() + if isinstance(component, Choice): + tagSet = component.effectiveTagSet else: - tagSet = c.getTagSet() - if isinstance(c, base.AbstractConstructedAsn1Item): + tagSet = component.tagSet + if isinstance(component, base.AbstractConstructedAsn1Item): myClone.setComponentByType( - tagSet, c.clone(cloneValueFlag=cloneValueFlag) - ) - else: - myClone.setComponentByType(tagSet, c.clone()) - - def setComponentByPosition(self, idx, value=None, verifyConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if self._currentIdx is not None: - self._componentValues[self._currentIdx] = None - if value is None: - if self._componentValues[idx] is None: - self._componentValues[idx] = self._componentType.getTypeByPosition(idx).clone() - self._componentValuesSet = 1 - self._currentIdx = idx - return self - elif not isinstance(value, base.Asn1Item): - value = self._componentType.getTypeByPosition(idx).clone( - value=value + tagSet, component.clone(cloneValueFlag=cloneValueFlag) ) - if verifyConstraints: - if self._componentTypeLen: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - self._componentValues[idx] = value + else: + myClone.setComponentByType(tagSet, component.clone()) + + def getComponentByPosition(self, idx): + __doc__ = Set.__doc__ + + if self._currentIdx is None or self._currentIdx != idx: + return Set.getComponentByPosition(self, idx) + + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`). + + Parameters + ---------- + idx: :class:`int` + Component index (zero-based). Must either refer to existing + component or to N+1 component. In the latter case a new component + type gets instantiated (if *componentType* is set, or given ASN.1 + object is taken otherwise) and appended to the |ASN.1| sequence. + + value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A Python value to initialize |ASN.1| component with (if *componentType* is set) + or ASN.1 value object to assign to |ASN.1| component. Once a new value is + set to *idx* component, previous value is dropped. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ + oldIdx = self._currentIdx + Set.setComponentByPosition(self, idx, value, verifyConstraints, matchTags, matchConstraints) self._currentIdx = idx - self._componentValuesSet = 1 + if oldIdx is not None and oldIdx != idx: + self._componentValues[oldIdx] = None return self - def getMinTagSet(self): - if self._tagSet: - return self._tagSet + @property + def minTagSet(self): + if self.tagSet: + return self.tagSet else: - return self._componentType.genMinTagSet() + return self.componentType.minTagSet - def getEffectiveTagSet(self): - if self._tagSet: - return self._tagSet + @property + def effectiveTagSet(self): + """Return a :class:`~pyasn1.type.tag.TagSet` object of the currently initialized component or self (if |ASN.1| is tagged).""" + if self.tagSet: + return self.tagSet else: - c = self.getComponent() - if isinstance(c, Choice): - return c.getEffectiveTagSet() - else: - return c.getTagSet() + component = self.getComponent() + return component.effectiveTagSet - def getTagMap(self): - if self._tagSet: - return Set.getTagMap(self) + @property + def tagMap(self): + """"Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping + ASN.1 tags to ASN.1 objects contained within callee. + """ + if self.tagSet: + return Set.tagMap.fget(self) else: - return Set.getComponentTagMap(self) + return self.componentType.tagMapUnique def getComponent(self, innerFlag=0): + """Return currently assigned component of the |ASN.1| object. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a PyASN1 object + """ if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') else: @@ -1129,7 +2793,14 @@ class Choice(Set): else: return c - def getName(self, innerFlag=0): + def getName(self, innerFlag=False): + """Return the name of currently assigned component of the |ASN.1| object. + + Returns + ------- + : :py:class:`str` + |ASN.1| component name + """ if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') else: @@ -1137,20 +2808,68 @@ class Choice(Set): c = self._componentValues[self._currentIdx] if isinstance(c, Choice): return c.getName(innerFlag) - return self._componentType.getNameByPosition(self._currentIdx) + return self.componentType.getNameByPosition(self._currentIdx) + + @property + def isValue(self): + """Indicate if |ASN.1| component is set and represents ASN.1 type or ASN.1 value. + + The PyASN1 type objects can only participate in types comparison + and serve as a blueprint for serialization codecs to resolve + ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + + Returns + ------- + : :class:`bool` + :class:`True` if |ASN.1| component is set and represent value and type, + :class:`False` if |ASN.1| component is not set or it represents just ASN.1 type. + """ + if self._currentIdx is None: + return False + + return self._componentValues[self._currentIdx].isValue + + # compatibility stubs + + def getMinTagSet(self): + return self.minTagSet - def setDefaultComponents(self): pass class Any(OctetString): - tagSet = baseTagSet = tag.TagSet() # untagged - typeId = 6 + __doc__ = OctetString.__doc__ - def getTagMap(self): - return tagmap.TagMap( - { self.getTagSet(): self }, - { eoo.endOfOctets.getTagSet(): eoo.endOfOctets }, - self + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.TagSet() # untagged + + #: Set (on class, not on instance) or return a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = OctetString.getTypeId() + + @property + def tagMap(self): + """"Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping + ASN.1 tags to ASN.1 objects contained within callee. + """ + try: + return self._tagMap + + except AttributeError: + self._tagMap = tagmap.TagMap( + {self.tagSet: self}, + {eoo.endOfOctets.tagSet: eoo.endOfOctets}, + self ) + return self._tagMap + # XXX # coercion rules? diff --git a/src/pyasn1/type/useful.py b/src/pyasn1/type/useful.py index 17665348..a05a9a60 100644 --- a/src/pyasn1/type/useful.py +++ b/src/pyasn1/type/useful.py @@ -1,17 +1,187 @@ -# ASN.1 "useful" types -from pyasn1.type import char, tag +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import datetime +from pyasn1.type import univ, char, tag +from pyasn1.compat import string, dateandtime +from pyasn1 import error + +__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime'] + +NoValue = univ.NoValue +noValue = univ.noValue + class ObjectDescriptor(char.GraphicString): + __doc__ = char.GraphicString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects tagSet = char.GraphicString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7) - ) + ) -class GeneralizedTime(char.VisibleString): + # Optimization for faster codec lookup + typeId = char.GraphicString.getTypeId() + + +class TimeMixIn(object): + + _yearsDigits = 4 + _hasSubsecond = False + _optionalMinutes = False + _shortTZ = False + + class FixedOffset(datetime.tzinfo): + """Fixed offset in minutes east from UTC.""" + + # defaulted arguments required + # https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html + def __init__(self, offset=0, name='UTC'): + self.__offset = datetime.timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return datetime.timedelta(0) + + UTC = FixedOffset() + + @property + def asDateTime(self): + """Create :py:class:`datetime.datetime` object from a |ASN.1| object. + + Returns + ------- + : + new instance of :py:class:`datetime.datetime` object + """ + text = str(self) + if text.endswith('Z'): + tzinfo = TimeMixIn.UTC + text = text[:-1] + + elif '-' in text or '+' in text: + if '+' in text: + text, plusminus, tz = string.partition(text, '+') + else: + text, plusminus, tz = string.partition(text, '-') + + if self._shortTZ and len(tz) == 2: + tz += '00' + + if len(tz) != 4: + raise error.PyAsn1Error('malformed time zone offset %s' % tz) + + try: + minutes = int(tz[:2]) * 60 + int(tz[2:]) + if plusminus == '-': + minutes *= -1 + + except ValueError: + raise error.PyAsn1Error('unknown time specification %s' % self) + + tzinfo = TimeMixIn.FixedOffset(minutes, '?') + + else: + tzinfo = None + + if '.' in text or ',' in text: + if '.' in text: + text, _, ms = string.partition(text, '.') + else: + text, _, ms = string.partition(text, ',') + + try: + ms = int(ms) * 10000 + + except ValueError: + raise error.PyAsn1Error('bad sub-second time specification %s' % self) + + else: + ms = 0 + + if self._optionalMinutes and len(text) - self._yearsDigits == 6: + text += '0000' + elif len(text) - self._yearsDigits == 8: + text += '00' + + try: + dt = dateandtime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S') + + except ValueError: + raise error.PyAsn1Error('malformed datetime format %s' % self) + + return dt.replace(microsecond=ms, tzinfo=tzinfo) + + @classmethod + def fromDateTime(cls, dt): + """Create |ASN.1| object from a :py:class:`datetime.datetime` object. + + Parameters + ---------- + dt : :py:class:`datetime.datetime` object + The `datetime.datetime` object to initialize the |ASN.1| object from + + + Returns + ------- + : + new instance of |ASN.1| value + """ + text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S') + if cls._hasSubsecond: + text += '.%d' % (dt.microsecond // 10000) + + if dt.utcoffset(): + seconds = dt.utcoffset().seconds + if seconds < 0: + text += '-' + else: + text += '+' + text += '%.2d%.2d' % (seconds // 3600, seconds % 3600) + else: + text += 'Z' + + return cls(text) + + +class GeneralizedTime(char.VisibleString, TimeMixIn): + __doc__ = char.VisibleString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects tagSet = char.VisibleString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24) - ) + ) -class UTCTime(char.VisibleString): + # Optimization for faster codec lookup + typeId = char.VideotexString.getTypeId() + + _yearsDigits = 4 + _hasSubsecond = True + _optionalMinutes = True + _shortTZ = True + + +class UTCTime(char.VisibleString, TimeMixIn): + __doc__ = char.VisibleString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects tagSet = char.VisibleString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23) - ) + ) + + # Optimization for faster codec lookup + typeId = char.VideotexString.getTypeId() + + _yearsDigits = 2 + _hasSubsecond = False + _optionalMinutes = False + _shortTZ = False