/ adafruit_binascii.py
adafruit_binascii.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2014 Paul Sokolovsky 4 # Modified by Brent Rubell for Adafruit Industries, 2019 5 # 6 # Permission is hereby granted, free of charge, to any person obtaining a copy 7 # of this software and associated documentation files (the "Software"), to deal 8 # in the Software without restriction, including without limitation the rights 9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 # copies of the Software, and to permit persons to whom the Software is 11 # furnished to do so, subject to the following conditions: 12 # 13 # The above copyright notice and this permission notice shall be included in 14 # all copies or substantial portions of the Software. 15 # 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 # THE SOFTWARE. 23 """ 24 `adafruit_binascii` 25 ================================================================================ 26 27 Helpers for conversions between binary and ASCII 28 29 30 * Author(s): Paul Sokolovsky, Brent Rubell 31 32 Implementation Notes 33 -------------------- 34 35 **Hardware:** 36 37 **Software and Dependencies:** 38 39 * Adafruit CircuitPython firmware for the supported boards: 40 https://github.com/adafruit/circuitpython/releases 41 42 """ 43 try: 44 from binascii import hexlify, unhexlify 45 except ImportError: 46 pass 47 48 __version__ = "0.0.0-auto.0" 49 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_binascii.git" 50 51 # fmt: off 52 TABLE_A2B_B64 = ( 53 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 54 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 55 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 56 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 57 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 58 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 59 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 60 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 61 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 63 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 65 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 66 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 67 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 68 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 69 ) 70 # fmt: on 71 72 TABLE_B2A_B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 73 74 75 class Error(Exception): 76 """Exception raised on errors. These are usually programming errors.""" 77 78 # pylint: disable=unnecessary-pass 79 pass 80 81 82 if not "unhexlify" in globals(): 83 # pylint: disable=function-redefined 84 def unhexlify(hexstr): 85 """Return the binary data represented by hexstr. 86 :param str hexstr: Hexadecimal string. 87 88 """ 89 if len(hexstr) % 2 != 0: 90 raise Error("Odd-length string") 91 92 return bytes([int(hexstr[i : i + 2], 16) for i in range(0, len(hexstr), 2)]) 93 94 95 if not "hexlify" in globals(): 96 # pylint: disable=function-redefined 97 def hexlify(data): 98 """Return the hexadecimal representation of the 99 binary data. Every byte of data is converted into 100 the corresponding 2-digit hex representation. 101 The returned bytes object is therefore twice 102 as long as the length of data. 103 104 :param bytes data: Binary data, as bytes. 105 106 """ 107 if not data: 108 raise TypeError("Data provided is zero-length") 109 data = "".join("%02x" % i for i in data) 110 return bytes(data, "utf-8") 111 112 113 B2A_HEX = hexlify 114 A2B_HEX = unhexlify 115 116 117 def _transform(n): 118 if n == -1: 119 return "\xff" 120 return chr(n) 121 122 123 TABLE_A2B_B64 = "".join(map(_transform, TABLE_A2B_B64)) 124 assert len(TABLE_A2B_B64) == 256 125 126 127 def a2b_base64(b64_data): 128 """Convert a block of base64 data back to binary and return the binary data. 129 130 :param str b64_data: Base64 data. 131 132 """ 133 res = [] 134 quad_pos = 0 135 leftchar = 0 136 leftbits = 0 137 last_char_was_a_pad = False 138 139 for char in b64_data: 140 char = chr(char) 141 if char == "=": 142 if quad_pos > 2 or (quad_pos == 2 and last_char_was_a_pad): 143 break # stop on 'xxx=' or on 'xx==' 144 last_char_was_a_pad = True 145 else: 146 n = ord(TABLE_A2B_B64[ord(char)]) 147 if n == 0xFF: 148 continue # ignore strange characters 149 # 150 # Shift it in on the low end, and see if there's 151 # a byte ready for output. 152 quad_pos = (quad_pos + 1) & 3 153 leftchar = (leftchar << 6) | n 154 leftbits += 6 155 # 156 if leftbits >= 8: 157 leftbits -= 8 158 res.append((leftchar >> leftbits).to_bytes(1, "big")) 159 leftchar &= (1 << leftbits) - 1 160 # 161 last_char_was_a_pad = False 162 else: 163 if leftbits != 0: 164 raise Exception("Incorrect padding") 165 166 return b"".join(res) 167 168 169 def b2a_base64(bin_data): 170 """Convert binary data to a line of ASCII characters in base64 coding. 171 172 :param str bin_data: Binary data string, as bytes 173 174 """ 175 newlength = (len(bin_data) + 2) // 3 176 newlength = newlength * 4 + 1 177 res = [] 178 179 leftchar = 0 180 leftbits = 0 181 for char in bin_data: 182 # Shift into our buffer, and output any 6bits ready 183 leftchar = (leftchar << 8) | char 184 leftbits += 8 185 res.append(TABLE_B2A_B64[(leftchar >> (leftbits - 6)) & 0x3F]) 186 leftbits -= 6 187 if leftbits >= 6: 188 res.append(TABLE_B2A_B64[(leftchar >> (leftbits - 6)) & 0x3F]) 189 leftbits -= 6 190 # 191 if leftbits == 2: 192 res.append(TABLE_B2A_B64[(leftchar & 3) << 4]) 193 res.append("=") 194 res.append("=") 195 elif leftbits == 4: 196 res.append(TABLE_B2A_B64[(leftchar & 0xF) << 2]) 197 res.append("=") 198 res.append("\n") 199 return bytes("".join(res), "ascii")