internet_checksum/lib.rs
1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! RFC 1071 "internet checksum" computation.
6//!
7//! This crate implements the "internet checksum" defined in [RFC 1071] and
8//! updated in [RFC 1141] and [RFC 1624], which is used by many different
9//! protocols' packet formats. The checksum operates by computing the 1s
10//! complement of the 1s complement sum of successive 16-bit words of the input.
11//!
12//! [RFC 1071]: https://tools.ietf.org/html/rfc1071
13//! [RFC 1141]: https://tools.ietf.org/html/rfc1141
14//! [RFC 1624]: https://tools.ietf.org/html/rfc1624
15
16// Optimizations applied:
17//
18// 0. Byteorder independence: as described in RFC 1071 section 2.(B)
19// The sum of 16-bit integers can be computed in either byte order,
20// so this actually saves us from the unnecessary byte swapping on
21// an LE machine. As perfed on a gLinux workstation, that swapping
22// can account for ~20% of the runtime.
23//
24// 1. Widen the accumulator: doing so enables us to process a bigger
25// chunk of data once at a time, achieving some kind of poor man's
26// SIMD. Currently a u128 accumulator is used.
27//
28// 2. Process more at a time: we add in increments of u64 rather than u16.
29//
30// 3. Induce the compiler to produce `adc` instruction: this is a very
31// useful instruction to implement 1's complement addition and available
32// on both x86 and ARM. The functions `adc_uXX` are for this use.
33
34/// Compute the checksum of "bytes".
35///
36/// `checksum(bytes)` is shorthand for:
37///
38/// ```rust
39/// # use internet_checksum::Checksum;
40/// # let bytes = &[];
41/// # let _ = {
42/// let mut c = Checksum::new();
43/// c.add_bytes(bytes);
44/// c.checksum()
45/// # };
46/// ```
47#[inline]
48pub fn checksum(bytes: &[u8]) -> [u8; 2] {
49 let mut c = Checksum::new();
50 c.add_bytes(bytes);
51 c.checksum()
52}
53
54/// Updates bytes in an existing checksum.
55///
56/// `update` updates a checksum to reflect that the already-checksummed bytes
57/// `old` have been removed and replaced with the bytes in `new`, which may not
58/// have the same length as `old`. It implements the algorithm described in
59/// Equation 3 in [RFC 1624]. The first byte must be at an even number offset in
60/// the original input. If an odd number offset byte needs to be updated, the
61/// caller should simply include the preceding byte as well. If an odd number of
62/// bytes is given, it is assumed that these are the last bytes of the input. If
63/// an odd number of bytes in the middle of the input needs to be updated, the
64/// preceding or following byte of the input should be added to make an even
65/// number of bytes.
66///
67/// [RFC 1624]: https://tools.ietf.org/html/rfc1624
68#[inline]
69fn update_internal(checksum: [u8; 2], old: &[u8], new: &[u8]) -> [u8; 2] {
70 // We compute on the sum, not the one's complement of the sum. checksum
71 // is the one's complement of the sum, so we need to get back to the
72 // sum. Thus, we negate checksum.
73 // HC' = ~HC
74 let mut sum = !u16::from_ne_bytes(checksum);
75
76 // Let's reuse `Checksum::add_bytes` to update our checksum
77 // so that we can get the speedup for free. Using
78 // [RFC 1071 Eqn. 3], we can efficiently update our new checksum.
79 let mut c1 = Checksum::new();
80 let mut c2 = Checksum::new();
81 c1.add_bytes(old);
82 c2.add_bytes(new);
83
84 // Note, `c1.checksum_inner()` is actually ~m in [Eqn. 3]
85 // `c2.checksum_inner()` is actually ~m' in [Eqn. 3]
86 // so we have to negate `c2.checksum_inner()` first to get m'.
87 // HC' += ~m, c1.checksum_inner() == ~m.
88 sum = adc_u16(sum, c1.checksum_inner());
89 // HC' += m', c2.checksum_inner() == ~m'.
90 sum = adc_u16(sum, !c2.checksum_inner());
91 // HC' = ~HC.
92 (!sum).to_ne_bytes()
93}
94
95/// Updates bytes in an existing checksum.
96///
97/// `update` updates a checksum to reflect that the already-checksummed bytes
98/// `old` have been updated to contain the values in `new`. It implements the
99/// algorithm described in Equation 3 in [RFC 1624]. The first byte must be at
100/// an even number offset in the original input. If an odd number offset byte
101/// needs to be updated, the caller should simply include the preceding byte as
102/// well. If an odd number of bytes is given, it is assumed that these are the
103/// last bytes of the input. If an odd number of bytes in the middle of the
104/// input needs to be updated, the preceding or following byte of the input
105/// should be added to make an even number of bytes.
106///
107/// # Panics
108///
109/// `update` panics if `old.len() != new.len()`.
110///
111/// [RFC 1624]: https://tools.ietf.org/html/rfc1624
112#[inline]
113pub fn update(checksum: [u8; 2], old: &[u8], new: &[u8]) -> [u8; 2] {
114 assert_eq!(old.len(), new.len());
115 update_internal(checksum, old, new)
116}
117
118/// Updates a checksum to reflect that the already-checksummed bytes `bytes`
119/// have been removed.
120///
121/// `remove` implements the algorithm described in [RFC 1624 Eqn. 3] for the
122/// special case where the replacement data is all zeroes. The first byte must
123/// be at an even number offset in the original input. If an odd number offset
124/// byte needs to be removed, the caller should include a preceding zero byte.
125/// If an odd number of bytes in the middle of the input needs to be removed, a
126/// preceding or following zero byte should be added to make an even number of
127/// bytes.
128///
129/// [RFC 1624]: https://tools.ietf.org/html/rfc1624
130#[inline]
131pub fn remove(checksum: [u8; 2], bytes: &[u8]) -> [u8; 2] {
132 update_internal(checksum, bytes, &[])
133}
134
135/// Updates a checksum to reflect that the bytes `bytes` have been added.
136///
137/// `add` implements the algorithm described in [RFC 1624 Eqn. 3] for the
138/// special case where the previous data was all zeroes. The first byte must be
139/// at an even number offset in the checksummed data, or else a zero byte must
140/// be prepended.
141///
142/// [RFC 1624]: https://tools.ietf.org/html/rfc1624
143#[inline]
144pub fn add(checksum: [u8; 2], bytes: &[u8]) -> [u8; 2] {
145 update_internal(checksum, &[], bytes)
146}
147
148/// RFC 1071 "internet checksum" computation.
149///
150/// `Checksum` implements the "internet checksum" defined in [RFC 1071] and
151/// updated in [RFC 1141] and [RFC 1624], which is used by many different
152/// protocols' packet formats. The checksum operates by computing the 1s
153/// complement of the 1s complement sum of successive 16-bit words of the input.
154///
155/// [RFC 1071]: https://tools.ietf.org/html/rfc1071
156/// [RFC 1141]: https://tools.ietf.org/html/rfc1141
157/// [RFC 1624]: https://tools.ietf.org/html/rfc1624
158#[derive(Default)]
159pub struct Checksum {
160 // Accumulate the sum into a u128, despite the fact that the `Checksum`
161 // implementation adds 8-byte or smaller chunks at a time. This effectively
162 // allows us to ignore overflow, which has been demonstrated to improve
163 // performance.
164 //
165 // Adding an 8-byte chunk to a u128 can be done safely without overflow up
166 // to u64::MAX times. Thus, we need not worry about overflow unless we were
167 // to checksum more than 8 * 2^64 bytes, or ~147 exabytes. We ignore this
168 // possibility.
169 sum: u128,
170 // Since odd-length inputs are treated specially, we store the trailing byte
171 // for use in future calls to add_bytes(), and only treat it as a true
172 // trailing byte in checksum().
173 trailing_byte: Option<u8>,
174}
175
176impl Checksum {
177 /// Initialize a new checksum.
178 #[inline]
179 pub const fn new() -> Self {
180 Checksum { sum: 0, trailing_byte: None }
181 }
182
183 /// Add bytes to the checksum.
184 ///
185 /// If `bytes` does not contain an even number of bytes, a single zero byte
186 /// will be added to the end before updating the checksum.
187 ///
188 /// Note that `add_bytes` has some fixed overhead regardless of the size of
189 /// `bytes`. Where performance is a concern, prefer fewer calls to
190 /// `add_bytes` with larger input over more calls with smaller input.
191 #[inline]
192 pub fn add_bytes(&mut self, mut bytes: &[u8]) {
193 if bytes.is_empty() {
194 return;
195 }
196
197 let mut sum = self.sum;
198
199 // Deal with previous trailing byte, if we have one.
200 // NB: Don't use `if let Some(t) = self.trailing_byte.take()`. It slows
201 // down the fast path (i.e. the `None` case).
202 if self.trailing_byte.is_some() {
203 let trailing = self.trailing_byte.take().unwrap();
204 sum += u16::from_ne_bytes([trailing, bytes[0]]) as u128;
205 bytes = &bytes[1..];
206 }
207
208 // NB: Even though our accumulator is 16 bytes, summing in 8 byte chunks
209 // (rather than 16 byte chunks) leads to better optimized machine code
210 // on 64 bit platforms.
211 while let Some(chunk) = bytes.first_chunk::<8>() {
212 sum += u64::from_ne_bytes(*chunk) as u128;
213 bytes = &bytes[8..];
214 }
215
216 // Handle the tail.
217 if let Some(chunk) = bytes.first_chunk::<4>() {
218 sum += u32::from_ne_bytes(*chunk) as u128;
219 bytes = &bytes[4..];
220 }
221 if let Some(chunk) = bytes.first_chunk::<2>() {
222 sum += u16::from_ne_bytes(*chunk) as u128;
223 bytes = &bytes[2..];
224 }
225 if bytes.len() == 1 {
226 // Stash the trailing byte.
227 self.trailing_byte = Some(bytes[0]);
228 }
229
230 self.sum = sum;
231 }
232
233 /// Computes the checksum, but in big endian byte order.
234 fn checksum_inner(&self) -> u16 {
235 let mut sum = self.sum;
236 if let Some(byte) = self.trailing_byte {
237 sum += u16::from_ne_bytes([byte, 0]) as u128;
238 }
239 !normalize(sum)
240 }
241
242 /// Computes the one's complement sum and returns the array representation.
243 ///
244 /// `partial_checksum` returns the one's complement sum of all data added
245 /// using `add_bytes` so far. Calling `partial_checksum` does *not* reset
246 /// the checksum. More bytes may be added after calling `partial_checksum`,
247 /// and they will be added to the checksum as expected.
248 ///
249 /// `partial_checksum` will return `None` if an odd number of bytes have
250 /// been added so far.
251 pub fn partial_checksum(&self) -> Option<[u8; 2]> {
252 if self.trailing_byte.is_some() {
253 return None;
254 }
255 Some(normalize(self.sum).to_ne_bytes())
256 }
257
258 /// Computes the checksum, and returns the array representation.
259 ///
260 /// `checksum` returns the checksum of all data added using `add_bytes` so
261 /// far. Calling `checksum` does *not* reset the checksum. More bytes may be
262 /// added after calling `checksum`, and they will be added to the checksum
263 /// as expected.
264 ///
265 /// If an odd number of bytes have been added so far, the checksum will be
266 /// computed as though a single 0 byte had been added at the end in order to
267 /// even out the length of the input.
268 #[inline]
269 pub fn checksum(&self) -> [u8; 2] {
270 self.checksum_inner().to_ne_bytes()
271 }
272}
273
274macro_rules! impl_adc {
275 ($name: ident, $t: ty) => {
276 /// implements 1's complement addition for $t,
277 /// exploiting the carry flag on a 2's complement machine.
278 /// In practice, the adc instruction will be generated.
279 fn $name(a: $t, b: $t) -> $t {
280 let (s, c) = a.overflowing_add(b);
281 s + (c as $t)
282 }
283 };
284}
285
286impl_adc!(adc_u16, u16);
287impl_adc!(adc_u32, u32);
288impl_adc!(adc_u64, u64);
289
290/// Normalizes the accumulator by mopping up the
291/// overflow until it fits in a `u16`.
292fn normalize(a: u128) -> u16 {
293 let t = adc_u64(a as u64, (a >> 64) as u64);
294 let t = adc_u32(t as u32, (t >> 32) as u32);
295 adc_u16(t as u16, (t >> 16) as u16)
296}
297
298#[cfg(test)]
299mod tests {
300 use rand::{RngExt as _, SeedableRng as _};
301
302 use rand_xorshift::XorShiftRng;
303
304 use super::*;
305
306 /// Create a new deterministic RNG from a seed.
307 fn new_rng(mut seed: u128) -> XorShiftRng {
308 if seed == 0 {
309 // XorShiftRng can't take 0 seeds
310 seed = 1;
311 }
312 XorShiftRng::from_seed(seed.to_ne_bytes())
313 }
314
315 #[test]
316 fn test_checksum() {
317 for buf in IPV4_HEADERS {
318 // compute the checksum as normal
319 let mut c = Checksum::new();
320 c.add_bytes(&buf);
321 assert_eq!(c.checksum(), [0u8; 2]);
322 // compute the checksum one byte at a time to make sure our
323 // trailing_byte logic works
324 let mut c = Checksum::new();
325 for byte in *buf {
326 c.add_bytes(&[*byte]);
327 }
328 assert_eq!(c.checksum(), [0u8; 2]);
329
330 // Make sure that it works even if we overflow u32. Performing this
331 // loop 2 * 2^16 times is guaranteed to cause such an overflow
332 // because 0xFFFF + 0xFFFF > 2^16, and we're effectively adding
333 // (0xFFFF + 0xFFFF) 2^16 times. We verify the overflow as well by
334 // making sure that, at least once, the sum gets smaller from one
335 // loop iteration to the next.
336 let mut c = Checksum::new();
337 c.add_bytes(&[0xFF, 0xFF]);
338 for _ in 0..((2 * (1 << 16)) - 1) {
339 c.add_bytes(&[0xFF, 0xFF]);
340 }
341 assert_eq!(c.checksum(), [0u8; 2]);
342 }
343 }
344
345 #[test]
346 fn test_partial_checksum() {
347 for buf in IPV4_HEADERS {
348 // Partial checksum should compute for even length slices.
349 for i in (0..buf.len()).step_by(2) {
350 let mut part = Checksum::new();
351 part.add_bytes(&buf[..i]);
352
353 let mut c = Checksum::new();
354 c.add_bytes(
355 &part
356 .partial_checksum()
357 .expect("partial checksum should compute for even length slices"),
358 );
359 c.add_bytes(&buf[i..]);
360 assert_eq!(c.checksum(), [0u8; 2]);
361 }
362 // Partial checksum should not compute for odd length slices.
363 for i in (1..buf.len()).step_by(2) {
364 let mut part = Checksum::new();
365 part.add_bytes(&buf[..i]);
366 assert_eq!(part.partial_checksum(), None);
367 }
368 // Partial checksum should be the complement of the checksum.
369 let mut c = Checksum::new();
370 c.add_bytes(buf);
371 assert_eq!(c.partial_checksum(), Some([0xFF; 2]));
372 }
373 }
374
375 #[test]
376 fn test_update() {
377 for b in IPV4_HEADERS {
378 let mut buf = Vec::new();
379 buf.extend_from_slice(b);
380
381 let mut c = Checksum::new();
382 c.add_bytes(&buf);
383 assert_eq!(c.checksum(), [0u8; 2]);
384
385 // replace the destination IP with the loopback address
386 let old = [buf[16], buf[17], buf[18], buf[19]];
387 (&mut buf[16..20]).copy_from_slice(&[127, 0, 0, 1]);
388 let updated = update(c.checksum(), &old, &[127, 0, 0, 1]);
389 let from_scratch = {
390 let mut c = Checksum::new();
391 c.add_bytes(&buf);
392 c.checksum()
393 };
394 assert_eq!(updated, from_scratch);
395 }
396 }
397
398 #[test]
399 fn test_update_noop() {
400 for b in IPV4_HEADERS {
401 let mut buf = Vec::new();
402 buf.extend_from_slice(b);
403
404 let mut c = Checksum::new();
405 c.add_bytes(&buf);
406 assert_eq!(c.checksum(), [0u8; 2]);
407
408 // Replace the destination IP with the same address. I.e. this
409 // update should be a no-op.
410 let old = [buf[16], buf[17], buf[18], buf[19]];
411 let updated = update(c.checksum(), &old, &old);
412 let from_scratch = {
413 let mut c = Checksum::new();
414 c.add_bytes(&buf);
415 c.checksum()
416 };
417 assert_eq!(updated, from_scratch);
418 }
419 }
420
421 #[test]
422 fn test_remove() {
423 for b in IPV4_HEADERS {
424 let mut buf = Vec::new();
425 buf.extend_from_slice(b);
426
427 let mut c = Checksum::new();
428 c.add_bytes(&buf);
429 let original_csum = c.checksum();
430
431 let removed = remove(original_csum, &buf[16..]);
432
433 let mut c2 = Checksum::new();
434 c2.add_bytes(&buf[..16]);
435 let expected_csum = c2.checksum();
436 assert_eq!(removed, expected_csum);
437 }
438 }
439
440 #[test]
441 fn test_add() {
442 for b in IPV4_HEADERS {
443 let mut buf = Vec::new();
444 buf.extend_from_slice(b);
445
446 let mut c = Checksum::new();
447 c.add_bytes(&buf);
448 let original_csum = c.checksum();
449
450 let new_bytes = [127, 0, 0, 1];
451 let added = add(original_csum, &new_bytes);
452
453 let mut c2 = Checksum::new();
454 c2.add_bytes(&buf);
455 c2.add_bytes(&new_bytes);
456 let expected_csum = c2.checksum();
457 assert_eq!(added, expected_csum);
458 }
459 }
460
461 #[test]
462 fn test_smoke_update() {
463 let mut rng = new_rng(70_812_476_915_813);
464
465 for _ in 0..2048 {
466 // use an odd length so we test the odd length logic
467 const BUF_LEN: usize = 31;
468 let buf: [u8; BUF_LEN] = rng.random();
469 let mut c = Checksum::new();
470 c.add_bytes(&buf);
471
472 let (begin, end) = loop {
473 let begin = rng.random_range(0..BUF_LEN);
474 let end = begin + (rng.random_range(0..(BUF_LEN + 1 - begin)));
475 // update requires that begin is even and end is either even or
476 // the end of the input
477 if begin % 2 == 0 && (end % 2 == 0 || end == BUF_LEN) {
478 break (begin, end);
479 }
480 };
481
482 let mut new_buf = buf;
483 for i in begin..end {
484 new_buf[i] = rng.random();
485 }
486 let updated = update(c.checksum(), &buf[begin..end], &new_buf[begin..end]);
487 let from_scratch = {
488 let mut c = Checksum::new();
489 c.add_bytes(&new_buf);
490 c.checksum()
491 };
492 assert_eq!(updated, from_scratch);
493 }
494 }
495
496 /// IPv4 headers.
497 ///
498 /// This data was obtained by capturing live network traffic.
499 const IPV4_HEADERS: &[&[u8]] = &[
500 &[
501 0x45, 0x00, 0x00, 0x34, 0x00, 0x00, 0x40, 0x00, 0x40, 0x06, 0xae, 0xea, 0xc0, 0xa8,
502 0x01, 0x0f, 0xc0, 0xb8, 0x09, 0x6a,
503 ],
504 &[
505 0x45, 0x20, 0x00, 0x74, 0x5b, 0x6e, 0x40, 0x00, 0x37, 0x06, 0x5c, 0x1c, 0xc0, 0xb8,
506 0x09, 0x6a, 0xc0, 0xa8, 0x01, 0x0f,
507 ],
508 &[
509 0x45, 0x20, 0x02, 0x8f, 0x00, 0x00, 0x40, 0x00, 0x3b, 0x11, 0xc9, 0x3f, 0xac, 0xd9,
510 0x05, 0x6e, 0xc0, 0xa8, 0x01, 0x0f,
511 ],
512 ];
513
514 // This test checks that an input, found by a fuzzer, no longer causes a crash due to addition
515 // overflow.
516 #[test]
517 fn test_large_buffer_addition_overflow() {
518 let mut sum = Checksum { sum: 0, trailing_byte: None };
519 let bytes = [
520 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
521 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
522 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
523 ];
524 sum.add_bytes(&bytes[..]);
525 }
526
527 // Regression test for https://fxbug.dev/515774797.
528 //
529 // Verify that checksum calculations produce the same result, no matter if
530 // the bytes are added at once, or in odd-length chunks.
531 #[test]
532 fn test_odd_length_checksum() {
533 // Determine the expected value. Per RFC 1071, an odd length of bytes
534 // should be padded at the end with a 0.
535 let mut c = Checksum::new();
536 c.add_bytes(&[1, 2, 3, 0]);
537 let expected_checksum = c.checksum();
538
539 // Add the bytes all at once.
540 let mut c = Checksum::new();
541 c.add_bytes(&[1, 2, 3]);
542 assert_eq!(c.checksum(), expected_checksum);
543
544 // Add the bytes in two passes (first pass uses an odd number of bytes).
545 let mut c = Checksum::new();
546 c.add_bytes(&[1]);
547 c.add_bytes(&[2, 3]);
548 assert_eq!(c.checksum(), expected_checksum);
549 }
550
551 // Verify that we properly perform bounds checks against the byte buffer.
552 // Failure to do so would result in index-out-of-bounds panics.
553 #[test]
554 fn test_add_zero_bytes() {
555 let mut c = Checksum::new();
556 c.add_bytes(&[]);
557 assert_eq!(c.checksum(), [255, 255]);
558
559 // Try again, but this time set a trailing_byte.
560 let mut c = Checksum::new();
561 c.add_bytes(&[0]);
562 c.add_bytes(&[]);
563 assert_eq!(c.checksum(), [255, 255]);
564
565 // Try once more, but now complete the trailing byte exactly (no remainder).
566 let mut c = Checksum::new();
567 c.add_bytes(&[0]);
568 c.add_bytes(&[0]);
569 assert_eq!(c.checksum(), [255, 255]);
570 }
571
572 // Regression test for https://fxbug.dev/515753165.
573 //
574 // The checksum implementation manually tracks the carry bit during
575 // arithmetic overflows. Verify that we correctly handle the edge case where
576 // adding the carry bit from a previous overflow causes a second overflow to
577 // occur.
578 #[test]
579 fn test_carry_loss() {
580 const MAX: [u8; 16] = u128::MAX.to_ne_bytes();
581 const ONE: [u8; 16] = 1u128.to_ne_bytes();
582
583 let mut c1 = Checksum::new();
584 c1.add_bytes(&MAX);
585 c1.add_bytes(&ONE);
586 c1.add_bytes(&MAX);
587
588 let mut c2 = Checksum::new();
589 let bytes = [MAX, ONE, MAX].concat();
590 c2.add_bytes(&bytes);
591
592 assert_eq!(c1.checksum(), c2.checksum());
593 }
594}