~ubuntu-branches/ubuntu/wily/remuco-server/wily

« back to all changes in this revision

Viewing changes to base/module/remuco/serial.py

  • Committer: Bazaar Package Importer
  • Author(s): Chow Loong Jin
  • Date: 2009-03-30 00:59:36 UTC
  • Revision ID: james.westby@ubuntu.com-20090330005936-hkxki384hm0d33gj
Tags: upstream-0.8.2.1
ImportĀ upstreamĀ versionĀ 0.8.2.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# =============================================================================
 
2
#
 
3
#    Remuco - A remote control system for media players.
 
4
#    Copyright (C) 2006-2009 Oben Sonne <obensonne@googlemail.com>
 
5
#
 
6
#    This file is part of Remuco.
 
7
#
 
8
#    Remuco is free software: you can redistribute it and/or modify
 
9
#    it under the terms of the GNU General Public License as published by
 
10
#    the Free Software Foundation, either version 3 of the License, or
 
11
#    (at your option) any later version.
 
12
#
 
13
#    Remuco is distributed in the hope that it will be useful,
 
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
#    GNU General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU General Public License
 
19
#    along with Remuco.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
# =============================================================================
 
22
 
 
23
import inspect
 
24
import struct
 
25
import array
 
26
import types
 
27
 
 
28
from remuco import log
 
29
 
 
30
TYPE_Y = 1
 
31
TYPE_I = 2
 
32
TYPE_B = 3
 
33
TYPE_S = 4
 
34
TYPE_AY = 5
 
35
TYPE_AI = 6
 
36
TYPE_AS = 7
 
37
TYPE_L = 8
 
38
TYPE_N = 9
 
39
TYPE_AN = 10
 
40
TYPE_AB = 11
 
41
TYPE_AL = 12
 
42
 
 
43
class Bin:
 
44
    
 
45
    NET_ENCODING = "UTF-8" # codec for data exchanged with clients
 
46
    NET_ENCODING_ALT = ("UTF-8", "UTF8", "utf-8", "utf8") # synonyms
 
47
    HOST_ENCODING = NET_ENCODING # will be updated with value from config file
 
48
    
 
49
    def __init__(self, buff=None):
 
50
        
 
51
        self.__data = buff or array.array('c')
 
52
        self.__off = 0
 
53
        
 
54
    def get_buff(self):
 
55
        if isinstance(self.__data, basestring):
 
56
            return self.__data
 
57
        elif isinstance(self.__data, array.array):
 
58
            return self.__data.tostring()
 
59
        else:
 
60
            log.error("** BUG ** unexpected buffer type")
 
61
        
 
62
    def read_boolean(self):
 
63
        
 
64
        b = self.read_byte()
 
65
        if b == 0:
 
66
            return False
 
67
        else:
 
68
            return True
 
69
 
 
70
    def read_byte(self):
 
71
        
 
72
        y = struct.unpack_from('b', self.__data, offset=self.__off)[0]
 
73
        self.__off += 1
 
74
        return y
 
75
        
 
76
    def read_short(self):
 
77
        
 
78
        n = struct.unpack_from('!h', self.__data, offset=self.__off)[0]
 
79
        self.__off += 2
 
80
        return n
 
81
    
 
82
    def read_int(self):
 
83
        
 
84
        i = struct.unpack_from('!i', self.__data, offset=self.__off)[0]
 
85
        self.__off += 4
 
86
        return i
 
87
    
 
88
    def read_long(self):
 
89
        
 
90
        l = struct.unpack_from('!q', self.__data, offset=self.__off)[0]
 
91
        self.__off += 8
 
92
        return l
 
93
    
 
94
    def read_string(self):
 
95
        """ Read a string.
 
96
        
 
97
        The read raw string will be converted from Bin.NET_ENCODING to
 
98
        Bin.HOST_ENCODING.
 
99
        """
 
100
        
 
101
        s = self.__read_string()
 
102
        
 
103
        if Bin.HOST_ENCODING not in Bin.NET_ENCODING_ALT:
 
104
            try:
 
105
                s = unicode(s, Bin.NET_ENCODING).encode(Bin.HOST_ENCODING)
 
106
            except UnicodeDecodeError, e:
 
107
                log.warning("could not decode '%s' with codec %s (%s)" %
 
108
                            (s, Bin.NET_ENCODING, e))
 
