Skip to main content

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}