6 class EarlyEnd(Exception):
9 class LateEnd(Exception):
12 def read((data, pos), length):
13 data2 = data[pos:pos + length]
14 if len(data2) != length:
16 return data2, (data, pos + length)
18 def size((data, pos)):
19 return len(data) - pos
24 # the same data can have only one unpacked representation, but multiple packed binary representations
27 rval = getattr(self, '_hash', None)
30 rval = self._hash = hash((type(self), frozenset(self.__dict__.items())))
36 def __eq__(self, other):
37 return type(other) is type(self) and other.__dict__ == self.__dict__
39 def __ne__(self, other):
40 return not (self == other)
42 def _unpack(self, data):
43 obj, (data2, pos) = self.read((data, 0))
53 f = self.write(None, obj)
63 def unpack(self, data):
64 obj = self._unpack(data)
67 data2 = self._pack(obj)
69 if self._unpack(data2) != obj:
70 raise AssertionError()
75 data = self._pack(obj)
78 if self._unpack(data) != obj:
79 raise AssertionError((self._unpack(data), obj))
83 class VarIntType(Type):
85 data, file = read(file, 1)
90 desc, length, minimum = '<H', 2, 0xfd
92 desc, length, minimum = '<I', 4, 2**16
94 desc, length, minimum = '<Q', 8, 2**32
96 raise AssertionError()
97 data2, file = read(file, length)
98 res, = struct.unpack(desc, data2)
100 raise AssertionError('VarInt not canonically packed')
103 def write(self, file, item):
105 return file, struct.pack('<B', item)
107 return file, struct.pack('<BH', 0xfd, item)
108 elif item <= 0xffffffff:
109 return file, struct.pack('<BI', 0xfe, item)
110 elif item <= 0xffffffffffffffff:
111 return file, struct.pack('<BQ', 0xff, item)
113 raise ValueError('int too large for varint')
115 class VarStrType(Type):
116 _inner_size = VarIntType()
118 def read(self, file):
119 length, file = self._inner_size.read(file)
120 return read(file, length)
122 def write(self, file, item):
123 return self._inner_size.write(file, len(item)), item
125 class EnumType(Type):
126 def __init__(self, inner, values):
131 for k, v in values.iteritems():
133 raise ValueError('duplicate value in values')
137 def read(self, file):
138 data, file = self.inner.read(file)
139 if data not in self.keys:
140 raise ValueError('enum data (%r) not in values (%r)' % (data, self.values))
141 return self.keys[data], file
143 def write(self, file, item):
144 if item not in self.values:
145 raise ValueError('enum item (%r) not in values (%r)' % (item, self.values))
146 return self.inner.write(file, self.values[item])
148 class ListType(Type):
149 _inner_size = VarIntType()
151 def __init__(self, type):
154 def read(self, file):
155 length, file = self._inner_size.read(file)
157 for i in xrange(length):
158 item, file = self.type.read(file)
162 def write(self, file, item):
163 file = self._inner_size.write(file, len(item))
165 file = self.type.write(file, subitem)
168 class StructType(Type):
169 __slots__ = 'desc length'.split(' ')
171 def __init__(self, desc):
173 self.length = struct.calcsize(self.desc)
175 def read(self, file):
176 data, file = read(file, self.length)
177 return struct.unpack(self.desc, data)[0], file
179 def write(self, file, item):
180 return file, struct.pack(self.desc, item)
183 __slots__ = 'bytes step format_str max'.split(' ')
185 def __new__(cls, bits, endianness='little'):
187 assert endianness in ['little', 'big']
188 if bits in [8, 16, 32, 64]:
189 return StructType(('<' if endianness == 'little' else '>') + {8: 'B', 16: 'H', 32: 'I', 64: 'Q'}[bits])
191 return Type.__new__(cls, bits, endianness)
193 def __init__(self, bits, endianness='little'):
195 assert endianness in ['little', 'big']
197 self.step = -1 if endianness == 'little' else 1
198 self.format_str = '%%0%ix' % (2*self.bytes)
201 def read(self, file, b2a_hex=binascii.b2a_hex):
202 data, file = read(file, self.bytes)
203 return int(b2a_hex(data[::self.step]), 16), file
205 def write(self, file, item, a2b_hex=binascii.a2b_hex):
206 if not 0 <= item < self.max:
207 raise ValueError('invalid int value - %r' % (item,))
208 return file, a2b_hex(self.format_str % (item,))[::self.step]
210 class IPV6AddressType(Type):
211 def read(self, file):
212 data, file = read(file, 16)
213 if data[:12] != '00000000000000000000ffff'.decode('hex'):
214 raise ValueError('ipv6 addresses not supported yet')
215 return '.'.join(str(ord(x)) for x in data[12:]), file
217 def write(self, file, item):
218 bits = map(int, item.split('.'))
220 raise ValueError('invalid address: %r' % (bits,))
221 data = '00000000000000000000ffff'.decode('hex') + ''.join(chr(x) for x in bits)
222 assert len(data) == 16, len(data)
227 def get_record(fields):
228 fields = tuple(sorted(fields))
231 if fields not in _record_types:
232 class _Record(object):
235 return repr(dict(self))
236 def __getitem__(self, key):
237 return getattr(self, key)
238 def __setitem__(self, key, value):
239 setattr(self, key, value)
241 # for field in self.__slots__:
242 # yield field, getattr(self, field)
244 return self.__slots__
245 def __eq__(self, other):
246 if isinstance(other, dict):
247 return dict(self) == other
248 elif isinstance(other, _Record):
249 return all(self[k] == other[k] for k in self.keys())
251 def __ne__(self, other):
252 return not (self == other)
253 _record_types[fields] = _Record
254 return _record_types[fields]()
256 class ComposedType(Type):
257 def __init__(self, fields):
258 self.fields = tuple(fields)
260 def read(self, file):
261 item = get_record(k for k, v in self.fields)
262 for key, type_ in self.fields:
263 item[key], file = type_.read(file)
266 def write(self, file, item):
267 for key, type_ in self.fields:
268 file = type_.write(file, item[key])
271 class PossiblyNoneType(Type):
272 def __init__(self, none_value, inner):
273 self.none_value = none_value
276 def read(self, file):
277 value, file = self.inner.read(file)
278 return None if value == self.none_value else value, file
280 def write(self, file, item):
281 if item == self.none_value:
282 raise ValueError('none_value used')
283 return self.inner.write(file, self.none_value if item is None else item)