summaryrefslogtreecommitdiffstats
path: root/src/commonlib/bsd/ipchksum.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/commonlib/bsd/ipchksum.c')
-rw-r--r--src/commonlib/bsd/ipchksum.c52
1 files changed, 52 insertions, 0 deletions
diff --git a/src/commonlib/bsd/ipchksum.c b/src/commonlib/bsd/ipchksum.c
new file mode 100644
index 000000000000..a40b86cbb40b
--- /dev/null
+++ b/src/commonlib/bsd/ipchksum.c
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */
+
+#include <commonlib/bsd/ipchksum.h>
+
+/* See RFC 1071 for mathematical explanations of why we can first sum in a larger register and
+ then narrow down, why we don't need to worry about endianness, etc. */
+uint16_t ipchksum(const void *data, size_t size)
+{
+ const uint8_t *p1 = data;
+ unsigned long wide_sum = 0;
+ uint32_t sum = 0;
+ size_t i = 0;
+
+ while (wide_sum) {
+ sum += wide_sum & 0xFFFF;
+ wide_sum >>= 16;
+ }
+ sum = (sum & 0xFFFF) + (sum >> 16);
+
+ for (; i < size; i++) {
+ uint32_t v = p1[i];
+ if (i % 2)
+ v <<= 8;
+ sum += v;
+
+ /* Doing this unconditionally seems to be faster. */
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+
+ return (uint16_t)~sum;
+}
+
+uint16_t ipchksum_add(size_t offset, uint16_t first, uint16_t second)
+{
+ first = ~first;
+ second = ~second;
+
+ /*
+ * Since the checksum is calculated in 16-bit chunks, if the offset at which
+ * the data covered by the second checksum would start (if both data streams
+ * came one after the other) is odd, that means the second stream starts in
+ * the middle of a 16-bit chunk. This means the second checksum is byte
+ * swapped compared to what we need it to be, and we must swap it back.
+ */
+ if (offset % 2)
+ second = (second >> 8) | (second << 8);
+
+ uint32_t sum = first + second;
+ sum = (sum & 0xFFFF) + (sum >> 16);
+
+ return (uint16_t)~sum;
+}