~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

  • Committer: Martin Pool
  • Date: 2008-07-14 07:39:30 UTC
  • mto: This revision was merged to the branch mainline in revision 3537.
  • Revision ID: mbp@sourcefrog.net-20080714073930-z8nl2c44jal0eozs
Update test for knit.check() to expect it to recurse into fallback vfs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""RemoteTransport client for the smart-server.
18
18
 
20
20
imported from bzrlib.smart.
21
21
"""
22
22
 
23
 
from __future__ import absolute_import
24
 
 
25
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
26
24
 
27
25
from cStringIO import StringIO
30
28
    config,
31
29
    debug,
32
30
    errors,
33
 
    remote,
34
31
    trace,
35
32
    transport,
36
33
    urlutils,
37
34
    )
38
35
from bzrlib.smart import client, medium
 
36
from bzrlib.symbol_versioning import (deprecated_method, one_four)
39
37
 
40
38
 
41
39
class _SmartStat(object):
53
51
 
54
52
    The connection has a notion of the current directory to which it's
55
53
    connected; this is incorporated in filenames passed to the server.
56
 
 
57
 
    This supports some higher-level RPC operations and can also be treated
 
54
    
 
55
    This supports some higher-level RPC operations and can also be treated 
58
56
    like a Transport to do file-like operations.
59
57
 
60
58
    The connection can be made over a tcp socket, an ssh pipe or a series of
62
60
    RemoteTCPTransport, etc.
63
61
    """
64
62
 
65
 
    # When making a readv request, cap it at requesting 5MB of data
66
 
    _max_readv_bytes = 5*1024*1024
67
 
 
68
63
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
69
64
    # responsibilities: Put those on SmartClient or similar. This is vital for
70
65
    # the ability to support multiple versions of the smart protocol over time:
71
 
    # RemoteTransport is an adapter from the Transport object model to the
 
66
    # RemoteTransport is an adapter from the Transport object model to the 
72
67
    # SmartClient model, not an encoder.
73
68
 
74
69
    # FIXME: the medium parameter should be private, only the tests requires
91
86
            should only be used for testing purposes; normally this is
92
87
            determined from the medium.
93
88
        """
94
 
        super(RemoteTransport, self).__init__(
95
 
            url, _from_transport=_from_transport)
 
89
        super(RemoteTransport, self).__init__(url,
 
90
                                              _from_transport=_from_transport)
96
91
 
97
92
        # The medium is the connection, except when we need to share it with
98
93
        # other objects (RemoteBzrDir, RemoteRepository etc). In these cases
99
94
        # what we want to share is really the shared connection.
100
95
 
101
 
        if (_from_transport is not None
102
 
            and isinstance(_from_transport, RemoteTransport)):
103
 
            _client = _from_transport._client
104
 
        elif _from_transport is None:
 
96
        if _from_transport is None:
105
97
            # If no _from_transport is specified, we need to intialize the
106
98
            # shared medium.
107
99
            credentials = None
137
129
        # No credentials
138
130
        return None, None
139
131
 
140
 
    def _report_activity(self, bytes, direction):
141
 
        """See Transport._report_activity.
142
 
 
143
 
        Does nothing; the smart medium will report activity triggered by a
144
 
        RemoteTransport.
