{
  "id": "P088",
  "slug": "codec-workbench",
  "key": "P088-codec-workbench",
  "title": "Codec workbench",
  "summary": "Text as bytes, and bytes as hex, Base64 and Base32 (RFC 4648) both ways, with padding and the bits of one Base64 group shown; decoding is strict, accepting only canonical encodings and saying why anything else is refused.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P088 - Codec workbench\n\nText as bytes, and bytes as hex, Base64 and Base32 (RFC 4648), both ways.\nEncoding shows the padding and the bits of the first Base64 group.\nDecoding is strict: it accepts only canonical encodings, and says why it\nrefuses anything else.\n\n- `main.eml` - the menu, reading text and encodings, and the reports\n- `codec.eml` - characters and their codes, hex, Base64 and Base32 both\n  ways, and binary digits\n\nHow each part works:\n\n- Characters: the printable ASCII characters except the double quote and\n  the backslash (which would need escaping in the program's own text) map\n  to their codes, 32 to 126. Bytes that are not among them are shown as\n  hex only.\n- Bits without bit operations: a group of bytes becomes one whole number\n  (n x 256 + byte, again and again), and its 6- or 5-bit pieces come out\n  by dividing by powers of two. The numbers stay below 2^40.\n- Base64: three bytes are 24 bits, which make four 6-bit characters. A\n  last group of one or two bytes makes two or three characters, padded\n  with = to four.\n- Base32, as in the corpus case `base32-codec-padding`: five bytes are 40\n  bits, eight 5-bit characters. A last group of 1, 2, 3 or 4 bytes makes 2,\n  4, 5 or 7 characters, with 6, 4, 3 or 1 =.\n- Strict decoding: the length must be whole groups, = may only end the last\n  group (in Base32 only as 6, 4, 3 or 1 of them), and the bits the padding\n  leaves over must be 0. \"TWF=\" would read as \"Ma\" with two stray bits, so\n  it is refused. Every byte string then has exactly one accepted encoding.\n\nWhat is checked: menu choices 1 to 5; text of up to 60 characters from the\nset above; hex with an even number of digits, in either case. An empty\nanswer cancels.\n\nSessions: `sessions/basic.in` gives:\n- \"Man\" (TWFu, no padding, its bits shown), \"Hello, EML!\" and \"Ma\" (TWE=,\n  JVQQ====);\n- \"Hello, EML!\" decoded back from Base64 and from Base32, \"Hello\" from\n  upper-case hex, and AP8=, two bytes that are not printable.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- text with a double quote, with a backslash, and of 61 characters;\n- hex \"abc\" and \"zz\";\n- Base64 \"TWE\", \"TW=E\", \"TWF=\" (stray bits) and \"TW!=\";\n- Base32 \"JBSWY3D=\" (stray bits), \"JBSWY3==\" (two = cannot end a group)\n  and lower-case \"jbswy3dp\".\n\nBuilt on the verified corpus case `base32-codec-padding` (the padding\ntable, and the round trip at every length).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P088 codec workbench: text to hex, Base64 and Base32, the bits behind\n# one Base64 group, and decoding each of them back - strictly, with the\n# reason when a string is not a valid encoding.\nimport codec\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef show_bytes(bs):\n    codec.text_of(bs) => t\n    (\"bytes: \" + str(len(bs)) + \" (hex \" + codec.hex_encode(bs) + \")\") ^0\n    if len(bs) == 0:\n        \"text: (empty)\" ^0\n    elif t == \"\":\n        \"text: not all of them are printable characters this program shows\" ^0\n    else:\n        (\"text:  \" + t) ^0\n\ndef picture(bs):\n    # The first group of three bytes as bits, cut into 6-bit pieces.\n    0 => n\n    \"\" => b8\n    for k in [0:2]:\n        0 => b\n        if k < len(bs):\n            bs[k] => b\n        n * 256 + b => n\n        if k < len(bs):\n            if b8 != \"\":\n                b8 + \" \" => b8\n            b8 + codec.bits(b, 8) => b8\n    (\"  bytes \" + b8) ^0\n    \"\" => b6\n    \"\" => cs\n    [262144, 4096, 64, 1] => p\n    len(bs) => left\n    if left > 3:\n        3 => left\n    for k in [0:left]:\n        codec.quotient(n, p[k]) % 64 => v\n        if b6 != \"\":\n            b6 + \" \" => b6\n        b6 + codec.bits(v, 6) => b6\n        cs + \"     \" + codec.B64[v] => cs\n    (\"  6-bit \" + b6) ^0\n    (\"  as   \" + cs) ^0\n\ndef encode():\n    trim(input(\"text (up to 60 printable characters, no double quote or backslash)> \")) => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return\n    codec.bytes_of(s) => bs\n    if len(s) > 60 or (len(bs) == 1 and bs[0] == -1):\n        \"That has a character this program does not handle, or is over 60.\" ^0\n        return\n    (\"hex:    \" + codec.hex_encode(bs)) ^0\n    (\"Base64: \" + codec.b64_encode(bs)) ^0\n    (\"Base32: \" + codec.b32_encode(bs)) ^0\n    \"The first Base64 group, bit by bit:\" ^0\n    picture(bs)\n    len(bs) % 3 => r3\n    if r3 == 1:\n        \"One byte is left at the end: 8 bits make 2 characters, and == pads the group to 4.\" ^0\n    elif r3 == 2:\n        \"Two bytes are left at the end: 16 bits make 3 characters, and = pads the group to 4.\" ^0\n    else:\n        \"The bytes come in whole threes, so Base64 needs no padding.\" ^0\n\ndef decode(which):\n    while True:\n        trim(input(which + \" to decode (empty to stop)> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        if which == \"hex\":\n            codec.hex_decode(s) => r\n        elif which == \"Base64\":\n            codec.b64_decode(s) => r\n        else:\n            codec.b32_decode(s) => r\n        if len(r) == 1:\n            show_bytes(r[0])\n            return\n        (\"Not valid \" + which + \": \" + r[1] + \".\") ^0\n\n\"== Codec workbench ==\" ^0\n\"Text as bytes, and bytes as hex, Base64 and Base32 - both ways, strictly.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        encode()\n    elif choice == \"2\":\n        decode(\"hex\")\n    elif choice == \"3\":\n        decode(\"Base64\")\n    elif choice == \"4\":\n        decode(\"Base32\")\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import codec\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef show_bytes(bs):\n    t = codec.text_of(bs)\n    print(\"bytes: \" + str(len(bs)) + \" (hex \" + codec.hex_encode(bs) + \")\")\n    if len(bs) == 0:\n        print(\"text: (empty)\")\n    elif t == \"\":\n        print(\"text: not all of them are printable characters this program shows\")\n    else:\n        print(\"text:  \" + t)\n\ndef picture(bs):\n    n = 0\n    b8 = \"\"\n    for k in range(0, 3):\n        b = 0\n        if k < len(bs):\n            b = bs[k]\n        n = n * 256 + b\n        if k < len(bs):\n            if b8 != \"\":\n                b8 = b8 + \" \"\n            b8 = b8 + codec.bits(b, 8)\n    print(\"  bytes \" + b8)\n    b6 = \"\"\n    cs = \"\"\n    p = [262144, 4096, 64, 1]\n    left = len(bs)\n    if left > 3:\n        left = 3\n    for k in range(0, left+1):\n        v = codec.quotient(n, p[k]) % 64\n        if b6 != \"\":\n            b6 = b6 + \" \"\n        b6 = b6 + codec.bits(v, 6)\n        cs = cs + \"     \" + codec.B64[v]\n    print(\"  6-bit \" + b6)\n    print(\"  as   \" + cs)\n\ndef encode():\n    s = trim(input(\"text (up to 60 printable characters, no double quote or backslash)> \"))\n    if s == \"\":\n        print(\"Cancelled.\")\n        return\n    bs = codec.bytes_of(s)\n    if len(s) > 60 or len(bs) == 1 and bs[0] == -1:\n        print(\"That has a character this program does not handle, or is over 60.\")\n        return\n    print(\"hex:    \" + codec.hex_encode(bs))\n    print(\"Base64: \" + codec.b64_encode(bs))\n    print(\"Base32: \" + codec.b32_encode(bs))\n    print(\"The first Base64 group, bit by bit:\")\n    picture(bs)\n    r3 = len(bs) % 3\n    if r3 == 1:\n        print(\"One byte is left at the end: 8 bits make 2 characters, and == pads the group to 4.\")\n    elif r3 == 2:\n        print(\"Two bytes are left at the end: 16 bits make 3 characters, and = pads the group to 4.\")\n    else:\n        print(\"The bytes come in whole threes, so Base64 needs no padding.\")\n\ndef decode(which):\n    while True:\n        s = trim(input(which + \" to decode (empty to stop)> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        if which == \"hex\":\n            r = codec.hex_decode(s)\n        elif which == \"Base64\":\n            r = codec.b64_decode(s)\n        else:\n            r = codec.b32_decode(s)\n        if len(r) == 1:\n            show_bytes(r[0])\n            return\n        print(\"Not valid \" + which + \": \" + r[1] + \".\")\n\nprint(\"== Codec workbench ==\")\nprint(\"Text as bytes, and bytes as hex, Base64 and Base32 - both ways, strictly.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        encode()\n    elif choice == \"2\":\n        decode(\"hex\")\n    elif choice == \"3\":\n        decode(\"Base64\")\n    elif choice == \"4\":\n        decode(\"Base32\")\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "codec.eml",
      "eml": "# P088 codec workbench - text as bytes, and bytes as hex, Base64 and Base32\n# (RFC 4648), both ways. Bits are worked with plain arithmetic: a group of\n# bytes becomes one whole number (n x 256 + byte ...), and its 6- or 5-bit\n# pieces come out by division by powers of two. The numbers stay below\n# 2^40, far under 2^53, so the float division used is exact.\n\n\" !\" => PART1\n\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\" => PART2\n\"]^_`abcdefghijklmnopqrstuvwxyz{|}~\" => PART3\n\"0123456789abcdef\" => HEX\n\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\" => B64\n\"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\" => B32\n\ndef index_in(s, c):\n    for i in [0:len(s) - 1]:\n        if s[i] == c:\n            return i\n    return -1\n\ndef code_of(c):\n    # The ASCII code of a printable character other than \" and \\, or -1.\n    index_in(PART1, c) => i\n    if i >= 0:\n        return 32 + i\n    index_in(PART2, c) => i\n    if i >= 0:\n        return 35 + i\n    index_in(PART3, c) => i\n    if i >= 0:\n        return 93 + i\n    return -1\n\ndef char_of(n):\n    # The character for an ASCII code, or \"\" if this program does not show it.\n    if n >= 32 and n <= 33:\n        return PART1[n - 32]\n    if n >= 35 and n <= 91:\n        return PART2[n - 35]\n    if n >= 93 and n <= 126:\n        return PART3[n - 93]\n    return \"\"\n\ndef bytes_of(text):\n    [] => out\n    for c in text:\n        code_of(c) => n\n        if n < 0:\n            return [-1]\n        out + [n] => out\n    return out\n\ndef text_of(bs):\n    # The bytes as text, or \"\" if any is not a printable character here.\n    \"\" => s\n    for b in bs:\n        char_of(b) => c\n        if c == \"\":\n            return \"\"\n        s + c => s\n    return s\n\ndef quotient(a, b):\n    return int((a - a % b) / b)\n\ndef hex_encode(bs):\n    \"\" => s\n    for b in bs:\n        s + HEX[quotient(b, 16)] + HEX[b % 16] => s\n    return s\n\ndef hex_decode(s):\n    # [bytes] or [[], why]. Upper or lower case digits.\n    \"0123456789ABCDEF\" => upper\n    if len(s) % 2 != 0:\n        return [[], \"hex needs two digits a byte, so an even number of them\"]\n    [] => out\n    0 => i\n    while i < len(s):\n        index_in(HEX, s[i]) => a\n        if a < 0:\n            index_in(upper, s[i]) => a\n        index_in(HEX, s[i + 1]) => b\n        if b < 0:\n            index_in(upper, s[i + 1]) => b\n        if a < 0 or b < 0:\n            return [[], \"it has a character that is not a hex digit\"]\n        out + [a * 16 + b] => out\n        i + 2 => i\n    return [out]\n\ndef b64_encode(bs):\n    # Three bytes make 24 bits make four 6-bit characters. A last group of\n    # 1 or 2 bytes gives 2 or 3 characters, padded with = to four.\n    \"\" => s\n    0 => i\n    while i < len(bs):\n        len(bs) - i => left\n        if left > 3:\n            3 => left\n        0 => n\n        for k in [0:2]:\n            0 => b\n            if k < left:\n                bs[i + k] => b\n            n * 256 + b => n\n        [262144, 4096, 64, 1] => p\n        for k in [0:3]:\n            if k <= left:\n                s + B64[quotient(n, p[k]) % 64] => s\n            else:\n                s + \"=\" => s\n        i + 3 => i\n    return s\n\ndef b64_decode(s):\n    # [bytes] or [[], why]. Strict: a multiple of 4 characters, = only at\n    # the end and at most two, and the bits the padding leaves over are 0 -\n    # so every byte string has exactly one encoding.\n    if len(s) % 4 != 0:\n        return [[], \"Base64 comes in groups of 4 characters\"]\n    [] => out\n    0 => i\n    while i < len(s):\n        0 => n\n        0 => pad\n        for k in [0:3]:\n            s[i + k] => c\n            if c == \"=\":\n                if i + 4 < len(s) or k < 2:\n                    return [[], \"= can only end the last group, and at most two of them\"]\n                pad + 1 => pad\n                n * 64 => n\n            else:\n                if pad > 0:\n                    return [[], \"= can only end the last group, and at most two of them\"]\n                index_in(B64, c) => v\n                if v < 0:\n                    return [[], \"it has a character outside the Base64 alphabet\"]\n                n * 64 + v => n\n        if pad == 1 and n % 256 != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        if pad == 2 and n % 65536 != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        [65536, 256, 1] => p\n        for k in [0:2 - pad]:\n            out + [quotient(n, p[k]) % 256] => out\n        i + 4 => i\n    return [out]\n\ndef b32_encode(bs):\n    # The corpus case base32-codec-padding: five bytes make 40 bits make\n    # eight 5-bit characters. A last group of 1, 2, 3 or 4 bytes gives 2, 4,\n    # 5 or 7 characters and 6, 4, 3 or 1 =.\n    [0, 2, 4, 5, 7, 8] => chars\n    \"\" => s\n    0 => i\n    while i < len(bs):\n        len(bs) - i => left\n        if left > 5:\n            5 => left\n        0 => n\n        for k in [0:4]:\n            0 => b\n            if k < left:\n                bs[i + k] => b\n            n * 256 + b => n\n        34359738368 => p\n        for k in [0:7]:\n            if k < chars[left]:\n                s + B32[quotient(n, p) % 32] => s\n            else:\n                s + \"=\" => s\n            quotient(p, 32) => p\n        i + 5 => i\n    return s\n\ndef b32_decode(s):\n    # [bytes] or [[], why]. Strict like Base64: groups of 8, padding only as\n    # 6, 4, 3 or 1 at the very end, and the leftover bits 0.\n    if len(s) % 8 != 0:\n        return [[], \"Base32 comes in groups of 8 characters\"]\n    [] => out\n    0 => i\n    while i < len(s):\n        0 => n\n        0 => pad\n        for k in [0:7]:\n            s[i + k] => c\n            if c == \"=\":\n                if i + 8 < len(s):\n                    return [[], \"= can only end the last group\"]\n                pad + 1 => pad\n                n * 32 => n\n            else:\n                if pad > 0:\n                    return [[], \"= can only end the last group\"]\n                index_in(B32, c) => v\n                if v < 0:\n                    return [[], \"it has a character outside the Base32 alphabet\"]\n                n * 32 + v => n\n        # padding 0, 1, 3, 4, 6 leaves 5, 4, 3, 2, 1 bytes\n        [5, 4, -1, 3, 2, -1, 1] => kept\n        if pad > 6 or kept[pad] == -1:\n            return [[], \"a Base32 group can only end in 0, 1, 3, 4 or 6 =\"]\n        kept[pad] => nb\n        1 => drop\n        for k in [1:40 - 8 * nb]:\n            drop * 2 => drop\n        if n % drop != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        4294967296 => p\n        for k in [1:nb]:\n            out + [quotient(n, p) % 256] => out\n            quotient(p, 256) => p\n        i + 8 => i\n    return [out]\n\ndef bits(n, width):\n    # n as `width` binary digits.\n    \"\" => s\n    for k in [1:width]:\n        str(n % 2) + s => s\n        quotient(n, 2) => n\n    return s\n",
      "python": "PART1 = \" !\"\nPART2 = \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\"\nPART3 = \"]^_`abcdefghijklmnopqrstuvwxyz{|}~\"\nHEX = \"0123456789abcdef\"\nB64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\nB32 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\n\ndef index_in(s, c):\n    for i in range(0, len(s)):\n        if s[i] == c:\n            return i\n    return -1\n\ndef code_of(c):\n    i = index_in(PART1, c)\n    if i >= 0:\n        return 32 + i\n    i = index_in(PART2, c)\n    if i >= 0:\n        return 35 + i\n    i = index_in(PART3, c)\n    if i >= 0:\n        return 93 + i\n    return -1\n\ndef char_of(n):\n    if n >= 32 and n <= 33:\n        return PART1[n - 32]\n    if n >= 35 and n <= 91:\n        return PART2[n - 35]\n    if n >= 93 and n <= 126:\n        return PART3[n - 93]\n    return \"\"\n\ndef bytes_of(text):\n    out = []\n    for c in text:\n        n = code_of(c)\n        if n < 0:\n            return [-1]\n        out = out + [n]\n    return out\n\ndef text_of(bs):\n    s = \"\"\n    for b in bs:\n        c = char_of(b)\n        if c == \"\":\n            return \"\"\n        s = s + c\n    return s\n\ndef quotient(a, b):\n    return int((a - a % b) / b)\n\ndef hex_encode(bs):\n    s = \"\"\n    for b in bs:\n        s = s + HEX[quotient(b, 16)] + HEX[b % 16]\n    return s\n\ndef hex_decode(s):\n    upper = \"0123456789ABCDEF\"\n    if len(s) % 2 != 0:\n        return [[], \"hex needs two digits a byte, so an even number of them\"]\n    out = []\n    i = 0\n    while i < len(s):\n        a = index_in(HEX, s[i])\n        if a < 0:\n            a = index_in(upper, s[i])\n        b = index_in(HEX, s[i + 1])\n        if b < 0:\n            b = index_in(upper, s[i + 1])\n        if a < 0 or b < 0:\n            return [[], \"it has a character that is not a hex digit\"]\n        out = out + [a * 16 + b]\n        i = i + 2\n    return [out]\n\ndef b64_encode(bs):\n    s = \"\"\n    i = 0\n    while i < len(bs):\n        left = len(bs) - i\n        if left > 3:\n            left = 3\n        n = 0\n        for k in range(0, 3):\n            b = 0\n            if k < left:\n                b = bs[i + k]\n            n = n * 256 + b\n        p = [262144, 4096, 64, 1]\n        for k in range(0, 4):\n            if k <= left:\n                s = s + B64[quotient(n, p[k]) % 64]\n            else:\n                s = s + \"=\"\n        i = i + 3\n    return s\n\ndef b64_decode(s):\n    if len(s) % 4 != 0:\n        return [[], \"Base64 comes in groups of 4 characters\"]\n    out = []\n    i = 0\n    while i < len(s):\n        n = 0\n        pad = 0\n        for k in range(0, 4):\n            c = s[i + k]\n            if c == \"=\":\n                if i + 4 < len(s) or k < 2:\n                    return [[], \"= can only end the last group, and at most two of them\"]\n                pad = pad + 1\n                n = n * 64\n            else:\n                if pad > 0:\n                    return [[], \"= can only end the last group, and at most two of them\"]\n                v = index_in(B64, c)\n                if v < 0:\n                    return [[], \"it has a character outside the Base64 alphabet\"]\n                n = n * 64 + v\n        if pad == 1 and n % 256 != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        if pad == 2 and n % 65536 != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        p = [65536, 256, 1]\n        for k in range(0, 2 - pad+1):\n            out = out + [quotient(n, p[k]) % 256]\n        i = i + 4\n    return [out]\n\ndef b32_encode(bs):\n    chars = [0, 2, 4, 5, 7, 8]\n    s = \"\"\n    i = 0\n    while i < len(bs):\n        left = len(bs) - i\n        if left > 5:\n            left = 5\n        n = 0\n        for k in range(0, 5):\n            b = 0\n            if k < left:\n                b = bs[i + k]\n            n = n * 256 + b\n        p = 34359738368\n        for k in range(0, 8):\n            if k < chars[left]:\n                s = s + B32[quotient(n, p) % 32]\n            else:\n                s = s + \"=\"\n            p = quotient(p, 32)\n        i = i + 5\n    return s\n\ndef b32_decode(s):\n    if len(s) % 8 != 0:\n        return [[], \"Base32 comes in groups of 8 characters\"]\n    out = []\n    i = 0\n    while i < len(s):\n        n = 0\n        pad = 0\n        for k in range(0, 8):\n            c = s[i + k]\n            if c == \"=\":\n                if i + 8 < len(s):\n                    return [[], \"= can only end the last group\"]\n                pad = pad + 1\n                n = n * 32\n            else:\n                if pad > 0:\n                    return [[], \"= can only end the last group\"]\n                v = index_in(B32, c)\n                if v < 0:\n                    return [[], \"it has a character outside the Base32 alphabet\"]\n                n = n * 32 + v\n        kept = [5, 4, -1, 3, 2, -1, 1]\n        if pad > 6 or kept[pad] == -1:\n            return [[], \"a Base32 group can only end in 0, 1, 3, 4 or 6 =\"]\n        nb = kept[pad]\n        drop = 1\n        for k in range(1, 40 - 8 * nb+1):\n            drop = drop * 2\n        if n % drop != 0:\n            return [[], \"the bits the padding leaves over are not 0\"]\n        p = 4294967296\n        for k in range(1, nb+1):\n            out = out + [quotient(n, p) % 256]\n            p = quotient(p, 256)\n        i = i + 8\n    return [out]\n\ndef bits(n, width):\n    s = \"\"\n    for k in range(1, width+1):\n        s = str(n % 2) + s\n        n = quotient(n, 2)\n    return s\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nsay \"hi\"\n1\na\\b\n1\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n2\nabc\nzz\n\n3\nTWE\nTW=E\nTWF=\nTW!=\n\n4\nJBSWY3D=\nJBSWY3==\njbswy3dp\n\n5\n",
      "screen": "== Codec workbench ==\nText as bytes, and bytes as hex, Base64 and Base32 - both ways, strictly.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> say \"hi\"\nThat has a character this program does not handle, or is over 60.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> a\\b\nThat has a character this program does not handle, or is over 60.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nThat has a character this program does not handle, or is over 60.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 2\nhex to decode (empty to stop)> abc\nNot valid hex: hex needs two digits a byte, so an even number of them.\nhex to decode (empty to stop)> zz\nNot valid hex: it has a character that is not a hex digit.\nhex to decode (empty to stop)> \nCancelled.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 3\nBase64 to decode (empty to stop)> TWE\nNot valid Base64: Base64 comes in groups of 4 characters.\nBase64 to decode (empty to stop)> TW=E\nNot valid Base64: = can only end the last group, and at most two of them.\nBase64 to decode (empty to stop)> TWF=\nNot valid Base64: the bits the padding leaves over are not 0.\nBase64 to decode (empty to stop)> TW!=\nNot valid Base64: it has a character outside the Base64 alphabet.\nBase64 to decode (empty to stop)> \nCancelled.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 4\nBase32 to decode (empty to stop)> JBSWY3D=\nNot valid Base32: the bits the padding leaves over are not 0.\nBase32 to decode (empty to stop)> JBSWY3==\nNot valid Base32: a Base32 group can only end in 0, 1, 3, 4 or 6 =.\nBase32 to decode (empty to stop)> jbswy3dp\nNot valid Base32: it has a character outside the Base32 alphabet.\nBase32 to decode (empty to stop)> \nCancelled.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nMan\n1\nHello, EML!\n1\nMa\n3\nSGVsbG8sIEVNTCE=\n4\nJBSWY3DPFQQEKTKMEE======\n2\n48656C6C6F\n3\nAP8=\n5\n",
      "screen": "== Codec workbench ==\nText as bytes, and bytes as hex, Base64 and Base32 - both ways, strictly.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> Man\nhex:    4d616e\nBase64: TWFu\nBase32: JVQW4===\nThe first Base64 group, bit by bit:\n  bytes 01001101 01100001 01101110\n  6-bit 010011 010110 000101 101110\n  as        T     W     F     u\nThe bytes come in whole threes, so Base64 needs no padding.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> Hello, EML!\nhex:    48656c6c6f2c20454d4c21\nBase64: SGVsbG8sIEVNTCE=\nBase32: JBSWY3DPFQQEKTKMEE======\nThe first Base64 group, bit by bit:\n  bytes 01001000 01100101 01101100\n  6-bit 010010 000110 010101 101100\n  as        S     G     V     s\nTwo bytes are left at the end: 16 bits make 3 characters, and = pads the group to 4.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 1\ntext (up to 60 printable characters, no double quote or backslash)> Ma\nhex:    4d61\nBase64: TWE=\nBase32: JVQQ====\nThe first Base64 group, bit by bit:\n  bytes 01001101 01100001\n  6-bit 010011 010110 000100\n  as        T     W     E\nTwo bytes are left at the end: 16 bits make 3 characters, and = pads the group to 4.\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 3\nBase64 to decode (empty to stop)> SGVsbG8sIEVNTCE=\nbytes: 11 (hex 48656c6c6f2c20454d4c21)\ntext:  Hello, EML!\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 4\nBase32 to decode (empty to stop)> JBSWY3DPFQQEKTKMEE======\nbytes: 11 (hex 48656c6c6f2c20454d4c21)\ntext:  Hello, EML!\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 2\nhex to decode (empty to stop)> 48656C6C6F\nbytes: 5 (hex 48656c6c6f)\ntext:  Hello\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 3\nBase64 to decode (empty to stop)> AP8=\nbytes: 2 (hex 00ff)\ntext: not all of them are printable characters this program shows\n\n1) encode text  2) decode hex  3) decode Base64  4) decode Base32  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "base32-codec-padding",
      "caseId": "200-base32-codec-padding",
      "title": "The padding table is the whole specification"
    }
  ],
  "updated": "2026-10-15"
}
