Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / conch / test / test_connection.py
1 # Copyright (c) 2007-2010 Twisted Matrix Laboratories.
2 # See LICENSE for details
3
4 """
5 This module tests twisted.conch.ssh.connection.
6 """
7
8 import struct
9
10 from twisted.conch import error
11 from twisted.conch.ssh import channel, common, connection
12 from twisted.trial import unittest
13 from twisted.conch.test import test_userauth
14
15
16 class TestChannel(channel.SSHChannel):
17     """
18     A mocked-up version of twisted.conch.ssh.channel.SSHChannel.
19
20     @ivar gotOpen: True if channelOpen has been called.
21     @type gotOpen: C{bool}
22     @ivar specificData: the specific channel open data passed to channelOpen.
23     @type specificData: C{str}
24     @ivar openFailureReason: the reason passed to openFailed.
25     @type openFailed: C{error.ConchError}
26     @ivar inBuffer: a C{list} of strings received by the channel.
27     @type inBuffer: C{list}
28     @ivar extBuffer: a C{list} of 2-tuples (type, extended data) of received by
29         the channel.
30     @type extBuffer: C{list}
31     @ivar numberRequests: the number of requests that have been made to this
32         channel.
33     @type numberRequests: C{int}
34     @ivar gotEOF: True if the other side sent EOF.
35     @type gotEOF: C{bool}
36     @ivar gotOneClose: True if the other side closed the connection.
37     @type gotOneClose: C{bool}
38     @ivar gotClosed: True if the channel is closed.
39     @type gotClosed: C{bool}
40     """
41     name = "TestChannel"
42     gotOpen = False
43
44     def logPrefix(self):
45         return "TestChannel %i" % self.id
46
47     def channelOpen(self, specificData):
48         """
49         The channel is open.  Set up the instance variables.
50         """
51         self.gotOpen = True
52         self.specificData = specificData
53         self.inBuffer = []
54         self.extBuffer = []
55         self.numberRequests = 0
56         self.gotEOF = False
57         self.gotOneClose = False
58         self.gotClosed = False
59
60     def openFailed(self, reason):
61         """
62         Opening the channel failed.  Store the reason why.
63         """
64         self.openFailureReason = reason
65
66     def request_test(self, data):
67         """
68         A test request.  Return True if data is 'data'.
69
70         @type data: C{str}
71         """
72         self.numberRequests += 1
73         return data == 'data'
74
75     def dataReceived(self, data):
76         """
77         Data was received.  Store it in the buffer.
78         """
79         self.inBuffer.append(data)
80
81     def extReceived(self, code, data):
82         """
83         Extended data was received.  Store it in the buffer.
84         """
85         self.extBuffer.append((code, data))
86
87     def eofReceived(self):
88         """
89         EOF was received.  Remember it.
90         """
91         self.gotEOF = True
92
93     def closeReceived(self):
94         """
95         Close was received.  Remember it.
96         """
97         self.gotOneClose = True
98
99     def closed(self):
100         """
101         The channel is closed.  Rembember it.
102         """
103         self.gotClosed = True
104
105 class TestAvatar:
106     """
107     A mocked-up version of twisted.conch.avatar.ConchUser
108     """
109     _ARGS_ERROR_CODE = 123
110
111     def lookupChannel(self, channelType, windowSize, maxPacket, data):
112         """
113         The server wants us to return a channel.  If the requested channel is
114         our TestChannel, return it, otherwise return None.
115         """
116         if channelType == TestChannel.name:
117             return TestChannel(remoteWindow=windowSize,
118                     remoteMaxPacket=maxPacket,
119                     data=data, avatar=self)
120         elif channelType == "conch-error-args":
121             # Raise a ConchError with backwards arguments to make sure the
122             # connection fixes it for us.  This case should be deprecated and
123             # deleted eventually, but only after all of Conch gets the argument
124             # order right.
125             raise error.ConchError(
126                 self._ARGS_ERROR_CODE, "error args in wrong order")
127
128
129     def gotGlobalRequest(self, requestType, data):
130         """
131         The client has made a global request.  If the global request is
132         'TestGlobal', return True.  If the global request is 'TestData',
133         return True and the request-specific data we received.  Otherwise,
134         return False.
135         """
136         if requestType == 'TestGlobal':
137             return True
138         elif requestType == 'TestData':
139             return True, data
140         else:
141             return False
142
143
144
145 class TestConnection(connection.SSHConnection):
146     """
147     A subclass of SSHConnection for testing.
148
149     @ivar channel: the current channel.
150     @type channel. C{TestChannel}
151     """
152
153     def logPrefix(self):
154         return "TestConnection"
155
156     def global_TestGlobal(self, data):
157         """
158         The other side made the 'TestGlobal' global request.  Return True.
159         """
160         return True
161
162     def global_Test_Data(self, data):
163         """
164         The other side made the 'Test-Data' global request.  Return True and
165         the data we received.
166         """
167         return True, data
168
169     def channel_TestChannel(self, windowSize, maxPacket, data):
170         """
171         The other side is requesting the TestChannel.  Create a C{TestChannel}
172         instance, store it, and return it.
173         """
174         self.channel = TestChannel(remoteWindow=windowSize,
175                 remoteMaxPacket=maxPacket, data=data)
176         return self.channel
177
178     def channel_ErrorChannel(self, windowSize, maxPacket, data):
179         """
180         The other side is requesting the ErrorChannel.  Raise an exception.
181         """
182         raise AssertionError('no such thing')
183
184
185
186 class ConnectionTestCase(unittest.TestCase):
187
188     if test_userauth.transport is None:
189         skip = "Cannot run without both PyCrypto and pyasn1"
190
191     def setUp(self):
192         self.transport = test_userauth.FakeTransport(None)
193         self.transport.avatar = TestAvatar()
194         self.conn = TestConnection()
195         self.conn.transport = self.transport
196         self.conn.serviceStarted()
197
198     def _openChannel(self, channel):
199         """
200         Open the channel with the default connection.
201         """
202         self.conn.openChannel(channel)
203         self.transport.packets = self.transport.packets[:-1]
204         self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(struct.pack('>2L',
205             channel.id, 255) + '\x00\x02\x00\x00\x00\x00\x80\x00')
206
207     def tearDown(self):
208         self.conn.serviceStopped()
209
210     def test_linkAvatar(self):
211         """
212         Test that the connection links itself to the avatar in the
213         transport.
214         """
215         self.assertIdentical(self.transport.avatar.conn, self.conn)
216
217     def test_serviceStopped(self):
218         """
219         Test that serviceStopped() closes any open channels.
220         """
221         channel1 = TestChannel()
222         channel2 = TestChannel()
223         self.conn.openChannel(channel1)
224         self.conn.openChannel(channel2)
225         self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00' * 4)
226         self.assertTrue(channel1.gotOpen)
227         self.assertFalse(channel2.gotOpen)
228         self.conn.serviceStopped()
229         self.assertTrue(channel1.gotClosed)
230
231     def test_GLOBAL_REQUEST(self):
232         """
233         Test that global request packets are dispatched to the global_*
234         methods and the return values are translated into success or failure
235         messages.
236         """
237         self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\xff')
238         self.assertEqual(self.transport.packets,
239                 [(connection.MSG_REQUEST_SUCCESS, '')])
240         self.transport.packets = []
241         self.conn.ssh_GLOBAL_REQUEST(common.NS('TestData') + '\xff' +
242                 'test data')
243         self.assertEqual(self.transport.packets,
244                 [(connection.MSG_REQUEST_SUCCESS, 'test data')])
245         self.transport.packets = []
246         self.conn.ssh_GLOBAL_REQUEST(common.NS('TestBad') + '\xff')
247         self.assertEqual(self.transport.packets,
248                 [(connection.MSG_REQUEST_FAILURE, '')])
249         self.transport.packets = []
250         self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\x00')
251         self.assertEqual(self.transport.packets, [])
252
253     def test_REQUEST_SUCCESS(self):
254         """
255         Test that global request success packets cause the Deferred to be
256         called back.
257         """
258         d = self.conn.sendGlobalRequest('request', 'data', True)
259         self.conn.ssh_REQUEST_SUCCESS('data')
260         def check(data):
261             self.assertEqual(data, 'data')
262         d.addCallback(check)
263         d.addErrback(self.fail)
264         return d
265
266     def test_REQUEST_FAILURE(self):
267         """
268         Test that global request failure packets cause the Deferred to be
269         erred back.
270         """
271         d = self.conn.sendGlobalRequest('request', 'data', True)
272         self.conn.ssh_REQUEST_FAILURE('data')
273         def check(f):
274             self.assertEqual(f.value.data, 'data')
275         d.addCallback(self.fail)
276         d.addErrback(check)
277         return d
278
279     def test_CHANNEL_OPEN(self):
280         """
281         Test that open channel packets cause a channel to be created and
282         opened or a failure message to be returned.
283         """
284         del self.transport.avatar
285         self.conn.ssh_CHANNEL_OPEN(common.NS('TestChannel') +
286                 '\x00\x00\x00\x01' * 4)
287         self.assertTrue(self.conn.channel.gotOpen)
288         self.assertEqual(self.conn.channel.conn, self.conn)
289         self.assertEqual(self.conn.channel.data, '\x00\x00\x00\x01')
290         self.assertEqual(self.conn.channel.specificData, '\x00\x00\x00\x01')
291         self.assertEqual(self.conn.channel.remoteWindowLeft, 1)
292         self.assertEqual(self.conn.channel.remoteMaxPacket, 1)
293         self.assertEqual(self.transport.packets,
294                 [(connection.MSG_CHANNEL_OPEN_CONFIRMATION,
295                     '\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00'
296                     '\x00\x00\x80\x00')])
297         self.transport.packets = []
298         self.conn.ssh_CHANNEL_OPEN(common.NS('BadChannel') +
299                 '\x00\x00\x00\x02' * 4)
300         self.flushLoggedErrors()
301         self.assertEqual(self.transport.packets,
302                 [(connection.MSG_CHANNEL_OPEN_FAILURE,
303                     '\x00\x00\x00\x02\x00\x00\x00\x03' + common.NS(
304                     'unknown channel') + common.NS(''))])
305         self.transport.packets = []
306         self.conn.ssh_CHANNEL_OPEN(common.NS('ErrorChannel') +
307                 '\x00\x00\x00\x02' * 4)
308         self.flushLoggedErrors()
309         self.assertEqual(self.transport.packets,
310                 [(connection.MSG_CHANNEL_OPEN_FAILURE,
311                     '\x00\x00\x00\x02\x00\x00\x00\x02' + common.NS(
312                     'unknown failure') + common.NS(''))])
313
314
315     def _lookupChannelErrorTest(self, code):
316         """
317         Deliver a request for a channel open which will result in an exception
318         being raised during channel lookup.  Assert that an error response is
319         delivered as a result.
320         """
321         self.transport.avatar._ARGS_ERROR_CODE = code
322         self.conn.ssh_CHANNEL_OPEN(
323             common.NS('conch-error-args') + '\x00\x00\x00\x01' * 4)
324         errors = self.flushLoggedErrors(error.ConchError)
325         self.assertEqual(
326             len(errors), 1, "Expected one error, got: %r" % (errors,))
327         self.assertEqual(errors[0].value.args, (123, "error args in wrong order"))
328         self.assertEqual(
329             self.transport.packets,
330             [(connection.MSG_CHANNEL_OPEN_FAILURE,
331               # The response includes some bytes which identifying the
332               # associated request, as well as the error code (7b in hex) and
333               # the error message.
334               '\x00\x00\x00\x01\x00\x00\x00\x7b' + common.NS(
335                         'error args in wrong order') + common.NS(''))])
336
337
338     def test_lookupChannelError(self):
339         """
340         If a C{lookupChannel} implementation raises L{error.ConchError} with the
341         arguments in the wrong order, a C{MSG_CHANNEL_OPEN} failure is still
342         sent in response to the message.
343
344         This is a temporary work-around until L{error.ConchError} is given
345         better attributes and all of the Conch code starts constructing
346         instances of it properly.  Eventually this functionality should be
347         deprecated and then removed.
348         """
349         self._lookupChannelErrorTest(123)
350
351
352     def test_lookupChannelErrorLongCode(self):
353         """
354         Like L{test_lookupChannelError}, but for the case where the failure code
355         is represented as a C{long} instead of a C{int}.
356         """
357         self._lookupChannelErrorTest(123L)
358
359
360     def test_CHANNEL_OPEN_CONFIRMATION(self):
361         """
362         Test that channel open confirmation packets cause the channel to be
363         notified that it's open.
364         """
365         channel = TestChannel()
366         self.conn.openChannel(channel)
367         self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00'*5)
368         self.assertEqual(channel.remoteWindowLeft, 0)
369         self.assertEqual(channel.remoteMaxPacket, 0)
370         self.assertEqual(channel.specificData, '\x00\x00\x00\x00')
371         self.assertEqual(self.conn.channelsToRemoteChannel[channel],
372                 0)
373         self.assertEqual(self.conn.localToRemoteChannel[0], 0)
374
375     def test_CHANNEL_OPEN_FAILURE(self):
376         """
377         Test that channel open failure packets cause the channel to be
378         notified that its opening failed.
379         """
380         channel = TestChannel()
381         self.conn.openChannel(channel)
382         self.conn.ssh_CHANNEL_OPEN_FAILURE('\x00\x00\x00\x00\x00\x00\x00'
383                 '\x01' + common.NS('failure!'))
384         self.assertEqual(channel.openFailureReason.args, ('failure!', 1))
385         self.assertEqual(self.conn.channels.get(channel), None)
386
387
388     def test_CHANNEL_WINDOW_ADJUST(self):
389         """
390         Test that channel window adjust messages add bytes to the channel
391         window.
392         """
393         channel = TestChannel()
394         self._openChannel(channel)
395         oldWindowSize = channel.remoteWindowLeft
396         self.conn.ssh_CHANNEL_WINDOW_ADJUST('\x00\x00\x00\x00\x00\x00\x00'
397                 '\x01')
398         self.assertEqual(channel.remoteWindowLeft, oldWindowSize + 1)
399
400     def test_CHANNEL_DATA(self):
401         """
402         Test that channel data messages are passed up to the channel, or
403         cause the channel to be closed if the data is too large.
404         """
405         channel = TestChannel(localWindow=6, localMaxPacket=5)
406         self._openChannel(channel)
407         self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS('data'))
408         self.assertEqual(channel.inBuffer, ['data'])
409         self.assertEqual(self.transport.packets,
410                 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
411                     '\x00\x00\x00\x04')])
412         self.transport.packets = []
413         longData = 'a' * (channel.localWindowLeft + 1)
414         self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS(longData))
415         self.assertEqual(channel.inBuffer, ['data'])
416         self.assertEqual(self.transport.packets,
417                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
418         channel = TestChannel()
419         self._openChannel(channel)
420         bigData = 'a' * (channel.localMaxPacket + 1)
421         self.transport.packets = []
422         self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x01' + common.NS(bigData))
423         self.assertEqual(channel.inBuffer, [])
424         self.assertEqual(self.transport.packets,
425                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
426
427     def test_CHANNEL_EXTENDED_DATA(self):
428         """
429         Test that channel extended data messages are passed up to the channel,
430         or cause the channel to be closed if they're too big.
431         """
432         channel = TestChannel(localWindow=6, localMaxPacket=5)
433         self._openChannel(channel)
434         self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
435                                             '\x00' + common.NS('data'))
436         self.assertEqual(channel.extBuffer, [(0, 'data')])
437         self.assertEqual(self.transport.packets,
438                 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
439                     '\x00\x00\x00\x04')])
440         self.transport.packets = []
441         longData = 'a' * (channel.localWindowLeft + 1)
442         self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
443                                             '\x00' + common.NS(longData))
444         self.assertEqual(channel.extBuffer, [(0, 'data')])
445         self.assertEqual(self.transport.packets,
446                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
447         channel = TestChannel()
448         self._openChannel(channel)
449         bigData = 'a' * (channel.localMaxPacket + 1)
450         self.transport.packets = []
451         self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x01\x00\x00\x00'
452                                             '\x00' + common.NS(bigData))
453         self.assertEqual(channel.extBuffer, [])
454         self.assertEqual(self.transport.packets,
455                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
456
457     def test_CHANNEL_EOF(self):
458         """
459         Test that channel eof messages are passed up to the channel.
460         """
461         channel = TestChannel()
462         self._openChannel(channel)
463         self.conn.ssh_CHANNEL_EOF('\x00\x00\x00\x00')
464         self.assertTrue(channel.gotEOF)
465
466     def test_CHANNEL_CLOSE(self):
467         """
468         Test that channel close messages are passed up to the channel.  Also,
469         test that channel.close() is called if both sides are closed when this
470         message is received.
471         """
472         channel = TestChannel()
473         self._openChannel(channel)
474         self.conn.sendClose(channel)
475         self.conn.ssh_CHANNEL_CLOSE('\x00\x00\x00\x00')
476         self.assertTrue(channel.gotOneClose)
477         self.assertTrue(channel.gotClosed)
478
479     def test_CHANNEL_REQUEST_success(self):
480         """
481         Test that channel requests that succeed send MSG_CHANNEL_SUCCESS.
482         """
483         channel = TestChannel()
484         self._openChannel(channel)
485         self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS('test')
486                 + '\x00')
487         self.assertEqual(channel.numberRequests, 1)
488         d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
489             'test') + '\xff' + 'data')
490         def check(result):
491             self.assertEqual(self.transport.packets,
492                     [(connection.MSG_CHANNEL_SUCCESS, '\x00\x00\x00\xff')])
493         d.addCallback(check)
494         return d
495
496     def test_CHANNEL_REQUEST_failure(self):
497         """
498         Test that channel requests that fail send MSG_CHANNEL_FAILURE.
499         """
500         channel = TestChannel()
501         self._openChannel(channel)
502         d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
503             'test') + '\xff')
504         def check(result):
505             self.assertEqual(self.transport.packets,
506                     [(connection.MSG_CHANNEL_FAILURE, '\x00\x00\x00\xff'
507                         )])
508         d.addCallback(self.fail)
509         d.addErrback(check)
510         return d
511
512     def test_CHANNEL_REQUEST_SUCCESS(self):
513         """
514         Test that channel request success messages cause the Deferred to be
515         called back.
516         """
517         channel = TestChannel()
518         self._openChannel(channel)
519         d = self.conn.sendRequest(channel, 'test', 'data', True)
520         self.conn.ssh_CHANNEL_SUCCESS('\x00\x00\x00\x00')
521         def check(result):
522             self.assertTrue(result)
523         return d
524
525     def test_CHANNEL_REQUEST_FAILURE(self):
526         """
527         Test that channel request failure messages cause the Deferred to be
528         erred back.
529         """
530         channel = TestChannel()
531         self._openChannel(channel)
532         d = self.conn.sendRequest(channel, 'test', '', True)
533         self.conn.ssh_CHANNEL_FAILURE('\x00\x00\x00\x00')
534         def check(result):
535             self.assertEqual(result.value.value, 'channel request failed')
536         d.addCallback(self.fail)
537         d.addErrback(check)
538         return d
539
540     def test_sendGlobalRequest(self):
541         """
542         Test that global request messages are sent in the right format.
543         """
544         d = self.conn.sendGlobalRequest('wantReply', 'data', True)
545         # must be added to prevent errbacking during teardown
546         d.addErrback(lambda failure: None)
547         self.conn.sendGlobalRequest('noReply', '', False)
548         self.assertEqual(self.transport.packets,
549                 [(connection.MSG_GLOBAL_REQUEST, common.NS('wantReply') +
550                     '\xffdata'),
551                  (connection.MSG_GLOBAL_REQUEST, common.NS('noReply') +
552                      '\x00')])
553         self.assertEqual(self.conn.deferreds, {'global':[d]})
554
555     def test_openChannel(self):
556         """
557         Test that open channel messages are sent in the right format.
558         """
559         channel = TestChannel()
560         self.conn.openChannel(channel, 'aaaa')
561         self.assertEqual(self.transport.packets,
562                 [(connection.MSG_CHANNEL_OPEN, common.NS('TestChannel') +
563                     '\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x80\x00aaaa')])
564         self.assertEqual(channel.id, 0)
565         self.assertEqual(self.conn.localChannelID, 1)
566
567     def test_sendRequest(self):
568         """
569         Test that channel request messages are sent in the right format.
570         """
571         channel = TestChannel()
572         self._openChannel(channel)
573         d = self.conn.sendRequest(channel, 'test', 'test', True)
574         # needed to prevent errbacks during teardown.
575         d.addErrback(lambda failure: None)
576         self.conn.sendRequest(channel, 'test2', '', False)
577         channel.localClosed = True # emulate sending a close message
578         self.conn.sendRequest(channel, 'test3', '', True)
579         self.assertEqual(self.transport.packets,
580                 [(connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
581                     common.NS('test') + '\x01test'),
582                  (connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
583                      common.NS('test2') + '\x00')])
584         self.assertEqual(self.conn.deferreds[0], [d])
585
586     def test_adjustWindow(self):
587         """
588         Test that channel window adjust messages cause bytes to be added
589         to the window.
590         """
591         channel = TestChannel(localWindow=5)
592         self._openChannel(channel)
593         channel.localWindowLeft = 0
594         self.conn.adjustWindow(channel, 1)
595         self.assertEqual(channel.localWindowLeft, 1)
596         channel.localClosed = True
597         self.conn.adjustWindow(channel, 2)
598         self.assertEqual(channel.localWindowLeft, 1)
599         self.assertEqual(self.transport.packets,
600                 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
601                     '\x00\x00\x00\x01')])
602
603     def test_sendData(self):
604         """
605         Test that channel data messages are sent in the right format.
606         """
607         channel = TestChannel()
608         self._openChannel(channel)
609         self.conn.sendData(channel, 'a')
610         channel.localClosed = True
611         self.conn.sendData(channel, 'b')
612         self.assertEqual(self.transport.packets,
613                 [(connection.MSG_CHANNEL_DATA, '\x00\x00\x00\xff' +
614                     common.NS('a'))])
615
616     def test_sendExtendedData(self):
617         """
618         Test that channel extended data messages are sent in the right format.
619         """
620         channel = TestChannel()
621         self._openChannel(channel)
622         self.conn.sendExtendedData(channel, 1, 'test')
623         channel.localClosed = True
624         self.conn.sendExtendedData(channel, 2, 'test2')
625         self.assertEqual(self.transport.packets,
626                 [(connection.MSG_CHANNEL_EXTENDED_DATA, '\x00\x00\x00\xff' +
627                     '\x00\x00\x00\x01' + common.NS('test'))])
628
629     def test_sendEOF(self):
630         """
631         Test that channel EOF messages are sent in the right format.
632         """
633         channel = TestChannel()
634         self._openChannel(channel)
635         self.conn.sendEOF(channel)
636         self.assertEqual(self.transport.packets,
637                 [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
638         channel.localClosed = True
639         self.conn.sendEOF(channel)
640         self.assertEqual(self.transport.packets,
641                 [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
642
643     def test_sendClose(self):
644         """
645         Test that channel close messages are sent in the right format.
646         """
647         channel = TestChannel()
648         self._openChannel(channel)
649         self.conn.sendClose(channel)
650         self.assertTrue(channel.localClosed)
651         self.assertEqual(self.transport.packets,
652                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
653         self.conn.sendClose(channel)
654         self.assertEqual(self.transport.packets,
655                 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
656
657         channel2 = TestChannel()
658         self._openChannel(channel2)
659         channel2.remoteClosed = True
660         self.conn.sendClose(channel2)
661         self.assertTrue(channel2.gotClosed)
662
663     def test_getChannelWithAvatar(self):
664         """
665         Test that getChannel dispatches to the avatar when an avatar is
666         present. Correct functioning without the avatar is verified in
667         test_CHANNEL_OPEN.
668         """
669         channel = self.conn.getChannel('TestChannel', 50, 30, 'data')
670         self.assertEqual(channel.data, 'data')
671         self.assertEqual(channel.remoteWindowLeft, 50)
672         self.assertEqual(channel.remoteMaxPacket, 30)
673         self.assertRaises(error.ConchError, self.conn.getChannel,
674                 'BadChannel', 50, 30, 'data')
675
676     def test_gotGlobalRequestWithoutAvatar(self):
677         """
678         Test that gotGlobalRequests dispatches to global_* without an avatar.
679         """
680         del self.transport.avatar
681         self.assertTrue(self.conn.gotGlobalRequest('TestGlobal', 'data'))
682         self.assertEqual(self.conn.gotGlobalRequest('Test-Data', 'data'),
683                 (True, 'data'))
684         self.assertFalse(self.conn.gotGlobalRequest('BadGlobal', 'data'))
685
686
687     def test_channelClosedCausesLeftoverChannelDeferredsToErrback(self):
688         """
689         Whenever an SSH channel gets closed any Deferred that was returned by a
690         sendRequest() on its parent connection must be errbacked.
691         """
692         channel = TestChannel()
693         self._openChannel(channel)
694
695         d = self.conn.sendRequest(
696             channel, "dummyrequest", "dummydata", wantReply=1)
697         d = self.assertFailure(d, error.ConchError)
698         self.conn.channelClosed(channel)
699         return d
700
701
702
703 class TestCleanConnectionShutdown(unittest.TestCase):
704     """
705     Check whether correct cleanup is performed on connection shutdown.
706     """
707     if test_userauth.transport is None:
708         skip = "Cannot run without both PyCrypto and pyasn1"
709
710     def setUp(self):
711         self.transport = test_userauth.FakeTransport(None)
712         self.transport.avatar = TestAvatar()
713         self.conn = TestConnection()
714         self.conn.transport = self.transport
715
716
717     def test_serviceStoppedCausesLeftoverGlobalDeferredsToErrback(self):
718         """
719         Once the service is stopped any leftover global deferred returned by
720         a sendGlobalRequest() call must be errbacked.
721         """
722         self.conn.serviceStarted()
723
724         d = self.conn.sendGlobalRequest(
725             "dummyrequest", "dummydata", wantReply=1)
726         d = self.assertFailure(d, error.ConchError)
727         self.conn.serviceStopped()
728         return d
729
730