1
# Copyright (C) 2006 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 (
31
def _recv_tuple(from_file):
32
req_line = from_file.readline()
33
return _decode_tuple(req_line)
36
def _decode_tuple(req_line):
37
if req_line == None or req_line == '':
39
if req_line[-1] != '\n':
40
raise errors.SmartProtocolError("request %r not terminated" % req_line)
41
return tuple(req_line[:-1].split('\x01'))
44
def _encode_tuple(args):
45
"""Encode the tuple args to a bytestream."""
46
return '\x01'.join(args) + '\n'
49
class SmartProtocolBase(object):
50
"""Methods common to client and server"""
52
# TODO: this only actually accomodates a single block; possibly should
53
# support multiple chunks?
54
def _encode_bulk_data(self, body):
55
"""Encode body as a bulk data chunk."""
56
return ''.join(('%d\n' % len(body), body, 'done\n'))
58
def _serialise_offsets(self, offsets):
59
"""Serialise a readv offset list."""
61
for start, length in offsets:
62
txt.append('%d,%d' % (start, length))
66
class SmartServerRequestProtocolOne(SmartProtocolBase):
67
"""Server-side encoding and decoding logic for smart version 1."""
69
def __init__(self, backing_transport, write_func):
70
self._backing_transport = backing_transport
71
self.excess_buffer = ''
72
self._finished = False
74
self.has_dispatched = False
76
self._body_decoder = None
77
self._write_func = write_func
79
def accept_bytes(self, bytes):
80
"""Take bytes, and advance the internal state machine appropriately.
82
:param bytes: must be a byte string
84
assert isinstance(bytes, str)
85
self.in_buffer += bytes
86
if not self.has_dispatched:
87
if '\n' not in self.in_buffer:
90
self.has_dispatched = True
92
first_line, self.in_buffer = self.in_buffer.split('\n', 1)
94
req_args = _decode_tuple(first_line)
95
self.request = request.SmartServerRequestHandler(
96
self._backing_transport, commands=vfs.vfs_commands)
97
self.request.dispatch_command(req_args[0], req_args[1:])
98
if self.request.finished_reading:
100
self.excess_buffer = self.in_buffer
102
self._send_response(self.request.response.args,
103
self.request.response.body)
104
except KeyboardInterrupt:
106
except Exception, exception:
107
# everything else: pass to client, flush, and quit
108
self._send_response(('error', str(exception)))
111
if self.has_dispatched:
113
# nothing to do.XXX: this routine should be a single state
115
self.excess_buffer += self.in_buffer
118
if self._body_decoder is None:
119
self._body_decoder = LengthPrefixedBodyDecoder()
120
self._body_decoder.accept_bytes(self.in_buffer)
121
self.in_buffer = self._body_decoder.unused_data
122
body_data = self._body_decoder.read_pending_data()
123
self.request.accept_body(body_data)
124
if self._body_decoder.finished_reading:
125
self.request.end_of_body()
126
assert self.request.finished_reading, \
127
"no more body, request not finished"
128
if self.request.response is not None:
129
self._send_response(self.request.response.args,
130
self.request.response.body)
131
self.excess_buffer = self.in_buffer
134
assert not self.request.finished_reading, \
135
"no response and we have finished reading."
137
def _send_response(self, args, body=None):
138
"""Send a smart server response down the output stream."""
139
assert not self._finished, 'response already sent'
140
self._finished = True
141
self._write_func(_encode_tuple(args))
143
assert isinstance(body, str), 'body must be a str'
144
bytes = self._encode_bulk_data(body)
145
self._write_func(bytes)
147
def next_read_size(self):
150
if self._body_decoder is None:
153
return self._body_decoder.next_read_size()
156
class LengthPrefixedBodyDecoder(object):
157
"""Decodes the length-prefixed bulk data."""
160
self.bytes_left = None
161
self.finished_reading = False
162
self.unused_data = ''
163
self.state_accept = self._state_accept_expecting_length
164
self.state_read = self._state_read_no_data
166
self._trailer_buffer = ''
168
def accept_bytes(self, bytes):
169
"""Decode as much of bytes as possible.
171
If 'bytes' contains too much data it will be appended to
174
finished_reading will be set when no more data is required. Further
175
data will be appended to self.unused_data.
177
# accept_bytes is allowed to change the state
178
current_state = self.state_accept
179
self.state_accept(bytes)
180
while current_state != self.state_accept:
181
current_state = self.state_accept
182
self.state_accept('')
184
def next_read_size(self):
185
if self.bytes_left is not None:
186
# Ideally we want to read all the remainder of the body and the
188
return self.bytes_left + 5
189
elif self.state_accept == self._state_accept_reading_trailer:
190
# Just the trailer left
191
return 5 - len(self._trailer_buffer)
192
elif self.state_accept == self._state_accept_expecting_length:
193
# There's still at least 6 bytes left ('\n' to end the length, plus
197
# Reading excess data. Either way, 1 byte at a time is fine.
200
def read_pending_data(self):
201
"""Return any pending data that has been decoded."""
202
return self.state_read()
204
def _state_accept_expecting_length(self, bytes):
205
self._in_buffer += bytes
206
pos = self._in_buffer.find('\n')
209
self.bytes_left = int(self._in_buffer[:pos])
210
self._in_buffer = self._in_buffer[pos+1:]
211
self.bytes_left -= len(self._in_buffer)
212
self.state_accept = self._state_accept_reading_body
213
self.state_read = self._state_read_in_buffer
215
def _state_accept_reading_body(self, bytes):
216
self._in_buffer += bytes
217
self.bytes_left -= len(bytes)
218
if self.bytes_left <= 0:
220
if self.bytes_left != 0:
221
self._trailer_buffer = self._in_buffer[self.bytes_left:]
222
self._in_buffer = self._in_buffer[:self.bytes_left]
223
self.bytes_left = None
224
self.state_accept = self._state_accept_reading_trailer
226
def _state_accept_reading_trailer(self, bytes):
227
self._trailer_buffer += bytes
228
# TODO: what if the trailer does not match "done\n"? Should this raise
229
# a ProtocolViolation exception?
230
if self._trailer_buffer.startswith('done\n'):
231
self.unused_data = self._trailer_buffer[len('done\n'):]
232
self.state_accept = self._state_accept_reading_unused
233
self.finished_reading = True
235
def _state_accept_reading_unused(self, bytes):
236
self.unused_data += bytes
238
def _state_read_no_data(self):
241
def _state_read_in_buffer(self):
242
result = self._in_buffer
247
# XXX: TODO: Create a SmartServerRequestHandler which will take the responsibility
248
# for delivering the data for a request. This could be done with as the
249
# StreamServer, though that would create conflation between request and response
250
# which may be undesirable.
253
class SmartClientRequestProtocolOne(SmartProtocolBase):
254
"""The client-side protocol for smart version 1."""
256
def __init__(self, request):
257
"""Construct a SmartClientRequestProtocolOne.
259
:param request: A SmartClientMediumRequest to serialise onto and
262
self._request = request
263
self._body_buffer = None
265
def call(self, *args):
266
bytes = _encode_tuple(args)
267
self._request.accept_bytes(bytes)
268
self._request.finished_writing()
270
def call_with_body_bytes(self, args, body):
271
"""Make a remote call of args with body bytes 'body'.
273
After calling this, call read_response_tuple to find the result out.
275
bytes = _encode_tuple(args)
276
self._request.accept_bytes(bytes)
277
bytes = self._encode_bulk_data(body)
278
self._request.accept_bytes(bytes)
279
self._request.finished_writing()
281
def call_with_body_readv_array(self, args, body):
282
"""Make a remote call with a readv array.
284
The body is encoded with one line per readv offset pair. The numbers in
285
each pair are separated by a comma, and no trailing \n is emitted.
287
bytes = _encode_tuple(args)
288
self._request.accept_bytes(bytes)
289
readv_bytes = self._serialise_offsets(body)
290
bytes = self._encode_bulk_data(readv_bytes)
291
self._request.accept_bytes(bytes)
292
self._request.finished_writing()
294
def cancel_read_body(self):
295
"""After expecting a body, a response code may indicate one otherwise.
297
This method lets the domain client inform the protocol that no body
298
will be transmitted. This is a terminal method: after calling it the
299
protocol is not able to be used further.
301
self._request.finished_reading()
303
def read_response_tuple(self, expect_body=False):
304
"""Read a response tuple from the wire.
306
This should only be called once.
308
result = self._recv_tuple()
310
self._request.finished_reading()
313
def read_body_bytes(self, count=-1):
314
"""Read bytes from the body, decoding into a byte stream.
316
We read all bytes at once to ensure we've checked the trailer for
317
errors, and then feed the buffer back as read_body_bytes is called.
319
if self._body_buffer is not None:
320
return self._body_buffer.read(count)
321
_body_decoder = LengthPrefixedBodyDecoder()
323
while not _body_decoder.finished_reading:
324
bytes_wanted = _body_decoder.next_read_size()
325
bytes = self._request.read_bytes(bytes_wanted)
326
_body_decoder.accept_bytes(bytes)
327
self._request.finished_reading()
328
self._body_buffer = StringIO(_body_decoder.read_pending_data())
329
# XXX: TODO check the trailer result.
330
return self._body_buffer.read(count)
332
def _recv_tuple(self):
333
"""Receive a tuple from the medium request."""
335
while not line or line[-1] != '\n':
336
# TODO: this is inefficient - but tuples are short.
337
new_char = self._request.read_bytes(1)
339
assert new_char != '', "end of file reading from server."
340
return _decode_tuple(line)
342
def query_version(self):
343
"""Return protocol version number of the server."""
345
resp = self.read_response_tuple()
346
if resp == ('ok', '1'):
349
raise errors.SmartProtocolError("bad response %r" % (resp,))