Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Computer Networks  Module 3 – Data link layer: framing and error detection

Parity, checksums and Hamming codes

How parity bits, two-dimensional parity, the 16-bit Internet checksum and Hamming codes detect bit errors, and how a Hamming code corrects one.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free: 30 minutes

What you will learn

  • Compute single and two-dimensional parity and say which errors each detects
  • Compute the 16-bit Internet checksum of RFC 1071 and verify a received header
  • Use minimum Hamming distance to state how many errors a code detects and corrects
  • Encode data with Hamming(7,4) and correct a single-bit error

Before you start

What you’ll learn · Before you start

What you will learn

  • Compute single and two-dimensional parity and say which errors each detects
  • Compute the 16-bit Internet checksum of RFC 1071 and verify a received header
  • Use minimum Hamming distance to state how many errors a code detects and corrects
  • Encode data with Hamming(7,4) and correct a single-bit error

Before you start

On this page

Noise on a link flips bits, and the receiver cannot see which ones. Every error-detecting code answers the same way: the sender adds a few redundant bits computed from the data, so that most damage leaves the data and those bits disagreeing. A parity bit makes the number of 1s even. A checksum adds the data up, and the 16-bit Internet checksum that IPv4, UDP and TCP use adds it in 16-bit words. A Hamming code adds several parity bits arranged so that the receiver can tell which bit flipped, and flip it back. This lesson builds all four in Python, measures which errors each one misses by trying every error pattern, and shows the rule, minimum Hamming distance, that says in advance how many errors a code can detect and how many it can correct.

Parity: one bit that counts the 1s

With even parity, the sender adds one bit chosen so that the total number of 1s is even; the receiver counts the 1s again and rejects the data if the count is odd. (Odd parity, the same idea with an odd count, is also used.) One flipped bit makes the count odd, and so does any odd number of flipped bits. Two flipped bits, or any even number, leave it even:

One parity bit on the letter K Python · parity.py
"""One even-parity bit on the 7-bit code of the letter K: one flipped bit is caught, two are not."""


def even_parity(bits):
    """The bit that makes the number of 1s even."""
    return sum(bits) % 2


k = [1, 0, 0, 1, 0, 1, 1]  # "K" is 1001011: four 1s, so its parity bit is 0
sent = k + [even_parity(k)]
one_flip = sent.copy()
one_flip[2] ^= 1
two_flips = one_flip.copy()
two_flips[5] ^= 1
for name, word in [("sent", sent), ("one bit flipped", one_flip), ("two bits flipped", two_flips)]:
    print(f"{name:17} {''.join(map(str, word))}  parity check {'passes' if sum(word) % 2 == 0 else 'FAILS'}")

Output

sent              10010110  parity check passes
one bit flipped   10110110  parity check FAILS
two bits flipped  10110010  parity check passes

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 parity.py

The 7-bit code of the letter K gets its parity bit, and of the two damaged copies, the one with one flipped bit is caught and the one with two is not. Among all the ways a block of bits can be damaged, about half flip an even number of bits, so a parity bit lets about half of all damaged blocks through. That is acceptable only where errors are rare and come one at a time, and it is why parity alone protects so little on a network link.

Two-dimensional parity: rows and columns

Arrange the data in a block, add a parity bit to every row and a parity bit to every column (a last bit, in the corner, checks the parity bits themselves), and a single flipped bit makes exactly one row and one column fail. The receiver flips the bit where they cross:

A 5 x 5 grid of bits with one damaged bit; only its row check and its column check fail, so they point at it.10111ok01000fails11011ok00101ok00101okokokfailsokokchecks

Two-dimensional parity points at the damaged bit

Text description of the diagram

The diagram shows the 4 x 4 block of data bits from parity.py as it arrives, after one data bit was damaged. A parity bit ends each row, a row of column parities runs along the bottom (the parity bits have dashed borders), and the checks are written beside the last column and under the last row.

  • The rows, as received, are 1 0 1 1 1, then 0 1 0 0 0, then 1 1 0 1 1, then 0 0 1 0 1, and the column-parity row 0 0 1 0 1.
  • The damaged bit is in the second row and the third column: it was sent as 1 and arrived as 0, and it has a thick double border.
  • Every row check says ok except the second row's, which fails; every column check says ok except the third column's, which fails.
  • The failing row and the failing column cross at the damaged bit, so the receiver can flip it back.