145
 
        """
146
 
        pass
147
 
 
148
132
    def is_readonly(self):
149
133
        """Smart server transport can do read/write file operations."""
150
134
        try:
159
143
        elif resp == ('no', ):
160
144
            return False
161
145
        else:
162
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
146
            self._translate_error(resp)
 
147
        raise errors.UnexpectedSmartServerResponse(resp)
163
148
 
164
149
    def get_smart_client(self):
165
150
        return self._get_connection()
167
152
    def get_smart_medium(self):
168
153
        return self._get_connection()
169
154
 
 
155
    @deprecated_method(one_four)
 
156
    def get_shared_medium(self):
 
157
        return self._get_shared_connection()
 
158
 
170
159
    def _remote_path(self, relpath):
171
160
        """Returns the Unicode version of the absolute path for relpath."""
172
 
        return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
 
161
        return self._combine_paths(self._path, relpath)
173
162
 
174
163
    def _call(self, method, *args):
175
 
        resp = self._call2(method, *args)
176
 
        self._ensure_ok(resp)
 
164
        try:
 
165
            resp = self._call2(method, *args)
 
166
        except errors.ErrorFromSmartServer, err:
 
167
            self._translate_error(err.error_tuple)
 
168
        self._translate_error(resp)
177
169
 
178
170
    def _call2(self, method, *args):
179
171
        """Call a method on the remote server."""
180
172
        try:
181
173
            return self._client.call(method, *args)
182
174
        except errors.ErrorFromSmartServer, err:
183
 
            # The first argument, if present, is always a path.
184
 
            if args:
185
 
                context = {'relpath': args[0]}
186
 
            else:
187
 
                context = {}
188
 
            self._translate_error(err, **context)
 
175
            self._translate_error(err.error_tuple)
189
176
 
190
177
    def _call_with_body_bytes(self, method, args, body):
191
178
        """Call a method on the remote server with body bytes."""
192
179
        try:
193
180
            return self._client.call_with_body_bytes(method, args, body)
194
181
        except errors.ErrorFromSmartServer, err:
195
 
            # The first argument, if present, is always a path.
196
 
            if args:
197
 
                context = {'relpath': args[0]}
198
 
            else:
199
 
                context = {}
200
 
            self._translate_error(err, **context)
 
182
            self._translate_error(err.error_tuple)
201
183
 
202
184
    def has(self, relpath):
203
185
        """Indicate whether a remote file of the given name exists or not.
210
192
        elif resp == ('no', ):
211
193
            return False
212
194
        else:
213
 
            raise errors.UnexpectedSmartServerResponse(resp)
 
195
            self._translate_error(resp)
214
196
 
215
197
    def get(self, relpath):
216
198
        """Return file-like object reading the contents of a remote file.
217
 
 
 
199
        
218
200
        :see: Transport.get_bytes()/get_file()
219
201
        """
220
202
        return StringIO(self.get_bytes(relpath))
224
206
        try:
225
207
            resp, response_handler = self._client.call_expecting_body('get', remote)
226
208
        except errors.ErrorFromSmartServer, err:
227
 
            self._translate_error(err, relpath)
 
209
            self._translate_error(err.error_tuple, relpath)
228
210
        if resp != ('ok', ):
229
211
            response_handler.cancel_read_body()
230
212
            raise errors.UnexpectedSmartServerResponse(resp)
239
221
    def mkdir(self, relpath, mode=None):
240
222
        resp = self._call2('mkdir', self._remote_path(relpath),
241
223
            self._serialise_optional_mode(mode))
 
224
        self._translate_error(resp)
242
225
 
243
226
    def open_write_stream(self, relpath, mode=None):
244
227
        """See Transport.open_write_stream."""
247
230
        transport._file_streams[self.abspath(relpath)] = result
248
231
        return result
249
232
 
250
 
    def put_bytes(self, relpath, raw_bytes, mode=None):
251
 
        if not isinstance(raw_bytes, str):
252
 
            raise TypeError(
253
 
                'raw_bytes must be a plain string, not %s' % type(raw_bytes))
254
 
        resp = self._call_with_body_bytes(
255
 
            'put',
 
233
    def put_bytes(self, relpath, upload_contents, mode=None):
 
234
        # FIXME: upload_file is probably not safe for non-ascii characters -
 
235
        # should probably just pass all parameters as length-delimited
 
236
        # strings?
 
237
        if type(upload_contents) is unicode:
 
238
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
239
            # compatible with other transports.
 
240
            raise UnicodeEncodeError(
 
241
                'undefined', upload_contents, 0, 1,
 
242
                'put_bytes must be given bytes, not unicode.')
 
243
        resp = self._call_with_body_bytes('put',
256
244
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
257
 
            raw_bytes)
258
 
        self._ensure_ok(resp)
259
 
        return len(raw_bytes)
 
245
            upload_contents)
 
