1"""Implements (a subset of) Sun XDR -- eXternal Data Representation.
2
3See: RFC 1014
4
5"""
6
7import struct
8try:
9    from cStringIO import StringIO as _StringIO
10except ImportError:
11    from StringIO import StringIO as _StringIO
12from functools import wraps
13
14__all__ = ["Error", "Packer", "Unpacker", "ConversionError"]
15
16# exceptions
17class Error(Exception):
18    """Exception class for this module. Use:
19
20    except xdrlib.Error, var:
21        # var has the Error instance for the exception
22
23    Public ivars:
24        msg -- contains the message
25
26    """
27    def __init__(self, msg):
28        self.msg = msg
29    def __repr__(self):
30        return repr(self.msg)
31    def __str__(self):
32        return str(self.msg)
33
34
35class ConversionError(Error):
36    pass
37
38def raise_conversion_error(function):
39    """ Wrap any raised struct.errors in a ConversionError. """
40
41    @wraps(function)
42    def result(self, value):
43        try:
44            return function(self, value)
45        except struct.error as e:
46            raise ConversionError(e.args[0])
47    return result
48
49
50class Packer:
51    """Pack various data representations into a buffer."""
52
53    def __init__(self):
54        self.reset()
55
56    def reset(self):
57        self.__buf = _StringIO()
58
59    def get_buffer(self):
60        return self.__buf.getvalue()
61    # backwards compatibility
62    get_buf = get_buffer
63
64    @raise_conversion_error
65    def pack_uint(self, x):
66        self.__buf.write(struct.pack('>L', x))
67
68    @raise_conversion_error
69    def pack_int(self, x):
70        self.__buf.write(struct.pack('>l', x))
71
72    pack_enum = pack_int
73
74    def pack_bool(self, x):
75        if x: self.__buf.write('\0\0\0\1')
76        else: self.__buf.write('\0\0\0\0')
77
78    def pack_uhyper(self, x):
79        try:
80            self.pack_uint(x>>32 & 0xffffffffL)
81        except (TypeError, struct.error) as e:
82            raise ConversionError(e.args[0])
83        try:
84            self.pack_uint(x & 0xffffffffL)
85        except (TypeError, struct.error) as e:
86            raise ConversionError(e.args[0])
87
88    pack_hyper = pack_uhyper
89
90    @raise_conversion_error
91    def pack_float(self, x):
92        self.__buf.write(struct.pack('>f', x))
93
94    @raise_conversion_error
95    def pack_double(self, x):
96        self.__buf.write(struct.pack('>d', x))
97
98    def pack_fstring(self, n, s):
99        if n < 0:
100            raise ValueError, 'fstring size must be nonnegative'
101        data = s[:n]
102        n = ((n+3)//4)*4
103        data = data + (n - len(data)) * '\0'
104        self.__buf.write(data)
105
106    pack_fopaque = pack_fstring
107
108    def pack_string(self, s):
109        n = len(s)
110        self.pack_uint(n)
111        self.pack_fstring(n, s)
112
113    pack_opaque = pack_string
114    pack_bytes = pack_string
115
116    def pack_list(self, list, pack_item):
117        for item in list:
118            self.pack_uint(1)
119            pack_item(item)
120        self.pack_uint(0)
121
122    def pack_farray(self, n, list, pack_item):
123        if len(list) != n:
124            raise ValueError, 'wrong array size'
125        for item in list:
126            pack_item(item)
127
128    def pack_array(self, list, pack_item):
129        n = len(list)
130        self.pack_uint(n)
131        self.pack_farray(n, list, pack_item)
132
133
134
135class Unpacker:
136    """Unpacks various data representations from the given buffer."""
137
138    def __init__(self, data):
139        self.reset(data)
140
141    def reset(self, data):
142        self.__buf = data
143        self.__pos = 0
144
145    def get_position(self):
146        return self.__pos
147
148    def set_position(self, position):
149        self.__pos = position
150
151    def get_buffer(self):
152        return self.__buf
153
154    def done(self):
155        if self.__pos < len(self.__buf):
156            raise Error('unextracted data remains')
157
158    def unpack_uint(self):
159        i = self.__pos
160        self.__pos = j = i+4
161        data = self.__buf[i:j]
162        if len(data) < 4:
163            raise EOFError
164        x = struct.unpack('>L', data)[0]
165        try:
166            return int(x)
167        except OverflowError:
168            return x
169
170    def unpack_int(self):
171        i = self.__pos
172        self.__pos = j = i+4
173        data = self.__buf[i:j]
174        if len(data) < 4:
175            raise EOFError
176        return struct.unpack('>l', data)[0]
177
178    unpack_enum = unpack_int
179
180    def unpack_bool(self):
181        return bool(self.unpack_int())
182
183    def unpack_uhyper(self):
184        hi = self.unpack_uint()
185        lo = self.unpack_uint()
186        return long(hi)<<32 | lo
187
188    def unpack_hyper(self):
189        x = self.unpack_uhyper()
190        if x >= 0x8000000000000000L:
191            x = x - 0x10000000000000000L
192        return x
193
194    def unpack_float(self):
195        i = self.__pos
196        self.__pos = j = i+4
197        data = self.__buf[i:j]
198        if len(data) < 4:
199            raise EOFError
200        return struct.unpack('>f', data)[0]
201
202    def unpack_double(self):
203        i = self.__pos
204        self.__pos = j = i+8
205        data = self.__buf[i:j]
206        if len(data) < 8:
207            raise EOFError
208        return struct.unpack('>d', data)[0]
209
210    def unpack_fstring(self, n):
211        if n < 0:
212            raise ValueError, 'fstring size must be nonnegative'
213        i = self.__pos
214        j = i + (n+3)//4*4
215        if j > len(self.__buf):
216            raise EOFError
217        self.__pos = j
218        return self.__buf[i:i+n]
219
220    unpack_fopaque = unpack_fstring
221
222    def unpack_string(self):
223        n = self.unpack_uint()
224        return self.unpack_fstring(n)
225
226    unpack_opaque = unpack_string
227    unpack_bytes = unpack_string
228
229    def unpack_list(self, unpack_item):
230        list = []
231        while 1:
232            x = self.unpack_uint()
233            if x == 0: break
234            if x != 1:
235                raise ConversionError, '0 or 1 expected, got %r' % (x,)
236            item = unpack_item()
237            list.append(item)
238        return list
239
240    def unpack_farray(self, n, unpack_item):
241        list = []
242        for i in range(n):
243            list.append(unpack_item())
244        return list
245
246    def unpack_array(self, unpack_item):
247        n = self.unpack_uint()
248        return self.unpack_farray(n, unpack_item)
249