Two-dimensional parity, and every error of up to four bits Python · two_d_parity.py
"""Two-dimensional parity on a 4 x 4 block of bits: find and fix one flipped bit, then try every 1- to 4-bit error."""
from itertools import combinations


def even_parity(bits):
    return sum(bits) % 2


def add_parity(rows):
    """Append a parity bit to each row, then a row of column parities (its last bit checks both)."""
    rows = [r + [even_parity(r)] for r in rows]
    return rows + [[even_parity(col) for col in zip(*rows)]]


def failing(block):
    """The rows and the columns whose parity no longer checks."""
    bad_rows = [i for i, r in enumerate(block) if sum(r) % 2]
    bad_cols = [j for j, c in enumerate(zip(*block)) if sum(c) % 2]
    return bad_rows, bad_cols


data = [[1, 0, 1, 1], [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0]]
block = add_parity(data)
print("block with parity:", ["".join(map(str, r)) for r in block])
block[1][2] ^= 1  # one bit of the data is hit by noise
rows, cols = failing(block)
print("one flip: rows", rows, "and columns", cols, "fail, so the bad bit is", (rows[0], cols[0]))
block[rows[0]][cols[0]] ^= 1  # flip it back: corrected
block[0][0] ^= 1
block[0][3] ^= 1  # two flips in one row
rows, cols = failing(block)
print("two flips in one row: rows", rows, "and columns", cols, "fail: detected, but not located")
block[0][0] ^= 1
block[0][3] ^= 1

# Every pattern of 1 to 4 flipped bits among the 25 bits, and how many of them the checks miss.
cells = [(i, j) for i in range(5) for j in range(5)]
print()
for n in range(1, 5):
    missed = total = 0
    for flips in combinations(cells, n):
        for i, j in flips:
            block[i][j] ^= 1
        total += 1
        missed += failing(block) == ([], [])
        for i, j in flips:
            block[i][j] ^= 1
    print(f"{n} bit{'s' if n > 1 else ' '} flipped: {total:6,} patterns, {missed:3} undetected")

Output

block with parity: ['10111', '01100', '11011', '00101', '00101']
one flip: rows [1] and columns [2] fail, so the bad bit is (1, 2)
two flips in one row: rows [] and columns [0, 3] fail: detected, but not located

1 bit  flipped:     25 patterns,   0 undetected
2 bits flipped:    300 patterns,   0 undetected
3 bits flipped:  2,300 patterns,   0 undetected
4 bits flipped: 12,650 patterns, 100 undetected

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_d_parity.py

The program finds the flipped bit of the diagram from the failing row and column, flips it back, and then shows what two-dimensional parity cannot do. Two flips in the same row leave that row’s parity even, but two columns fail: the error is detected but cannot be located. And the program tried every one of the 15,275 patterns of one to four flipped bits among the 25 bits: all 2,625 patterns of one, two or three flips were detected, and only 100 of the 12,650 four-bit patterns got through. Those 100 are the corners of rectangles: two flips in each of two rows, in the same two columns, keep every row and every column even. Choose 2 rows of 5 and 2 columns of 5 and you get 10 × 10 = 100.

The cost is 9 parity bits for 16 data bits. Two-dimensional parity shows the idea behind error correction (enough independent checks pin a bad bit down), but it pays far more redundant bits for each corrected bit than the codes below.

Hamming distance: how many errors a code can handle

The Hamming distance between two words of the same length is the number of positions in which they differ: 0110011 and 0100011 are at distance 1, while 0110011 and 1110000 are at distance 3. A code is a set of allowed words, its codewords, and its minimum distance d is the smallest distance between any two of them. From d alone you can read off what the code can do:

detects up to d−1 flipped bits\text{detects up to } d-1 \text{ flipped bits} corrects up to ⌊d−12⌋ flipped bits\text{corrects up to } \left\lfloor \frac{d-1}{2} \right\rfloor \text{ flipped bits}