246
        self._translate_error(resp)
 
247
        return len(upload_contents)
260
248
 
261
 
    def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
 
249
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
262
250
                             create_parent_dir=False,
263
251
                             dir_mode=None):
264
252
        """See Transport.put_bytes_non_atomic."""
271
259
            'put_non_atomic',
272
260
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
273
261
             create_parent_str, self._serialise_optional_mode(dir_mode)),
274
 
            raw_bytes)
275
 
        self._ensure_ok(resp)
 
262
            bytes)
 
263
        self._translate_error(resp)
276
264
 
277
265
    def put_file(self, relpath, upload_file, mode=None):
278
266
        # its not ideal to seek back, but currently put_non_atomic_file depends
294
282
 
295
283
    def append_file(self, relpath, from_file, mode=None):
296
284
        return self.append_bytes(relpath, from_file.read(), mode)
297
 
 
 
285
        
298
286
    def append_bytes(self, relpath, bytes, mode=None):
299
287
        resp = self._call_with_body_bytes(
300
288
            'append',
302
290
            bytes)
303
291
        if resp[0] == 'appended':
304
292
            return int(resp[1])
305
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
293
        self._translate_error(resp)
306
294
 
307
295
    def delete(self, relpath):
308
296
        resp = self._call2('delete', self._remote_path(relpath))
309
 
        self._ensure_ok(resp)
 
297
        self._translate_error(resp)
310
298
 
311
299
    def external_url(self):
312
300
        """See bzrlib.transport.Transport.external_url."""
316
304
    def recommended_page_size(self):
317
305
        """Return the recommended page size for this transport."""
318
306
        return 64 * 1024
319
 
 
 
307
        
320
308
    def _readv(self, relpath, offsets):
321
309
        if not offsets:
322
310
            return
324
312
        offsets = list(offsets)
325
313
 
326
314
        sorted_offsets = sorted(offsets)
 
315
        # turn the list of offsets into a stack
 
316
        offset_stack = iter(offsets)
 
317
        cur_offset_and_size = offset_stack.next()
327
318
        coalesced = list(self._coalesce_offsets(sorted_offsets,
328
319
                               limit=self._max_readv_combine,
329
 
                               fudge_factor=self._bytes_to_read_before_seek,
330
 
                               max_size=self._max_readv_bytes))
331
 
 
332
 
        # now that we've coallesced things, avoid making enormous requests
333
 
        requests = []
334
 
        cur_request = []
335
 
        cur_len = 0
336
 
        for c in coalesced:
337
 
            if c.length + cur_len > self._max_readv_bytes:
338
 
                requests.append(cur_request)
339
 
                cur_request = [c]
340
 
                cur_len = c.length
341
 
                continue
342
 
            cur_request.append(c)
343
 
            cur_len += c.length
344
 
        if cur_request:
345
 
            requests.append(cur_request)
346
 
        if 'hpss' in debug.debug_flags:
347
 
            trace.mutter('%s.readv %s offsets => %s coalesced'
348
 
                         ' => %s requests (%s)',
349
 
                         self.__class__.__name__, len(offsets), len(coalesced),
350
 
                         len(requests), sum(map(len, requests)))
 
320
                               fudge_factor=self._bytes_to_read_before_seek))
 
321
 
 
322
        try:
 
323
            result = self._client.call_with_body_readv_array(
 
324
                ('readv', self._remote_path(relpath),),
 
325
                [(c.start, c.length) for c in coalesced])
 
326
            resp, response_handler = result
 
327
        except errors.ErrorFromSmartServer, err:
 
328
            self._translate_error(err.error_tuple)
 
329
 
 
330
        if resp[0] != 'readv':
 
