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