The reasoning, which Richard Hamming set out in 1950, is short. Fewer than d flips can never turn one codeword into another, so the damage always shows. And if at most t bits flip with 2t + 1 ≤ d, the received word is still closer to the codeword that was sent than to any other, so the receiver can correct it by choosing the nearest codeword. The program below finds d for three small codes by comparing every pair of codewords:

Minimum distance, found by brute force Python · distance.py
"""Minimum Hamming distance of three small codes, found by comparing every pair of codewords."""
from itertools import combinations, product


def distance(a, b):
    """How many positions two words of the same length differ in."""
    return sum(x != y for x, y in zip(a, b))


def hamming74(d1, d2, d3, d4):
    """Hamming(7,4): positions 1 to 7 hold p1 p2 d1 p4 d2 d3 d4, with even parity."""
    return (d1 ^ d2 ^ d4, d1 ^ d3 ^ d4, d1, d2 ^ d3 ^ d4, d2, d3, d4)


codes = {
    "3 data bits + 1 parity bit": [bits + (sum(bits) % 2,) for bits in product((0, 1), repeat=3)],
    "1 bit sent three times": [(b, b, b) for b in (0, 1)],
    "Hamming(7,4)": [hamming74(*bits) for bits in product((0, 1), repeat=4)],
}
print("0110011 vs 0100011:", distance("0110011", "0100011"))
print("0110011 vs 1110000:", distance("0110011", "1110000"))
print()
for name, code in codes.items():
    d = min(distance(a, b) for a, b in combinations(code, 2))
    print(f"{name}: {len(code)} codewords, minimum distance {d}: detects {d - 1}, corrects {(d - 1) // 2}")

Output

0110011 vs 0100011: 1
0110011 vs 1110000: 3

3 data bits + 1 parity bit: 8 codewords, minimum distance 2: detects 1, corrects 0
1 bit sent three times: 2 codewords, minimum distance 3: detects 2, corrects 1
Hamming(7,4): 16 codewords, minimum distance 3: detects 2, corrects 1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 distance.py

A parity bit gives d = 2: it detects one error and corrects none. Sending each bit three times gives d = 3 and lets the receiver take a majority vote, at the cost of three bits for every data bit. Hamming(7,4) also reaches d = 3, with only 3 check bits for every 4 data bits. Two-dimensional parity from the section above has d = 4, which is why its smallest undetected errors had four flipped bits; it detects three and corrects one.

Detecting and correcting compete for the same distance. A code with d = 3 can either correct one error, or detect up to two, but not both at once: a receiver that corrects every single-bit error will silently “correct” a two-bit error into the wrong codeword, as the next section shows.

Hamming(7,4): parity bits that point at the error

Hamming’s construction numbers the positions of a codeword from 1 and puts check bits at the positions that are powers of two, 1, 2 and 4; the data bits fill positions 3, 5, 6 and 7. Each check bit gives even parity over the positions whose number has its bit set in binary. In the table, the last three columns say whether p1, p2 or p4 checks each position:

Position Holds p1 p2 p4
1 (001) p1 yes no no
2 (010) p2 no yes no
3 (011) d1 yes yes no
4 (100) p4 no no yes
5 (101) d2 yes no yes
6 (110) d3 no yes yes
7 (111) d4 yes yes yes

The receiver repeats the three checks. Each failing check contributes its own weight, 1, 2 or 4, and the sum, called the syndrome, is the position of the flipped bit: a bit at position 6 (binary 110) is covered by p2 and p4, so exactly those two checks fail and 2 + 4 = 6. An equivalent shortcut is to XOR together the position numbers of all the 1s in the word, which gives 0 for a correct codeword.

Hamming(7,4): every single-bit error, and one double error Python · hamming74.py
"""Hamming(7,4): encode 4 data bits, flip each of the 7 bits in turn, and let the syndrome point at it."""