331
            # This should raise an exception
 
332
            response_handler.cancel_read_body()
 
333
            raise errors.UnexpectedSmartServerResponse(resp)
 
334
 
 
335
        # FIXME: this should know how many bytes are needed, for clarity.
 
336
        data = response_handler.read_body_bytes()
351
337
        # Cache the results, but only until they have been fulfilled
352
338
        data_map = {}
353
 
        # turn the list of offsets into a single stack to iterate
354
 
        offset_stack = iter(offsets)
355
 
        # using a list so it can be modified when passing down and coming back
356
 
        next_offset = [offset_stack.next()]
357
 
        for cur_request in requests:
358
 
            try:
359
 
                result = self._client.call_with_body_readv_array(
360
 
                    ('readv', self._remote_path(relpath),),
361
 
                    [(c.start, c.length) for c in cur_request])
362
 
                resp, response_handler = result
363
 
            except errors.ErrorFromSmartServer, err:
364
 
                self._translate_error(err, relpath)
365
 
 
366
 
            if resp[0] != 'readv':
367
 
                # This should raise an exception
368
 
                response_handler.cancel_read_body()
369
 
                raise errors.UnexpectedSmartServerResponse(resp)
370
 
 
371
 
            for res in self._handle_response(offset_stack, cur_request,
372
 
                                             response_handler,
373
 
                                             data_map,
374
 
                                             next_offset):
375
 
                yield res
376
 
 
377
 
    def _handle_response(self, offset_stack, coalesced, response_handler,
378
 
                         data_map, next_offset):
379
 
        cur_offset_and_size = next_offset[0]
380
 
        # FIXME: this should know how many bytes are needed, for clarity.
381
 
        data = response_handler.read_body_bytes()
382
 
        data_offset = 0
383
339
        for c_offset in coalesced:
384
340
            if len(data) < c_offset.length:
385
341
                raise errors.ShortReadvError(relpath, c_offset.start,
386
342
                            c_offset.length, actual=len(data))
387
343
            for suboffset, subsize in c_offset.ranges:
388
344
                key = (c_offset.start+suboffset, subsize)
389
 
                this_data = data[data_offset+suboffset:
390
 
                                 data_offset+suboffset+subsize]
391
 
                # Special case when the data is in-order, rather than packing
392
 
                # into a map and then back out again. Benchmarking shows that
393
 
                # this has 100% hit rate, but leave in the data_map work just
394
 
                # in case.
395
 
                # TODO: Could we get away with using buffer() to avoid the
396
 
                #       memory copy?  Callers would need to realize they may
397
 
                #       not have a real string.
398
 
                if key == cur_offset_and_size:
399
 
                    yield cur_offset_and_size[0], this_data
400
 
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
401
 
                else:
402
 
                    data_map[key] = this_data
403
 
            data_offset += c_offset.length
 
345
                data_map[key] = data[suboffset:suboffset+subsize]
 
346
            data = data[c_offset.length:]
404
347
 
405
348
            # Now that we've read some data, see if we can yield anything back
406
349
            while cur_offset_and_size in data_map:
407
350
                this_data = data_map.pop(cur_offset_and_size)
408
351
                yield cur_offset_and_size[0], this_data
409
 
                cur_offset_and_size = next_offset[0] = offset_stack.next()
 
352
                cur_offset_and_size = offset_stack.next()
410
353
 
411
354
    def rename(self, rel_from, rel_to):
