File: connection.py

package info (click to toggle)
pydb 1.26-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 2,512 kB
  • ctags: 1,061
  • sloc: python: 4,200; perl: 2,479; lisp: 866; sh: 780; makefile: 633; ansic: 16
file content (403 lines) | stat: -rw-r--r-- 12,679 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# -*- coding: utf-8 -*-
"""Lower-level classes to support communication between separate
processes which might reside be on separate computers.

Can be used remote debugging via a socket or via a serial
communication line, or via a FIFO.

Modified from Matt Fleming's 2006 Google Summer of Code project.

$Id: connection.py,v 1.10 2008/12/22 15:17:25 rockyb Exp $"""

NotImplementedMessage = "This method must be overriden in a subclass"

### Exceptions
class ConnectionFailed(Exception): pass
class DroppedConnection(Exception): pass
class ReadError(Exception): pass
class WriteError(Exception): pass

class ConnectionInterface(object):
    """ This is an abstract class that specifies the interface a server
    connection class must implement. If a target is given, we'll
    set up a connection on that target
    """
    def connect(self, target):
        """Use this to set the target. It can also be specified
        on the initialization."""
        raise NotImplementedError, NotImplementedMessage

    def disconnect(self):
        """ This method is called to disconnect connections."""
        raise NotImplementedError, NotImplementedMessage

    def readline(self):
        """ This method reads a line of data of maximum length 'bufsize'
        from the connected debugger.
        """
        raise NotImplementedError, NotImplementedMessage

    def write(self, msg):
        """ This method is used to write to a debugger that is
        connected to this server.
        """
        raise NotImplementedError, NotImplementedMessage

# end ConnectionInterface

### This might go in a different file
# Note: serial protocol does not require the distinction between server and
# client.
class ConnectionSerial(ConnectionInterface):

    """ This connection class that allows a connection to a
    target via a serial line. 
    """

    def __init__(self):
        ConnectionInterface.__init__(self)
        self.input = None
        self.output = None

    def connect(self, device):
        """ Create our fileobject by opening the serial device for
        this connection. A serial device must be specified,
        (e.g. /dev/ttyS0, /dev/ttya, COM1, etc.).
        """
        self._dev = device
        try:
            self.input = open(self._dev, 'r')
            self.output = open(self._dev, 'w')
        except IOError,e:
            # Use e[1] for more detail about why the connection failed
            raise ConnectionFailed, e[1]

    def disconnect(self):
        """ Close the serial device. """
        if self.output is None and self.input is None:
            return
        self.output.close()
        self.input.close()

    def readline(self, bufsize=2048):
        try:
            line = self.input.readline(bufsize)
        except IOError, e:
            raise ReadError, e[1]
        return line

    def write(self, msg):
        if msg[-1] is not '\n':
            msg += '\n'
        try:
            self.output.write(msg)
            self.output.flush()
        except IOError, e:
            raise WriteError, e[1]

# end class ConnectionSerial

### This might go in a different file
import socket

class ConnectionServerTCP(ConnectionInterface):
    """This is an implementation of a server class that uses the TCP
    protocol as its means of communication.
    """
    def __init__(self):
        self.listening = False
        self._sock = self.output = self.input = None
        ConnectionInterface.__init__(self)
        
    def connect(self, addr, reuseaddr=True):
        """Set to allow a connection from a client. 'addr' specifies
        the hostname and port combination of the server.
        """
        try:
            h,p = addr.split(':')
        except ValueError:
            raise ConnectionFailed, 'Invalid address'
        self.host = h
        self.port = int(p)
        if not self.listening:
            self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            if reuseaddr:
                self._sock.setsockopt(socket.SOL_SOCKET,
                                      socket.SO_REUSEADDR, 1)
            try:
                self._sock.bind((self.host, self.port))
            except socket.error, e:
                # Use e[1] as a more detailed error message
                raise ConnectionFailed, e[1]
            self._sock.listen(1)
            self.listening = True
        self.output, addr = self._sock.accept()
        self.input = self.output

    def disconnect(self):
        if self.output is None or self._sock is None:
            return
        self.output.close()
        self._sock.close()
        self._sock = None
        self.listening = False

    def readline(self, bufsize=2048):
        try:
            line = self.input.recv(bufsize)
        except socket.error, e:
            raise ReadError, e[1]
        if not line:
            raise ReadError, 'Connection closed'
        if line[-1] != '\n': line += '\n'
        return line

    def write(self, msg):
        try:
            self.output.sendall(msg)
        except socket.error, e:
            raise WriteError, e[1]

# end ConnectionServerTCP

class ConnectionClientTCP(ConnectionInterface):
    """ A class that allows a connection to be made from a debugger
    to a server via TCP.
    """
    def __init__(self):
        """ Specify the address to connection to. """
        ConnectionInterface.__init__(self)
        self._sock = self.output = self.input = None
        self.connected = True

    def connect(self, addr):
        """Connect to the server. 'input' reads data from the
        server. 'output' writes data to the server.  Specify the
        address of the server (e.g. host:2020).  """
        try:
            h, p = addr.split(':')
        except ValueError:
            raise ConnectionFailed, 'Invalid address'
        self.host = h
        self.port = int(p)
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            self._sock.connect((self.host, self.port))
        except socket.error, e:
            raise ConnectionFailed, e[1]
        self.connected = True

    def write(self, msg):
        try:
            self._sock.sendall(msg)
        except socket.error, e:
            raise WriteError, e[1]

    def readline(self, bufsize=2048):
        try:
            line = self._sock.recv(bufsize)
        except socket.error, e:
            raise ReadError, e[1]
        if not line:
            raise ReadError, 'Connection closed'
        return line

    def disconnect(self):
        """ Close the socket to the server. """
        # We shouldn't bail if we haven't been connected yet
        if self._sock is None:
            return
        else:
            self._sock.close()
        self._sock = None
        self.connected = False