def encode(data):
    """'1011' -> '0110011': positions 1 to 7 are p1 p2 d1 p4 d2 d3 d4, with even parity."""
    d1, d2, d3, d4 = (int(c) for c in data)
    p1 = d1 ^ d2 ^ d4  # checks positions 1, 3, 5, 7 (bit value 1 set in the position number)
    p2 = d1 ^ d3 ^ d4  # checks positions 2, 3, 6, 7 (bit value 2 set)
    p4 = d2 ^ d3 ^ d4  # checks positions 4, 5, 6, 7 (bit value 4 set)
    return "".join(map(str, (p1, p2, d1, p4, d2, d3, d4)))


def syndrome(word):
    """The position (1 to 7) of a single flipped bit, or 0 when every check passes."""
    s = 0
    for position in range(1, 8):
        if word[position - 1] == "1":
            s ^= position  # the XOR of the positions of all 1s
    return s


def decode(word):
    """Correct up to one flipped bit, then return the 4 data bits."""
    s = syndrome(word)
    if s:
        bits = list(word)
        bits[s - 1] = "1" if bits[s - 1] == "0" else "0"
        word = "".join(bits)
    return word[2] + word[4] + word[5] + word[6]


def flip(word, *positions):
    bits = list(word)
    for p in positions:
        bits[p - 1] = "1" if bits[p - 1] == "0" else "0"
    return "".join(bits)


sent = encode("1011")
print("data 1011 is sent as", sent, "with syndrome", syndrome(sent))
for p in range(1, 8):
    received = flip(sent, p)
    print(f"bit {p} flipped: {received}  syndrome {syndrome(received)} ({syndrome(received):03b})  decoded {decode(received)}")

two = flip(sent, 2, 6)
print(f"\nbits 2 and 6 flipped: {two}  syndrome {syndrome(two)}  decoded {decode(two)}  (wrong: two errors look like one)")

Output

data 1011 is sent as 0110011 with syndrome 0
bit 1 flipped: 1110011  syndrome 1 (001)  decoded 1011
bit 2 flipped: 0010011  syndrome 2 (010)  decoded 1011
bit 3 flipped: 0100011  syndrome 3 (011)  decoded 1011
bit 4 flipped: 0111011  syndrome 4 (100)  decoded 1011
bit 5 flipped: 0110111  syndrome 5 (101)  decoded 1011
bit 6 flipped: 0110001  syndrome 6 (110)  decoded 1011
bit 7 flipped: 0110010  syndrome 7 (111)  decoded 1011

bits 2 and 6 flipped: 0010001  syndrome 4  decoded 1001  (wrong: two errors look like one)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hamming74.py

Every single flip produced its own position as the syndrome, and every one was corrected back to the data 1011. The last line is the warning from the previous section: flipping bits 2 and 6 gives the syndrome 2 XOR 6 = 4, so the decoder flips a third bit and returns 1001, wrong and without complaint.

The construction grows: r check bits can point at any of 2r−12^r - 1 positions, so they protect up to 2r−r−12^r - r - 1 data bits, which gives Hamming(15,11) and Hamming(31,26). Adding one more parity bit over the whole word raises the minimum distance to 4, which lets a receiver correct any single error and still detect any double one.

The Internet checksum

IPv4, UDP and TCP do not use parity: they use the Internet checksum of RFC 1071, which treats the data as 16-bit numbers and adds them up in ones’ complement arithmetic:

  1. Pair the bytes into 16-bit words, most significant byte first; an odd number of bytes gets one zero byte at the end.
  2. Add the words. Whenever a sum overflows 16 bits, add the carry back in at the bottom (the end-around carry).
  3. The checksum is the complement of the sum: every bit flipped.

For the IPv4 header, the checksum field is set to zero while the sender computes it (RFC 791). The receiver adds up every word of the header including the checksum, and a header that arrived intact sums to 0xFFFF.

The checksum of an IPv4 header, computed, checked and updated Python · internet_checksum.py
"""The 16-bit Internet checksum (RFC 1071) of an IPv4 header, checked, then updated after a router lowers the TTL."""

# A 20-byte IPv4 header with the checksum field (bytes 10 and 11) set to zero. Addresses from the
# documentation ranges of RFC 5737: 192.0.2.10 to 198.51.100.20, TTL 64, protocol 6 (TCP).
header = bytearray.fromhex("4500 003c 1c46 4000 4006 0000 c000 020a c633 6414")