109
            except UnicodeEncodeError, e:
 
110
                log.warning("could not encode '%s' with codec %s (%s)" %
 
111
                            (s, Bin.HOST_ENCODING, e))
 
112
                
 
113
        return s
 
114
 
 
115
    def read_type(self, expected):
 
116
        
 
117
        type = self.read_byte()
 
118
         
 
119
        if type != expected:
 
120
            log.warning("bin data malformed (expected type %d, have %d)" %
 
121
                        (expected, type))
 
122
            return False
 
123
        else:
 
124
            return True
 
125
        
 
126
    def read_array_boolean(self):
 
127
        
 
128
        return self.__read_array(self.read_boolean)
 
129
 
 
130
    def read_array_byte(self):
 
131
        
 
132
        return self.__read_array(self.read_byte)
 
133
 
 
134
    def read_array_short(self):
 
135
        
 
136
        return self.__read_array(self.read_short)
 
137
    
 
138
    def read_array_int(self):
 
139
        
 
140
        return self.__read_array(self.read_int)
 
141
    
 
142
    def read_array_long(self):
 
143
        
 
144
        return self.__read_array(self.read_long)
 
145
    
 
146
    def read_array_string(self):
 
147
        
 
148
        return self.__read_array(self.read_string)
 
149
            
 
150
    def __read_string(self):
 
151
        """ Read a string as it is, i.e. without any codec conversion. """
 
152
        
 
153
        l = self.read_short()
 
154
        s = struct.unpack_from('%ds' % l, self.__data, offset=self.__off)[0]
 
155
        self.__off += l
 
156
        return s
 
157
        
 
158
    def __read_array(self, fn_read_element):
 
159
        
 
160
        num = self.read_int()
 
161
        
 
162
        a = []
 
163
        
 
164
        for i in range(0,num):
 
165
            
 
166
            a.append(fn_read_element())
 
167
            
 
168
        return a
 
169
    
 
170
    def get_unused_data(self):
 
171
        
 
172
        return len(self.__data) - self.__off
 
173
        
 
174
    def write_type(self, type):
 
175
        
 
176
        self.write_byte(type)
 
177
    
 
178
    def write_boolean(self, b):
 
179
        
 
180
        if b:
 
181
            self.write_byte(1)
 
182
        else:
 
183
            self.write_byte(0)
 
184
        
 
185
    def write_byte(self, y):
 
186
        
 
187
        if y is None: y = 0
 
188
        self.__data.extend(' ' * 1)
 
189
        struct.pack_into('b', self.__data, self.__off, y)
 
190
        self.__off += 1
 
191
 
 
192
    def write_short(self, n):
 
193
        
 
194
        if n is None: n = 0
 
195
        self.__data.extend(' ' * 2)
 
196
        struct.pack_into('!h', self.__data, self.__off, n)
 
197
        self.__off += 2
 
198
 
 
199
    def write_int(self, i):
 
200
        
 
201
        if i is None: i = 0
 
202
        self.__data.extend(' ' * 4)
 
203
        struct.pack_into('!i', self.__data, self.__off, i)
 
204
        self.__off += 4
 
205
 
 
206
    def write_long(self, l):
 
207
        
 
208
        if l is None: l = 0
 
209
        self.__data.extend(' ' * 8)
 
210
        struct.pack_into('!q', self.__data, self.__off, l)
 
211
        self.__off += 8
 
212
 
 
213
    def write_string(self, s):
 
214
        """ Write a string. 
 
215
        
 
216
        If the string is a unicode string, it will be encoded as a normal string
 
217
        in Bin.NET_ENCODING. If it already is a normal string it will be
 
218
        converted from Bin.HOST_ENCODING to Bin.NET_ENCODING.
 
219
        
 
220
        """
 
221
        if s is None:
 
222
            self.__write_string(s)
 
223
            return
 
224
        
 
225
        if isinstance(s, unicode):
 
226
            
 
227
            try:
 
228
                s = s.encode(Bin.NET_ENCODING)
 
229
            except UnicodeEncodeError, e:
 
230
                log.warning("could not encode '%s' with codec %s (%s)" %
 
231
                            (s, Bin.NET_ENCODING, e))
 
232
                s = str(s)
 
233
        
 
234
        elif Bin.HOST_ENCODING not in Bin.NET_ENCODING_ALT:
 
