Loading content...
The previous page established why error control is essential—physical channels corrupt data, and unchecked corruption leads to catastrophic failures. Now we confront the first practical challenge: How do we know when an error has occurred?
This is a surprisingly subtle problem. When a receiver gets a sequence of bits, it has no way to compare them against the original—it only sees what arrived. The transmitter must embed enough redundant information that corruptions leave detectable traces. The art of error detection lies in adding minimal redundancy while maximizing the probability of catching errors.
This page covers the theory and practice of error detection: parity bits, checksums, and Cyclic Redundancy Checks (CRC). You'll understand the mathematical principles behind each technique, their detection capabilities and limitations, and why CRC has become the dominant technique for Data Link Layer error detection.
All error detection schemes share a common principle: adding redundant bits that constrain valid data patterns. By transmitting more bits than strictly necessary to represent the data, we create relationships between bits that errors will violate.
Conceptual Framework:
Consider a simple analogy: if I tell you 'the sum of digits is 15', you can verify that '456' is consistent (4+5+6=15) but '457' is not (4+5+7=16). The sum is redundant information—it's derivable from the digits—but its presence allows verification.
Formally, we can think of error detection as follows:
With r check bits, the codeword space C contains 2^k elements out of 2^n = 2^(k+r) possible strings. The ratio |C|/|V| = 2^k / 2^(k+r) = 2^(-r) represents the fraction of all possible strings that are valid codewords. If errors are random, the probability that a corrupted codeword accidentally lands on another valid codeword is approximately 2^(-r). With 32 check bits (as in CRC-32), this is 1 in 4 billion—excellent odds.
The parity bit is the most basic error detection mechanism: a single bit appended to data that makes the total count of 1s either even (even parity) or odd (odd parity).
Even Parity:
Odd Parity:
Mathematical Basis:
Parity can be understood as XOR (exclusive-or) arithmetic:
XOR has the property that any bit that is 1 'toggles' the result. If an error flips any single bit, the XOR of all bits changes from 0 to 1, revealing the error.
| Step | Data/Bits | Parity Calculation | Result |
|---|---|---|---|
| Original Data | 1011010 | 1⊕0⊕1⊕1⊕0⊕1⊕0 = 0 | Even count (4 ones) |
| Add Even Parity | 10110100 | Append 0 | Total: 4 ones (even) |
| Transmit | 10110100 | — | — |
| No Error Received | 10110100 | XOR all = 0 | Valid ✓ |
| Single-bit Error | 10010100 | XOR all = 1 | Error detected ✓ |
| Two-bit Error | 10010000 | XOR all = 0 | Error MISSED ✗ |
Limitations of Simple Parity:
Parity detects all single-bit errors but fails catastrophically for multi-bit errors:
Two-Dimensional Parity:
An improvement organizes data into a matrix and computes parity for each row AND each column:
Data arranged in 4×7 matrix with row and column parities: Original Data (7 bits per row × 4 rows):1 0 1 1 0 0 0 | 1 (row parity)1 1 0 1 0 1 1 | 0 0 1 0 0 1 0 1 | 0 1 0 0 0 1 1 0 | 0 -----------------1 0 1 0 0 0 0 | 0 (column parities) Detection Capability:- All 1-bit errors: DETECTED (affects one row AND one column parity)- All 2-bit errors: DETECTED (unless in same row/column with even spacing)- All 3-bit errors: DETECTED- Many 4-bit errors: DETECTED- Some 4-bit errors: MISSED (if they form a rectangle) Error Location:- Single-bit error: intersection of failed row and column parities- This enables CORRECTION of single-bit errors!Parity checking was widely used in early computer memory (single-bit parity) and modem protocols. Two-dimensional parity evolved into more sophisticated codes. Modern systems rarely use simple parity for data transmission, but the concept underlies more advanced techniques. ECC memory uses related principles with Hamming codes.
A checksum treats the data as a sequence of numbers and computes an arithmetic sum (or related function) that can verify integrity. The most widely used variant is the Internet Checksum employed by IP, TCP, UDP, and ICMP.
The Internet Checksum Algorithm:
One's Complement Arithmetic:
Unlike standard binary addition, one's complement arithmetic has a special property: carry out of the most significant bit is 'wrapped around' and added to the least significant bit. This keeps the result within 16 bits and has desirable error detection properties.
1234567891011121314151617181920212223242526272829303132333435363738
def internet_checksum(data: bytes) -> int: """ Compute the Internet checksum (RFC 1071). Used by IP, TCP, UDP, ICMP for header/data integrity. """ # Pad with zero if odd number of bytes if len(data) % 2 == 1: data += b'\x00' # Sum all 16-bit words checksum = 0 for i in range(0, len(data), 2): word = (data[i] << 8) + data[i + 1] checksum += word # Wrap around carry (one's complement addition) checksum = (checksum & 0xFFFF) + (checksum >> 16) # Take one's complement of final sum checksum = ~checksum & 0xFFFF return checksum # Example usagedata = bytes([0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, # Checksum field zeroed 0xC0, 0xA8, 0x00, 0x01, # Source: 192.168.0.1 0xC0, 0xA8, 0x00, 0xC7]) # Dest: 192.168.0.199 checksum = internet_checksum(data)print(f"Checksum: 0x{checksum:04X}") # Example output: 0xB861 # Verification: Include checksum in data, result should be 0xFFFFdata_with_checksum = bytearray(data)data_with_checksum[10] = (checksum >> 8) & 0xFFdata_with_checksum[11] = checksum & 0xFFverify = internet_checksum(bytes(data_with_checksum))print(f"Verification: 0x{verify:04X}") # Should be 0xFFFF (all ones)Verification Process:
The receiver includes the checksum field when recomputing:
Properties of the Internet Checksum:
The Internet checksum was designed for software implementation speed, not detection strength. Its weakness against word swaps and certain burst patterns makes it unsuitable as the sole protection for link-layer frames where errors are common. This is why Ethernet and most DLL protocols use CRC instead. TCP/UDP checksums provide a 'second layer' defense, not primary protection.
The Cyclic Redundancy Check (CRC) is the dominant error detection technique for data link layer protocols. Unlike simple checksums, CRC is based on polynomial division in modulo-2 arithmetic, providing mathematically guaranteed detection of specific error patterns.
The Core Idea:
Polynomial Representation:
A bit string is interpreted as polynomial coefficients:
| CRC Name | Polynomial | Binary | Degree (r) | Usage |
|---|---|---|---|---|
| CRC-8 | x⁸ + x² + x + 1 | 100000111 | 8 | ATM header, I²C |
| CRC-16-CCITT | x¹⁶ + x¹² + x⁵ + 1 | 10001000000100001 | 16 | HDLC, X.25, Bluetooth |
| CRC-16-IBM | x¹⁶ + x¹⁵ + x² + 1 | 11000000000000101 | 16 | USB, Modbus |
| CRC-32 | x³² + x²⁶ + x²³ + x²² + x¹⁶ + x¹² + x¹¹ + x¹⁰ + x⁸ + x⁷ + x⁵ + x⁴ + x² + x + 1 | (33 bits) | 32 | Ethernet, ZIP, PNG, MPEG-2 |
| CRC-64 | x⁶⁴ + x⁶² + x⁵⁷ + x⁵⁵ + x⁵⁴ + ... | (65 bits) | 64 | ECMA-182, ISO 3309 |
Modulo-2 Arithmetic:
CRC computations use modulo-2 arithmetic where:
This arithmetic has no carries, making hardware implementation extremely efficient (shift registers and XOR gates).
The polynomial representation isn't arbitrary—it enables powerful mathematical analysis. Polynomial algebra lets us prove which error patterns are detectable. A CRC with generator G(x) of degree r will detect: ALL burst errors of length ≤ r, ALL odd numbers of bit errors (if x+1 is a factor of G(x)), ALL single-bit errors, and most longer burst errors with probability 1 - 2^(-r).
Let's walk through CRC computation with a concrete example to solidify understanding.
Example Setup:
Step 1: Prepare the dividend
Append r zero bits (where r = degree of generator) to the data:
This is equivalent to multiplying the data polynomial by xʳ, making room for the CRC.
Step 2: Perform polynomial division
CRC Polynomial Division (Modulo-2):Generator G(x) = 10011 1 1 0 1 0 1 1 0 0 1 ← Quotient (not used in CRC) ─────────────────────10011 │ 1 1 0 1 0 1 1 0 1 1 0 0 0 0 ← Dividend (Data + 0000) 1 0 0 1 1 ← XOR with generator (aligned at MSB) ───────── 1 0 0 1 1 1 1 0 0 1 1 ← XOR (aligned at new MSB) ───────── 0 0 0 0 0 0 ← Result is 0, bring down next bits 1 0 1 1 0 1 0 0 1 1 ← XOR ───────── 0 1 0 1 0 0 1 0 0 1 1 ← (Can't XOR, MSB is 0, bring down) 0 1 0 0 1 0 1 0 0 1 1 ← (Can't XOR, MSB is 0) 0 0 1 0 0 0 1 0 0 1 1 ───────── 0 0 1 1 1 ← Partial (continue...) Final Remainder: 1 1 1 0 ← This is the CRC! Transmitted Frame: 1101011011 | 1110 ───────── ──── Data CRCStep 3: Append CRC to data
The transmitted codeword is: Data concatenated with CRC remainder
Step 4: Receiver verification
Receiver divides the entire received frame (data + CRC) by the same generator:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
def crc_compute(data: int, data_bits: int, generator: int, crc_bits: int) -> int: """ Compute CRC using polynomial division. Args: data: Integer representation of data bits data_bits: Number of bits in data generator: Generator polynomial (includes leading 1) crc_bits: Number of CRC bits (degree of generator) Returns: CRC remainder value """ # Append crc_bits zeros to data (multiply by x^r) dividend = data << crc_bits # Perform polynomial division for i in range(data_bits - 1, -1, -1): if dividend & (1 << (i + crc_bits)): # Check MSB of current window dividend ^= generator << i # XOR with aligned generator # Remainder is in the last crc_bits positions return dividend & ((1 << crc_bits) - 1) def crc_verify(received: int, total_bits: int, generator: int, crc_bits: int) -> bool: """ Verify CRC of received frame. Returns True if frame is valid (no errors detected). """ remainder = 0 for i in range(total_bits - 1, -1, -1): remainder = (remainder << 1) | ((received >> i) & 1) if remainder & (1 << crc_bits): remainder ^= generator return remainder == 0 # Example: Computing CRC-4 with generator x^4 + x + 1 = 10011 = 19data = 0b1101011011 # 10 bitsgenerator = 0b10011 # 5 bits (degree 4)crc_bits = 4 crc = crc_compute(data, 10, generator, crc_bits)print(f"Data: {data:010b}")print(f"CRC: {crc:04b}") # Output: 1110 # Create transmitted frametransmitted = (data << crc_bits) | crcprint(f"Transmitted: {transmitted:014b}") # 11010110111110 # Verify at receiver (no errors)is_valid = crc_verify(transmitted, 14, generator, crc_bits)print(f"Valid: {is_valid}") # True # Simulate single-bit errorcorrupted = transmitted ^ (1 << 5) # Flip bit 5is_valid_corrupted = crc_verify(corrupted, 14, generator, crc_bits)print(f"Corrupted valid: {is_valid_corrupted}") # False - error detected!In practice, CRC is computed using Linear Feedback Shift Registers (LFSRs), which process one bit per clock cycle. The shift register taps correspond to the generator polynomial coefficients. Modern NICs compute CRC-32 for Gigabit Ethernet frames in hardware at line rate—processing billions of bits per second with zero CPU overhead.
CRC's power comes from its mathematically provable detection guarantees. Unlike checksums that detect errors probabilistically, CRC provides deterministic detection for specific error classes.
Theoretical Foundation:
An error pattern E(x) goes undetected if and only if E(x) is divisible by G(x). By choosing G(x) carefully, we ensure common error patterns are NOT divisible by G(x).
Guaranteed Detection Properties:
| Error Type | Guaranteed Detection? | Detection Probability |
|---|---|---|
| Single-bit error | Yes (100%) | 1.0 |
| Two-bit error (any separation) | Yes (100%) | 1.0 |
| Odd number of bit errors | Yes (100%) | 1.0 |
| Burst ≤ 32 bits | Yes (100%) | 1.0 |
| Burst = 33 bits | No | 1 - 2⁻³¹ ≈ 0.9999999995 |
| Burst > 33 bits | No | 1 - 2⁻³² ≈ 0.99999999977 |
| Random errors (any pattern) | No | 1 - 2⁻³² ≈ 0.99999999977 |
Why CRC-32 for Ethernet?
Ethernet frames can be up to 1518 bytes (12,144 bits). CRC-32 guarantees:
This combination of strong guarantees, high probability detection of non-guaranteed cases, and minimal overhead makes CRC-32 ideal for link-layer error detection.
CRC detects random transmission errors excellently but provides NO protection against intentional modification. An attacker who modifies the data can easily recompute a valid CRC. For security against tampering, cryptographic message authentication codes (MACs) like HMAC are required. CRC is for reliability, not security.
Real-world CRC implementations include optimizations and variations beyond the basic algorithm.
Bit Ordering (Reflection):
CRC standards specify bit ordering that can be confusing:
Ethernet CRC-32 uses reflection: data is reflected, CRC is computed, then result is reflected again. This historical choice relates to hardware shift register design.
Initial Value:
Instead of initializing the CRC register to zero, many implementations use all-ones (0xFFFFFFFF for CRC-32). This catches errors in leading zero bits that would otherwise be invisible to the calculation.
Final XOR:
Some standards XOR the final CRC with a constant (often all-ones). Combined with non-zero initialization, this improves detection of certain error patterns.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
def crc32_ethernet(data: bytes) -> int: """ Compute Ethernet CRC-32 exactly as specified in IEEE 802.3. Characteristics: - Polynomial: 0x04C11DB7 (normal form) - Initial value: 0xFFFFFFFF - Reflect input bytes: Yes - Reflect output: Yes - Final XOR: 0xFFFFFFFF """ # CRC-32 lookup table (precomputed for efficiency) CRC32_TABLE = [] for byte in range(256): crc = byte for _ in range(8): if crc & 1: crc = (crc >> 1) ^ 0xEDB88320 # Reflected polynomial else: crc >>= 1 CRC32_TABLE.append(crc) # Compute CRC crc = 0xFFFFFFFF # Initial value (all ones) for byte in data: crc = CRC32_TABLE[(crc ^ byte) & 0xFF] ^ (crc >> 8) return crc ^ 0xFFFFFFFF # Final XOR # Example: Compute CRC for an Ethernet frame payloadpayload = b"Hello, Ethernet!"crc = crc32_ethernet(payload)print(f"CRC-32: 0x{crc:08X}") # Append CRC to frame (little-endian for transmission)frame_with_crc = payload + crc.to_bytes(4, 'little')print(f"Frame: {frame_with_crc.hex()}") # Verification: CRC of (data + correct CRC) should equal magic value# The magic residue for Ethernet CRC-32 is 0xC704DD7Bdef verify_crc(frame_with_crc: bytes) -> bool: computed = crc32_ethernet(frame_with_crc) return computed == 0xC704DD7B # Magic residue print(f"Valid: {verify_crc(frame_with_crc)}") # TrueLookup Table Optimization:
The bit-by-bit division algorithm is too slow for high-speed implementations. Table-driven CRC processes one byte (or more) at a time:
Faster variants use larger tables (4KB for byte-at-a-time, 32KB for word-at-a-time) or parallel slicing techniques that process multiple bytes simultaneously.
Modern CPUs include CRC instructions (Intel CRC32 in SSE4.2, ARM CRC32 extension) that compute CRC at memory bandwidth speeds. Network interface cards compute CRC entirely in hardware, achieving line-rate CRC with zero CPU overhead. Software CRC is now mainly for compatibility and testing.
Having studied parity, checksums, and CRC, let's synthesize a comparative analysis to guide technique selection:
| Aspect | Parity | Internet Checksum | CRC-32 |
|---|---|---|---|
| Overhead bits | 1 per 7-8 data bits | 16 bits | 32 bits |
| Single-bit errors | Detected | Detected | Detected |
| Two-bit errors | Missed | Some missed | Detected |
| Burst errors | Poor | Moderate | Excellent (≤32 bits: 100%) |
| Word swap | Detected | Missed | Detected |
| Undetected error probability | ~50% for 2+ errors | ~1/65,536 | ~1/4 billion |
| Computation complexity | O(n) XORs | O(n) additions | O(n) XORs + table lookup |
| Hardware support | Trivial | Easy | Dedicated silicon |
| Primary use | Memory ECC | IP/TCP/UDP headers | Ethernet, storage |
Modern networks don't rely on a single error detection layer. Ethernet frames have CRC-32; IP has header checksums; TCP/UDP have checksums covering pseudo-header, header, and data. Each layer independently detects errors that might slip through others or be introduced between layers (memory errors, software bugs). This redundancy is intentional.
We've explored the mathematics and practice of error detection—the first pillar of error control. Let's consolidate:
What's Next:
Error detection tells us something is wrong—but what if we could fix the error without retransmission? The next page explores Forward Error Correction (FEC): techniques that add enough redundancy to reconstruct the original data from corrupted transmissions. This is essential for real-time applications and long-delay channels where retransmission is impractical.
You now understand the theory and practice of error detection at the Data Link Layer. From parity's simplicity to CRC's polynomial power, these techniques form the first line of defense against transmission errors. Next, we'll explore the more sophisticated world of error correction codes.