# this code is public domain
#
# written by Bram Cohen, 2001-01-25

import string
import sha

true = 1
false = 0

__doc__ = """encodes and decodes keys for Envelope Mail.

functions are encode_seed and decode_seed
"""

_tb = {
    '2':  0, '3':  1, '4':  2, '5':  3, '6':  4, '7':  5, '8':  6, '9':  7,
    'A':  8, 'B':  9, 'C': 10, 'D': 11, 'E': 12, 'F': 13, 'G': 14, 'H': 15,
    'I': 16, 'J': 17, 'K': 18, 'M': 19, 'N': 20, 'P': 21, 'Q': 22, 'R': 23,
    'S': 24, 'T': 25, 'U': 26, 'V': 27, 'W': 28, 'X': 29, 'Y': 30, 'Z': 31}

_ta = [
    '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'M', 'N', 'P', 'Q', 'R',
    'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

def encode_seed(seed):
    """
    encodes a 14-bytes seed to human readable form
    """
    assert len(seed) == 14
    xor = 0
    for i in seed:
        xor = xor ^ ord(i)
    seed = seed + chr(xor)
    result = _encode_five(seed[:5]) + _encode_five(seed[5:10]) + _encode_five(seed[10:])
    return result[:4] + ' ' + result[4:8] + ' ' + result[8:12] + ' ' + result[12:16] + ' ' + result[16:20] + ' ' + result[20:]

def _encode_five(stuff):
    a, b, c, d, e = stuff
    a, b, c, d, e = ord(a), ord(b), ord(c), ord(d), ord(e)
    return _ta[a >> 3] + _ta[((a & 7) << 2) | (b >> 6)] + \
        _ta[(b >> 1) & 31] + _ta[((b & 1) << 4) | (c >> 4)] + \
        _ta[((c & 15) << 1) | (d >> 7)] + _ta[(d >> 2) & 31] + \
        _ta[((d & 3) << 3) | (e >> 5)] + _ta[e & 31]

def decode_seed(seed):
    """
    decodes a human readable form of a seed back to binary

    very forgiving about extra whitespace and upper/lower case
    
    returns (result, failure_reason) exactly one of which is None - 
    the other is a string
    """
    seed = string.join(filter(lambda x: x not in string.whitespace, list(seed)), '')
    seed = string.upper(seed)
    for i in seed:
        if i not in '23456789ABCDEFGHIJKMNPQRSTUVWXYZ':
            return (None, 'illegal character')
    if len(seed) != 24:
        return (None, 'wrong length')
    result = _decode_eight(seed[:8]) + _decode_eight(seed[8:16]) + _decode_eight(seed[16:])
    xor = 0
    for i in result[:-1]:
        xor = xor ^ ord(i)
    if chr(xor) != result[-1]:
        return (None, 'wrong checksum')
    return (result[:-1], None)

def _decode_eight(stuff):
    a, b, c, d, e, f, g, h = map(lambda x: _tb[x], stuff)
    result = [a << 3 | (b >> 2) , ((b & 3) << 6) | (c << 1) | (d >> 4),
        ((d & 15) << 4) | (e >> 1), ((e & 1) << 7) | (f << 2) | (g >> 3),
        ((g & 7) << 5) | h]
    return string.join(map(chr, result), '')

def test_change_first_flunks():
    t = 'ggtpr9gt8gagrc'
    e = encode_seed(t)
    if e[0] == '8':
        e = '7' + e[1:]
    else:
        e = '8' + e[1:]
    assert decode_seed(e) == (None, 'wrong checksum')

def test_change_last_flunks():
    t = 'ggtpr9gt8gagrc'
    e = encode_seed(t)
    if e[-1] == '8':
        e = e[:-1] + '7'
    else:
        e = e[:-1] + '8'
    assert decode_seed(e) == (None, 'wrong checksum')

def test_encode_and_decode_lots():
    seed = 'a'
    for i in xrange(1000):
        seed = sha.sha(seed).digest()[:14]
        assert decode_seed(encode_seed(seed)) == (seed, None)

def test_decode_extra_whitespace():
    t = 'ggtpr9gt8gagrc'
    e = '\r\n \t' + encode_seed(t) + '\r\n \t'
    assert decode_seed(e) == (t, None)

def test_wrong_length_seed_asserts():
    t = 'ggtpr9gt8gagrc7'
    try:
        encode_seed(t)
        return false
    except:
        pass

def test_wrong_length_encoding_fails():
    t = 'ggtpr9gt8gagrc'
    e = encode_seed(t)
    assert decode_seed(e + 'h') == (None, 'wrong length')

def test_accepts_lower_case():
    t = 'ggtpr9gt8gagrc'
    e = string.lower(encode_seed(t))
    assert decode_seed(e) == (t, None)

mojo_test_flag = 1





