From e8f259647c04cf9a41130c8387cef2690d8ec19e Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Wed, 8 Jul 2026 17:32:09 -0700 Subject: [PATCH] Merge commit from fork --- pyasn1/type/univ.py | 21 ++++++++++--- tests/codec/ber/test_decoder.py | 54 +++++++++++++++++++++++++++------ tests/codec/cer/test_decoder.py | 11 +++++++ tests/codec/der/test_decoder.py | 20 ++++++++++++ tests/type/test_univ.py | 40 ++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 15 deletions(-) diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index a90c648..5d6c23a 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1444,7 +1444,7 @@ class Real(base.AbstractSimpleAsn1Item): def __normalizeBase10(value): m, b, e = value while m and m % 10 == 0: - m /= 10 + m //= 10 e += 1 return m, b, e @@ -1595,10 +1595,21 @@ class Real(base.AbstractSimpleAsn1Item): def __float__(self): if self._value in self._inf: return self._value - else: - return float( - self._value[0] * pow(self._value[1], self._value[2]) - ) + + mantissa, base, exponent = self._value + + if not mantissa: + return 0.0 + + if base == 2: + return math.ldexp(float(mantissa), exponent) + + # base is 10 (prettyIn() rejects everything else); refuse to + # materialize astronomically large integers via pow() + if exponent > sys.float_info.max_10_exp: + raise OverflowError('Real value too large to convert to float') + + return float(mantissa * pow(base, exponent)) def __abs__(self): return self.clone(abs(float(self))) diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py index ba7dfec..9c98864 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py @@ -13,7 +13,7 @@ except ImportError: from tests.base import BaseTestCase from pyasn1.type import tag, namedtype, univ, char -from pyasn1.codec.ber import decoder, eoo +from pyasn1.codec.ber import decoder, encoder, eoo from pyasn1.compat.octets import ints2octs, str2octs, null from pyasn1.error import PyAsn1Error @@ -501,17 +501,51 @@ class RealDecoderTestCase(BaseTestCase): ints2octs((9, 4, 161, 255, 1, 3)) ) == (univ.Real((3, 2, -1020)), null) -# TODO: this requires Real type comparison fix + def testBin6(self): # large exponent, base = 16 + value, rest = decoder.decode( + ints2octs((9, 5, 162, 0, 255, 255, 1)) + ) + + assert tuple(value) == (1, 2, 262140) + assert rest == null + + def testBin7(self): # large exponent in 4-octet form, base = 16 + value, rest = decoder.decode( + ints2octs((9, 7, 227, 4, 1, 35, 69, 103, 1)) + ) -# def testBin6(self): -# assert decoder.decode( -# ints2octs((9, 5, 162, 0, 255, 255, 1)) -# ) == (univ.Real((1, 2, 262140)), null) + assert tuple(value) == (-1, 2, 76354972) + assert rest == null + + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) + value, rest = decoder.decode(substrate) + + assert tuple(value) == (-1, 2, 76354972) + assert rest == null + + def testLongFormBinaryRealExponentLength(self): + value, rest = decoder.decode( + ints2octs((9, 6, 0x83, 3, 0x0f, 0x42, 0x40, 1)) + ) -# def testBin7(self): -# assert decoder.decode( -# ints2octs((9, 7, 227, 4, 1, 35, 69, 103, 1)) -# ) == (univ.Real((-1, 2, 76354972)), null) + assert tuple(value) == (1, 2, 1000000) + assert rest == null + + def testLargeBinaryPrettyPrintOverflow(self): + value, rest = decoder.decode( + b'\t\t\xeb\x060662.666\xd0B\x00\x00\x00\x00\x00\x00\x00' + ) + + assert value.prettyPrint() == '' + assert rest == b'6\xd0B\x00\x00\x00\x00\x00\x00\x00' + + try: + float(value) + except OverflowError: + pass + else: + assert 0, '__float__() tolerated overflow' def testPlusInf(self): assert decoder.decode( diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py index 828c17f..c4a91db 100644 --- a/tests/codec/cer/test_decoder.py +++ b/tests/codec/cer/test_decoder.py @@ -14,6 +14,8 @@ except ImportError: from tests.base import BaseTestCase from pyasn1.codec.cer import decoder +from pyasn1.codec.cer import encoder +from pyasn1.type import univ from pyasn1.compat.octets import ints2octs, str2octs, null from pyasn1.error import PyAsn1Error @@ -65,6 +66,16 @@ class OctetStringDecoderTestCase(BaseTestCase): # TODO: test failures on short chunked and long unchunked substrate samples +class RealDecoderTestCase(BaseTestCase): + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) + value, rest = decoder.decode(substrate) + + assert tuple(value) == (-1, 2, 76354972) + assert rest == null + + + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py index 6f292fd..08101a4 100644 --- a/tests/codec/der/test_decoder.py +++ b/tests/codec/der/test_decoder.py @@ -14,6 +14,8 @@ except ImportError: from tests.base import BaseTestCase from pyasn1.codec.der import decoder +from pyasn1.codec.der import encoder +from pyasn1.type import univ from pyasn1.compat.octets import ints2octs, null from pyasn1.error import PyAsn1Error @@ -72,6 +73,25 @@ class OctetStringDecoderTestCase(BaseTestCase): assert 0, 'chunked encoding tolerated' +class RealDecoderTestCase(BaseTestCase): + def testCanonicalLargeBinaryReal(self): + substrate = encoder.encode(univ.Real((1, 2, 1000000))) + assert substrate == ints2octs((9, 5, 0x82, 0x0f, 0x42, 0x40, 1)) + + value, rest = decoder.decode(substrate) + + assert tuple(value) == (1, 2, 1000000) + assert rest == null + + def testLargeBinaryRoundTrip(self): + substrate = encoder.encode(univ.Real((-1, 2, 76354972))) + value, rest = decoder.decode(substrate) + + assert tuple(value) == (-1, 2, 76354972) + assert rest == null + + + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index f5124df..3453d11 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -632,9 +632,49 @@ class RealTestCase(BaseTestCase): def testFloat(self): assert float(univ.Real(4.0)) == 4.0, '__float__() fails' + def testFloatBase10Precision(self): + assert float(univ.Real((3, 10, 23))) == 3e23, '__float__() lost base-10 behavior' + + def testFloatOverflow(self): + try: + float(univ.Real((1, 2, 1000000))) + except OverflowError: + pass + else: + assert 0, '__float__() tolerated overflow' + + assert univ.Real((1, 2, 1000000)).prettyPrint() == '' + + def testFloatUnderflow(self): + assert float(univ.Real((1, 2, -1000000))) == 0.0, '__float__() failed underflow' + + def testFloatZeroMantissa(self): + assert float(univ.Real((0, 10, 1000000000))) == 0.0, '__float__() failed zero mantissa' + assert float(univ.Real((0, 2, 1000000000))) == 0.0, '__float__() failed zero mantissa' + + def testFloatBase10Overflow(self): + try: + float(univ.Real((1, 10, sys.float_info.max_10_exp + 1))) + except OverflowError: + pass + else: + assert 0, '__float__() tolerated base-10 overflow' + + def testFloatBase10NormalizedOverflow(self): + try: + float(univ.Real((10, 10, sys.float_info.max_10_exp))) + except OverflowError: + pass + else: + assert 0, '__float__() tolerated normalized base-10 overflow' + def testPrettyIn(self): assert univ.Real((3, 10, 0)) == 3, 'prettyIn() fails' + def testPrettyInBigBase10Mantissa(self): + assert tuple(univ.Real((10 ** 400, 10, 0))) == (1, 10, 400), \ + 'prettyIn() big mantissa normalization fails' + # infinite float values def testStrInf(self): assert str(univ.Real('inf')) == 'inf', 'str() fails'