412
355
        self._call('rename',
421
364
    def rmdir(self, relpath):
422
365
        resp = self._call('rmdir', self._remote_path(relpath))
423
366
 
424
 
    def _ensure_ok(self, resp):
425
 
        if resp[0] != 'ok':
426
 
            raise errors.UnexpectedSmartServerResponse(resp)
427
 
 
428
 
    def _translate_error(self, err, relpath=None):
429
 
        remote._translate_error(err, path=relpath)
 
367
    def _translate_error(self, resp, orig_path=None):
 
368
        """Raise an exception from a response"""
 
369
        if resp is None:
 
370
            what = None
 
371
        else:
 
372
            what = resp[0]
 
373
        if what == 'ok':
 
374
            return
 
375
        elif what == 'NoSuchFile':
 
376
            if orig_path is not None:
 
377
                error_path = orig_path
 
378
            else:
 
379
                error_path = resp[1]
 
380
            raise errors.NoSuchFile(error_path)
 
381
        elif what == 'error':
 
382
            raise errors.SmartProtocolError(unicode(resp[1]))
 
383
        elif what == 'FileExists':
 
384
            raise errors.FileExists(resp[1])
 
385
        elif what == 'DirectoryNotEmpty':
 
386
            raise errors.DirectoryNotEmpty(resp[1])
 
387
        elif what == 'ShortReadvError':
 
388
            raise errors.ShortReadvError(resp[1], int(resp[2]),
 
389
                                         int(resp[3]), int(resp[4]))
 
390
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
391
            encoding = str(resp[1]) # encoding must always be a string
 
392
            val = resp[2]
 
393
            start = int(resp[3])
 
394
            end = int(resp[4])
 
395
            reason = str(resp[5]) # reason must always be a string
 
396
            if val.startswith('u:'):
 
397
                val = val[2:].decode('utf-8')
 
398
            elif val.startswith('s:'):
 
399
                val = val[2:].decode('base64')
 
400
            if what == 'UnicodeDecodeError':
 
401
                raise UnicodeDecodeError(encoding, val, start, end, reason)
 
402
            elif what == 'UnicodeEncodeError':
 
403
                raise UnicodeEncodeError(encoding, val, start, end, reason)
 
404
        elif what == "ReadOnlyError":
 
405
            raise errors.TransportNotPossible('readonly transport')
 
406
        elif what == "ReadError":
 
407
            if orig_path is not None:
 
408
                error_path = orig_path
 
409
            else:
 
410
                error_path = resp[1]
 
411
            raise errors.ReadError(error_path)
 
412
        elif what == "PermissionDenied":
 
413
            if orig_path is not None:
 
414
                error_path = orig_path
 
415
            else:
 
416
                error_path = resp[1]
 
417
            raise errors.PermissionDenied(error_path)
 
418
        else:
 
419
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
430
420
 
431
421
    def disconnect(self):
432
 
        m = self.get_smart_medium()
433
 
        if m is not None:
434
 
            m.disconnect()
 
422
        self.get_smart_medium().disconnect()
 
423
 
 
424
    def delete_tree(self, relpath):
 
425
        raise errors.TransportNotPossible('readonly transport')
435
426
 
436
427
    def stat(self, relpath):
437
428
        resp = self._call2('stat', self._remote_path(relpath))
438
429
        if resp[0] == 'stat':
439
430
            return _SmartStat(int(resp[1]), int(resp[2], 8))
440
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
431
        else:
 
432
            self._translate_error(resp)
441
433
 
442
434
    ## def lock_read(self, relpath):
443
435
    ##     """Lock the given file for shared (read) access.
459
451
        resp = self._call2('list_dir', self._remote_path(relpath))
460
452
        if resp[0] == 'names':
461
453
            return [name.encode('ascii') for name in resp[1:]]
462
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
454
        else:
 
455
            self._translate_error(resp)
463
456
 
464
457
    def iter_files_recursive(self):
465
458
        resp = self._call2('iter_files_recursive', self._remote_path(''))
466
459
        if resp[0] == 'names':
467
460
            return resp[1:]
468
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
461
        else:
 
462
            self._translate_error(resp)
469
463
 
470
464
 
471
465
class RemoteTCPTransport(RemoteTransport):
472
466
    """Connection to smart server over plain tcp.
473
 
 
 
467
    
474
468
    This is essentially just a factory to get 'RemoteTransport(url,
475
469
        SmartTCPClientMedium).
476
470
    """
477
471
 
478
472
    def _build_medium(self):
479
473
        client_medium = medium.SmartTCPClientMedium(
480
 
            self._parsed_url.host, self._parsed_url.port, self.base)
 
474
            self._host, self._port, self.base)
