Skip to content

Step 1 of 5 · Reading · ~3 min

Read

Decoding

Four Characters Become Three Bytes

Lesson 3, "Three Bytes Become Four Characters", run backwards: four characters in, three bytes out, with the = count deciding how many of those bytes are real.

How it works

Four base64 characters carry four 6-bit values -- lesson 2's alphabet, "The 64 Characters", read the other way round, character to index. Four sixes are 24 bits, and 24 bits is also three 8-bit bytes. That is the whole lesson: the same 24 bits, recut into three groups of eight instead of four of six.

The shifts and masks are lesson 3's, now on 8-bit fields -- 255 is the mask that keeps eight bits, where 63 kept six.

Packing is lesson 3's cut run backwards: v0 goes 18 places up, each later value shifts 6 fewer than the one before it, the last not at all, and | merges the four into one 24-bit number. Cutting walks back down that number in steps of eight:

b0 = (n >> 16) & 255

Each later byte shifts 8 fewer, so the third needs no shift at all -- only its mask.

Trace SGk=. Count the = first, then pack it as the value 0:

character    S        G        k        =
value        18       6        36       0   (pad, packed as 0)
bits         010010   000110   100100   000000

24 bits      010010000110100100000000
regrouped    01001000 01101001 00000000
bytes        48       69       00

Nothing moved between the 24 bits row and the regrouped row: the same twenty-four digits, cut after every eighth instead of after every sixth.

The = count then says how many of the three bytes to keep -- no = keeps 3, one keeps 2, two keep 1. One = here, so keep two and print 4869. The 00 behind them is the encoder's zero fill from lesson 4, "When the Last Group Is Short": dropped, never rounded up into a byte.

The algorithm

for each line of input:
    hex = empty string
    for i = 0, 4, 8, ... while i + 4 <= line length:
        for each byte in decode_group(the four characters at i):
            append it as two lowercase hex digits to hex
    print hex and a newline

decode_group(group):                        <- your part
    pad = how many '=' the group contains
    v0..v3 = each character's 6-bit value, using 0 for '='
    n = the four values packed into one 24-bit number
    cut three bytes out of n, most significant first
    return the first (3 - pad) of them

What the grader runs

Input arrives on stdin, the program's standard input: zero or more lines, each a well-formed base64 string -- length a non-zero multiple of 4, characters from A-Z a-z 0-9 + /, at most two = and only at the end. Bad input is lesson 6's job, "A Strict Decoder Refuses Bad Input".

For each input line print one line: the decoded bytes as lowercase hex, two digits per byte, no separators. Empty stdin prints nothing at all.

input:
SGVsbG8=
SGk=
SA==
////

output:
48656c6c6f
4869
48
ffffff
input:
AAAA
AA==

output:
000000
00

Print exactly this, one result per line, newline-terminated, nothing else.

Notes

  • A decoded zero byte is still a byte, printed like any other: AAA= is 0000 and AAE= is 0001 -- never 01 or 1, and hex letters are lowercase.
  • All-ones data is checked at both pad counts: //8= is ffff, /w== is ff. Three = never appears.
  • Only a line's last group can be padded, so trim by each group's own = count, not a fixed number. Lines run to 600 characters.
  • Primary source: RFC 4648, https://www.rfc-editor.org/rfc/rfc4648

Your exercise

Implement decode_group: one 4-character group in, the byte values it carries out. The starter holds the lesson 2 alphabet and lookup, walks each line four characters at a time and prints the hex. Most common mistake: keeping the trailing zero byte of a padded group, so SA== gives 4800 instead of 48.

Up nextA Strict Decoder Refuses Bad InputDecoding

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…