/ 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")