Skip to content

Commit 1a20c4e

Browse files
Bind out crc64 (#597)
1 parent 2dae492 commit 1a20c4e

File tree

7 files changed

+102
-14
lines changed

7 files changed

+102
-14
lines changed

awscrt/checksums.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ def crc32c(input: bytes, previous_crc32c: int = 0) -> int:
2121
Returns an unsigned 32-bit integer.
2222
"""
2323
return _awscrt.checksums_crc32c(input, previous_crc32c)
24+
25+
26+
def crc64nvme(input: bytes, previous_crc64nvme: int = 0) -> int:
27+
"""
28+
Perform a CRC64 NVME computation.
29+
If continuing to update a running CRC, pass its value into `previous_crc64nvme`.
30+
Returns an unsigned 64-bit integer.
31+
"""
32+
return _awscrt.checksums_crc64nvme(input, previous_crc64nvme)

source/checksums.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88

99
PyObject *aws_py_checksums_crc32(PyObject *self, PyObject *args);
1010
PyObject *aws_py_checksums_crc32c(PyObject *self, PyObject *args);
11+
PyObject *aws_py_checksums_crc64nvme(PyObject *self, PyObject *args);
1112

1213
#endif /* AWS_CRT_PYTHON_CHECKSUMS_H */

source/crc.c

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
#include "aws/checksums/crc.h"
99
#include "aws/common/byte_buf.h"
10-
PyObject *checksums_crc_common(PyObject *args, uint32_t (*checksum_fn)(const uint8_t *, int, uint32_t)) {
10+
PyObject *checksums_crc32_common(PyObject *args, uint32_t (*checksum_fn)(const uint8_t *, size_t, uint32_t)) {
1111
Py_buffer input;
1212
PyObject *py_previousCrc;
1313
PyObject *py_result = NULL;
@@ -39,18 +39,11 @@ PyObject *checksums_crc_common(PyObject *args, uint32_t (*checksum_fn)(const uin
3939

4040
/* clang-format off */
4141
Py_BEGIN_ALLOW_THREADS
42-
/* Avoid truncation of length for very large buffers. crc() takes
43-
length as an int, which may be narrower than Py_ssize_t. */
44-
while ((size_t)len > INT_MAX) {
45-
val = checksum_fn(buf, INT_MAX, val);
46-
buf += (size_t)INT_MAX;
47-
len -= (size_t)INT_MAX;
48-
}
49-
val = checksum_fn(buf, (int)len, val);
42+
val = checksum_fn(buf, (size_t)len, val);
5043
Py_END_ALLOW_THREADS
5144
/* clang-format on */
5245
} else {
53-
val = checksum_fn(input.buf, (int)input.len, val);
46+
val = checksum_fn(input.buf, (size_t)input.len, val);
5447
}
5548
py_result = PyLong_FromUnsignedLong(val);
5649
done:
@@ -62,10 +55,52 @@ PyObject *checksums_crc_common(PyObject *args, uint32_t (*checksum_fn)(const uin
6255

6356
PyObject *aws_py_checksums_crc32(PyObject *self, PyObject *args) {
6457
(void)self;
65-
return checksums_crc_common(args, aws_checksums_crc32);
58+
return checksums_crc32_common(args, aws_checksums_crc32_ex);
6659
}
6760

6861
PyObject *aws_py_checksums_crc32c(PyObject *self, PyObject *args) {
6962
(void)self;
70-
return checksums_crc_common(args, aws_checksums_crc32c);
63+
return checksums_crc32_common(args, aws_checksums_crc32c_ex);
64+
}
65+
66+
PyObject *aws_py_checksums_crc64nvme(PyObject *self, PyObject *args) {
67+
(void)self;
68+
Py_buffer input;
69+
PyObject *py_previousCrc64;
70+
PyObject *py_result = NULL;
71+
72+
if (!PyArg_ParseTuple(args, "s*O", &input, &py_previousCrc64)) {
73+
return NULL;
74+
}
75+
76+
/* Note: PyArg_ParseTuple() doesn't do overflow checking on unsigned values
77+
* so use PyLong_AsUnsignedLongLong() to get the value of the previousCrc arg */
78+
uint64_t previousCrc = PyLong_AsUnsignedLongLong(py_previousCrc64);
79+
80+
if (previousCrc == (uint64_t)-1 && PyErr_Occurred()) {
81+
goto done;
82+
}
83+
84+
if (!PyBuffer_IsContiguous(&input, 'C')) {
85+
PyErr_SetString(PyExc_ValueError, "input must be contiguous buffer");
86+
goto done;
87+
}
88+
89+
/* Releasing the GIL for very small buffers is inefficient
90+
and may lower performance */
91+
if (input.len > 1024 * 5) {
92+
/* clang-format off */
93+
Py_BEGIN_ALLOW_THREADS
94+
previousCrc = aws_checksums_crc64nvme_ex(input.buf, (size_t)input.len, previousCrc);
95+
Py_END_ALLOW_THREADS
96+
/* clang-format on */
97+
} else {
98+
previousCrc = aws_checksums_crc64nvme_ex(input.buf, (size_t)input.len, previousCrc);
99+
}
100+
py_result = PyLong_FromUnsignedLongLong(previousCrc);
101+
done:
102+
if (input.obj) {
103+
PyBuffer_Release(&input);
104+
}
105+
return py_result;
71106
}

source/module.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ static PyMethodDef s_module_methods[] = {
730730
/* Checksum primitives */
731731
AWS_PY_METHOD_DEF(checksums_crc32, METH_VARARGS),
732732
AWS_PY_METHOD_DEF(checksums_crc32c, METH_VARARGS),
733+
AWS_PY_METHOD_DEF(checksums_crc64nvme, METH_VARARGS),
733734

734735
/* HTTP */
735736
AWS_PY_METHOD_DEF(http_connection_close, METH_VARARGS),

test/test_checksums.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,48 @@ def test_crc32c_huge_buffer(self):
9595
val = checksums.crc32c(huge_buffer)
9696
self.assertEqual(0x572a7c8a, val)
9797

98+
def test_crc64nvme_zeros_one_shot(self):
99+
output = checksums.crc64nvme(bytes(32))
100+
expected = 0xcf3473434d4ecf3b
101+
self.assertEqual(expected, output)
102+
103+
def test_crc64nvme_zeros_iterated(self):
104+
output = 0
105+
for i in range(32):
106+
output = checksums.crc64nvme(bytes(1), output)
107+
expected = 0xcf3473434d4ecf3b
108+
self.assertEqual(expected, output)
109+
110+
def test_crc64nvme_values_one_shot(self):
111+
output = checksums.crc64nvme(''.join(chr(i) for i in range(32)))
112+
expected = 0xb9d9d4a8492cbd7f
113+
self.assertEqual(expected, output)
114+
115+
def test_crc64nvme_values_iterated(self):
116+
output = 0
117+
for i in range(32):
118+
output = checksums.crc64nvme(chr(i), output)
119+
expected = 0xb9d9d4a8492cbd7f
120+
self.assertEqual(expected, output)
121+
122+
def test_crc64nvme_large_buffer(self):
123+
# stress test gil optimization for 32 bit architecture which cannot handle huge buffer
124+
large_buffer = bytes(25 * 2**20)
125+
val = checksums.crc64nvme(large_buffer)
126+
self.assertEqual(0x5b6f5045463ca45e, val)
127+
128+
def test_crc64nvme_huge_buffer(self):
129+
if sys.platform.startswith('freebsd'):
130+
# Skip this test for freebsd, as it simply crashes instead of raising exception in this case
131+
raise unittest.SkipTest('Skip this test for freebsd')
132+
try:
133+
INT_MAX = 2**32 - 1
134+
huge_buffer = bytes(INT_MAX + 5)
135+
except BaseException:
136+
raise unittest.SkipTest('Machine cant allocate giant buffer for giant buffer test')
137+
val = checksums.crc64nvme(huge_buffer)
138+
self.assertEqual(0x2645c28052b1fbb0, val)
139+
98140

99141
if __name__ == '__main__':
100142
unittest.main()

0 commit comments

Comments
 (0)