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,))