diff options
author | Ilya Etingof <etingof@gmail.com> | 2017-02-20 02:07:14 +0100 |
---|---|---|
committer | Ilya Etingof <etingof@gmail.com> | 2017-02-20 02:07:14 +0100 |
commit | 9109a27ab2ef8929c4d7797e3003bafd9030fd61 (patch) | |
tree | 6d6d9673dad9e046a425dac9858eef7f057cd240 | |
parent | e49eff44b28c1961edb9e19e68f2d400c5106d54 (diff) | |
download | pyasn1-git-9109a27ab2ef8929c4d7797e3003bafd9030fd61.tar.gz |
integer codec refactored to use Python integer serialization functions
-rw-r--r-- | CHANGES.rst | 4 | ||||
-rw-r--r-- | pyasn1/codec/ber/decoder.py | 47 | ||||
-rw-r--r-- | pyasn1/codec/ber/encoder.py | 89 | ||||
-rw-r--r-- | pyasn1/compat/binary.py | 9 |
4 files changed, 99 insertions, 50 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 39a5b28..e470ca4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,8 +2,10 @@ Revision 0.2.3, released XX-02-2017 ----------------------------------- -- Decoding performance improvement by maintaining a single shared +- Improved SEQUENCE/SET/CHOICE decoding performance by maintaining a single shared NamedType object for all instances of SEQUENCE/SET object. +- Improved INTEGER encoding/decoding by switching to Python's built-in + integer serialization functions. - ASN.1 character types refactored to keep unicode contents internally (rather than serialized octet stream) and duck-type it directly. - ASN.1 OctetString initialized from a Python object performs bytes() diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 4fdb19c..d01754f 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -4,6 +4,7 @@ # Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com> # License: http://pyasn1.sf.net/license.html # +from sys import version_info from pyasn1.type import base, tag, univ, char, useful, tagmap from pyasn1.codec.ber import eoo from pyasn1.compat.octets import str2octs, oct2int, isOctetsType @@ -87,39 +88,31 @@ explicitTagDecoder = ExplicitTagDecoder() class IntegerDecoder(AbstractSimpleDecoder): protoComponent = univ.Integer(0) - precomputedValues = { - str2octs('\x00'): 0, - str2octs('\x01'): 1, - str2octs('\x02'): 2, - str2octs('\x03'): 3, - str2octs('\x04'): 4, - str2octs('\x05'): 5, - str2octs('\x06'): 6, - str2octs('\x07'): 7, - str2octs('\x08'): 8, - str2octs('\x09'): 9, - str2octs('\xff'): -1, - str2octs('\xfe'): -2, - str2octs('\xfd'): -3, - str2octs('\xfc'): -4, - str2octs('\xfb'): -5 - } + + if version_info[0:2] < (3, 2): + @staticmethod + def _from_octets(octets, signed=False): + value = long(octets.encode('hex'), 16) + + if signed and oct2int(octets[0]) & 0x80: + return value - (1 << len(octets) * 8) + + return value + + else: + @staticmethod + def _from_octets(octets, signed=False): + return int.from_bytes(octets, 'big', signed=signed) def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): 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 = self._from_octets(head, signed=True) + return self._createComponent(asn1Spec, tagSet, value), tail diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index c578b64..d69e283 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -4,6 +4,9 @@ # Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com> # License: http://pyasn1.sf.net/license.html # +from sys import version_info +if version_info[0] < 3: + from binascii import a2b_hex from pyasn1.type import base, tag, univ, char, useful from pyasn1.codec.ber import eoo from pyasn1.compat.octets import int2oct, oct2int, ints2octs, null, str2octs @@ -102,29 +105,79 @@ class BooleanEncoder(AbstractItemEncoder): class IntegerEncoder(AbstractItemEncoder): supportIndefLenMode = 0 supportCompactZero = False + encodedZero = ints2octs((0,)) + + if version_info[0:2] > (3, 1): + @staticmethod + def _to_octets(value, signed=False): + length = value.bit_length() + + if signed and length % 8 == 0: + length += 1 + + return value.to_bytes(length // 8 + 1, 'big', signed=signed) + + else: + @staticmethod + def _to_octets(value, signed=False): + if value < 0: + if signed: + # bits in unsigned number + hexValue = hex(abs(value)) + bits = len(hexValue) - 2 + if hexValue.endswith('L'): + bits -= 1 + if bits & 1: + bits += 1 + bits *= 4 + + # two's complement form + maxValue = 1 << bits + valueToEncode = (value + maxValue) % maxValue + + else: + raise OverflowError('can\'t convert negative int to unsigned') + else: + 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: + hexLength = len(hexValue) // 2 + + padLength = bits // 8 + + if padLength > hexLength: + hexValue = '00' * (padLength - hexLength) + hexValue + + 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 encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if value == 0: # shortcut for zero value + 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 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 True: - octets.insert(0, value & 0xff) - if value == 0 or value == -1: - break - 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 self.encodedZero, 0 + + return self._to_octets(int(value), signed=True), 0 class BitStringEncoder(AbstractItemEncoder): diff --git a/pyasn1/compat/binary.py b/pyasn1/compat/binary.py index 6047df5..68f05c2 100644 --- a/pyasn1/compat/binary.py +++ b/pyasn1/compat/binary.py @@ -7,10 +7,11 @@ 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): + if value <= 1: + return '0b' + str(value) else: - return bin(x >> 1) + str(x & 1) + # TODO: convert recursion into iteration + return bin(value >> 1) + str(value & 1) else: bin = bin |