dexter has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/python/pyosmocom/+/38289?usp=email )


Change subject: construct: move __bytes_required to utils and finish the 
implementation
......................................................................

construct: move __bytes_required to utils and finish the implementation

The method __bytes_required in the GreedyInteger class computes how many
byte a given integer number requires, when it is encoded. This method
currently only works for unsigned integers. Let's move it to utils and
add support for signed integers.

Related: SYS#7094
Change-Id: I9ae4acc30dbd5fb6a6b24b10254ffb205bfde52d
---
M src/osmocom/construct.py
M src/osmocom/utils.py
M tests/test_utils.py
3 files changed, 77 insertions(+), 21 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/python/pyosmocom 
refs/changes/89/38289/1

diff --git a/src/osmocom/construct.py b/src/osmocom/construct.py
index def5d85..7c0f034 100644
--- a/src/osmocom/construct.py
+++ b/src/osmocom/construct.py
@@ -16,7 +16,7 @@
 from construct.core import evaluate
 from construct.lib import integertypes

-from osmocom.utils import b2h, h2b, swap_nibbles
+from osmocom.utils import b2h, h2b, swap_nibbles, int_bytes_required

 # (C) 2021-2022 by Harald Welte <[email protected]>
 #
@@ -587,29 +587,10 @@
         except ValueError as e:
             raise IntegerError(str(e), path=path)

-    def __bytes_required(self, i, minlen=0):
-        if self.signed:
-            raise NotImplementedError("FIXME: Implement support for encoding 
signed integer")
-
-        # compute how many bytes we need
-        nbytes = 1
-        while True:
-            i = i >> 8
-            if i == 0:
-                break
-            else:
-                nbytes = nbytes + 1
-
-        # round up to the minimum number
-        # of bytes we anticipate
-        nbytes = max(nbytes, minlen)
-
-        return nbytes
-
     def _build(self, obj, stream, context, path):
         if not isinstance(obj, integertypes):
             raise IntegerError(f"value {obj} is not an integer", path=path)
-        length = self.__bytes_required(obj, self.minlen)
+        length = int_bytes_required(obj, self.minlen, self.signed)
         try:
             data = obj.to_bytes(length, byteorder='big', signed=self.signed)
         except ValueError as e:
diff --git a/src/osmocom/utils.py b/src/osmocom/utils.py
index 009eb04..5947453 100644
--- a/src/osmocom/utils.py
+++ b/src/osmocom/utils.py
@@ -137,6 +137,44 @@
     return (n + 1)//2


+def int_bytes_required(number: int, minlen:int = 0, signed:bool = False):
+    """compute how many bytes an integer requires when it is encoded into bytes
+    Args:
+            number : integer number
+            minlen : minimum length
+            signed : compute the number of bytes for a signed integer (two's 
complement)
+    Returns:
+            Integer 'nbytes', which is the number of bytes required to encode 
'number'
+    """
+
+    if signed == False and number < 0:
+        raise ValueError("expecting a positive number")
+
+    # Compute how many bytes we need for the absolute (positive) value of the 
given number
+    nbytes = 1
+    i = abs(number)
+    while True:
+        i = i >> 8
+        if i == 0:
+            break
+        else:
+            nbytes = nbytes + 1
+
+    # When we deal with signed numbers, then the two's complement applies. 
This means that we must check if the given
+    # number would still fit in the value range of the number of bytes we have 
calculated above. If not, one more
+    # byte is required.
+    if signed:
+        value_range_limit = pow(2,nbytes*8) // 2
+        if number < -value_range_limit:
+            nbytes = nbytes + 1
+        elif number >= value_range_limit:
+            nbytes = nbytes + 1
+
+    # round up to the minimum number of bytes we anticipate
+    nbytes = max(nbytes, minlen)
+    return nbytes
+
+
 def str_sanitize(s: str) -> str:
     """replace all non printable chars, line breaks and whitespaces, with ' ', 
make sure that
     there are no whitespaces at the end and at the beginning of the string.
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 8e4732f..0f22e35 100755
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -54,5 +54,42 @@
         self.assertEqual(str(hexstr('ABCD')), 'abcd')


+class Test_int_bytes_required(unittest.TestCase):
+
+    def test_int_bytes_required(self):
+
+        tests = [
+            # unsigned positive numbers
+            ( 1, 0, False ),
+            ( 1, 1, False ),
+            ( 1, 255, False ),
+            ( 2, 256, False ),
+            ( 2, 65535, False ),
+            ( 3, 65536, False ),
+            ( 2, 65535, False ),
+            ( 3, 16777215, False ),
+            ( 4, 16777216, False ),
+
+            # signed positive numbers
+            ( 1, 0, True ),
+            ( 1, 1, True ),
+            ( 1, 127, True ),
+            ( 2, 128, True ),
+            ( 2, 32767, True ),
+            ( 3, 32768, True ),
+
+            # signed negative numbers
+            ( 1, -0, True ),
+            ( 1, -1, True ),
+            ( 1, -128, True ),
+            ( 2, -129, True ),
+            ( 2, -32768, True ),
+            ( 3, -32769, True ),
+        ]
+
+        for t in tests:
+            self.assertEqual(t[0], int_bytes_required(t[1], signed = t[2]))
+
+
 if __name__ == "__main__":
        unittest.main()

--
To view, visit https://gerrit.osmocom.org/c/python/pyosmocom/+/38289?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: python/pyosmocom
Gerrit-Branch: master
Gerrit-Change-Id: I9ae4acc30dbd5fb6a6b24b10254ffb205bfde52d
Gerrit-Change-Number: 38289
Gerrit-PatchSet: 1
Gerrit-Owner: dexter <[email protected]>

Reply via email to