def add(*words):
    """Ones' complement addition: a carry out of the top bit is added back at the bottom (end-around carry)."""
    total = 0
    for w in words:
        total += w
        total = (total & 0xFFFF) + (total >> 16)
    return total


def words_of(data):
    """The data as 16-bit words; an odd length is padded with one zero byte."""
    if len(data) % 2:
        data = bytes(data) + b"\x00"
    return [data[i] << 8 | data[i + 1] for i in range(0, len(data), 2)]


def internet_checksum(data):
    """The complement of the ones' complement sum of the data's 16-bit words."""
    return ~add(*words_of(data)) & 0xFFFF


print("words:   ", " ".join(f"{w:04x}" for w in words_of(header)))
print(f"sum:      {add(*words_of(header)):04x}")
checksum = internet_checksum(header)
print(f"checksum: {checksum:04x}")
header[10:12] = checksum.to_bytes(2, "big")
print(f"receiver: the sum of all ten words, checksum included, is {add(*words_of(header)):04x}")

# A router forwards the packet: TTL 64 becomes 63, so the word 4006 becomes 3f06.
old_word = header[8] << 8 | header[9]
header[8] -= 1
new_word = header[8] << 8 | header[9]
hc = header[10] << 8 | header[11]
incremental = ~add(~hc & 0xFFFF, ~old_word & 0xFFFF, new_word) & 0xFFFF  # RFC 1624, equation 3
header[10:12] = b"\x00\x00"
print()
print(f"after the TTL change: incremental {incremental:04x}, recomputed {internet_checksum(header):04x}")

# RFC 1624's own example: the other fields sum to CD7A and one field changes from 5555 to 3285.
c, m, m_new = 0xCD7A, 0x5555, 0x3285
hc = ~add(c, m) & 0xFFFF
print()
print(f"old checksum {hc:04x}, recomputed after the change {~add(c, m_new) & 0xFFFF:04x}")
print(f"RFC 1141 shortcut, HC + m + ~m':   {add(hc, m, ~m_new & 0xFFFF):04x}")
print(f"RFC 1624 equation 3, ~(~HC + ~m + m'): {~add(~hc & 0xFFFF, ~m & 0xFFFF, m_new) & 0xFFFF:04x}")

Output

words:    4500 003c 1c46 4000 4006 0000 c000 020a c633 6414
sum:      cddb
checksum: 3224
receiver: the sum of all ten words, checksum included, is ffff

after the TTL change: incremental 3324, recomputed 3324

old checksum dd2f, recomputed after the change 0000
RFC 1141 shortcut, HC + m + ~m':   ffff
RFC 1624 equation 3, ~(~HC + ~m + m'): 0000

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 internet_checksum.py

The header’s ten words sum to 0xCDDB, so the checksum is 0x3224, and the receiver’s sum over all ten words comes out at 0xFFFF. That is no coincidence: adding any number to its own complement sets every bit, and in ones’ complement arithmetic 0xFFFF is a second way of writing zero.

Updating the checksum after a change

The IPv4 checksum covers only the header, and RFC 791 has every router recompute it, because the router changes a field: it lowers the time to live (TTL) by one. Rather than sum the whole header again, a router can update the checksum from the change alone. RFC 1624 gives the formula, with HC the old checksum, m the old 16-bit word and m′ the new one, and ~ the complement:

HC′=∼(∼HC+∼m+m′)HC' = \sim\!\left(\sim HC + \sim m + m'\right)

In the run, the TTL went from 64 to 63, so the word 0x4006 became 0x3F06, and the incremental update and the full recomputation both gave 0x3324. The last three lines replay RFC 1624’s own example of why the formula is written this way. The older shortcut of RFC 1141, HC + m + ~m′, gives 0xFFFF where the correct checksum is 0x0000. Both mean zero to a receiver that sums the whole header and compares the total with 0xFFFF, as RFC 1071 recommends, but an implementation that recomputes the checksum and compares it with the field rejects a good packet.

