laforge has uploaded this change for review.

View Change

Introduce a new 'hexstr' type to represent hex-strings

hexstr differs from str in that comparisons are case-insensitive, and it offers encoding-free
conversion from hexstr to bytes and vice-versa.

Change-Id: I16c0df809bc11ec0f98e8ade404f9b82072e3a06
---
M src/osmocom/utils.py
A tests/test_utils.py
2 files changed, 88 insertions(+), 0 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/python/pyosmocom refs/changes/23/38023/1
diff --git a/src/osmocom/utils.py b/src/osmocom/utils.py
index cbcdd46..96476f1 100644
--- a/src/osmocom/utils.py
+++ b/src/osmocom/utils.py
@@ -138,6 +138,36 @@
except:
return False

+class hexstr(str):
+ """Class derived from 'str', represeting a string of hexadecimal digits. It differs in that
+ comparisons are case-insensitive, and it offers encoding-free conversion from hexstr to bytes
+ and vice-versa."""
+ def __new__(cls, s: str):
+ if not all(c in string.hexdigits for c in s):
+ raise ValueError('Input must be hexadecimal digits only')
+ # store as lower case digits
+ return super().__new__(cls, s.lower())
+
+ def __eq__(self, other):
+ # make sure comparison is done case-insensitive
+ return str(self) == other.lower()
+
+ def __getitem__(self, val):
+ # make sure slicing a hexstr will return a hexstr
+ return hexstr(super().__getitem__(val))
+
+ def to_bytes(self):
+ """return hex-string converted to bytes"""
+ s = str(self)
+ if len(s) & 1:
+ raise ValueError('Cannot convert hex string with odd number of digits')
+ return h2b(s)
+
+ @classmethod
+ def from_bytes(cls, bt: bytes) -> 'hexstr':
+ """instantiate hex-string from bytes"""
+ return cls(b2h(bt))
+
#########################################################################
# ARGPARSE HELPERS
#########################################################################
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100755
index 0000000..8e4732f
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+# (C) 2022 by Harald Welte <laforge@osmocom.org>
+# All Rights Reserved
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from osmocom.utils import *
+
+class TestHexstr(unittest.TestCase):
+ def test_cmp(self):
+ s = hexstr('aBcD')
+ self.assertEqual(s, 'abcd')
+ self.assertEqual(s, 'ABCD')
+ self.assertEqual(s, 'AbCd')
+ self.assertEqual(s, hexstr('AbCd'))
+
+ def test_tobytes(self):
+ s = hexstr('aBcDeF')
+ self.assertEqual(s.to_bytes(), b'\xab\xcd\xef')
+
+ def test_tobytes_odd(self):
+ s2 = hexstr('aBc')
+ with self.assertRaises(ValueError):
+ s2.to_bytes()
+
+ def test_frombytes(self):
+ s = hexstr.from_bytes(b'\x01\x02\xaa')
+ self.assertEqual(s, '0102aa')
+
+ def test_slice(self):
+ s = hexstr('abcdef')
+ slice1 = s[-2:]
+ self.assertTrue(isinstance(slice1, hexstr))
+ self.assertEqual(slice1, 'ef')
+ slice2 = s[1]
+ self.assertTrue(isinstance(slice2, hexstr))
+ self.assertEqual(slice2, 'b')
+
+ def test_str_lower(self):
+ self.assertEqual(str(hexstr('ABCD')), 'abcd')
+
+
+if __name__ == "__main__":
+ unittest.main()

To view, visit change 38023. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: python/pyosmocom
Gerrit-Branch: master
Gerrit-Change-Id: I16c0df809bc11ec0f98e8ade404f9b82072e3a06
Gerrit-Change-Number: 38023
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge@osmocom.org>