tinypacks.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/usr/bin/env python3
  2. #
  3. # TinyPacks - Copyright (c) 2012 Francisco Castro <http://fran.cc>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  18. # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  21. # DEALINGS IN THE SOFTWARE.
  22. try:
  23. from struct import pack, unpack
  24. const = int
  25. except:
  26. from ustruct import pack, unpack
  27. TP_NONE = const(0x00)
  28. TP_BOOLEAN = const(0x20)
  29. TP_INTEGER = const(0x40)
  30. TP_REAL = const(0x60)
  31. TP_STRING = const(0x80)
  32. TP_BYTES = const(0xA0)
  33. TP_LIST = const(0xC0)
  34. TP_MAP = const(0xE0)
  35. TP_SMALL_SIZE_MASK = const(0x1F)
  36. TP_SMALL_SIZE_MAX = const(0x1E)
  37. TP_EXTENDED_SIZE_16 = const(0x1F)
  38. TP_EXTENDED_SIZE_32 = const(0xFFFF)
  39. TP_TYPE_MASK = const(0b11100000)
  40. TP_FAMILY_MASK = const(0b11000000)
  41. TP_NUMBER = const(0b01000000)
  42. TP_BLOCK = const(0b10000000)
  43. TP_CONTAINER = const(0b11000000)
  44. def bit_len(n):
  45. s = bin(n) # binary representation: bin(-37) --> '-0b100101'
  46. s = s.lstrip("-0b") # remove leading zeros and minus sign
  47. return len(s) # len('100101') --> 6
  48. def dumpb(obj, use_double=False):
  49. if obj is None:
  50. return bytes([TP_NONE])
  51. if isinstance(obj, bool):
  52. if obj:
  53. return bytes( [0x21,0x01] )
  54. return bytes( [TP_BOOLEAN] )
  55. if isinstance(obj, int):
  56. bit_length = bit_len(obj)
  57. if bit_length == 0:
  58. return bytes([TP_INTEGER])
  59. if bit_length <= 7:
  60. return pack(">Bb", TP_INTEGER | 1, obj)
  61. if bit_length <= 15:
  62. return pack(">Bh", TP_INTEGER | 2, obj)
  63. if bit_length <= 31:
  64. return pack(">Bl", TP_INTEGER | 4, obj)
  65. if bit_length <= 63:
  66. return pack(">Bq", TP_INTEGER | 8, obj)
  67. raise ValueError("Integer number too big")
  68. if isinstance(obj, float):
  69. if obj == 0:
  70. return bytes([TP_REAL])
  71. if use_double:
  72. return pack(">Bd", TP_REAL | 8, obj)
  73. return pack(">Bf", TP_REAL | 4, obj)
  74. if isinstance(obj, str):
  75. obj = bytes(obj, "utf_8")
  76. blen = len(obj)
  77. if blen <= TP_SMALL_SIZE_MAX:
  78. return pack(">B%is" % blen, TP_STRING | blen, obj)
  79. if blen < 0xFFFF:
  80. return pack(">BH%is" % blen, TP_STRING | TP_EXTENDED_SIZE_16, blen, obj)
  81. if blen < 0xFFFFFFFF:
  82. return pack(">BHL%is" % blen, TP_STRING | TP_EXTENDED_SIZE_16, TP_EXTENDED_SIZE_32, blen, obj)
  83. raise ValueError("String too long")
  84. if isinstance(obj, (bytes,bytearray) ):
  85. blen = len(obj)
  86. if blen <= TP_SMALL_SIZE_MAX:
  87. return pack(">B%is" % blen, TP_BYTES | blen, obj)
  88. if blen < 0xFFFF:
  89. return pack(">BH%is" % blen, TP_BYTES | TP_EXTENDED_SIZE_16, blen, obj)
  90. if blen < 0xFFFFFFFF:
  91. return pack(">BHL%is" % blen, TP_BYTES | TP_EXTENDED_SIZE_16, TP_EXTENDED_SIZE_32, blen, obj)
  92. raise ValueError("Bytearray too long")
  93. if isinstance(obj, (list, tuple)):
  94. content = b"".join([pack(value) for value in obj])
  95. blen = len(content)
  96. if blen <= TP_SMALL_SIZE_MAX:
  97. return pack(">B%is" % blen, TP_LIST | blen, content)
  98. if blen < 0xFFFF:
  99. return pack(">BH%is" % blen, TP_LIST | TP_EXTENDED_SIZE_16, blen, content)
  100. if blen < 0xFFFFFFFF:
  101. return pack(">BHL%is" % blen, TP_LIST | TP_EXTENDED_SIZE_16, TP_EXTENDED_SIZE_32, blen, content)
  102. raise ValueError("List too long")
  103. if isinstance(obj, dict):
  104. elements = []
  105. for item in list(obj.items()):
  106. elements.append(dumpb(item[0]))
  107. elements.append(dumpb(item[1]))
  108. content = b"".join(elements)
  109. blen = len(content)
  110. if blen <= TP_SMALL_SIZE_MAX:
  111. return pack(">B%is" % blen, TP_MAP | blen, content)
  112. if blen < 0xFFFF:
  113. return pack(">BH%is" % blen, TP_MAP | TP_EXTENDED_SIZE_16, blen, content)
  114. if blen < 0xFFFFFFFF:
  115. return pack(">BHL%is" % blen, TP_MAP | TP_EXTENDED_SIZE_16, TP_EXTENDED_SIZE_32, blen, content)
  116. raise ValueError("Dict too long")
  117. raise ValueError("Unknown type")
  118. import sys
  119. def loadb(ba):
  120. def _loadb(ba):
  121. if len(ba) == 0:
  122. raise ValueError("Cannot unpack an empty pack")
  123. ct = ba[0] & TP_TYPE_MASK
  124. content_length = ba[0] & TP_SMALL_SIZE_MASK
  125. element_length = content_length + 1
  126. if content_length != TP_EXTENDED_SIZE_16:
  127. content_raw = ba[1:element_length]
  128. else:
  129. content_length = unpack(">BH", ba[0:3])[1]
  130. element_length = content_length + 3
  131. if content_length != TP_EXTENDED_SIZE_32:
  132. content_raw = ba[3:element_length]
  133. else:
  134. content_length = unpack(">BHL", bytes)[2]
  135. element_length = content_length + 7
  136. content_raw = ba[7:element_length]
  137. if ct == TP_NONE:
  138. obj = None
  139. elif ct == TP_BOOLEAN:
  140. obj = False
  141. if content_length:
  142. if content_raw[0]:
  143. obj=True
  144. else:
  145. raise ValueError("Invalid True value")
  146. elif ct == TP_INTEGER:
  147. if content_length == 0:
  148. obj = 0
  149. elif content_length == 1:
  150. obj = unpack(">b", content_raw)[0]
  151. elif content_length == 2:
  152. obj = unpack(">h", content_raw)[0]
  153. elif content_length == 4:
  154. obj = unpack(">l", content_raw)[0]
  155. elif content_length == 8:
  156. obj = unpack(">q", content_raw)[0]
  157. else:
  158. raise ValueError("Integer number too big")
  159. elif ct == TP_REAL:
  160. if content_length == 0:
  161. obj = 0.0
  162. elif content_length == 4:
  163. obj = unpack(">f", content_raw)[0]
  164. elif content_length == 8:
  165. obj = unpack(">d", content_raw)[0]
  166. else:
  167. raise ValueError("Real number too big")
  168. elif ct == TP_STRING:
  169. obj = content_raw.decode("utf8")
  170. elif ct == TP_BYTES:
  171. obj = content_raw
  172. elif ct == TP_LIST:
  173. obj = []
  174. while len(content_raw):
  175. item, content_raw = unpack(content_raw)
  176. obj.append(item)
  177. elif ct == TP_MAP:
  178. obj = {}
  179. while len(content_raw):
  180. key, content_raw = _loadb(content_raw)
  181. if len(content_raw):
  182. value, content_raw = _loadb(content_raw)
  183. obj[key] = value
  184. else:
  185. raise ValueError("Dict key/value invalid")
  186. return (obj, ba[element_length:])
  187. return _loadb(ba)[0]