235
            log.debug("convert '%s' from %s to %s" %
 
236
                      (s, Bin.HOST_ENCODING, Bin.NET_ENCODING))
 
237
            try:
 
238
                s = unicode(s, Bin.HOST_ENCODING).encode(Bin.NET_ENCODING)
 
239
            except UnicodeDecodeError, e:
 
240
                log.warning("could not decode '%s' with codec %s (%s)" %
 
241
                            (s, Bin.HOST_ENCODING, e))
 
242
            except UnicodeEncodeError, e:
 
243
                log.warning("could not encode '%s' with codec %s (%s)" %
 
244
                            (s, Bin.NET_ENCODING, e))
 
245
            
 
246
        self.__write_string(s)
 
247
 
 
248
    def write_array_boolean(self, ba):
 
249
        
 
250
        self.__write_array(ba, self.write_boolean)
 
251
 
 
252
    def write_array_byte(self, ba):
 
253
        
 
254
        if isinstance(ba, str): # byte sequences often come as strings
 
255
            self.__write_string(ba, len_as_int=True)
 
256
        else:
 
257
            self.__write_array(ba, self.write_byte)
 
258
 
 
259
    def write_array_short(self, na):
 
260
        
 
261
        self.__write_array(na, self.write_short)
 
262
 
 
263
    def write_array_int(self, ia):
 
264
        
 
265
        self.__write_array(ia, self.write_int)
 
266
 
 
267
    def write_array_long(self, ia):
 
268
        
 
269
        self.__write_array(ia, self.write_long)
 
270
 
 
271
    def write_array_string(self, sa):
 
272
        
 
273
        self.__write_array(sa, self.write_string)
 
274
 
 
275
    def __write_string(self, s, len_as_int=False):
 
276
        """ Write a string. 
 
277
        
 
278
        The string is written as is, i.e. there is no codec conversion.
 
279
        """
 
280
        
 
281
        if s is None:
 
282
            s = ""
 
283
        
 
284
        l = len(s)
 
285
        
 
286
        if len_as_int:
 
287
            self.write_int(l)
 
288
        else:
 
289
            self.write_short(l)
 
290
        
 
291
        self.__data.extend(' ' * l)
 
292
        struct.pack_into('%ds' % l, self.__data, self.__off, s)
 
293
        self.__off += l
 
294
        
 
295
    def __write_array(self, a, fn_element_write):
 
296
        
 
297
        if a is None:
 
298
            l = 0
 
299
        else:
 
300
            l = len(a)
 
301
        
 
302
        self.write_int(l)
 
303
        
 
304
        for i in range(l):
 
305
            
 
306
            fn_element_write(a[i])
 
307
 
 
308
class Serializable(object):
 
309
 
 
310
    def get_fmt(self):
 
311
        
 
312
        raise NotImplementedError
 
313
        
 
314
    def get_data(self):
 
315
 
 
316
        raise NotImplementedError
 
317
        
 
318
    def set_data(self, data):
 
319
 
 
320
        raise NotImplementedError
 
321
    
 
322
def pack(serializable):
 
323
 
 
324
    fmt = serializable.get_fmt()
 
325
    
 
326
    data = serializable.get_data()
 
327
    
 
328
    if len(fmt) != len(data):
 
329
        log.error("** BUG ** format string and data differ in length")
 
330
        return None
 
331
        
 
332
    #log.debug("data to pack: %s" % str(data))
 
333
 
 
334
    bin = Bin()
 
335
    
 
336
    try:
 
337
 
 
338
        for i in range(0,len(fmt)):
 
339
            
 
340
            type = fmt[i]
 
341
            
 
342
            bin.write_byte(type)
 
343
            
 
344
            if type == TYPE_Y:
 
345
                
 
346
                bin.write_byte(data[i])
 
347
                
 
348
            elif type == TYPE_B:
 
349
                
 
350
                bin.write_boolean(data[i])
 
351
        
 
352
            elif type == TYPE_N:
 
353
                
 
354
                bin.write_short(data[i])
 
355
                
 
356
            elif type == TYPE_I:
 
357
                
 
358
                bin.write_int(data[i])
 
359
                
 
360
            elif type == TYPE_L:
 
361
                
 
362
                bin.write_long(data[i])
 
363
                
 
364
            elif type == TYPE_S:
 
365
                
 
366
                bin.write_string(data[i])
 