#end ConnectionClientTCP

### This might go in a different file
import os

class ConnectionFIFO(ConnectionInterface):
    """A class for communicating akin to a named pipe. Since I haven't
    been able to figure out how to make os.mkfifo work, we'll use two
    files instead. Each process reads on one and writes on the
    other. The read of one is attached to the write of the other and
    vice versa.
    """

    def __init__(self, is_server):
        """is_server is a boolean which is used to ensure that the
        read FIFO of one process is attachd tothe write FIFO of the
        other. We arbitrarily call one the 'server' and one the
        'client'.
        """
        ConnectionInterface.__init__(self)
        ## FIXME check to see if is_server is boolean? 
        self.is_server = is_server
        self.inp = self.mode = self.filename = None
        
    def connect(self, filename, mode=0644):
        """Set up FIFOs for read and write connections based on the
        filename parameter passed. If no filename parameter is given,
        use the filename specified on instance creation.

        If there is a problem creating the FIFO we will return a
        ConnectionFailed exception."""

        self.filename  = filename
        self.fname_in  = self.infile()
        self.fname_out = self.outfile()
        self.open_outfile()

        self.mode = mode
        if self.is_server:
            # Wait for a connection from a client
            import time
            while not os.path.exists(self.fname_in):
                time.sleep(0.5)
        try:
            self.inp = open(self.fname_in, 'r')
        except IOError, e:
            raise ConnectionFailed, "%s: %s:" % (self.fname_out, e[1])
        
    def disconnect(self):
        """Close input and output files and remove from the filesystem
        the output file."""
        if not self.inp or not self.outp:
            return
        self.outp.close()
        outfile = self.outfile()
        if outfile is not None and os.path.isfile(outfile):
            os.unlink(outfile)
        self.inp.close()
        self.inp = self.outp = None

    def infile(self):
        """Return the input FIFO name for the object"""
        if self.is_server:
            return self.filename + ".in"
        else:
            return self.filename + ".out"
        
    def open_outfile(self):
        """Return the output FIFO name for the object"""
        try:
            self.outp = open(self.fname_out, 'w')
        except IOError, e:
            raise ConnectionFailed, e[1]

    def outfile(self):
        """Return the output FIFO name for the object"""
        if self.is_server:
            return self.filename + ".out"
        else:
            return self.filename + ".in"
        
    def readline(self):
        try:
            line = self.inp.readline()
        except IOError, e:
            raise ReadError, e[1]
        if not line:
            raise ReadError, 'Connection closed'
        return line

    def write(self, msg):
        if msg[-1] != '\n': msg += '\n'
        try:
            self.outp.write(msg)
            self.outp.flush()
        except IOError, e:
            raise WriteError, e[1]
        
# end ConnectionFIFO

def import_hook(target):
    cls = target[target.rfind('.')+1:]
    target = target[:target.rfind('.')]
    try:
        pkg = __import__(target, globals(), locals(), [])
    except ImportError:
        return None
    return getattr(pkg, cls)
 
class ConnectionClientFactory:

    """A factory class that provides a connection for use with a client
    for example, with a target function.
    """
    # @staticmethod  # Works only on Python 2.4 and up
    def create(target):
        if target.lower() == 'tcp':
            return ConnectionClientTCP()
        elif target.lower() == 'serial':
            return ConnectionSerial()
        elif target.lower() == 'fifo':
            return ConnectionFIFO(is_server=True)
        else:
            return import_hook(target)
    create = staticmethod(create)   # Works on all Python versions

class ConnectionServerFactory:

    """A factory class that provides a connection to be used with
    a pdbserver.
    """
    # @staticmethod  # Works only on Python 2.4 and up
    def create(target):
        if target.lower() == 'tcp':
            return ConnectionServerTCP()
        elif target.lower() == 'serial':
            return ConnectionSerial()
        elif target.lower() == 'fifo':
            return ConnectionFIFO(is_server=True)
        else:
            return import_hook(target)
    create = staticmethod(create)   # Works on all Python versions

# When invoked as main program, do some basic tests 
if __name__=='__main__':
    # FIFO test
    import thread
    
    fname='test_file'
    server = ConnectionFIFO(is_server=True)
    client = ConnectionFIFO(is_server=False)
    thread.start_new_thread(server.connect, (fname,))
    client.connect(filename=fname)
    line = 'this is a test\n'
    client.write(line)
    ### FIXME
    import time
    time.sleep(0.05)

    l2 = server.readline()
    assert l2 == line
    line = 'Another test\n'
    server.write(line)
    l2 = client.readline()
    assert l2 == line
    client.disconnect()
    server.disconnect()

    # TCP test
    port = 8000
    while True:
        addr   = '127.0.0.1:%d' % port
        server = ConnectionServerTCP()
        try:
            thread.start_new_thread(server.connect, (addr,))
            print "port: %d" % port
            break
        except IOError, e:
            if e[0] == 'Address already in use':
                if port < 8010:
                    port += 1
                    print "port: %d" % port
                    continue
            import sys
            sys.exit(0)
            
    client = ConnectionClientTCP()
    client.disconnect()
    server.disconnect()