481
475
        return client_medium, None
482
476
 
483
477
 
490
484
 
491
485
    def _build_medium(self):
492
486
        client_medium = medium.SmartTCPClientMedium(
493
 
            self._parsed_url.host, self._parsed_url.port, self.base)
 
487
            self._host, self._port, self.base)
494
488
        client_medium._protocol_version = 2
495
489
        client_medium._remember_remote_is_before((1, 6))
496
490
        return client_medium, None
504
498
    """
505
499
 
506
500
    def _build_medium(self):
 
501
        # ssh will prompt the user for a password if needed and if none is
 
502
        # provided but it will not give it back, so no credentials can be
 
503
        # stored.
507
504
        location_config = config.LocationConfig(self.base)
508
505
        bzr_remote_path = location_config.get_bzr_remote_path()
509
 
        user = self._parsed_url.user
510
 
        if user is None:
511
 
            auth = config.AuthenticationConfig()
512
 
            user = auth.get_user('ssh', self._parsed_url.host,
513
 
                self._parsed_url.port)
514
 
        ssh_params = medium.SSHParams(self._parsed_url.host,
515
 
                self._parsed_url.port, user, self._parsed_url.password,
516
 
                bzr_remote_path)
517
 
        client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
518
 
        return client_medium, (user, self._parsed_url.password)
 
506
        client_medium = medium.SmartSSHClientMedium(self._host, self._port,
 
507
            self._user, self._password, self.base,
 
508
            bzr_remote_path=bzr_remote_path)
 
509
        return client_medium, None
519
510
 
520
511
 
521
512
class RemoteHTTPTransport(RemoteTransport):
522
513
    """Just a way to connect between a bzr+http:// url and http://.
523
 
 
 
514
    
524
515
    This connection operates slightly differently than the RemoteSSHTransport.
525
516
    It uses a plain http:// transport underneath, which defines what remote
526
517
    .bzr/smart URL we are connected to. From there, all paths that are sent are
535
526
            # url only for an intial construction (when the url came from the
536
527
            # command-line).
537
528
            http_url = base[len('bzr+'):]
538
 
            self._http_transport = transport.get_transport_from_url(http_url)
 
529
            self._http_transport = transport.get_transport(http_url)
539
530
        else:
540
531
            self._http_transport = http_transport
541
532
        super(RemoteHTTPTransport, self).__init__(
575
566
                                   _from_transport=self,
576
567
                                   http_transport=self._http_transport)
577
568
 
578
 
    def _redirected_to(self, source, target):
579
 
        """See transport._redirected_to"""
580
 
        redirected = self._http_transport._redirected_to(source, target)
581
 
        if (redirected is not None
582
 
            and isinstance(redirected, type(self._http_transport))):
583
 
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
584
 
                                       http_transport=redirected)
585
 
        else:
586
 
            # Either None or a transport for a different protocol
587
 
            return redirected
588
 
 
589
 
 
590
 
class HintingSSHTransport(transport.Transport):
591
 
    """Simple transport that handles ssh:// and points out bzr+ssh://."""
592
 
 
593
 
    def __init__(self, url):
594
 
        raise errors.UnsupportedProtocol(url,
595
 
            'bzr supports bzr+ssh to operate over ssh, use "bzr+%s".' % url)
596
 
 
597
569
 
598
570
def get_test_permutations():
599
571
    """Return (transport, server) permutations for testing."""
600
572
    ### We may need a little more test framework support to construct an
601
573
    ### appropriate RemoteTransport in the future.
602
 
    from bzrlib.tests import test_server
603
 
    return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]
 
574
    from bzrlib.smart import server
 
575
    return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]