367
                
 
368
            elif type == TYPE_AB:
 
369
                
 
370
                bin.write_array_boolean(data[i])
 
371
                
 
372
            elif type == TYPE_AY:
 
373
                
 
374
                bin.write_array_byte(data[i])
 
375
                
 
376
            elif type == TYPE_AN:
 
377
                
 
378
                bin.write_array_short(data[i])
 
379
 
 
380
            elif type == TYPE_AI:
 
381
                
 
382
                bin.write_array_int(data[i])
 
383
 
 
384
            elif type == TYPE_AL:
 
385
                
 
386
                bin.write_array_long(data[i])
 
387
 
 
388
            elif type == TYPE_AS:
 
389
                
 
390
                bin.write_array_string(data[i])
 
391
 
 
392
            else:
 
393
                log.error("** BUG ** unknown type (%d) in format string" % type)
 
394
                return None
 
395
        
 
396
    except struct.error, e:
 
397
        
 
398
        log.exception("** BUG ** %s" % e)
 
399
        
 
400
        return None
 
401
    
 
402
    return bin.get_buff()
 
403
 
 
404
def unpack(serializable, bytes):
 
405
    """ Deserialize a Serializable.
 
406
    
 
407
    @param serializable:
 
408
        the Serializable to apply the binary data to (may be a class, in which
 
409
        case a new instance of this class is created)
 
410
    @param bytes:
 
411
        binary data (serialized Serializable)
 
412
    
 
413
    @return: 'serializable' itself if it is an instance of Serializable, a new
 
414
        instance of 'serializable' if it is a class or None if an error
 
415
        occurred
 
416
    """
 
417
    
 
418
    if inspect.isclass(serializable):
 
419
        serializable = serializable()
 
420
    
 
421
    fmt = serializable.get_fmt()
 
422
    
 
423
    if fmt and not bytes:
 
424
        log.warning("there is no data to unpack")
 
425
        return None
 
426
    
 
427
    data = []
 
428
    
 
429
    bin = Bin(buff=bytes)
 
430
    
 
431
    try:
 
432
 
 
433
        for type in fmt:
 
434
            
 
435
            if not bin.read_type(type):
 
436
                return None
 
437
            
 
438
            if type == TYPE_Y:
 
439
                
 
440
                data.append(bin.read_byte())
 
441
                
 
442
            elif type == TYPE_B:
 
443
                
 
444
                data.append(bin.read_boolean())
 
445
        
 
446
            elif type == TYPE_N:
 
447
                
 
448
                data.append(bin.read_short())
 
449
                
 
450
            elif type == TYPE_I:
 
451
                
 
452
                data.append(bin.read_int())
 
453
                
 
454
            elif type == TYPE_L:
 
455
                
 
456
                data.append(bin.read_long())
 
457
                
 
458
            elif type == TYPE_S:
 
459
                
 
460
                data.append(bin.read_string())
 
461
                
 
462
            elif type == TYPE_AB:
 
463
                
 
464
                data.append(bin.read_array_boolean())
 
465
                
 
466
            elif type == TYPE_AY:
 
467
                
 
468
                data.append(bin.read_array_byte())
 
469
                
 
470
            elif type == TYPE_AN:
 
471
                
 
472
                data.append(bin.read_array_short())
 
473
 
 
474
            elif type == TYPE_AI:
 
475
                
 
476
                data.append(bin.read_array_int())
 
477
 
 
478
            elif type == TYPE_AL:
 
479
                
 
480
                data.append(bin.read_array_long())
 
481
 
 
482
            elif type == TYPE_AS:
 
483
                
 
484
                data.append(bin.read_array_string())
 
485
 
 
486
            else:
 
487
                
 
488
                log.warning("bin data malformed (unknown data type: %d)" % type)
 
489
                return None
 
490
        
 
491
    except struct.error, e:
 
492
        
 
493
        log.warning("bin data malformed (%s)" % e)
 
494
        
 
495
        return None
 
496
    
 
497
    unused = bin.get_unused_data()
 
498
    if unused:
 
499
        log.warning("there are %d unused bytes" % unused)
 
500
        return None
 
501
    
 
502
    serializable.set_data(data)
 
503
    
 
504
    #log.debug("unpacked data  : %s" % str(data))
 
505
 
 
506
    return serializable
 
507
 
 
508