IPv6 dropped the header checksum altogether. RFC 8200 makes the UDP checksum compulsory instead (in IPv4 it is optional) and notes that the fields of the IPv6 header are “not covered by an internet-layer checksum”: the link’s CRC protects each hop, and the transport checksum, which also covers the addresses, protects the whole path. Routers no longer have a checksum to update at all.

What the checksum cannot see

The Internet checksum is cheap: one addition per 16-bit word, in any order. RFC 1071 lists that order independence as a feature, because it lets implementations add words in parallel or in whichever byte order the computer uses. It is also the checksum’s weakness:

Every single-bit and two-bit error of one IPv4 header Python · checksum_blind_spots.py
"""What the Internet checksum cannot see: words in another order, and some pairs of flipped bits."""
from itertools import combinations

# The header of internet_checksum.py, with its checksum 3224 filled in.
header = bytes.fromhex("4500 003c 1c46 4000 4006 3224 c000 020a c633 6414")


def add(*words):
    total = 0
    for w in words:
        total += w
        total = (total & 0xFFFF) + (total >> 16)
    return total


def passes(data):
    """The receiver's test: the ones' complement sum of every word, checksum included, is FFFF."""
    return add(*(data[i] << 8 | data[i + 1] for i in range(0, len(data), 2))) == 0xFFFF


