comparison hgext/fsmonitor/pywatchman/pybser.py @ 28432:2377c4ac4eec

fsmonitor: dependencies for new experimental extension In preparation for the filesystem monitor extension, include the pywatchman library. The fbmonitor extension relies on this library to communicate with the Watchman service. The library is BSD licensed and is taken from https://github.com/facebook/watchman/tree/master/python. This package has not been updated to mercurial code standards.
author Martijn Pieters <mjpieters@fb.com>
date Wed, 02 Mar 2016 16:25:12 +0000
parents
children 16f4b341288d
comparison
equal deleted inserted replaced
28431:a7e3b72cf756 28432:2377c4ac4eec
1 # Copyright 2015 Facebook, Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are met:
6 #
7 # * Redistributions of source code must retain the above copyright notice,
8 # this list of conditions and the following disclaimer.
9 #
10 # * Redistributions in binary form must reproduce the above copyright notice,
11 # this list of conditions and the following disclaimer in the documentation
12 # and/or other materials provided with the distribution.
13 #
14 # * Neither the name Facebook nor the names of its contributors may be used to
15 # endorse or promote products derived from this software without specific
16 # prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import collections
30 import ctypes
31 import struct
32 import sys
33
34 BSER_ARRAY = '\x00'
35 BSER_OBJECT = '\x01'
36 BSER_STRING = '\x02'
37 BSER_INT8 = '\x03'
38 BSER_INT16 = '\x04'
39 BSER_INT32 = '\x05'
40 BSER_INT64 = '\x06'
41 BSER_REAL = '\x07'
42 BSER_TRUE = '\x08'
43 BSER_FALSE = '\x09'
44 BSER_NULL = '\x0a'
45 BSER_TEMPLATE = '\x0b'
46 BSER_SKIP = '\x0c'
47
48 # Leave room for the serialization header, which includes
49 # our overall length. To make things simpler, we'll use an
50 # int32 for the header
51 EMPTY_HEADER = "\x00\x01\x05\x00\x00\x00\x00"
52
53 # Python 3 conditional for supporting Python 2's int/long types
54 if sys.version_info > (3,):
55 long = int
56
57 def _int_size(x):
58 """Return the smallest size int that can store the value"""
59 if -0x80 <= x <= 0x7F:
60 return 1
61 elif -0x8000 <= x <= 0x7FFF:
62 return 2
63 elif -0x80000000 <= x <= 0x7FFFFFFF:
64 return 4
65 elif long(-0x8000000000000000) <= x <= long(0x7FFFFFFFFFFFFFFF):
66 return 8
67 else:
68 raise RuntimeError('Cannot represent value: ' + str(x))
69
70
71 class _bser_buffer(object):
72
73 def __init__(self):
74 self.buf = ctypes.create_string_buffer(8192)
75 struct.pack_into(str(len(EMPTY_HEADER)) + 's', self.buf, 0, EMPTY_HEADER)
76 self.wpos = len(EMPTY_HEADER)
77
78 def ensure_size(self, size):
79 while ctypes.sizeof(self.buf) - self.wpos < size:
80 ctypes.resize(self.buf, ctypes.sizeof(self.buf) * 2)
81
82 def append_long(self, val):
83 size = _int_size(val)
84 to_write = size + 1
85 self.ensure_size(to_write)
86 if size == 1:
87 struct.pack_into('=cb', self.buf, self.wpos, BSER_INT8, val)
88 elif size == 2:
89 struct.pack_into('=ch', self.buf, self.wpos, BSER_INT16, val)
90 elif size == 4:
91 struct.pack_into('=ci', self.buf, self.wpos, BSER_INT32, val)
92 elif size == 8:
93 struct.pack_into('=cq', self.buf, self.wpos, BSER_INT64, val)
94 else:
95 raise RuntimeError('Cannot represent this long value')
96 self.wpos += to_write
97
98
99 def append_string(self, s):
100 if isinstance(s, unicode):
101 s = s.encode('utf-8')
102 s_len = len(s)
103 size = _int_size(s_len)
104 to_write = 2 + size + s_len
105 self.ensure_size(to_write)
106 if size == 1:
107 struct.pack_into('=ccb' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT8, s_len, s)
108 elif size == 2:
109 struct.pack_into('=cch' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT16, s_len, s)
110 elif size == 4:
111 struct.pack_into('=cci' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT32, s_len, s)
112 elif size == 8:
113 struct.pack_into('=ccq' + str(s_len) + 's', self.buf, self.wpos, BSER_STRING, BSER_INT64, s_len, s)
114 else:
115 raise RuntimeError('Cannot represent this string value')
116 self.wpos += to_write
117
118
119 def append_recursive(self, val):
120 if isinstance(val, bool):
121 needed = 1
122 self.ensure_size(needed)
123 if val:
124 to_encode = BSER_TRUE
125 else:
126 to_encode = BSER_FALSE
127 struct.pack_into('=c', self.buf, self.wpos, to_encode)
128 self.wpos += needed
129 elif val is None:
130 needed = 1
131 self.ensure_size(needed)
132 struct.pack_into('=c', self.buf, self.wpos, BSER_NULL)
133 self.wpos += needed
134 elif isinstance(val, (int, long)):
135 self.append_long(val)
136 elif isinstance(val, (str, unicode)):
137 self.append_string(val)
138 elif isinstance(val, float):
139 needed = 9
140 self.ensure_size(needed)
141 struct.pack_into('=cd', self.buf, self.wpos, BSER_REAL, val)
142 self.wpos += needed
143 elif isinstance(val, collections.Mapping) and isinstance(val, collections.Sized):
144 val_len = len(val)
145 size = _int_size(val_len)
146 needed = 2 + size
147 self.ensure_size(needed)
148 if size == 1:
149 struct.pack_into('=ccb', self.buf, self.wpos, BSER_OBJECT, BSER_INT8, val_len)
150 elif size == 2:
151 struct.pack_into('=cch', self.buf, self.wpos, BSER_OBJECT, BSER_INT16, val_len)
152 elif size == 4:
153 struct.pack_into('=cci', self.buf, self.wpos, BSER_OBJECT, BSER_INT32, val_len)
154 elif size == 8:
155 struct.pack_into('=ccq', self.buf, self.wpos, BSER_OBJECT, BSER_INT64, val_len)
156 else:
157 raise RuntimeError('Cannot represent this mapping value')
158 self.wpos += needed
159 for k, v in val.iteritems():
160 self.append_string(k)
161 self.append_recursive(v)
162 elif isinstance(val, collections.Iterable) and isinstance(val, collections.Sized):
163 val_len = len(val)
164 size = _int_size(val_len)
165 needed = 2 + size
166 self.ensure_size(needed)
167 if size == 1:
168 struct.pack_into('=ccb', self.buf, self.wpos, BSER_ARRAY, BSER_INT8, val_len)
169 elif size == 2:
170 struct.pack_into('=cch', self.buf, self.wpos, BSER_ARRAY, BSER_INT16, val_len)
171 elif size == 4:
172 struct.pack_into('=cci', self.buf, self.wpos, BSER_ARRAY, BSER_INT32, val_len)
173 elif size == 8:
174 struct.pack_into('=ccq', self.buf, self.wpos, BSER_ARRAY, BSER_INT64, val_len)
175 else:
176 raise RuntimeError('Cannot represent this sequence value')
177 self.wpos += needed
178 for v in val:
179 self.append_recursive(v)
180 else:
181 raise RuntimeError('Cannot represent unknown value type')
182
183
184 def dumps(obj):
185 bser_buf = _bser_buffer()
186 bser_buf.append_recursive(obj)
187 # Now fill in the overall length
188 obj_len = bser_buf.wpos - len(EMPTY_HEADER)
189 struct.pack_into('=i', bser_buf.buf, 3, obj_len)
190 return bser_buf.buf.raw[:bser_buf.wpos]
191
192
193 def _bunser_int(buf, pos):
194 try:
195 int_type = buf[pos]
196 except IndexError:
197 raise ValueError('Invalid bser int encoding, pos out of range')
198 if int_type == BSER_INT8:
199 needed = 2
200 fmt = '=b'
201 elif int_type == BSER_INT16:
202 needed = 3
203 fmt = '=h'
204 elif int_type == BSER_INT32:
205 needed = 5
206 fmt = '=i'
207 elif int_type == BSER_INT64:
208 needed = 9
209 fmt = '=q'
210 else:
211 raise ValueError('Invalid bser int encoding 0x%02x' % int(int_type))
212 int_val = struct.unpack_from(fmt, buf, pos + 1)[0]
213 return (int_val, pos + needed)
214
215
216 def _bunser_string(buf, pos):
217 str_len, pos = _bunser_int(buf, pos + 1)
218 str_val = struct.unpack_from(str(str_len) + 's', buf, pos)[0]
219 return (str_val, pos + str_len)
220
221
222 def _bunser_array(buf, pos, mutable=True):
223 arr_len, pos = _bunser_int(buf, pos + 1)
224 arr = []
225 for i in range(arr_len):
226 arr_item, pos = _bser_loads_recursive(buf, pos, mutable)
227 arr.append(arr_item)
228
229 if not mutable:
230 arr = tuple(arr)
231
232 return arr, pos
233
234
235 # This is a quack-alike with the bserObjectType in bser.c
236 # It provides by getattr accessors and getitem for both index
237 # and name.
238 class _BunserDict(object):
239 __slots__ = ('_keys', '_values')
240
241 def __init__(self, keys, values):
242 self._keys = keys
243 self._values = values
244
245 def __getattr__(self, name):
246 return self.__getitem__(name)
247
248 def __getitem__(self, key):
249 if isinstance(key, (int, long)):
250 return self._values[key]
251 elif key.startswith('st_'):
252 # hack^Wfeature to allow mercurial to use "st_size" to
253 # reference "size"
254 key = key[3:]
255 try:
256 return self._values[self._keys.index(key)]
257 except ValueError as ex:
258 raise KeyError('_BunserDict has no key %s' % key)
259
260 def __len__(self):
261 return len(self._keys)
262
263 def _bunser_object(buf, pos, mutable=True):
264 obj_len, pos = _bunser_int(buf, pos + 1)
265 if mutable:
266 obj = {}
267 else:
268 keys = []
269 vals = []
270
271 for i in range(obj_len):
272 key, pos = _bunser_string(buf, pos)
273 val, pos = _bser_loads_recursive(buf, pos, mutable)
274 if mutable:
275 obj[key] = val
276 else:
277 keys.append(key)
278 vals.append(val)
279
280 if not mutable:
281 obj = _BunserDict(keys, vals)
282
283 return obj, pos
284
285
286 def _bunser_template(buf, pos, mutable=True):
287 if buf[pos + 1] != BSER_ARRAY:
288 raise RuntimeError('Expect ARRAY to follow TEMPLATE')
289 keys, pos = _bunser_array(buf, pos + 1)
290 nitems, pos = _bunser_int(buf, pos)
291 arr = []
292 for i in range(nitems):
293 if mutable:
294 obj = {}
295 else:
296 vals = []
297
298 for keyidx in range(len(keys)):
299 if buf[pos] == BSER_SKIP:
300 pos += 1
301 ele = None
302 else:
303 ele, pos = _bser_loads_recursive(buf, pos, mutable)
304
305 if mutable:
306 key = keys[keyidx]
307 obj[key] = ele
308 else:
309 vals.append(ele)
310
311 if not mutable:
312 obj = _BunserDict(keys, vals)
313
314 arr.append(obj)
315 return arr, pos
316
317
318 def _bser_loads_recursive(buf, pos, mutable=True):
319 val_type = buf[pos]
320 if (val_type == BSER_INT8 or val_type == BSER_INT16 or
321 val_type == BSER_INT32 or val_type == BSER_INT64):
322 return _bunser_int(buf, pos)
323 elif val_type == BSER_REAL:
324 val = struct.unpack_from('=d', buf, pos + 1)[0]
325 return (val, pos + 9)
326 elif val_type == BSER_TRUE:
327 return (True, pos + 1)
328 elif val_type == BSER_FALSE:
329 return (False, pos + 1)
330 elif val_type == BSER_NULL:
331 return (None, pos + 1)
332 elif val_type == BSER_STRING:
333 return _bunser_string(buf, pos)
334 elif val_type == BSER_ARRAY:
335 return _bunser_array(buf, pos, mutable)
336 elif val_type == BSER_OBJECT:
337 return _bunser_object(buf, pos, mutable)
338 elif val_type == BSER_TEMPLATE:
339 return _bunser_template(buf, pos, mutable)
340 else:
341 raise RuntimeError('unhandled bser opcode 0x%02x' % (val_type,))
342
343
344 def pdu_len(buf):
345 if buf[0:2] != EMPTY_HEADER[0:2]:
346 raise RuntimeError('Invalid BSER header')
347 expected_len, pos = _bunser_int(buf, 2)
348 return expected_len + pos
349
350
351 def loads(buf, mutable=True):
352 if buf[0:2] != EMPTY_HEADER[0:2]:
353 raise RuntimeError('Invalid BSER header')
354 expected_len, pos = _bunser_int(buf, 2)
355 if len(buf) != expected_len + pos:
356 raise RuntimeError('bser data len != header len')
357 return _bser_loads_recursive(buf, pos, mutable)[0]
358
359 # no-check-code -- this is a 3rd party library