2 # -*- coding: utf-8 -*-
4 # protocol.py - decode binary messages received from WeeChat/relay
6 # Copyright (C) 2011-2014 Sébastien Helleu <flashcode@flashtux.org>
8 # This file is part of QWeeChat, a Qt remote GUI for WeeChat.
10 # QWeeChat is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
15 # QWeeChat is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
25 # For info about protocol and format of messages, please read document
26 # "WeeChat Relay Protocol", available at: http://weechat.org/doc/
30 # 2011-11-23, Sébastien Helleu <flashcode@flashtux.org>:
34 import collections
, struct
, zlib
36 if hasattr(collections
, 'OrderedDict'):
38 class WeechatDict(collections
.OrderedDict
):
40 return '{%s}' % ', '.join(['%s: %s' % (repr(key
), repr(self
[key
])) for key
in self
])
46 def __init__(self
, objtype
, value
, separator
='\n'):
47 self
.objtype
= objtype
;
49 self
.separator
= separator
50 self
.indent
= ' ' if separator
== '\n' else ''
51 self
.separator1
= '\n%s' % self
.indent
if separator
== '\n' else ''
53 def _str_value(self
, v
):
54 if type(v
) is str and not v
is None:
58 def _str_value_hdata(self
):
59 lines
= ['%skeys: %s%s%spath: %s' % (self
.separator1
, str(self
.value
['keys']), self
.separator
, self
.indent
, str(self
.value
['path']))]
60 for i
, item
in enumerate(self
.value
['items']):
61 lines
.append(' item %d:%s%s' % ((i
+ 1), self
.separator
,
62 self
.separator
.join(['%s%s: %s' % (self
.indent
* 2, key
, self
._str
_value
(value
)) for key
, value
in item
.items()])))
63 return '\n'.join(lines
)
65 def _str_value_infolist(self
):
66 lines
= ['%sname: %s' % (self
.separator1
, self
.value
['name'])]
67 for i
, item
in enumerate(self
.value
['items']):
68 lines
.append(' item %d:%s%s' % ((i
+ 1), self
.separator
,
69 self
.separator
.join(['%s%s: %s' % (self
.indent
* 2, key
, self
._str
_value
(value
)) for key
, value
in item
.items()])))
70 return '\n'.join(lines
)
72 def _str_value_other(self
):
73 return self
._str
_value
(self
.value
)
76 self
._obj
_cb
= {'hda': self
._str
_value
_hdata
,
77 'inl': self
._str
_value
_infolist
,
79 return '%s: %s' % (self
.objtype
, self
._obj
_cb
.get(self
.objtype
, self
._str
_value
_other
)())
82 class WeechatObjects(list):
83 def __init__(self
, separator
='\n'):
84 self
.separator
= separator
87 return self
.separator
.join([str(obj
) for obj
in self
])
91 def __init__(self
, size
, size_uncompressed
, compression
, uncompressed
, msgid
, objects
):
93 self
.size_uncompressed
= size_uncompressed
94 self
.compression
= compression
95 self
.uncompressed
= uncompressed
97 self
.objects
= objects
100 if self
.compression
!= 0:
101 return 'size: %d/%d (%d%%), id=\'%s\', objects:\n%s' % (
102 self
.size
, self
.size_uncompressed
,
103 100 - ((self
.size
* 100) // self
.size_uncompressed
),
104 self
.msgid
, self
.objects
)
106 return 'size: %d, id=\'%s\', objects:\n%s' % (self
.size
, self
.msgid
, self
.objects
)
110 """Decode binary message received from WeeChat/relay."""
113 self
._obj
_cb
= {'chr': self
._obj
_char
,
114 'int': self
._obj
_int
,
115 'lon': self
._obj
_long
,
116 'str': self
._obj
_str
,
117 'buf': self
._obj
_buffer
,
118 'ptr': self
._obj
_ptr
,
119 'tim': self
._obj
_time
,
120 'htb': self
._obj
_hashtable
,
121 'hda': self
._obj
_hdata
,
122 'inf': self
._obj
_info
,
123 'inl': self
._obj
_infolist
,
124 'arr': self
._obj
_array
,
128 """Read type in data (3 chars)."""
129 if len(self
.data
) < 3:
132 objtype
= str(self
.data
[0:3])
133 self
.data
= self
.data
[3:]
136 def _obj_len_data(self
, length_size
):
137 """Read length (1 or 4 bytes), then value with this length."""
138 if len(self
.data
) < length_size
:
142 length
= struct
.unpack('B', self
.data
[0:1])[0]
143 self
.data
= self
.data
[1:]
145 length
= self
._obj
_int
()
149 value
= self
.data
[0:length
]
150 self
.data
= self
.data
[length
:]
156 """Read a char in data."""
157 if len(self
.data
) < 1:
159 value
= struct
.unpack('b', self
.data
[0:1])[0]
160 self
.data
= self
.data
[1:]
164 """Read an integer in data (4 bytes)."""
165 if len(self
.data
) < 4:
168 value
= struct
.unpack('>i', self
.data
[0:4])[0]
169 self
.data
= self
.data
[4:]
173 """Read a long integer in data (length on 1 byte + value as string)."""
174 value
= self
._obj
_len
_data
(1)
177 return int(str(value
))
180 """Read a string in data (length on 4 bytes + content)."""
181 value
= self
._obj
_len
_data
(4)
186 def _obj_buffer(self
):
187 """Read a buffer in data (length on 4 bytes + data)."""
188 return self
._obj
_len
_data
(4)
191 """Read a pointer in data (length on 1 byte + value as string)."""
192 value
= self
._obj
_len
_data
(1)
195 return '0x%s' % str(value
)
198 """Read a time in data (length on 1 byte + value as string)."""
199 value
= self
._obj
_len
_data
(1)
202 return int(str(value
))
204 def _obj_hashtable(self
):
205 """Read a hashtable in data (type for keys + type for values + count + items)."""
206 type_keys
= self
._obj
_type
()
207 type_values
= self
._obj
_type
()
208 count
= self
._obj
_int
()
209 hashtable
= WeechatDict()
210 for i
in range(0, count
):
211 key
= self
._obj
_cb
[type_keys
]()
212 value
= self
._obj
_cb
[type_values
]()
213 hashtable
[key
] = value
216 def _obj_hdata(self
):
217 """Read a hdata in data."""
218 path
= self
._obj
_str
()
219 keys
= self
._obj
_str
()
220 count
= self
._obj
_int
()
221 list_path
= path
.split('/')
222 list_keys
= keys
.split(',')
224 dict_keys
= WeechatDict()
225 for key
in list_keys
:
226 items
= key
.split(':')
227 keys_types
.append(items
)
228 dict_keys
[items
[0]] = items
[1]
230 for i
in range(0, count
):
234 for p
in range(0, len(list_path
)):
235 pointers
.append(self
._obj
_ptr
())
236 for key
, objtype
in keys_types
:
237 item
[key
] = self
._obj
_cb
[objtype
]()
238 item
['__path'] = pointers
240 return {'path': list_path
,
247 """Read an info in data."""
248 name
= self
._obj
_str
()
249 value
= self
._obj
_str
()
252 def _obj_infolist(self
):
253 """Read an infolist in data."""
254 name
= self
._obj
_str
()
255 count_items
= self
._obj
_int
()
257 for i
in range(0, count_items
):
258 count_vars
= self
._obj
_int
()
259 variables
= WeechatDict()
260 for v
in range(0, count_vars
):
261 var_name
= self
._obj
_str
()
262 var_type
= self
._obj
_type
()
263 var_value
= self
._obj
_cb
[var_type
]()
264 variables
[var_name
] = var_value
265 items
.append(variables
)
266 return {'name': name, 'items': items}
268 def _obj_array(self
):
269 """Read an array of values in data."""
270 type_values
= self
._obj
_type
()
271 count_values
= self
._obj
_int
()
273 for i
in range(0, count_values
):
274 values
.append(self
._obj
_cb
[type_values
]())
277 def decode(self
, data
, separator
='\n'):
278 """Decode binary data and return list of objects."""
280 size
= len(self
.data
)
281 size_uncompressed
= size
283 # uncompress data (if it is compressed)
284 compression
= struct
.unpack('b', self
.data
[4:5])[0]
286 uncompressed
= zlib
.decompress(self
.data
[5:])
287 size_uncompressed
= len(uncompressed
) + 5
288 uncompressed
= '%s%s%s' % (struct
.pack('>i', size_uncompressed
), struct
.pack('b', 0), uncompressed
)
289 self
.data
= uncompressed
291 uncompressed
= self
.data
[:]
292 # skip length and compression flag
293 self
.data
= self
.data
[5:]
295 msgid
= self
._obj
_str
()
299 objects
= WeechatObjects(separator
=separator
)
300 while len(self
.data
) > 0:
301 objtype
= self
._obj
_type
()
302 value
= self
._obj
_cb
[objtype
]()
303 objects
.append(WeechatObject(objtype
, value
, separator
=separator
))
304 return WeechatMessage(size
, size_uncompressed
, compression
, uncompressed
, msgid
, objects
)
307 def hex_and_ascii(data
, bytes_per_line
=10):
308 """Convert a QByteArray to hex + ascii output."""
309 num_lines
= ((len(data
) - 1) // bytes_per_line
) + 1
313 for i
in range(0, num_lines
):
316 for char
in data
[i
*bytes_per_line
:(i
*bytes_per_line
)+bytes_per_line
]:
317 byte
= struct
.unpack('B', char
)[0]
318 str_hex
.append('%02X' % int(byte
))
319 if byte
>= 32 and byte
<= 127:
320 str_ascii
.append(char
)
322 str_ascii
.append('.')
323 fmt
= '%%-%ds %%s' % ((bytes_per_line
* 3) - 1)
324 lines
.append(fmt
% (' '.join(str_hex
), ''.join(str_ascii
)))
325 return '\n'.join(lines
)