def flip(data, *positions):
    """A copy of the data with the given bits flipped (bit 0 is the first bit sent)."""
    out = bytearray(data)
    for p in positions:
        out[p // 8] ^= 0x80 >> (p % 8)
    return bytes(out)


print("as sent:", passes(header))
swapped = header[:12] + header[16:20] + header[12:16]  # source and destination addresses trade places
print("addresses swapped:", passes(swapped))

bits = len(header) * 8
singles = [p for p in range(bits) if passes(flip(header, p))]
print(f"\n{bits} single-bit errors, undetected: {len(singles)}")

pairs = list(combinations(range(bits), 2))
missed = [(a, b) for a, b in pairs if passes(flip(header, a, b))]
print(f"{len(pairs):,} two-bit errors, undetected: {len(missed):,} ({len(missed) / len(pairs):.1%})")
same_column = all(a % 16 == b % 16 for a, b in missed)
opposite = all((header[a // 8] >> (7 - a % 8) & 1) != (header[b // 8] >> (7 - b % 8) & 1) for a, b in missed)
print("all of them hit the same bit position in two different words:", same_column)
print("all of them turn one 0 into 1 and one 1 into 0:", opposite)
a, b = missed[0]
print(f"the first: position {a % 16} (counting from the left, from 0) of word {a // 16} and of word {b // 16}")

Output

as sent: True
addresses swapped: True

160 single-bit errors, undetected: 0
12,720 two-bit errors, undetected: 260 (2.0%)
all of them hit the same bit position in two different words: True
all of them turn one 0 into 1 and one 1 into 0: True
the first: position 0 (counting from the left, from 0) of word 0 and of word 6

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 checksum_blind_spots.py

Swapping the source and destination addresses changes the header completely and leaves the sum untouched. Every one of the 160 single-bit errors was detected, but 260 of the 12,720 two-bit errors, 2.0 %, were not, and all of them had the same shape: the same bit position in two different words, one changing from 0 to 1 and the other from 1 to 0. One word grew by 2k2^k while the other shrank by 2k2^k, so the sum stayed the same.

A CRC has no such blind spots for errors this small. The 32-bit CRC of Ethernet keeps a minimum distance of at least 4 on frames of up to 12,144 bits, according to the measurements that RFC 3385 summarises, so it detects every one-, two- and three-bit error in any frame up to 1,518 bytes, the classic Ethernet maximum. That is why links check frames with CRCs, while the checksum stays in IP, UDP and TCP, where it is quick to compute and to update and where a CRC on the link has already caught most damage. The next lesson builds CRCs from scratch.

Error detection in the GATE CS syllabus

The GATE 2027 syllabus for Computer Science lists “Data link layer: error detection” in Section 10. For numerical answers, two results of this lesson carry most of the weight: a code with minimum distance d detects d − 1 errors and corrects ⌊(d − 1)/2⌋, and r Hamming check bits protect up to 2r−r−12^r - r - 1 data bits, so m data bits need the smallest r with 2r≥m+r+12^r \ge m + r + 1.

Interview questions

What does a code’s minimum Hamming distance tell you? It is the smallest number of bits in which any two codewords differ, and it fixes what the code can do: with minimum distance d, every pattern of up to d − 1 flipped bits is detected, because it cannot turn one codeword into another, and up to ⌊(d − 1)/2⌋ flipped bits can be corrected by choosing the nearest codeword. A single parity bit has d = 2 (detects one error, corrects none), and Hamming(7,4) has d = 3 (corrects one, or detects two).

Why does a receiver add up an IPv4 header, checksum included, and expect 0xFFFF? The sender stored the complement of the sum of all the other words in the checksum field. Adding a number and its complement sets every bit, so an intact header sums to 0xFFFF, which is zero in ones’ complement arithmetic. Any other total means that the header changed on the way.

Key takeaways

  • A parity bit detects every odd number of flipped bits and misses every even number.
  • Two-dimensional parity locates and corrects a single flipped bit; in the 5 × 5 block every 1-, 2- and 3-bit error was detected and 100 of 12,650 four-bit errors were missed.
  • A code with minimum distance d detects up to d − 1 errors and corrects up to ⌊(d − 1)/2⌋, but not both at full strength at once.
  • Hamming(7,4) puts check bits at positions 1, 2 and 4; the syndrome is the position of a single flipped bit, and two flipped bits are “corrected” into the wrong data.
  • The Internet checksum is the complement of the ones’ complement sum of 16-bit words; an intact header sums to 0xFFFF, and routers update it with RFC 1624’s formula when the TTL changes.
  • It misses reordered words and errors that cancel out, 2.0 % of all two-bit errors in the example header; links use CRCs, and IPv6 dropped its header checksum.

Exercise

Exercise · Medium · Python

An Internet checksum and a Hamming decoder

Write the two functions of codes.py:

  • internet_checksum(data) takes bytes and returns the 16-bit Internet checksum as an int: add the data as 16-bit big-endian words in ones' complement arithmetic (a carry out of the top bit is added back at the bottom), then return the complement of the sum. An odd number of bytes is padded with one zero byte. For the lesson's IPv4 header, internet_checksum(bytes.fromhex("4500003c1c46400040060000c000020ac6336414")) returns 0x3224.
  • hamming74_decode(word) takes a Hamming(7,4) codeword as a string of seven "0"/"1" characters, positions 1 to 7 from the left (p1 p2 d1 p4 d2 d3 d4, even parity), corrects at most one flipped bit and returns the four data bits d1 d2 d3 d4 as a string: hamming74_decode("0110011") and hamming74_decode("0100011") both return "1011".

The sample tests import both functions from codes.py.

Starter code · codes.py

def internet_checksum(data):
    """Return the 16-bit Internet checksum (RFC 1071) of data, as an int."""
    # Replace this line with your code.
    return 0


def hamming74_decode(word):
    """Correct at most one flipped bit of a Hamming(7,4) codeword such as "0110011" and return its 4 data bits."""
    # Replace this line with your code.
    return word[:4]
The sample tests · test_codes.py
from itertools import product

from codes import hamming74_decode, internet_checksum

HEADER = bytes.fromhex("4500003c1c46400040060000c000020ac6336414")


def encode(d1, d2, d3, d4):
    """Hamming(7,4) with even parity: positions 1 to 7 are p1 p2 d1 p4 d2 d3 d4."""
    return "".join(str(b) for b in (d1 ^ d2 ^ d4, d1 ^ d3 ^ d4, d1, d2 ^ d3 ^ d4, d2, d3, d4))


def test_checksum_of_the_lesson_header():
    """gives 0x3224 for the IPv4 header of the lesson"""
    assert internet_checksum(HEADER) == 0x3224


def test_header_with_its_checksum():
    """gives 0 when the data already holds its own checksum"""
    assert internet_checksum(HEADER[:10] + bytes.fromhex("3224") + HEADER[12:]) == 0


def test_end_around_carry():
    """adds a carry out of the top bit back at the bottom"""
    assert internet_checksum(bytes.fromhex("ffff0001")) == 0xFFFE
    assert internet_checksum(bytes.fromhex("8000800080008000")) == 0xFFFD


def test_odd_length_is_padded():
    """pads an odd number of bytes with one zero byte"""
    assert internet_checksum(b"\x01") == 0xFEFF
    assert internet_checksum(b"\x01\x02\x03") == 0xFBFD
    assert internet_checksum(b"") == 0xFFFF


def test_decode_without_errors():
    """returns the data bits of a codeword that arrived intact"""
    assert hamming74_decode("0110011") == "1011"
    assert hamming74_decode("0000000") == "0000"
    assert hamming74_decode("1111111") == "1111"


def test_decode_corrects_any_single_flip():
    """corrects a flip in any of the 7 positions of all 16 codewords"""
    for data in product((0, 1), repeat=4):
        word = encode(*data)
        expected = "".join(map(str, data))
        assert hamming74_decode(word) == expected, word
        for p in range(7):
            damaged = word[:p] + ("1" if word[p] == "0" else "0") + word[p + 1 :]
            assert hamming74_decode(damaged) == expected, damaged
A hint

For the checksum, pad first (if len(data) % 2: data += b"\x00"), then add data[i] << 8 | data[i + 1] for every even i, folding the carry back after each addition with total = (total & 0xFFFF) + (total >> 16); the answer is ~total & 0xFFFF. For the decoder, XOR together the positions (1 to 7) of every "1" in the word: the result is 0 for a correct codeword and the position of the flipped bit otherwise.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 A code has a minimum Hamming distance of 5. How many flipped bits can its receiver always correct?

    Type a number.

    Show the answer to question 1

    Answer: 2 bits

    A code corrects up to ⌊(d − 1)/2⌋ errors: ⌊4/2⌋ = 2. With two flips the received word is still at distance 2 from the codeword sent and at least 3 from every other codeword, so the nearest codeword is the right one. The same code could instead detect up to d − 1 = 4 flipped bits.

  2. Question 2 of 6 A 7-bit character travels with one even-parity bit. Which damage does the receiver miss?

    Choose one answer.

    Show the answer to question 2

    Answer: Any even number of flipped bits, such as two

    Each flip changes the count of 1s by one. An odd number of flips makes the count odd, which the check catches; an even number brings it back to even, which the check accepts. The two-flip line of parity.py shows it.

  3. Question 3 of 6 A Hamming(7,4) receiver gets the word 0111011 (positions 1 to 7 from the left). What is the syndrome, the position of the flipped bit?

    Type a number.

    Show the answer to question 3

    Answer: 4

    The 1s are at positions 2, 3, 4, 6 and 7, and 2 XOR 3 XOR 4 XOR 6 XOR 7 = 4. Equivalently, only the check bit p4 fails. Flipping position 4 back gives 0110011, the codeword for the data 1011, as in hamming74.py.

  4. Question 4 of 6 Two-dimensional parity protects a block of bits with row and column parities. Which of these errors does it always detect?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • Any three flipped bits
    • Any two flipped bits
    • Any single flipped bit

    Its minimum distance is 4, so every pattern of up to three flips is detected (parity.py checked all 2,625 of them in a 5 × 5 block). Four flips at the corners of a rectangle keep every row and column even: 100 of the 12,650 four-bit patterns got through.

  5. Question 5 of 6 Which change to an IPv4 header does the Internet checksum never detect?

    Choose one answer.

    Show the answer to question 5

    Answer: Two of its 16-bit words trading places

    Addition does not care about order, so moving words around leaves the sum, and so the checksum, the same: in checksum_blind_spots.py the header with its source and destination addresses swapped still passed. Every single-bit error and every change of one word's value is caught.

  6. Question 6 of 6 Why must a router recompute or update the IPv4 header checksum of every packet it forwards?

    Choose one answer.

    Show the answer to question 6

    Answer: It lowers the TTL, and the checksum covers the header that holds it

    RFC 791 has every router recompute and verify the header checksum because header fields such as the TTL change on the way. RFC 1624's formula updates it from the old and new value of the changed word; IPv6 has no header checksum, so its routers skip this work.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.