source file: p10/base64.py
file stats: 149 lines, 148 executed: 99.3% covered
   1. #!/usr/bin/env python
   2. 
   3. """ A mapping of base 64 characters to the relevant values """
   4. _map = {"A": 0,
   5.         "B": 1,
   6.         "C": 2,
   7.         "D": 3,
   8.         "E": 4,
   9.         "F": 5,
  10.         "G": 6,
  11.         "H": 7,
  12.         "I": 8,
  13.         "J": 9,
  14.         "K": 10,
  15.         "L": 11,
  16.         "M": 12,
  17.         "N": 13,
  18.         "O": 14,
  19.         "P": 15,
  20.         "Q": 16,
  21.         "R": 17,
  22.         "S": 18,
  23.         "T": 19,
  24.         "U": 20,
  25.         "V": 21,
  26.         "W": 22,
  27.         "X": 23,
  28.         "Y": 24,
  29.         "Z": 25,
  30.         "a": 26,
  31.         "b": 27,
  32.         "c": 28,
  33.         "d": 29,
  34.         "e": 30,
  35.         "f": 31,
  36.         "g": 32,
  37.         "h": 33,
  38.         "i": 34,
  39.         "j": 35,
  40.         "k": 36,
  41.         "l": 37,
  42.         "m": 38,
  43.         "n": 39,
  44.         "o": 40,
  45.         "p": 41,
  46.         "q": 42,
  47.         "r": 43,
  48.         "s": 44,
  49.         "t": 45,
  50.         "u": 46,
  51.         "v": 47,
  52.         "w": 48,
  53.         "x": 49,
  54.         "y": 50,
  55.         "z": 51,
  56.         "0": 52,
  57.         "1": 53,
  58.         "2": 54,
  59.         "3": 55,
  60.         "4": 56,
  61.         "5": 57,
  62.         "6": 58,
  63.         "7": 59,
  64.         "8": 60,
  65.         "9": 61,
  66.         "[": 62,
  67.         "]": 63}
  68. 
  69. _revmap = dict(zip(_map.values(), _map.keys()))
  70. 
  71. def toInt(chars):
  72.     """ Convert a base 64 string to its appropriate base-10 integer """
  73.     accum = 0
  74.     chars = list(chars)
  75.     chars.reverse()
  76.     chars = ''.join(chars)
  77.     power = 0
  78.     for char in chars:
  79.         try:
  80.             accum = accum + (_map[char] * (64 ** power))
  81.         except KeyError:
  82.             raise Base64Error('Invalid base64 character encountered', chars)
  83.         power = power + 1
  84.     return accum
  85. 
  86. def toBase64(num, pad):
  87.     """ Convert a base-10 integer to a base-64 string """
  88.     # Build a list of all the appropriate characters
  89.     parts = list()
  90.     power = 0
  91.     while num > (64 ** power):
  92.         power = power + 1
  93.     while power >= 0:
  94.         part = num / (64 ** power)
  95.         parts.append(_revmap[part])
  96.         num = num - (part * (64 ** power))
  97.         power = power - 1
  98.     parts = ''.join(parts)
  99.     if len(parts) > 1:
 100.         # Trim the valueless characters off the front, apart from if the value is a literal 0
 101.         parts = parts.lstrip('A')
 102. 
 103.     # Do padding
 104.     while len(parts) < pad:
 105.         parts = 'A' + parts
 106. 
 107.     return parts
 108. 
 109. def parseNumeric(numeric, maxclient):
 110.     """ Take a numeric and return a tuple of integers, the first representing the server numeric, the second the client numeric.
 111.         If the numeric is server-only, then the second element in the pair is set to None
 112.         The maxclient is used to return unique numerics """
 113. 
 114.     # Short and extended server only numerics
 115.     if len(numeric) == 1 or len(numeric) == 2:
 116.         return (toInt(numeric), None)
 117.     # Short server/client numerics
 118.     elif len(numeric) == 3:
 119.         server = toInt(numeric[0])
 120.         return (server, toInt(numeric[1:3]) & maxclient[server])
 121.     # Universal IRCU server/client numerics
 122.     elif len(numeric) == 4:
 123.         server = toInt(numeric[0])
 124.         return (server, toInt(numeric[1:4]) & maxclient[server])
 125.     # Extended server/client numerics
 126.     elif len(numeric) == 5:
 127.         server = toInt(numeric[0:2])
 128.         return (server, toInt(numeric[2:5]) & maxclient[server])
 129.     else:
 130.         raise Base64Error("Bad length for numeric", numeric)
 131. 
 132. def createNumeric((server, client)):
 133.     """ Create a numeric from a pair of integers - with the first representing the server numeric, the second the client.
 134.         This only generates extended (5 character) numerics for maximum compatibility """
 135. 
 136.     # Generate the server half
 137.     servernum = toBase64(server, 2)
 138. 
 139.     # Handle server-only numerics
 140.     clientnum = ""
 141.     if client != None:
 142.         # Generate client half
 143.         clientnum = toBase64(client, 3)
 144. 
 145.     return servernum + clientnum
 146. 
 147. class Base64Error(Exception):
 148.     """ An exception that is raised if there is an error generating or parsing the base 64 """
 149. 
 150.     numeric = ""
 151. 
 152.     def __init__(self, value, numeric):
 153.         self.value = value
 154.         self.numeric = numeric
 155. 
 156.     def __str__(self):
 157.         return repr(self.value) + " with numeric " + self.numeric