1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Wire-level encoding and decoding of requests and responses for the smart
22
from cStringIO import StringIO
24
from bzrlib import errors
25
from bzrlib.smart import request
28
def _recv_tuple(from_file):
29
req_line = from_file.readline()
30
return _decode_tuple(req_line)
33
def _decode_tuple(req_line):
34
if req_line == None or req_line == '':
36
if req_line[-1] != '\n':
37
raise errors.SmartProtocolError("request %r not terminated" % req_line)
38
return tuple(req_line[:-1].split('\x01'))
41
def _encode_tuple(args):
42
"""Encode the tuple args to a bytestream."""
43
return '\x01'.join(args) + '\n'
46
class SmartProtocolBase(object):
47
"""Methods common to client and server"""
49
# TODO: this only actually accomodates a single block; possibly should
50
# support multiple chunks?
51
def _encode_bulk_data(self, body):
52
"""Encode body as a bulk data chunk."""
53
return ''.join(('%d\n' % len(body), body, 'done\n'))
55
def _serialise_offsets(self, offsets):
56
"""Serialise a readv offset list."""
58
for start, length in offsets:
59
txt.append('%d,%d' % (start, length))
63
class SmartServerRequestProtocolOne(SmartProtocolBase):
64
"""Server-side encoding and decoding logic for smart version 1."""
66
def __init__(self, backing_transport, write_func):
67
self._backing_transport = backing_transport
68
self.excess_buffer = ''
69
self._finished = False
71
self.has_dispatched = False
73
self._body_decoder = None
74
self._write_func = write_func
76
def accept_bytes(self, bytes):
77
"""Take bytes, and advance the internal state machine appropriately.
79
:param bytes: must be a byte string
81
assert isinstance(bytes, str)
82
self.in_buffer += bytes
83
if not self.has_dispatched:
84
if '\n' not in self.in_buffer:
87
self.has_dispatched = True
89
first_line, self.in_buffer = self.in_buffer.split('\n', 1)
91
req_args = _decode_tuple(first_line)
92
self.request = request.SmartServerRequestHandler(
93
self._backing_transport, commands=request.request_handlers)
94
self.request.dispatch_command(req_args[0], req_args[1:])
95
if self.request.finished_reading:
97
self.excess_buffer = self.in_buffer
99
self._send_response(self.request.response.args,
100
self.request.response.body)
101
except KeyboardInterrupt:
103
except Exception, exception:
104
# everything else: pass to client, flush, and quit
105
self._send_response(('error', str(exception)))
108
if self.has_dispatched:
110
# nothing to do.XXX: this routine should be a single state
112
self.excess_buffer += self.in_buffer
115
if self._body_decoder is None:
116
self._body_decoder = LengthPrefixedBodyDecoder()
117
self._body_decoder.accept_bytes(self.in_buffer)
118
self.in_buffer = self._body_decoder.unused_data
119
body_data = self._body_decoder.read_pending_data()
120
self.request.accept_body(body_data)
121
if self._body_decoder.finished_reading:
122
self.request.end_of_body()
123
assert self.request.finished_reading, \
124
"no more body, request not finished"
125
if self.request.response is not None:
126
self._send_response(self.request.response.args,
127
self.request.response.body)
128
self.excess_buffer = self.in_buffer
131
assert not self.request.finished_reading, \
132
"no response and we have finished reading."
134
def _send_response(self, args, body=None):
135
"""Send a smart server response down the output stream."""
136
assert not self._finished, 'response already sent'
137
self._finished = True
138
self._write_func(_encode_tuple(args))
140
assert isinstance(body, str), 'body must be a str'
141
bytes = self._encode_bulk_data(body)
142
self._write_func(bytes)
144
def next_read_size(self):
147
if self._body_decoder is None:
150
return self._body_decoder.next_read_size()
153
class LengthPrefixedBodyDecoder(object):
154
"""Decodes the length-prefixed bulk data."""
157
self.bytes_left = None
158
self.finished_reading = False
159
self.unused_data = ''
160
self.state_accept = self._state_accept_expecting_length
161
self.state_read = self._state_read_no_data
163
self._trailer_buffer = ''
165
def accept_bytes(self, bytes):
166
"""Decode as much of bytes as possible.
168
If 'bytes' contains too much data it will be appended to
171
finished_reading will be set when no more data is required. Further
172
data will be appended to self.unused_data.
174
# accept_bytes is allowed to change the state
175
current_state = self.state_accept
176
self.state_accept(bytes)
177
while current_state != self.state_accept:
178
current_state = self.state_accept
179
self.state_accept('')
181
def next_read_size(self):
182
if self.bytes_left is not None:
183
# Ideally we want to read all the remainder of the body and the
185
return self.bytes_left + 5
186
elif self.state_accept == self._state_accept_reading_trailer:
187
# Just the trailer left
188
return 5 - len(self._trailer_buffer)
189
elif self.state_accept == self._state_accept_expecting_length:
190
# There's still at least 6 bytes left ('\n' to end the length, plus
194
# Reading excess data. Either way, 1 byte at a time is fine.
197
def read_pending_data(self):
198
"""Return any pending data that has been decoded."""
199
return self.state_read()
201
def _state_accept_expecting_length(self, bytes):
202
self._in_buffer += bytes
203
pos = self._in_buffer.find('\n')
206
self.bytes_left = int(self._in_buffer[:pos])
207
self._in_buffer = self._in_buffer[pos+1:]
208
self.bytes_left -= len(self._in_buffer)
209
self.state_accept = self._state_accept_reading_body
210
self.state_read = self._state_read_in_buffer
212
def _state_accept_reading_body(self, bytes):
213
self._in_buffer += bytes
214
self.bytes_left -= len(bytes)
215
if self.bytes_left <= 0:
217
if self.bytes_left != 0:
218
self._trailer_buffer = self._in_buffer[self.bytes_left:]
219
self._in_buffer = self._in_buffer[:self.bytes_left]
220
self.bytes_left = None
221
self.state_accept = self._state_accept_reading_trailer
223
def _state_accept_reading_trailer(self, bytes):
224
self._trailer_buffer += bytes
225
# TODO: what if the trailer does not match "done\n"? Should this raise
226
# a ProtocolViolation exception?
227
if self._trailer_buffer.startswith('done\n'):
228
self.unused_data = self._trailer_buffer[len('done\n'):]
229
self.state_accept = self._state_accept_reading_unused
230
self.finished_reading = True
232
def _state_accept_reading_unused(self, bytes):
233
self.unused_data += bytes
235
def _state_read_no_data(self):
238
def _state_read_in_buffer(self):
239
result = self._in_buffer
244
class SmartClientRequestProtocolOne(SmartProtocolBase):
245
"""The client-side protocol for smart version 1."""
247
def __init__(self, request):
248
"""Construct a SmartClientRequestProtocolOne.
250
:param request: A SmartClientMediumRequest to serialise onto and
253
self._request = request
254
self._body_buffer = None
256
def call(self, *args):
257
bytes = _encode_tuple(args)
258
self._request.accept_bytes(bytes)
259
self._request.finished_writing()
261
def call_with_body_bytes(self, args, body):
262
"""Make a remote call of args with body bytes 'body'.
264
After calling this, call read_response_tuple to find the result out.
266
bytes = _encode_tuple(args)
267
self._request.accept_bytes(bytes)
268
bytes = self._encode_bulk_data(body)
269
self._request.accept_bytes(bytes)
270
self._request.finished_writing()
272
def call_with_body_readv_array(self, args, body):
273
"""Make a remote call with a readv array.
275
The body is encoded with one line per readv offset pair. The numbers in
276
each pair are separated by a comma, and no trailing \n is emitted.
278
bytes = _encode_tuple(args)
279
self._request.accept_bytes(bytes)
280
readv_bytes = self._serialise_offsets(body)
281
bytes = self._encode_bulk_data(readv_bytes)
282
self._request.accept_bytes(bytes)
283
self._request.finished_writing()
285
def cancel_read_body(self):
286
"""After expecting a body, a response code may indicate one otherwise.
288
This method lets the domain client inform the protocol that no body
289
will be transmitted. This is a terminal method: after calling it the
290
protocol is not able to be used further.
292
self._request.finished_reading()
294
def read_response_tuple(self, expect_body=False):
295
"""Read a response tuple from the wire.
297
This should only be called once.
299
result = self._recv_tuple()
301
self._request.finished_reading()
304
def read_body_bytes(self, count=-1):
305
"""Read bytes from the body, decoding into a byte stream.
307
We read all bytes at once to ensure we've checked the trailer for
308
errors, and then feed the buffer back as read_body_bytes is called.
310
if self._body_buffer is not None:
311
return self._body_buffer.read(count)
312
_body_decoder = LengthPrefixedBodyDecoder()
314
while not _body_decoder.finished_reading:
315
bytes_wanted = _body_decoder.next_read_size()
316
bytes = self._request.read_bytes(bytes_wanted)
317
_body_decoder.accept_bytes(bytes)
318
self._request.finished_reading()
319
self._body_buffer = StringIO(_body_decoder.read_pending_data())
320
# XXX: TODO check the trailer result.
321
return self._body_buffer.read(count)
323
def _recv_tuple(self):
324
"""Receive a tuple from the medium request."""
326
while not line or line[-1] != '\n':
327
# TODO: this is inefficient - but tuples are short.
328
new_char = self._request.read_bytes(1)
330
assert new_char != '', "end of file reading from server."
331
return _decode_tuple(line)
333
def query_version(self):
334
"""Return protocol version number of the server."""
336
resp = self.read_response_tuple()
337
if resp == ('ok', '1'):
340
raise errors.SmartProtocolError("bad response %r" % (resp,))