summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2017-02-20 02:07:14 +0100
committerIlya Etingof <etingof@gmail.com>2017-02-20 02:07:14 +0100
commit9109a27ab2ef8929c4d7797e3003bafd9030fd61 (patch)
tree6d6d9673dad9e046a425dac9858eef7f057cd240
parente49eff44b28c1961edb9e19e68f2d400c5106d54 (diff)
downloadpyasn1-git-9109a27ab2ef8929c4d7797e3003bafd9030fd61.tar.gz
integer codec refactored to use Python integer serialization functions
-rw-r--r--CHANGES.rst4
-rw-r--r--pyasn1/codec/ber/decoder.py47
-rw-r--r--pyasn1/codec/ber/encoder.py89
-rw-r--r--pyasn1/compat/binary.py9
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