From 0b9b9e04830c40c7866b4ba23d6fa1a0aa80f039 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 27 Feb 2007 08:40:54 +0000 Subject: [PATCH] Implement bytes.fromhex(), with tests. --- Lib/test/test_bytes.py | 15 ++++++++ Objects/bytesobject.c | 86 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 83f6f93627f..b40d4194c99 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -396,6 +396,21 @@ class BytesTest(unittest.TestCase): seq.append(alloc) #print seq + def test_fromhex(self): + self.assertRaises(TypeError, bytes.fromhex) + self.assertRaises(TypeError, bytes.fromhex, 1) + self.assertEquals(bytes.fromhex(''), bytes()) + b = bytes([0x1a, 0x2b, 0x30]) + self.assertEquals(bytes.fromhex('1a2B30'), b) + self.assertEquals(bytes.fromhex(' 1A 2B 30 '), b) + self.assertEquals(bytes.fromhex(buffer('')), bytes()) + self.assertEquals(bytes.fromhex(buffer('0000')), bytes([0, 0])) + self.assertRaises(ValueError, bytes.fromhex, 'a') + self.assertRaises(ValueError, bytes.fromhex, 'rt') + self.assertRaises(ValueError, bytes.fromhex, '1a b cd') + self.assertRaises(ValueError, bytes.fromhex, '\x00') + self.assertRaises(ValueError, bytes.fromhex, '12 \x00 34') + def test_join(self): self.assertEqual(bytes.join([]), bytes()) self.assertEqual(bytes.join([bytes()]), bytes()) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 88e65858c6c..f2befed6bb1 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -970,17 +970,82 @@ bytes_join(PyObject *cls, PyObject *it) return NULL; } +PyDoc_STRVAR(fromhex_doc, +"bytes.fromhex(string) -> bytes\n\ +\n\ +Create a bytes object from a string of hexadecimal numbers.\n\ +Spaces between two numbers are accepted. Example:\n\ +bytes.fromhex('10 2030') -> bytes([0x10, 0x20, 0x30])."); + +static int +hex_digit_to_int(int c) +{ + if (isdigit(c)) + return c - '0'; + else { + if (isupper(c)) + c = tolower(c); + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + } + return -1; +} + +static PyObject * +bytes_fromhex(PyObject *cls, PyObject *args) +{ + PyObject *newbytes; + char *hex, *buf; + Py_ssize_t len, byteslen, i, j; + int top, bot; + + if (!PyArg_ParseTuple(args, "s#:fromhex", &hex, &len)) + return NULL; + + byteslen = len / 2; /* max length if there are no spaces */ + + newbytes = PyBytes_FromStringAndSize(NULL, byteslen); + if (!newbytes) + return NULL; + buf = PyBytes_AS_STRING(newbytes); + + for (i = j = 0; ; i += 2) { + /* skip over spaces in the input */ + while (Py_CHARMASK(hex[i]) == ' ') + i++; + if (i >= len) + break; + top = hex_digit_to_int(Py_CHARMASK(hex[i])); + bot = hex_digit_to_int(Py_CHARMASK(hex[i+1])); + if (top == -1 || bot == -1) { + PyErr_Format(PyExc_ValueError, + "non-hexadecimal number string '%c%c' found in " + "fromhex() arg at position %zd", + hex[i], hex[i+1], i); + goto error; + } + buf[j++] = (top << 4) + bot; + } + if (PyBytes_Resize(newbytes, j) < 0) + goto error; + return newbytes; + + error: + Py_DECREF(newbytes); + return NULL; +} + static PySequenceMethods bytes_as_sequence = { - (lenfunc)bytes_length, /*sq_length*/ - (binaryfunc)bytes_concat, /*sq_concat*/ - (ssizeargfunc)bytes_repeat, /*sq_repeat*/ - (ssizeargfunc)bytes_getitem, /*sq_item*/ - 0, /*sq_slice*/ - (ssizeobjargproc)bytes_setitem, /*sq_ass_item*/ - 0, /* sq_ass_slice */ + (lenfunc)bytes_length, /* sq_length */ + (binaryfunc)bytes_concat, /* sq_concat */ + (ssizeargfunc)bytes_repeat, /* sq_repeat */ + (ssizeargfunc)bytes_getitem, /* sq_item */ + 0, /* sq_slice */ + (ssizeobjargproc)bytes_setitem, /* sq_ass_item */ + 0, /* sq_ass_slice */ (objobjproc)bytes_contains, /* sq_contains */ - (binaryfunc)bytes_iconcat, /* sq_inplace_concat */ - (ssizeargfunc)bytes_irepeat, /* sq_inplace_repeat */ + (binaryfunc)bytes_iconcat, /* sq_inplace_concat */ + (ssizeargfunc)bytes_irepeat, /* sq_inplace_repeat */ }; static PyMappingMethods bytes_as_mapping = { @@ -1002,6 +1067,7 @@ static PyMethodDef bytes_methods[] = { {"decode", (PyCFunction)bytes_decode, METH_VARARGS, decode_doc}, {"__alloc__", (PyCFunction)bytes_alloc, METH_NOARGS, alloc_doc}, + {"fromhex", (PyCFunction)bytes_fromhex, METH_VARARGS|METH_CLASS, fromhex_doc}, {"join", (PyCFunction)bytes_join, METH_O|METH_CLASS, join_doc}, {NULL} }; @@ -1032,7 +1098,7 @@ PyTypeObject PyBytes_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ &bytes_as_buffer, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ /* bytes is 'final' or 'sealed' */ bytes_doc, /* tp_doc */ 0, /* tp_traverse */