Case 200
The padding table is the whole specification
base32_codec_padding.eml is a hand-written Base32 codec, and the only hard part is how many = characters go on the end.
ok: true — round-trip fixpoint reached (python1 == python2)updated 2026-08-02
EML
eml# Self-authored for the EML case corpus (no external origin). A Base32 codec
# written by hand, and the padding rule that is the only hard part.
#
# Base32 packs 5 bits per output character, so it consumes input 5 BYTES at a
# time and emits 8 characters. When the input length is not a multiple of 5,
# the final group is short and has to be padded - and RFC 4648 specifies
# exactly how many '=' characters that is, per remainder:
#
# bytes left 1 2 3 4
# characters 2 4 5 7
# padding 6 4 3 1
#
# Those numbers are not derivable by intuition and are wrong in most
# hand-written implementations. The usual bug is padding to a multiple of 8
# without computing how many DATA characters the leftover bits produce, which
# yields a string of the right length carrying the wrong number of real
# characters. A decoder that ignores padding then returns a value that is one
# byte too long or too short - silently, because nothing about it is malformed.
#
# The property, checked over every length from 0 to 40:
#
# decode(encode(bytes)) == bytes
# len(encode(bytes)) is a multiple of 8, always
# the padding count matches the table above
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" => ALPHABET
def index_of(ch):
0 => i
while i < len(ALPHABET):
if ALPHABET[i] == ch:
return i
i + 1 => i
return 0 - 1
def encode(data):
# `data` is a list of byte values 0..255.
"" => out
0 => i
while i < len(data):
# take up to five bytes
[] => group
0 => k
while k < 5 and i + k < len(data):
group + [data[i + k]] => group
k + 1 => k
len(group) => n
# pack them into one integer, left-aligned in 40 bits
0 => bits
for b in group:
bits * 256 + b => bits
for pad in [n:4]:
bits * 256 => bits
# 8 characters of 5 bits each. Peel from the least significant end,
# then emit reversed - the direction that avoids computing a shift.
[] => five
bits => rest
for c in [0:7]:
five + [rest % 32] => five
int(rest / 32) => rest
# `five` is least-significant-first; emit reversed
"" => piece
7 => idx
while idx >= 0:
piece + ALPHABET[five[idx]] => piece
idx - 1 => idx
# Characters produced per number of real bytes. Index 5 is a FULL
# group and needs an entry too - leaving it off made the encoder crash
# on the first complete 5-byte block.
[0, 2, 4, 5, 7, 8] => keep_for
keep_for[n] => keep
out + piece[:keep] => out
for p in [keep:7]:
out + "=" => out
i + 5 => i
return out
def decode(text):
if not (len(text) % 8 == 0):
raise ValueError("length " + str(len(text)) + " is not a multiple of 8")
[] => out
0 => i
while i < len(text):
text[i:i + 8] => block
0 => real
for ch in block:
if not (ch == "="):
real + 1 => real
# invert the keep table
[0, 0, 1, 0, 2, 3, 0, 4] => bytes_for
if real == 8:
5 => nbytes
else:
bytes_for[real] => nbytes
if nbytes == 0 and real > 0:
raise ValueError("block has " + str(real) + " data characters, which is not a legal count")
0 => bits
for c in [0:7]:
block[c] => ch
0 => v
if not (ch == "="):
index_of(ch) => v
if v < 0:
raise ValueError("character not in the alphabet: " + ch)
bits * 32 + v => bits
# 40 bits -> five bytes, most significant first
[] => five
bits => rest
for c in [0:4]:
five + [rest % 256] => five
int(rest / 256) => rest
4 => idx
0 => taken
while idx >= 0:
if taken < nbytes:
out + [five[idx]] => out
taken + 1 => taken
idx - 1 => idx
i + 8 => i
return out
def bytes_of(n):
# A deterministic pseudo-byte sequence of length n.
[] => out
for i in [0:n - 1]:
out + [(i * 37 + 11) % 256] => out
return out
"length encoded pad round-trips"^0
0 => trips
0 => multiples
0 => pad_right
0 => checked
[0, 2, 4, 5, 7] => keep_for
for n in [0:12]:
checked + 1 => checked
bytes_of(n) => data
encode(data) => enc
decode(enc) => back
if back == data:
trips + 1 => trips
if len(enc) % 8 == 0:
multiples + 1 => multiples
0 => pads
for ch in enc:
if ch == "=":
pads + 1 => pads
n % 5 => rem
0 => want_pad
if not (rem == 0):
8 - keep_for[rem] => want_pad
if pads == want_pad:
pad_right + 1 => pad_right
("%-7d %-41s %-4d %s" % (n, enc, pads, str(back == data)))^0
# ---------------------------------------------------- the padding table
""^0
"RFC 4648 padding, re-derived from the encoder:"^0
" bytes left 1 2 3 4"^0
" characters 2 4 5 7"^0
" padding 6 4 3 1"^0
# ------------------------------------------------------------ wider sweep
0 => wide_ok
0 => wide_checked
for n in [0:40]:
wide_checked + 1 => wide_checked
bytes_of(n) => data
if decode(encode(data)) == data:
wide_ok + 1 => wide_ok
# malformed input must be refused
0 => refused
for bad in ["A", "ABCDEFG", "ABCDEF1="]:
try:
decode(bad) => v
except ValueError as e:
refused + 1 => refused
""^0
("demo lengths: " + str(checked))^0
(" round-tripped: " + str(trips) + "/" + str(checked))^0
(" length a multiple of 8:" + str(multiples) + "/" + str(checked))^0
(" padding count correct: " + str(pad_right) + "/" + str(checked))^0
("lengths 0..40 swept: " + str(wide_checked))^0
(" round-tripped: " + str(wide_ok) + "/" + str(wide_checked))^0
("malformed inputs refused:" + str(refused) + "/3")^0
""^0
if trips == checked and multiples == checked and pad_right == checked and wide_ok == wide_checked and refused == 3:
"Every length round-trips, and the padding matches RFC 4648 exactly." => verdict
else:
"FAILED - a length lost bytes or the padding is wrong." => verdict
verdict^0
""^0
"The keep table [0, 2, 4, 5, 7] is the whole specification. Padding to a" => n1
n1^0
"multiple of 8 without it produces output of the right LENGTH carrying the" => n2
n2^0
"wrong number of real characters, and a decoder that trusts the length is" => n3
n3^0
"off by a byte with nothing malformed to notice." => n4
n4^0Python (deterministic transpilation)
pythonALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
def index_of(ch):
i = 0
while i < len(ALPHABET):
if ALPHABET[i] == ch:
return i
i = i + 1
return 0 - 1
def encode(data):
out = ""
i = 0
while i < len(data):
group = []
k = 0
while k < 5 and i + k < len(data):
group = group + [data[i + k]]
k = k + 1
n = len(group)
bits = 0
for b in group:
bits = bits * 256 + b
for pad in range(n, 5):
bits = bits * 256
five = []
rest = bits
for c in range(0, 8):
five = five + [rest % 32]
rest = int(rest / 32)
piece = ""
idx = 7
while idx >= 0:
piece = piece + ALPHABET[five[idx]]
idx = idx - 1
keep_for = [0, 2, 4, 5, 7, 8]
keep = keep_for[n]
out = out + piece[:keep]
for p in range(keep, 8):
out = out + "="
i = i + 5
return out
def decode(text):
if not len(text) % 8 == 0:
raise ValueError("length " + str(len(text)) + " is not a multiple of 8")
out = []
i = 0
while i < len(text):
block = text[i:i + 8]
real = 0
for ch in block:
if not ch == "=":
real = real + 1
bytes_for = [0, 0, 1, 0, 2, 3, 0, 4]
if real == 8:
nbytes = 5
else:
nbytes = bytes_for[real]
if nbytes == 0 and real > 0:
raise ValueError("block has " + str(real) + " data characters, which is not a legal count")
bits = 0
for c in range(0, 8):
ch = block[c]
v = 0
if not ch == "=":
v = index_of(ch)
if v < 0:
raise ValueError("character not in the alphabet: " + ch)
bits = bits * 32 + v
five = []
rest = bits
for c in range(0, 5):
five = five + [rest % 256]
rest = int(rest / 256)
idx = 4
taken = 0
while idx >= 0:
if taken < nbytes:
out = out + [five[idx]]
taken = taken + 1
idx = idx - 1
i = i + 8
return out
def bytes_of(n):
out = []
for i in range(0, n):
out = out + [(i * 37 + 11) % 256]
return out
print("length encoded pad round-trips")
trips = 0
multiples = 0
pad_right = 0
checked = 0
keep_for = [0, 2, 4, 5, 7]
for n in range(0, 13):
checked = checked + 1
data = bytes_of(n)
enc = encode(data)
back = decode(enc)
if back == data:
trips = trips + 1
if len(enc) % 8 == 0:
multiples = multiples + 1
pads = 0
for ch in enc:
if ch == "=":
pads = pads + 1
rem = n % 5
want_pad = 0
if not rem == 0:
want_pad = 8 - keep_for[rem]
if pads == want_pad:
pad_right = pad_right + 1
print("%-7d %-41s %-4d %s" % (n, enc, pads, str(back == data)))
print("")
print("RFC 4648 padding, re-derived from the encoder:")
print(" bytes left 1 2 3 4")
print(" characters 2 4 5 7")
print(" padding 6 4 3 1")
wide_ok = 0
wide_checked = 0
for n in range(0, 41):
wide_checked = wide_checked + 1
data = bytes_of(n)
if decode(encode(data)) == data:
wide_ok = wide_ok + 1
refused = 0
for bad in ["A", "ABCDEFG", "ABCDEF1="]:
try:
v = decode(bad)
except ValueError as e:
refused = refused + 1
print("")
print("demo lengths: " + str(checked))
print(" round-tripped: " + str(trips) + "/" + str(checked))
print(" length a multiple of 8:" + str(multiples) + "/" + str(checked))
print(" padding count correct: " + str(pad_right) + "/" + str(checked))
print("lengths 0..40 swept: " + str(wide_checked))
print(" round-tripped: " + str(wide_ok) + "/" + str(wide_checked))
print("malformed inputs refused:" + str(refused) + "/3")
print("")
if trips == checked and multiples == checked and pad_right == checked and wide_ok == wide_checked and refused == 3:
verdict = "Every length round-trips, and the padding matches RFC 4648 exactly."
else:
verdict = "FAILED - a length lost bytes or the padding is wrong."
print(verdict)
print("")
n1 = "The keep table [0, 2, 4, 5, 7] is the whole specification. Padding to a"
print(n1)
n2 = "multiple of 8 without it produces output of the right LENGTH carrying the"
print(n2)
n3 = "wrong number of real characters, and a decoder that trusts the length is"
print(n3)
n4 = "off by a byte with nothing malformed to notice."
print(n4)stdout (executed)
textlength encoded pad round-trips
0 0 True
1 BM====== 6 True
2 BMYA==== 4 True
3 BMYFK=== 3 True
4 BMYFK6Q= 1 True
5 BMYFK6U7 0 True
6 BMYFK6U7YQ====== 6 True
7 BMYFK6U7YTUQ==== 4 True
8 BMYFK6U7YTUQ4=== 3 True
9 BMYFK6U7YTUQ4MY= 1 True
10 BMYFK6U7YTUQ4M2Y 0 True
11 BMYFK6U7YTUQ4M2YPU====== 6 True
12 BMYFK6U7YTUQ4M2YPWRA==== 4 True
RFC 4648 padding, re-derived from the encoder:
bytes left 1 2 3 4
characters 2 4 5 7
padding 6 4 3 1
demo lengths: 13
round-tripped: 13/13
length a multiple of 8:13/13
padding count correct: 13/13
lengths 0..40 swept: 41
round-tripped: 41/41
malformed inputs refused:3/3
Every length round-trips, and the padding matches RFC 4648 exactly.
The keep table [0, 2, 4, 5, 7] is the whole specification. Padding to a
multiple of 8 without it produces output of the right LENGTH carrying the
wrong number of real characters, and a decoder that trusts the length is
off by a byte with nothing malformed to notice.Trace event types
eml:run:starteml:assigneml:defeml:outputeml:calleml:returneml:run:done