Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: third_party/twisted_8_1/twisted/protocols/amp.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.test.test_amp -*-
2 # Copyright (c) 2005 Divmod, Inc.
3 # Copyright (c) 2007 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6 """
7 This module implements AMP, the Asynchronous Messaging Protocol.
8
9 AMP is a protocol for sending multiple asynchronous request/response pairs over
10 the same connection. Requests and responses are both collections of key/value
11 pairs.
12
13 AMP is a very simple protocol which is not an application. This module is a
14 "protocol construction kit" of sorts; it attempts to be the simplest wire-level
15 implementation of Deferreds. AMP provides the following base-level features:
16
17 - Asynchronous request/response handling (hence the name)
18
19 - Requests and responses are both key/value pairs
20
21 - Binary transfer of all data: all data is length-prefixed. Your
22 application will never need to worry about quoting.
23
24 - Command dispatching (like HTTP Verbs): the protocol is extensible, and
25 multiple AMP sub-protocols can be grouped together easily.
26
27 The protocol implementation also provides a few additional features which are
28 not part of the core wire protocol, but are nevertheless very useful:
29
30 - Tight TLS integration, with an included StartTLS command.
31
32 - Handshaking to other protocols: because AMP has well-defined message
33 boundaries and maintains all incoming and outgoing requests for you, you
34 can start a connection over AMP and then switch to another protocol.
35 This makes it ideal for firewall-traversal applications where you may
36 have only one forwarded port but multiple applications that want to use
37 it.
38
39 Using AMP with Twisted is simple. Each message is a command, with a response.
40 You begin by defining a command type. Commands specify their input and output
41 in terms of the types that they expect to see in the request and response
42 key-value pairs. Here's an example of a command that adds two integers, 'a'
43 and 'b'::
44
45 class Sum(amp.Command):
46 arguments = [('a', amp.Integer()),
47 ('b', amp.Integer())]
48 response = [('total', amp.Integer())]
49
50 Once you have specified a command, you need to make it part of a protocol, and
51 define a responder for it. Here's a 'JustSum' protocol that includes a
52 responder for our 'Sum' command::
53
54 class JustSum(amp.AMP):
55 def sum(self, a, b):
56 total = a + b
57 print 'Did a sum: %d + %d = %d' % (a, b, total)
58 return {'total': total}
59 Sum.responder(sum)
60
61 Later, when you want to actually do a sum, the following expression will return
62 a Deferred which will fire with the result::
63
64 ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
65 lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
66 lambda result: result['total'])
67
68 You can also define the propagation of specific errors in AMP. For example,
69 for the slightly more complicated case of division, we might have to deal with
70 division by zero::
71
72 class Divide(amp.Command):
73 arguments = [('numerator', amp.Integer()),
74 ('denominator', amp.Integer())]
75 response = [('result', amp.Float())]
76 errors = {ZeroDivisionError: 'ZERO_DIVISION'}
77
78 The 'errors' mapping here tells AMP that if a responder to Divide emits a
79 L{ZeroDivisionError}, then the other side should be informed that an error of
80 the type 'ZERO_DIVISION' has occurred. Writing a responder which takes
81 advantage of this is very simple - just raise your exception normally::
82
83 class JustDivide(amp.AMP):
84 def divide(self, numerator, denominator):
85 result = numerator / denominator
86 print 'Divided: %d / %d = %d' % (numerator, denominator, total)
87 return {'result': result}
88 Divide.responder(divide)
89
90 On the client side, the errors mapping will be used to determine what the
91 'ZERO_DIVISION' error means, and translated into an asynchronous exception,
92 which can be handled normally as any L{Deferred} would be::
93
94 def trapZero(result):
95 result.trap(ZeroDivisionError)
96 print "Divided by zero: returning INF"
97 return 1e1000
98 ClientCreator(reactor, amp.AMP).connectTCP(...).addCallback(
99 lambda p: p.callRemote(Divide, numerator=1234,
100 denominator=0)
101 ).addErrback(trapZero)
102
103 For a complete, runnable example of both of these commands, see the files in
104 the Twisted repository::
105
106 doc/core/examples/ampserver.py
107 doc/core/examples/ampclient.py
108
109 On the wire, AMP is a protocol which uses 2-byte lengths to prefix keys and
110 values, and empty keys to separate messages::
111
112 <2-byte length><key><2-byte length><value>
113 <2-byte length><key><2-byte length><value>
114 ...
115 <2-byte length><key><2-byte length><value>
116 <NUL><NUL> # Empty Key == End of Message
117
118 And so on. Because it's tedious to refer to lengths and NULs constantly, the
119 documentation will refer to packets as if they were newline delimited, like
120 so::
121
122 C: _command: sum
123 C: _ask: ef639e5c892ccb54
124 C: a: 13
125 C: b: 81
126
127 S: _answer: ef639e5c892ccb54
128 S: total: 94
129
130 Notes:
131
132 Values are limited to the maximum encodable size in a 16-bit length, 65535
133 bytes.
134
135 Keys are limited to the maximum encodable size in a 8-bit length, 255 bytes.
136 Note that we still use 2-byte lengths to encode keys. This small redundancy
137 has several features:
138
139 - If an implementation becomes confused and starts emitting corrupt data,
140 or gets keys confused with values, many common errors will be
141 signalled immediately instead of delivering obviously corrupt packets.
142
143 - A single NUL will separate every key, and a double NUL separates
144 messages. This provides some redundancy when debugging traffic dumps.
145
146 - NULs will be present at regular intervals along the protocol, providing
147 some padding for otherwise braindead C implementations of the protocol,
148 so that <stdio.h> string functions will see the NUL and stop.
149
150 - This makes it possible to run an AMP server on a port also used by a
151 plain-text protocol, and easily distinguish between non-AMP clients (like
152 web browsers) which issue non-NUL as the first byte, and AMP clients,
153 which always issue NUL as the first byte.
154
155 """
156
157 __metaclass__ = type
158
159 import types, warnings
160
161 from cStringIO import StringIO
162 from struct import pack
163
164 from zope.interface import Interface, implements
165
166 from twisted.python.reflect import accumulateClassDict
167 from twisted.python.failure import Failure
168 from twisted.python import log, filepath
169
170 from twisted.internet.main import CONNECTION_LOST
171 from twisted.internet.error import PeerVerifyError, ConnectionLost
172 from twisted.internet.defer import Deferred, maybeDeferred, fail
173 from twisted.protocols.basic import Int16StringReceiver, StatefulStringProtocol
174
175 from twisted.internet._sslverify import problemsFromTransport
176
177 # I'd like this to use the exposed public API, but for some reason, when it was
178 # moved, these names were not exposed by internet.ssl.
179
180 from twisted.internet.ssl import CertificateOptions, Certificate, DN, KeyPair
181
182 ASK = '_ask'
183 ANSWER = '_answer'
184 COMMAND = '_command'
185 ERROR = '_error'
186 ERROR_CODE = '_error_code'
187 ERROR_DESCRIPTION = '_error_description'
188 UNKNOWN_ERROR_CODE = 'UNKNOWN'
189 UNHANDLED_ERROR_CODE = 'UNHANDLED'
190
191 MAX_KEY_LENGTH = 0xff
192 MAX_VALUE_LENGTH = 0xffff
193
194
195 class IBoxSender(Interface):
196 """
197 A transport which can send L{AmpBox} objects.
198 """
199
200 def sendBox(box):
201 """
202 Send an L{AmpBox}.
203
204 @raise ProtocolSwitched: if the underlying protocol has been
205 switched.
206
207 @raise ConnectionLost: if the underlying connection has already been
208 lost.
209 """
210
211 def unhandledError(failure):
212 """
213 An unhandled error occurred in response to a box. Log it
214 appropriately.
215
216 @param failure: a L{Failure} describing the error that occurred.
217 """
218
219
220
221 class IBoxReceiver(Interface):
222 """
223 An application object which can receive L{AmpBox} objects and dispatch them
224 appropriately.
225 """
226
227 def startReceivingBoxes(boxSender):
228 """
229 The L{ampBoxReceived} method will start being called; boxes may be
230 responded to by responding to the given L{IBoxSender}.
231
232 @param boxSender: an L{IBoxSender} provider.
233 """
234
235
236 def ampBoxReceived(box):
237 """
238 A box was received from the transport; dispatch it appropriately.
239 """
240
241
242 def stopReceivingBoxes(reason):
243 """
244 No further boxes will be received on this connection.
245
246 @type reason: L{Failure}
247 """
248
249
250
251 class IResponderLocator(Interface):
252 """
253 An application object which can look up appropriate responder methods for
254 AMP commands.
255 """
256
257 def locateResponder(self, name):
258 """
259 Locate a responder method appropriate for the named command.
260
261 @param name: the wire-level name (commandName) of the AMP command to be
262 responded to.
263
264 @return: a 1-argument callable that takes an L{AmpBox} with argument
265 values for the given command, and returns an L{AmpBox} containing
266 argument values for the named command, or a L{Deferred} that fires the
267 same.
268 """
269
270
271
272 class AmpError(Exception):
273 """
274 Base class of all Amp-related exceptions.
275 """
276
277
278
279 class ProtocolSwitched(Exception):
280 """
281 Connections which have been switched to other protocols can no longer
282 accept traffic at the AMP level. This is raised when you try to send it.
283 """
284
285
286
287 class OnlyOneTLS(AmpError):
288 """
289 This is an implementation limitation; TLS may only be started once per
290 connection.
291 """
292
293
294
295 class NoEmptyBoxes(AmpError):
296 """
297 You can't have empty boxes on the connection. This is raised when you
298 receive or attempt to send one.
299 """
300
301
302
303 class InvalidSignature(AmpError):
304 """
305 You didn't pass all the required arguments.
306 """
307
308
309
310 class TooLong(AmpError):
311 """
312 One of the protocol's length limitations was violated.
313
314 @ivar isKey: true if the string being encoded in a key position, false if
315 it was in a value position.
316
317 @ivar isLocal: Was the string encoded locally, or received too long from
318 the network? (It's only physically possible to encode "too long" values on
319 the network for keys.)
320
321 @ivar value: The string that was too long.
322
323 @ivar keyName: If the string being encoded was in a value position, what
324 key was it being encoded for?
325 """
326
327 def __init__(self, isKey, isLocal, value, keyName=None):
328 AmpError.__init__(self)
329 self.isKey = isKey
330 self.isLocal = isLocal
331 self.value = value
332 self.keyName = keyName
333
334
335 def __repr__(self):
336 hdr = self.isKey and "key" or "value"
337 if not self.isKey:
338 hdr += ' ' + repr(self.keyName)
339 lcl = self.isLocal and "local" or "remote"
340 return "%s %s too long: %d" % (lcl, hdr, len(self.value))
341
342
343
344 class BadLocalReturn(AmpError):
345 """
346 A bad value was returned from a local command; we were unable to coerce it.
347 """
348 def __init__(self, message, enclosed):
349 AmpError.__init__(self)
350 self.message = message
351 self.enclosed = enclosed
352
353
354 def __repr__(self):
355 return self.message + " " + self.enclosed.getBriefTraceback()
356
357 __str__ = __repr__
358
359
360
361 class RemoteAmpError(AmpError):
362 """
363 This error indicates that something went wrong on the remote end of the
364 connection, and the error was serialized and transmitted to you.
365 """
366 def __init__(self, errorCode, description, fatal=False, local=None):
367 """Create a remote error with an error code and description.
368
369 @param errorCode: the AMP error code of this error.
370
371 @param description: some text to show to the user.
372
373 @param fatal: a boolean, true if this error should terminate the
374 connection.
375
376 @param local: a local Failure, if one exists.
377 """
378 if local:
379 localwhat = ' (local)'
380 othertb = local.getBriefTraceback()
381 else:
382 localwhat = ''
383 othertb = ''
384 Exception.__init__(self, "Code<%s>%s: %s%s" % (
385 errorCode, localwhat,
386 description, othertb))
387 self.local = local
388 self.errorCode = errorCode
389 self.description = description
390 self.fatal = fatal
391
392
393
394 class UnknownRemoteError(RemoteAmpError):
395 """
396 This means that an error whose type we can't identify was raised from the
397 other side.
398 """
399 def __init__(self, description):
400 errorCode = UNKNOWN_ERROR_CODE
401 RemoteAmpError.__init__(self, errorCode, description)
402
403
404
405 class MalformedAmpBox(AmpError):
406 """
407 This error indicates that the wire-level protocol was malformed.
408 """
409
410
411
412 class UnhandledCommand(AmpError):
413 """
414 A command received via amp could not be dispatched.
415 """
416
417
418
419 class IncompatibleVersions(AmpError):
420 """
421 It was impossible to negotiate a compatible version of the protocol with
422 the other end of the connection.
423 """
424
425
426 PROTOCOL_ERRORS = {UNHANDLED_ERROR_CODE: UnhandledCommand}
427
428 class AmpBox(dict):
429 """
430 I am a packet in the AMP protocol, much like a regular str:str dictionary.
431 """
432 __slots__ = [] # be like a regular dictionary, don't magically
433 # acquire a __dict__...
434
435
436 def copy(self):
437 """
438 Return another AmpBox just like me.
439 """
440 newBox = self.__class__()
441 newBox.update(self)
442 return newBox
443
444
445 def serialize(self):
446 """
447 Convert me into a wire-encoded string.
448
449 @return: a str encoded according to the rules described in the module
450 docstring.
451 """
452 i = self.items()
453 i.sort()
454 L = []
455 w = L.append
456 for k, v in i:
457 if len(k) > MAX_KEY_LENGTH:
458 raise TooLong(True, True, k, None)
459 if len(v) > MAX_VALUE_LENGTH:
460 raise TooLong(False, True, v, k)
461 for kv in k, v:
462 w(pack("!H", len(kv)))
463 w(kv)
464 w(pack("!H", 0))
465 return ''.join(L)
466
467
468 def _sendTo(self, proto):
469 """
470 Serialize and send this box to a Amp instance. By the time it is being
471 sent, several keys are required. I must have exactly ONE of::
472
473 _ask
474 _answer
475 _error
476
477 If the '_ask' key is set, then the '_command' key must also be
478 set.
479
480 @param proto: an AMP instance.
481 """
482 proto.sendBox(self)
483
484 def __repr__(self):
485 return 'AmpBox(%s)' % (dict.__repr__(self),)
486
487 # amp.Box => AmpBox
488
489 Box = AmpBox
490
491 class QuitBox(AmpBox):
492 """
493 I am an AmpBox that, upon being sent, terminates the connection.
494 """
495 __slots__ = []
496
497
498 def __repr__(self):
499 return 'QuitBox(**%s)' % (super(QuitBox, self).__repr__(),)
500
501
502 def _sendTo(self, proto):
503 """
504 Immediately call loseConnection after sending.
505 """
506 super(QuitBox, self)._sendTo(proto)
507 proto.transport.loseConnection()
508
509
510
511 class _SwitchBox(AmpBox):
512 """
513 Implementation detail of ProtocolSwitchCommand: I am a AmpBox which sets
514 up state for the protocol to switch.
515 """
516
517 # DON'T set __slots__ here; we do have an attribute.
518
519 def __init__(self, innerProto, **kw):
520 """
521 Create a _SwitchBox with the protocol to switch to after being sent.
522
523 @param innerProto: the protocol instance to switch to.
524 @type innerProto: an IProtocol provider.
525 """
526 super(_SwitchBox, self).__init__(**kw)
527 self.innerProto = innerProto
528
529
530 def __repr__(self):
531 return '_SwitchBox(%r, **%s)' % (self.innerProto,
532 dict.__repr__(self),)
533
534
535 def _sendTo(self, proto):
536 """
537 Send me; I am the last box on the connection. All further traffic will be
538 over the new protocol.
539 """
540 super(_SwitchBox, self)._sendTo(proto)
541 proto._lockForSwitch()
542 proto._switchTo(self.innerProto)
543
544
545
546 class BoxDispatcher:
547 """
548 A L{BoxDispatcher} dispatches '_ask', '_answer', and '_error' L{AmpBox}es,
549 both incoming and outgoing, to their appropriate destinations.
550
551 Outgoing commands are converted into L{Deferred}s and outgoing boxes, and
552 associated tracking state to fire those L{Deferred} when '_answer' boxes
553 come back. Incoming '_answer' and '_error' boxes are converted into
554 callbacks and errbacks on those L{Deferred}s, respectively.
555
556 Incoming '_ask' boxes are converted into method calls on a supplied method
557 locator.
558
559 @ivar _outstandingRequests: a dictionary mapping request IDs to
560 L{Deferred}s which were returned for those requests.
561
562 @ivar locator: an object with a L{locateResponder} method that locates a
563 responder function that takes a Box and returns a result (either a Box or a
564 Deferred which fires one).
565
566 @ivar boxSender: an object which can send boxes, via the L{_sendBox}
567 method, such as an L{AMP} instance.
568 @type boxSender: L{IBoxSender}
569 """
570
571 implements(IBoxReceiver)
572
573 _failAllReason = None
574 _outstandingRequests = None
575 _counter = 0L
576 boxSender = None
577
578 def __init__(self, locator):
579 self._outstandingRequests = {}
580 self.locator = locator
581
582
583 def startReceivingBoxes(self, boxSender):
584 """
585 The given boxSender is going to start calling boxReceived on this
586 L{BoxDispatcher}.
587
588 @param boxSender: The L{IBoxSender} to send command responses to.
589 """
590 self.boxSender = boxSender
591
592
593 def stopReceivingBoxes(self, reason):
594 """
595 No further boxes will be received here. Terminate all currently
596 oustanding command deferreds with the given reason.
597 """
598 self.failAllOutgoing(reason)
599
600
601 def failAllOutgoing(self, reason):
602 """
603 Call the errback on all outstanding requests awaiting responses.
604
605 @param reason: the Failure instance to pass to those errbacks.
606 """
607 self._failAllReason = reason
608 OR = self._outstandingRequests.items()
609 self._outstandingRequests = None # we can never send another request
610 for key, value in OR:
611 value.errback(reason)
612
613
614 def _nextTag(self):
615 """
616 Generate protocol-local serial numbers for _ask keys.
617
618 @return: a string that has not yet been used on this connection.
619 """
620 self._counter += 1
621 return '%x' % (self._counter,)
622
623
624 def _sendBoxCommand(self, command, box, requiresAnswer=True):
625 """
626 Send a command across the wire with the given C{amp.Box}.
627
628 Mutate the given box to give it any additional keys (_command, _ask)
629 required for the command and request/response machinery, then send it.
630
631 If requiresAnswer is True, returns a C{Deferred} which fires when a
632 response is received. The C{Deferred} is fired with an C{amp.Box} on
633 success, or with an C{amp.RemoteAmpError} if an error is received.
634
635 If the Deferred fails and the error is not handled by the caller of
636 this method, the failure will be logged and the connection dropped.
637
638 @param command: a str, the name of the command to issue.
639
640 @param box: an AmpBox with the arguments for the command.
641
642 @param requiresAnswer: a boolean. Defaults to True. If True, return a
643 Deferred which will fire when the other side responds to this command.
644 If False, return None and do not ask the other side for acknowledgement.
645
646 @return: a Deferred which fires the AmpBox that holds the response to
647 this command, or None, as specified by requiresAnswer.
648
649 @raise ProtocolSwitched: if the protocol has been switched.
650 """
651 if self._failAllReason is not None:
652 return fail(self._failAllReason)
653 box[COMMAND] = command
654 tag = self._nextTag()
655 if requiresAnswer:
656 box[ASK] = tag
657 box._sendTo(self.boxSender)
658 if requiresAnswer:
659 result = self._outstandingRequests[tag] = Deferred()
660 else:
661 result = None
662 return result
663
664
665 def callRemoteString(self, command, requiresAnswer=True, **kw):
666 """
667 This is a low-level API, designed only for optimizing simple messages
668 for which the overhead of parsing is too great.
669
670 @param command: a str naming the command.
671
672 @param kw: arguments to the amp box.
673
674 @param requiresAnswer: a boolean. Defaults to True. If True, return a
675 Deferred which will fire when the other side responds to this command.
676 If False, return None and do not ask the other side for acknowledgement.
677
678 @return: a Deferred which fires the AmpBox that holds the response to
679 this command, or None, as specified by requiresAnswer.
680 """
681 box = Box(kw)
682 return self._sendBoxCommand(command, box)
683
684
685 def callRemote(self, commandType, *a, **kw):
686 """
687 This is the primary high-level API for sending messages via AMP. Invoke it
688 with a command and appropriate arguments to send a message to this
689 connection's peer.
690
691 @param commandType: a subclass of Command.
692 @type commandType: L{type}
693
694 @param a: Positional (special) parameters taken by the command.
695 Positional parameters will typically not be sent over the wire. The
696 only command included with AMP which uses positional parameters is
697 L{ProtocolSwitchCommand}, which takes the protocol that will be
698 switched to as its first argument.
699
700 @param kw: Keyword arguments taken by the command. These are the
701 arguments declared in the command's 'arguments' attribute. They will
702 be encoded and sent to the peer as arguments for the L{commandType}.
703
704 @return: If L{commandType} has a C{requiresAnswer} attribute set to
705 L{False}, then return L{None}. Otherwise, return a L{Deferred} which
706 fires with a dictionary of objects representing the result of this
707 call. Additionally, this L{Deferred} may fail with an exception
708 representing a connection failure, with L{UnknownRemoteError} if the
709 other end of the connection fails for an unknown reason, or with any
710 error specified as a key in L{commandType}'s C{errors} dictionary.
711 """
712
713 # XXX this takes command subclasses and not command objects on purpose.
714 # There's really no reason to have all this back-and-forth between
715 # command objects and the protocol, and the extra object being created
716 # (the Command instance) is pointless. Command is kind of like
717 # Interface, and should be more like it.
718
719 # In other words, the fact that commandType is instantiated here is an
720 # implementation detail. Don't rely on it.
721
722 co = commandType(*a, **kw)
723 return co._doCommand(self)
724
725
726 def unhandledError(self, failure):
727 """
728 This is a terminal callback called after application code has had a
729 chance to quash any errors.
730 """
731 return self.boxSender.unhandledError(failure)
732
733
734 def _answerReceived(self, box):
735 """
736 An AMP box was received that answered a command previously sent with
737 L{callRemote}.
738
739 @param box: an AmpBox with a value for its L{ANSWER} key.
740 """
741 question = self._outstandingRequests.pop(box[ANSWER])
742 question.addErrback(self.unhandledError)
743 question.callback(box)
744
745
746 def _errorReceived(self, box):
747 """
748 An AMP box was received that answered a command previously sent with
749 L{callRemote}, with an error.
750
751 @param box: an L{AmpBox} with a value for its L{ERROR}, L{ERROR_CODE},
752 and L{ERROR_DESCRIPTION} keys.
753 """
754 question = self._outstandingRequests.pop(box[ERROR])
755 question.addErrback(self.unhandledError)
756 errorCode = box[ERROR_CODE]
757 description = box[ERROR_DESCRIPTION]
758 if errorCode in PROTOCOL_ERRORS:
759 exc = PROTOCOL_ERRORS[errorCode](errorCode, description)
760 else:
761 exc = RemoteAmpError(errorCode, description)
762 question.errback(Failure(exc))
763
764
765 def _commandReceived(self, box):
766 """
767 @param box: an L{AmpBox} with a value for its L{COMMAND} and L{ASK}
768 keys.
769 """
770 cmd = box[COMMAND]
771 def formatAnswer(answerBox):
772 answerBox[ANSWER] = box[ASK]
773 return answerBox
774 def formatError(error):
775 if error.check(RemoteAmpError):
776 code = error.value.errorCode
777 desc = error.value.description
778 if error.value.fatal:
779 errorBox = QuitBox()
780 else:
781 errorBox = AmpBox()
782 else:
783 errorBox = QuitBox()
784 log.err(error) # here is where server-side logging happens
785 # if the error isn't handled
786 code = UNKNOWN_ERROR_CODE
787 desc = "Unknown Error"
788 errorBox[ERROR] = box[ASK]
789 errorBox[ERROR_DESCRIPTION] = desc
790 errorBox[ERROR_CODE] = code
791 return errorBox
792 deferred = self.dispatchCommand(box)
793 if ASK in box:
794 deferred.addCallbacks(formatAnswer, formatError)
795 deferred.addCallback(self._safeEmit)
796 deferred.addErrback(self.unhandledError)
797
798
799 def ampBoxReceived(self, box):
800 """
801 An AmpBox was received, representing a command, or an answer to a
802 previously issued command (either successful or erroneous). Respond to
803 it according to its contents.
804
805 @param box: an AmpBox
806
807 @raise NoEmptyBoxes: when a box is received that does not contain an
808 '_answer', '_command' / '_ask', or '_error' key; i.e. one which does not
809 fit into the command / response protocol defined by AMP.
810 """
811 if ANSWER in box:
812 self._answerReceived(box)
813 elif ERROR in box:
814 self._errorReceived(box)
815 elif COMMAND in box:
816 self._commandReceived(box)
817 else:
818 raise NoEmptyBoxes(box)
819
820
821 def _safeEmit(self, aBox):
822 """
823 Emit a box, ignoring L{ProtocolSwitched} and L{ConnectionLost} errors
824 which cannot be usefully handled.
825 """
826 try:
827 aBox._sendTo(self.boxSender)
828 except (ProtocolSwitched, ConnectionLost):
829 pass
830
831
832 def dispatchCommand(self, box):
833 """
834 A box with a _command key was received.
835
836 Dispatch it to a local handler call it.
837
838 @param proto: an AMP instance.
839 @param box: an AmpBox to be dispatched.
840 """
841 cmd = box[COMMAND]
842 responder = self.locator.locateResponder(cmd)
843 if responder is None:
844 return fail(RemoteAmpError(
845 UNHANDLED_ERROR_CODE,
846 "Unhandled Command: %r" % (cmd,),
847 False,
848 local=Failure(UnhandledCommand())))
849 return maybeDeferred(responder, box)
850
851
852
853 class CommandLocator:
854 """
855 A L{CommandLocator} is a collection of responders to AMP L{Command}s, with
856 the help of the L{Command.responder} decorator.
857 """
858
859 class __metaclass__(type):
860 """
861 This metaclass keeps track of all of the Command.responder-decorated
862 methods defined since the last CommandLocator subclass was defined. It
863 assumes (usually correctly, but unfortunately not necessarily so) that
864 those commands responders were all declared as methods of the class
865 being defined. Note that this list can be incorrect if users use the
866 Command.responder decorator outside the context of a CommandLocator
867 class declaration.
868
869 The Command.responder decorator explicitly cooperates with this
870 metaclass.
871 """
872
873 _currentClassCommands = []
874 def __new__(cls, name, bases, attrs):
875 commands = cls._currentClassCommands[:]
876 cls._currentClassCommands[:] = []
877 cd = attrs['_commandDispatch'] = {}
878 for base in bases:
879 cls._grabFromBase(cd, base)
880 for commandClass, responderFunc in commands:
881 cd[commandClass.commandName] = (commandClass, responderFunc)
882 subcls = type.__new__(cls, name, bases, attrs)
883 if (bases and (
884 subcls.lookupFunction != CommandLocator.lookupFunction)):
885 def locateResponder(self, name):
886 warnings.warn(
887 "Override locateResponder, not lookupFunction.",
888 category=PendingDeprecationWarning,
889 stacklevel=2)
890 return self.lookupFunction(name)
891 subcls.locateResponder = locateResponder
892 return subcls
893
894 def _grabFromBase(cls, cd, base):
895 if hasattr(base, "_commandDispatch"):
896 cd.update(base._commandDispatch)
897 for subbase in base.__bases__:
898 cls._grabFromBase(cd, subbase)
899 _grabFromBase = classmethod(_grabFromBase)
900
901 implements(IResponderLocator)
902
903
904 def _wrapWithSerialization(self, aCallable, command):
905 """
906 Wrap aCallable with its command's argument de-serialization
907 and result serialization logic.
908
909 @param aCallable: a callable with a 'command' attribute, designed to be
910 called with keyword arguments.
911
912 @param command: the command class whose serialization to use.
913
914 @return: a 1-arg callable which, when invoked with an AmpBox, will
915 deserialize the argument list and invoke appropriate user code for the
916 callable's command, returning a Deferred which fires with the result or
917 fails with an error.
918 """
919 def doit(box):
920 kw = command.parseArguments(box, self)
921 def checkKnownErrors(error):
922 key = error.trap(*command.allErrors)
923 code = command.allErrors[key]
924 desc = str(error.value)
925 return Failure(RemoteAmpError(
926 code, desc, key in command.fatalErrors, local=error))
927 def makeResponseFor(objects):
928 try:
929 return command.makeResponse(objects, self)
930 except:
931 # let's helpfully log this.
932 originalFailure = Failure()
933 raise BadLocalReturn(
934 "%r returned %r and %r could not serialize it" % (
935 aCallable,
936 objects,
937 command),
938 originalFailure)
939 return maybeDeferred(aCallable, **kw).addCallback(
940 makeResponseFor).addErrback(
941 checkKnownErrors)
942 return doit
943
944
945 def lookupFunction(self, name):
946 """
947 Deprecated synonym for L{locateResponder}
948 """
949 if self.__class__.lookupFunction != CommandLocator.lookupFunction:
950 return CommandLocator.locateResponder(self, name)
951 else:
952 warnings.warn("Call locateResponder, not lookupFunction.",
953 category=PendingDeprecationWarning,
954 stacklevel=2)
955 return self.locateResponder(name)
956
957
958 def locateResponder(self, name):
959 """
960 Locate a callable to invoke when executing the named command.
961
962 @param name: the normalized name (from the wire) of the command.
963
964 @return: a 1-argument function that takes a Box and returns a box or a
965 Deferred which fires a Box, for handling the command identified by the
966 given name, or None, if no appropriate responder can be found.
967 """
968 # Try to find a high-level method to invoke, and if we can't find one,
969 # fall back to a low-level one.
970 cd = self._commandDispatch
971 if name in cd:
972 commandClass, responderFunc = cd[name]
973 responderMethod = types.MethodType(
974 responderFunc, self, self.__class__)
975 return self._wrapWithSerialization(responderMethod, commandClass)
976
977
978
979 class SimpleStringLocator(object):
980 """
981 Implement the L{locateResponder} method to do simple, string-based
982 dispatch.
983 """
984
985 implements(IResponderLocator)
986
987 baseDispatchPrefix = 'amp_'
988
989 def locateResponder(self, name):
990 """
991 Locate a callable to invoke when executing the named command.
992
993 @return: a function with the name C{"amp_" + name} on L{self}, or None
994 if no such function exists. This function will then be called with the
995 L{AmpBox} itself as an argument.
996
997 @param name: the normalized name (from the wire) of the command.
998 """
999 fName = self.baseDispatchPrefix + (name.upper())
1000 return getattr(self, fName, None)
1001
1002
1003
1004 PYTHON_KEYWORDS = [
1005 'and', 'del', 'for', 'is', 'raise', 'assert', 'elif', 'from', 'lambda',
1006 'return', 'break', 'else', 'global', 'not', 'try', 'class', 'except',
1007 'if', 'or', 'while', 'continue', 'exec', 'import', 'pass', 'yield',
1008 'def', 'finally', 'in', 'print']
1009
1010
1011
1012 def _wireNameToPythonIdentifier(key):
1013 """
1014 (Private) Normalize an argument name from the wire for use with Python
1015 code. If the return value is going to be a python keyword it will be
1016 capitalized. If it contains any dashes they will be replaced with
1017 underscores.
1018
1019 The rationale behind this method is that AMP should be an inherently
1020 multi-language protocol, so message keys may contain all manner of bizarre
1021 bytes. This is not a complete solution; there are still forms of arguments
1022 that this implementation will be unable to parse. However, Python
1023 identifiers share a huge raft of properties with identifiers from many
1024 other languages, so this is a 'good enough' effort for now. We deal
1025 explicitly with dashes because that is the most likely departure: Lisps
1026 commonly use dashes to separate method names, so protocols initially
1027 implemented in a lisp amp dialect may use dashes in argument or command
1028 names.
1029
1030 @param key: a str, looking something like 'foo-bar-baz' or 'from'
1031
1032 @return: a str which is a valid python identifier, looking something like
1033 'foo_bar_baz' or 'From'.
1034 """
1035 lkey = key.replace("-", "_")
1036 if lkey in PYTHON_KEYWORDS:
1037 return lkey.title()
1038 return lkey
1039
1040
1041
1042 class Argument:
1043 """
1044 Base-class of all objects that take values from Amp packets and convert
1045 them into objects for Python functions.
1046 """
1047 optional = False
1048
1049
1050 def __init__(self, optional=False):
1051 """
1052 Create an Argument.
1053
1054 @param optional: a boolean indicating whether this argument can be
1055 omitted in the protocol.
1056 """
1057 self.optional = optional
1058
1059
1060 def retrieve(self, d, name, proto):
1061 """
1062 Retrieve the given key from the given dictionary, removing it if found.
1063
1064 @param d: a dictionary.
1065
1066 @param name: a key in L{d}.
1067
1068 @param proto: an instance of an AMP.
1069
1070 @raise KeyError: if I am not optional and no value was found.
1071
1072 @return: d[name].
1073 """
1074 if self.optional:
1075 value = d.get(name)
1076 if value is not None:
1077 del d[name]
1078 else:
1079 value = d.pop(name)
1080 return value
1081
1082
1083 def fromBox(self, name, strings, objects, proto):
1084 """
1085 Populate an 'out' dictionary with mapping names to Python values
1086 decoded from an 'in' AmpBox mapping strings to string values.
1087
1088 @param name: the argument name to retrieve
1089 @type name: str
1090
1091 @param strings: The AmpBox to read string(s) from, a mapping of
1092 argument names to string values.
1093 @type strings: AmpBox
1094
1095 @param objects: The dictionary to write object(s) to, a mapping of
1096 names to Python objects.
1097 @type objects: dict
1098
1099 @param proto: an AMP instance.
1100 """
1101 st = self.retrieve(strings, name, proto)
1102 nk = _wireNameToPythonIdentifier(name)
1103 if self.optional and st is None:
1104 objects[nk] = None
1105 else:
1106 objects[nk] = self.fromStringProto(st, proto)
1107
1108
1109 def toBox(self, name, strings, objects, proto):
1110 """
1111 Populate an 'out' AmpBox with strings encoded from an 'in' dictionary
1112 mapping names to Python values.
1113
1114 @param name: the argument name to retrieve
1115 @type name: str
1116
1117 @param strings: The AmpBox to write string(s) to, a mapping of
1118 argument names to string values.
1119 @type strings: AmpBox
1120
1121 @param objects: The dictionary to read object(s) from, a mapping of
1122 names to Python objects.
1123
1124 @type objects: dict
1125
1126 @param proto: the protocol we are converting for.
1127 @type proto: AMP
1128 """
1129 obj = self.retrieve(objects, _wireNameToPythonIdentifier(name), proto)
1130 if self.optional and obj is None:
1131 # strings[name] = None
1132 pass
1133 else:
1134 strings[name] = self.toStringProto(obj, proto)
1135
1136
1137 def fromStringProto(self, inString, proto):
1138 """
1139 Convert a string to a Python value.
1140
1141 @param inString: the string to convert.
1142
1143 @param proto: the protocol we are converting for.
1144 @type proto: AMP
1145
1146 @return: a Python object.
1147 """
1148 return self.fromString(inString)
1149
1150
1151 def toStringProto(self, inObject, proto):
1152 """
1153 Convert a Python object to a string.
1154
1155 @param inObject: the object to convert.
1156
1157 @param proto: the protocol we are converting for.
1158 @type proto: AMP
1159 """
1160 return self.toString(inObject)
1161
1162
1163 def fromString(self, inString):
1164 """
1165 Convert a string to a Python object. Subclasses must implement this.
1166
1167 @param inString: the string to convert.
1168 @type inString: str
1169
1170 @return: the decoded value from inString
1171 """
1172
1173
1174 def toString(self, inObject):
1175 """
1176 Convert a Python object into a string for passing over the network.
1177
1178 @param inObject: an object of the type that this Argument is intended
1179 to deal with.
1180
1181 @return: the wire encoding of inObject
1182 @rtype: str
1183 """
1184
1185
1186
1187 class Integer(Argument):
1188 """
1189 Convert to and from 'int'.
1190 """
1191 fromString = int
1192 def toString(self, inObject):
1193 return str(int(inObject))
1194
1195
1196
1197 class String(Argument):
1198 """
1199 Don't do any conversion at all; just pass through 'str'.
1200 """
1201 def toString(self, inObject):
1202 return inObject
1203
1204
1205 def fromString(self, inString):
1206 return inString
1207
1208
1209
1210 class Float(Argument):
1211 """
1212 Encode floating-point values on the wire as their repr.
1213 """
1214 fromString = float
1215 toString = repr
1216
1217
1218
1219 class Boolean(Argument):
1220 """
1221 Encode True or False as "True" or "False" on the wire.
1222 """
1223 def fromString(self, inString):
1224 if inString == 'True':
1225 return True
1226 elif inString == 'False':
1227 return False
1228 else:
1229 raise TypeError("Bad boolean value: %r" % (inString,))
1230
1231
1232 def toString(self, inObject):
1233 if inObject:
1234 return 'True'
1235 else:
1236 return 'False'
1237
1238
1239
1240 class Unicode(String):
1241 """
1242 Encode a unicode string on the wire as UTF-8.
1243 """
1244
1245 def toString(self, inObject):
1246 # assert isinstance(inObject, unicode)
1247 return String.toString(self, inObject.encode('utf-8'))
1248
1249
1250 def fromString(self, inString):
1251 # assert isinstance(inString, str)
1252 return String.fromString(self, inString).decode('utf-8')
1253
1254
1255
1256 class Path(Unicode):
1257 """
1258 Encode and decode L{filepath.FilePath} instances as paths on the wire.
1259
1260 This is really intended for use with subprocess communication tools:
1261 exchanging pathnames on different machines over a network is not generally
1262 meaningful, but neither is it disallowed; you can use this to communicate
1263 about NFS paths, for example.
1264 """
1265 def fromString(self, inString):
1266 return filepath.FilePath(Unicode.fromString(self, inString))
1267
1268
1269 def toString(self, inObject):
1270 return Unicode.toString(self, inObject.path)
1271
1272
1273
1274 class AmpList(Argument):
1275 """
1276 Convert a list of dictionaries into a list of AMP boxes on the wire.
1277
1278 For example, if you want to pass::
1279
1280 [{'a': 7, 'b': u'hello'}, {'a': 9, 'b': u'goodbye'}]
1281
1282 You might use an AmpList like this in your arguments or response list::
1283
1284 AmpList([('a', Integer()),
1285 ('b', Unicode())])
1286 """
1287 def __init__(self, subargs):
1288 """
1289 Create an AmpList.
1290
1291 @param subargs: a list of 2-tuples of ('name', argument) describing the
1292 schema of the dictionaries in the sequence of amp boxes.
1293 """
1294 self.subargs = subargs
1295
1296
1297 def fromStringProto(self, inString, proto):
1298 boxes = parseString(inString)
1299 values = [_stringsToObjects(box, self.subargs, proto)
1300 for box in boxes]
1301 return values
1302
1303
1304 def toStringProto(self, inObject, proto):
1305 return ''.join([_objectsToStrings(
1306 objects, self.subargs, Box(), proto
1307 ).serialize() for objects in inObject])
1308
1309 class Command:
1310 """
1311 Subclass me to specify an AMP Command.
1312
1313 @cvar arguments: A list of 2-tuples of (name, Argument-subclass-instance),
1314 specifying the names and values of the parameters which are required for
1315 this command.
1316
1317 @cvar response: A list like L{arguments}, but instead used for the return
1318 value.
1319
1320 @cvar errors: A mapping of subclasses of L{Exception} to wire-protocol tags
1321 for errors represented as L{str}s. Responders which raise keys from this
1322 dictionary will have the error translated to the corresponding tag on the
1323 wire. Invokers which receive Deferreds from invoking this command with
1324 L{AMP.callRemote} will potentially receive Failures with keys from this
1325 mapping as their value. This mapping is inherited; if you declare a
1326 command which handles C{FooError} as 'FOO_ERROR', then subclass it and
1327 specify C{BarError} as 'BAR_ERROR', responders to the subclass may raise
1328 either C{FooError} or C{BarError}, and invokers must be able to deal with
1329 either of those exceptions.
1330
1331 @cvar fatalErrors: like 'errors', but errors in this list will always
1332 terminate the connection, despite being of a recognizable error type.
1333
1334 @cvar commandType: The type of Box used to issue commands; useful only for
1335 protocol-modifying behavior like startTLS or protocol switching. Defaults
1336 to a plain vanilla L{Box}.
1337
1338 @cvar responseType: The type of Box used to respond to this command; only
1339 useful for protocol-modifying behavior like startTLS or protocol switching.
1340 Defaults to a plain vanilla L{Box}.
1341
1342 @ivar requiresAnswer: a boolean; defaults to True. Set it to False on your
1343 subclass if you want callRemote to return None. Note: this is a hint only
1344 to the client side of the protocol. The return-type of a command responder
1345 method must always be a dictionary adhering to the contract specified by
1346 L{response}, because clients are always free to request a response if they
1347 want one.
1348 """
1349
1350 class __metaclass__(type):
1351 """
1352 Metaclass hack to establish reverse-mappings for 'errors' and
1353 'fatalErrors' as class vars.
1354 """
1355 def __new__(cls, name, bases, attrs):
1356 re = attrs['reverseErrors'] = {}
1357 er = attrs['allErrors'] = {}
1358 if 'commandName' not in attrs:
1359 attrs['commandName'] = name
1360 newtype = type.__new__(cls, name, bases, attrs)
1361 errors = {}
1362 fatalErrors = {}
1363 accumulateClassDict(newtype, 'errors', errors)
1364 accumulateClassDict(newtype, 'fatalErrors', fatalErrors)
1365 for v, k in errors.iteritems():
1366 re[k] = v
1367 er[v] = k
1368 for v, k in fatalErrors.iteritems():
1369 re[k] = v
1370 er[v] = k
1371 return newtype
1372
1373 arguments = []
1374 response = []
1375 extra = []
1376 errors = {}
1377 fatalErrors = {}
1378
1379 commandType = Box
1380 responseType = Box
1381
1382 requiresAnswer = True
1383
1384
1385 def __init__(self, **kw):
1386 """
1387 Create an instance of this command with specified values for its
1388 parameters.
1389
1390 @param kw: a dict containing an appropriate value for each name
1391 specified in the L{arguments} attribute of my class.
1392
1393 @raise InvalidSignature: if you forgot any required arguments.
1394 """
1395 self.structured = kw
1396 givenArgs = kw.keys()
1397 forgotten = []
1398 for name, arg in self.arguments:
1399 pythonName = _wireNameToPythonIdentifier(name)
1400 if pythonName not in givenArgs and not arg.optional:
1401 forgotten.append(pythonName)
1402 if forgotten:
1403 raise InvalidSignature("forgot %s for %s" % (
1404 ', '.join(forgotten), self.commandName))
1405 forgotten = []
1406
1407
1408 def makeResponse(cls, objects, proto):
1409 """
1410 Serialize a mapping of arguments using this L{Command}'s
1411 response schema.
1412
1413 @param objects: a dict with keys matching the names specified in
1414 self.response, having values of the types that the Argument objects in
1415 self.response can format.
1416
1417 @param proto: an L{AMP}.
1418
1419 @return: an L{AmpBox}.
1420 """
1421 return _objectsToStrings(objects, cls.response, cls.responseType(),
1422 proto)
1423 makeResponse = classmethod(makeResponse)
1424
1425
1426 def makeArguments(cls, objects, proto):
1427 """
1428 Serialize a mapping of arguments using this L{Command}'s
1429 argument schema.
1430
1431 @param objects: a dict with keys similar to the names specified in
1432 self.arguments, having values of the types that the Argument objects in
1433 self.arguments can parse.
1434
1435 @param proto: an L{AMP}.
1436
1437 @return: An instance of this L{Command}'s C{commandType}.
1438 """
1439 return _objectsToStrings(objects, cls.arguments, cls.commandType(),
1440 proto)
1441 makeArguments = classmethod(makeArguments)
1442
1443
1444 def parseResponse(cls, box, protocol):
1445 """
1446 Parse a mapping of serialized arguments using this
1447 L{Command}'s response schema.
1448
1449 @param box: A mapping of response-argument names to the
1450 serialized forms of those arguments.
1451 @param protocol: The L{AMP} protocol.
1452
1453 @return: A mapping of response-argument names to the parsed
1454 forms.
1455 """
1456 return _stringsToObjects(box, cls.response, protocol)
1457 parseResponse = classmethod(parseResponse)
1458
1459
1460 def parseArguments(cls, box, protocol):
1461 """
1462 Parse a mapping of serialized arguments using this
1463 L{Command}'s argument schema.
1464
1465 @param box: A mapping of argument names to the seralized forms
1466 of those arguments.
1467 @param protocol: The L{AMP} protocol.
1468
1469 @return: A mapping of argument names to the parsed forms.
1470 """
1471 return _stringsToObjects(box, cls.arguments, protocol)
1472 parseArguments = classmethod(parseArguments)
1473
1474
1475 def responder(cls, methodfunc):
1476 """
1477 Declare a method to be a responder for a particular command.
1478
1479 This is a decorator.
1480
1481 Use like so::
1482
1483 class MyCommand(Command):
1484 arguments = [('a', ...), ('b', ...)]
1485
1486 class MyProto(AMP):
1487 def myFunMethod(self, a, b):
1488 ...
1489 MyCommand.responder(myFunMethod)
1490
1491 Notes: Although decorator syntax is not used within Twisted, this
1492 function returns its argument and is therefore safe to use with
1493 decorator syntax.
1494
1495 This is not thread safe. Don't declare AMP subclasses in other
1496 threads. Don't declare responders outside the scope of AMP subclasses;
1497 the behavior is undefined.
1498
1499 @param methodfunc: A function which will later become a method, which
1500 has a keyword signature compatible with this command's L{argument} list
1501 and returns a dictionary with a set of keys compatible with this
1502 command's L{response} list.
1503
1504 @return: the methodfunc parameter.
1505 """
1506 CommandLocator._currentClassCommands.append((cls, methodfunc))
1507 return methodfunc
1508 responder = classmethod(responder)
1509
1510
1511 # Our only instance method
1512 def _doCommand(self, proto):
1513 """
1514 Encode and send this Command to the given protocol.
1515
1516 @param proto: an AMP, representing the connection to send to.
1517
1518 @return: a Deferred which will fire or error appropriately when the
1519 other side responds to the command (or error if the connection is lost
1520 before it is responded to).
1521 """
1522
1523 def _massageError(error):
1524 error.trap(RemoteAmpError)
1525 rje = error.value
1526 errorType = self.reverseErrors.get(rje.errorCode,
1527 UnknownRemoteError)
1528 return Failure(errorType(rje.description))
1529
1530 d = proto._sendBoxCommand(self.commandName,
1531 self.makeArguments(self.structured, proto),
1532 self.requiresAnswer)
1533
1534 if self.requiresAnswer:
1535 d.addCallback(self.parseResponse, proto)
1536 d.addErrback(_massageError)
1537
1538 return d
1539
1540
1541
1542 class _NoCertificate:
1543 """
1544 This is for peers which don't want to use a local certificate. Used by
1545 AMP because AMP's internal language is all about certificates and this
1546 duck-types in the appropriate place; this API isn't really stable though,
1547 so it's not exposed anywhere public.
1548
1549 For clients, it will use ephemeral DH keys, or whatever the default is for
1550 certificate-less clients in OpenSSL. For servers, it will generate a
1551 temporary self-signed certificate with garbage values in the DN and use
1552 that.
1553 """
1554
1555 def __init__(self, client):
1556 """
1557 Create a _NoCertificate which either is or isn't for the client side of
1558 the connection.
1559
1560 @param client: True if we are a client and should truly have no
1561 certificate and be anonymous, False if we are a server and actually
1562 have to generate a temporary certificate.
1563
1564 @type client: bool
1565 """
1566 self.client = client
1567
1568
1569 def options(self, *authorities):
1570 """
1571 Behaves like L{twisted.internet.ssl.PrivateCertificate.options}().
1572 """
1573 if not self.client:
1574 # do some crud with sslverify to generate a temporary self-signed
1575 # certificate. This is SLOOOWWWWW so it is only in the absolute
1576 # worst, most naive case.
1577
1578 # We have to do this because OpenSSL will not let both the server
1579 # and client be anonymous.
1580 sharedDN = DN(CN='TEMPORARY CERTIFICATE')
1581 key = KeyPair.generate()
1582 cr = key.certificateRequest(sharedDN)
1583 sscrd = key.signCertificateRequest(sharedDN, cr, lambda dn: True, 1)
1584 cert = key.newCertificate(sscrd)
1585 return cert.options(*authorities)
1586 options = dict()
1587 if authorities:
1588 options.update(dict(verify=True,
1589 requireCertificate=True,
1590 caCerts=[auth.original for auth in authorities]) )
1591 occo = CertificateOptions(**options)
1592 return occo
1593
1594
1595
1596 class _TLSBox(AmpBox):
1597 """
1598 I am an AmpBox that, upon being sent, initiates a TLS connection.
1599 """
1600 __slots__ = []
1601
1602 def _keyprop(k, default):
1603 return property(lambda self: self.get(k, default))
1604
1605
1606 # These properties are described in startTLS
1607 certificate = _keyprop('tls_localCertificate', _NoCertificate(False))
1608 verify = _keyprop('tls_verifyAuthorities', None)
1609
1610 def _sendTo(self, proto):
1611 """
1612 Send my encoded value to the protocol, then initiate TLS.
1613 """
1614 ab = AmpBox(self)
1615 for k in ['tls_localCertificate',
1616 'tls_verifyAuthorities']:
1617 ab.pop(k, None)
1618 ab._sendTo(proto)
1619 proto._startTLS(self.certificate, self.verify)
1620
1621
1622
1623 class _LocalArgument(String):
1624 """
1625 Local arguments are never actually relayed across the wire. This is just a
1626 shim so that StartTLS can pretend to have some arguments: if arguments
1627 acquire documentation properties, replace this with something nicer later.
1628 """
1629
1630 def fromBox(self, name, strings, objects, proto):
1631 pass
1632
1633
1634
1635 class StartTLS(Command):
1636 """
1637 Use, or subclass, me to implement a command that starts TLS.
1638
1639 Callers of StartTLS may pass several special arguments, which affect the
1640 TLS negotiation:
1641
1642 - tls_localCertificate: This is a
1643 twisted.internet.ssl.PrivateCertificate which will be used to secure
1644 the side of the connection it is returned on.
1645
1646 - tls_verifyAuthorities: This is a list of
1647 twisted.internet.ssl.Certificate objects that will be used as the
1648 certificate authorities to verify our peer's certificate.
1649
1650 Each of those special parameters may also be present as a key in the
1651 response dictionary.
1652 """
1653
1654 arguments = [("tls_localCertificate", _LocalArgument(optional=True)),
1655 ("tls_verifyAuthorities", _LocalArgument(optional=True))]
1656
1657 response = [("tls_localCertificate", _LocalArgument(optional=True)),
1658 ("tls_verifyAuthorities", _LocalArgument(optional=True))]
1659
1660 responseType = _TLSBox
1661
1662 def __init__(self, **kw):
1663 """
1664 Create a StartTLS command. (This is private. Use AMP.callRemote.)
1665
1666 @param tls_localCertificate: the PrivateCertificate object to use to
1667 secure the connection. If it's None, or unspecified, an ephemeral DH
1668 key is used instead.
1669
1670 @param tls_verifyAuthorities: a list of Certificate objects which
1671 represent root certificates to verify our peer with.
1672 """
1673 self.certificate = kw.pop('tls_localCertificate', _NoCertificate(True))
1674 self.authorities = kw.pop('tls_verifyAuthorities', None)
1675 Command.__init__(self, **kw)
1676
1677
1678 def _doCommand(self, proto):
1679 """
1680 When a StartTLS command is sent, prepare to start TLS, but don't actuall y
1681 do it; wait for the acknowledgement, then initiate the TLS handshake.
1682 """
1683 d = Command._doCommand(self, proto)
1684 proto._prepareTLS(self.certificate, self.authorities)
1685 # XXX before we get back to user code we are going to start TLS...
1686 def actuallystart(response):
1687 proto._startTLS(self.certificate, self.authorities)
1688 return response
1689 d.addCallback(actuallystart)
1690 return d
1691
1692
1693
1694 class ProtocolSwitchCommand(Command):
1695 """
1696 Use this command to switch from something Amp-derived to a different
1697 protocol mid-connection. This can be useful to use amp as the
1698 connection-startup negotiation phase. Since TLS is a different layer
1699 entirely, you can use Amp to negotiate the security parameters of your
1700 connection, then switch to a different protocol, and the connection will
1701 remain secured.
1702 """
1703
1704 def __init__(self, _protoToSwitchToFactory, **kw):
1705 """
1706 Create a ProtocolSwitchCommand.
1707
1708 @param _protoToSwitchToFactory: a ProtocolFactory which will generate
1709 the Protocol to switch to.
1710
1711 @param kw: Keyword arguments, encoded and handled normally as
1712 L{Command} would.
1713 """
1714
1715 self.protoToSwitchToFactory = _protoToSwitchToFactory
1716 super(ProtocolSwitchCommand, self).__init__(**kw)
1717
1718
1719 def makeResponse(cls, innerProto, proto):
1720 return _SwitchBox(innerProto)
1721 makeResponse = classmethod(makeResponse)
1722
1723
1724 def _doCommand(self, proto):
1725 """
1726 When we emit a ProtocolSwitchCommand, lock the protocol, but don't actua lly
1727 switch to the new protocol unless an acknowledgement is received. If
1728 an error is received, switch back.
1729 """
1730 d = super(ProtocolSwitchCommand, self)._doCommand(proto)
1731 proto._lockForSwitch()
1732 def switchNow(ign):
1733 innerProto = self.protoToSwitchToFactory.buildProtocol(
1734 proto.transport.getPeer())
1735 proto._switchTo(innerProto, self.protoToSwitchToFactory)
1736 return ign
1737 def handle(ign):
1738 proto._unlockFromSwitch()
1739 self.protoToSwitchToFactory.clientConnectionFailed(
1740 None, Failure(CONNECTION_LOST))
1741 return ign
1742 return d.addCallbacks(switchNow, handle)
1743
1744
1745
1746 class BinaryBoxProtocol(StatefulStringProtocol, Int16StringReceiver):
1747 """
1748 A protocol for receving L{Box}es - key/value pairs - via length-prefixed
1749 strings. A box is composed of:
1750
1751 - any number of key-value pairs, described by:
1752 - a 2-byte network-endian packed key length (of which the first
1753 byte must be null, and the second must be non-null: i.e. the
1754 value of the length must be 1-255)
1755 - a key, comprised of that many bytes
1756 - a 2-byte network-endian unsigned value length (up to the maximum
1757 of 65535)
1758 - a value, comprised of that many bytes
1759 - 2 null bytes
1760
1761 In other words, an even number of strings prefixed with packed unsigned
1762 16-bit integers, and then a 0-length string to indicate the end of the box.
1763
1764 This protocol also implements 2 extra private bits of functionality related
1765 to the byte boundaries between messages; it can start TLS between two given
1766 boxes or switch to an entirely different protocol. However, due to some
1767 tricky elements of the implementation, the public interface to this
1768 functionality is L{ProtocolSwitchCommand} and L{StartTLS}.
1769
1770 @ivar boxReceiver: an L{IBoxReceiver} provider, whose L{ampBoxReceived}
1771 method will be invoked for each L{Box} that is received.
1772 """
1773
1774 implements(IBoxSender)
1775
1776 _justStartedTLS = False
1777 _startingTLSBuffer = None
1778 _locked = False
1779 _currentKey = None
1780 _currentBox = None
1781
1782 hostCertificate = None
1783 noPeerCertificate = False # for tests
1784 innerProtocol = None
1785 innerProtocolClientFactory = None
1786
1787 _sslVerifyProblems = ()
1788 # ^ Later this will become a mutable list - we can't get the handle during
1789 # connection shutdown thanks to the fact that Twisted destroys the socket
1790 # on our transport before notifying us of a lost connection (which I guess
1791 # is reasonable - the socket is dead by then) See a few lines below in
1792 # startTLS for details. --glyph
1793
1794
1795 def __init__(self, boxReceiver):
1796 self.boxReceiver = boxReceiver
1797
1798
1799 def _switchTo(self, newProto, clientFactory=None):
1800 """
1801 Switch this BinaryBoxProtocol's transport to a new protocol. You need
1802 to do this 'simultaneously' on both ends of a connection; the easiest
1803 way to do this is to use a subclass of ProtocolSwitchCommand.
1804
1805 @param newProto: the new protocol instance to switch to.
1806
1807 @param clientFactory: the ClientFactory to send the
1808 L{clientConnectionLost} notification to.
1809 """
1810 # All the data that Int16Receiver has not yet dealt with belongs to our
1811 # new protocol: luckily it's keeping that in a handy (although
1812 # ostensibly internal) variable for us:
1813 newProtoData = self.recvd
1814 # We're quite possibly in the middle of a 'dataReceived' loop in
1815 # Int16StringReceiver: let's make sure that the next iteration, the
1816 # loop will break and not attempt to look at something that isn't a
1817 # length prefix.
1818 self.recvd = ''
1819 # Finally, do the actual work of setting up the protocol and delivering
1820 # its first chunk of data, if one is available.
1821 self.innerProtocol = newProto
1822 self.innerProtocolClientFactory = clientFactory
1823 newProto.makeConnection(self.transport)
1824 newProto.dataReceived(newProtoData)
1825
1826
1827 def sendBox(self, box):
1828 """
1829 Send a amp.Box to my peer.
1830
1831 Note: transport.write is never called outside of this method.
1832
1833 @param box: an AmpBox.
1834
1835 @raise ProtocolSwitched: if the protocol has previously been switched.
1836
1837 @raise ConnectionLost: if the connection has previously been lost.
1838 """
1839 if self._locked:
1840 raise ProtocolSwitched(
1841 "This connection has switched: no AMP traffic allowed.")
1842 if self.transport is None:
1843 raise ConnectionLost()
1844 if self._startingTLSBuffer is not None:
1845 self._startingTLSBuffer.append(box)
1846 else:
1847 self.transport.write(box.serialize())
1848
1849
1850 def makeConnection(self, transport):
1851 """
1852 Notify L{boxReceiver} that it is about to receive boxes from this
1853 protocol by invoking L{startReceivingBoxes}.
1854 """
1855 self.boxReceiver.startReceivingBoxes(self)
1856 Int16StringReceiver.makeConnection(self, transport)
1857
1858
1859 def dataReceived(self, data):
1860 """
1861 Either parse incoming data as L{AmpBox}es or relay it to our nested
1862 protocol.
1863 """
1864 if self._justStartedTLS:
1865 self._justStartedTLS = False
1866 # If we already have an inner protocol, then we don't deliver data to
1867 # the protocol parser any more; we just hand it off.
1868 if self.innerProtocol is not None:
1869 self.innerProtocol.dataReceived(data)
1870 return
1871 return Int16StringReceiver.dataReceived(self, data)
1872
1873
1874 def connectionLost(self, reason):
1875 """
1876 The connection was lost; notify any nested protocol.
1877 """
1878 if self.innerProtocol is not None:
1879 self.innerProtocol.connectionLost(reason)
1880 if self.innerProtocolClientFactory is not None:
1881 self.innerProtocolClientFactory.clientConnectionLost(None, reaso n)
1882 # XXX this may be a slight oversimplification, but I believe that if
1883 # there are pending SSL errors, they _are_ the reason that the
1884 # connection was lost. a totally correct implementation of this would
1885 # set up a simple state machine to track whether any bytes were
1886 # received after startTLS was called. --glyph
1887 problems = self._sslVerifyProblems
1888 if problems:
1889 failReason = Failure(problems[0])
1890 elif self._justStartedTLS:
1891 # We just started TLS and haven't received any data. This means
1892 # the other connection didn't like our cert (although they may not
1893 # have told us why - later Twisted should make 'reason' into a TLS
1894 # error.)
1895 failReason = PeerVerifyError(
1896 "Peer rejected our certificate for an unknown reason.")
1897 else:
1898 failReason = reason
1899 self.boxReceiver.stopReceivingBoxes(failReason)
1900
1901
1902
1903 def proto_init(self, string):
1904 """
1905 String received in the 'init' state.
1906 """
1907 self._currentBox = AmpBox()
1908 return self.proto_key(string)
1909
1910
1911 def proto_key(self, string):
1912 """
1913 String received in the 'key' state. If the key is empty, a complete
1914 box has been received.
1915 """
1916 if string:
1917 self._currentKey = string
1918 return 'value'
1919 else:
1920 self.boxReceiver.ampBoxReceived(self._currentBox)
1921 self._currentBox = None
1922 return 'init'
1923
1924
1925 def proto_value(self, string):
1926 """
1927 String received in the 'value' state.
1928 """
1929 self._currentBox[self._currentKey] = string
1930 self._currentKey = None
1931 return 'key'
1932
1933
1934 def _lockForSwitch(self):
1935 """
1936 Lock this binary protocol so that no further boxes may be sent. This
1937 is used when sending a request to switch underlying protocols. You
1938 probably want to subclass ProtocolSwitchCommand rather than calling
1939 this directly.
1940 """
1941 self._locked = True
1942
1943
1944 def _unlockFromSwitch(self):
1945 """
1946 Unlock this locked binary protocol so that further boxes may be sent
1947 again. This is used after an attempt to switch protocols has failed
1948 for some reason.
1949 """
1950 if self.innerProtocol is not None:
1951 raise ProtocolSwitched("Protocol already switched. Cannot unlock.")
1952 self._locked = False
1953
1954
1955 def _prepareTLS(self, certificate, verifyAuthorities):
1956 """
1957 Used by StartTLSCommand to put us into the state where we don't
1958 actually send things that get sent, instead we buffer them. see
1959 L{_sendBox}.
1960 """
1961 self._startingTLSBuffer = []
1962 if self.hostCertificate is not None:
1963 raise OnlyOneTLS(
1964 "Previously authenticated connection between %s and %s "
1965 "is trying to re-establish as %s" % (
1966 self.hostCertificate,
1967 self.peerCertificate,
1968 (certificate, verifyAuthorities)))
1969
1970
1971 def _startTLS(self, certificate, verifyAuthorities):
1972 """
1973 Used by TLSBox to initiate the SSL handshake.
1974
1975 @param certificate: a L{twisted.internet.ssl.PrivateCertificate} for
1976 use locally.
1977
1978 @param verifyAuthorities: L{twisted.internet.ssl.Certificate} instances
1979 representing certificate authorities which will verify our peer.
1980 """
1981 self.hostCertificate = certificate
1982 self._justStartedTLS = True
1983 if verifyAuthorities is None:
1984 verifyAuthorities = ()
1985 self.transport.startTLS(certificate.options(*verifyAuthorities))
1986 # Remember that mutable list that we were just talking about? Here
1987 # it is. sslverify.py takes care of populating this list as
1988 # necessary. --glyph
1989 self._sslVerifyProblems = problemsFromTransport(self.transport)
1990 stlsb = self._startingTLSBuffer
1991 if stlsb is not None:
1992 self._startingTLSBuffer = None
1993 for box in stlsb:
1994 self.sendBox(box)
1995
1996
1997 def _getPeerCertificate(self):
1998 if self.noPeerCertificate:
1999 return None
2000 return Certificate.peerFromTransport(self.transport)
2001 peerCertificate = property(_getPeerCertificate)
2002
2003
2004 def unhandledError(self, failure):
2005 """
2006 The buck stops here. This error was completely unhandled, time to
2007 terminate the connection.
2008 """
2009 log.msg("Amp server or network failure "
2010 "unhandled by client application:")
2011 log.err(failure)
2012 log.msg(
2013 "Dropping connection! "
2014 "To avoid, add errbacks to ALL remote commands!")
2015 if self.transport is not None:
2016 self.transport.loseConnection()
2017
2018
2019 def _defaultStartTLSResponder(self):
2020 """
2021 The default TLS responder doesn't specify any certificate or anything.
2022
2023 From a security perspective, it's little better than a plain-text
2024 connection - but it is still a *bit* better, so it's included for
2025 convenience.
2026
2027 You probably want to override this by providing your own StartTLS.respon der.
2028 """
2029 return {}
2030 StartTLS.responder(_defaultStartTLSResponder)
2031
2032
2033
2034 class AMP(BinaryBoxProtocol, BoxDispatcher,
2035 CommandLocator, SimpleStringLocator):
2036 """
2037 This protocol is an AMP connection. See the module docstring for protocol
2038 details.
2039 """
2040
2041 _ampInitialized = False
2042
2043 def __init__(self, boxReceiver=None, locator=None):
2044 # For backwards compatibility. When AMP did not separate parsing logic
2045 # (L{BinaryBoxProtocol}), request-response logic (L{BoxDispatcher}) and
2046 # command routing (L{CommandLocator}), it did not have a constructor.
2047 # Now it does, so old subclasses might have defined their own that did
2048 # not upcall. If this flag isn't set, we'll call the constructor in
2049 # makeConnection before anything actually happens.
2050 self._ampInitialized = True
2051 if boxReceiver is None:
2052 boxReceiver = self
2053 if locator is None:
2054 locator = self
2055 boxSender = self
2056 BoxDispatcher.__init__(self, locator)
2057 BinaryBoxProtocol.__init__(self, boxReceiver)
2058
2059
2060 def locateResponder(self, name):
2061 """
2062 Unify the implementations of L{CommandLocator} and
2063 L{SimpleStringLocator} to perform both kinds of dispatch, preferring
2064 L{CommandLocator}.
2065 """
2066 firstResponder = CommandLocator.locateResponder(self, name)
2067 if firstResponder is not None:
2068 return firstResponder
2069 secondResponder = SimpleStringLocator.locateResponder(self, name)
2070 return secondResponder
2071
2072
2073 def __repr__(self):
2074 """
2075 A verbose string representation which gives us information about this
2076 AMP connection.
2077 """
2078 return '<%s %s at 0x%x>' % (
2079 self.__class__.__name__,
2080 self.innerProtocol, id(self))
2081
2082
2083 def makeConnection(self, transport):
2084 """
2085 Emit a helpful log message when the connection is made.
2086 """
2087 if not self._ampInitialized:
2088 # See comment in the constructor re: backward compatibility. I
2089 # should probably emit a deprecation warning here.
2090 AMP.__init__(self)
2091 # Save these so we can emit a similar log message in L{connectionLost}.
2092 self._transportPeer = transport.getPeer()
2093 self._transportHost = transport.getHost()
2094 log.msg("%s connection established (HOST:%s PEER:%s)" % (
2095 self.__class__.__name__,
2096 self._transportHost,
2097 self._transportPeer))
2098 BinaryBoxProtocol.makeConnection(self, transport)
2099
2100
2101 def connectionLost(self, reason):
2102 """
2103 Emit a helpful log message when the connection is lost.
2104 """
2105 log.msg("%s connection lost (HOST:%s PEER:%s)" %
2106 (self.__class__.__name__,
2107 self._transportHost,
2108 self._transportPeer))
2109 BinaryBoxProtocol.connectionLost(self, reason)
2110 self.transport = None
2111
2112
2113
2114 class _ParserHelper:
2115 """
2116 A box receiver which records all boxes received.
2117 """
2118 def __init__(self):
2119 self.boxes = []
2120
2121
2122 def getPeer(self):
2123 return 'string'
2124
2125
2126 def getHost(self):
2127 return 'string'
2128
2129 disconnecting = False
2130
2131
2132 def startReceivingBoxes(self, sender):
2133 """
2134 No initialization is required.
2135 """
2136
2137
2138 def ampBoxReceived(self, box):
2139 self.boxes.append(box)
2140
2141
2142 # Synchronous helpers
2143 def parse(cls, fileObj):
2144 """
2145 Parse some amp data stored in a file.
2146
2147 @param fileObj: a file-like object.
2148
2149 @return: a list of AmpBoxes encoded in the given file.
2150 """
2151 parserHelper = cls()
2152 bbp = BinaryBoxProtocol(boxReceiver=parserHelper)
2153 bbp.makeConnection(parserHelper)
2154 bbp.dataReceived(fileObj.read())
2155 return parserHelper.boxes
2156 parse = classmethod(parse)
2157
2158
2159 def parseString(cls, data):
2160 """
2161 Parse some amp data stored in a string.
2162
2163 @param data: a str holding some amp-encoded data.
2164
2165 @return: a list of AmpBoxes encoded in the given string.
2166 """
2167 return cls.parse(StringIO(data))
2168 parseString = classmethod(parseString)
2169
2170
2171
2172 parse = _ParserHelper.parse
2173 parseString = _ParserHelper.parseString
2174
2175 def _stringsToObjects(strings, arglist, proto):
2176 """
2177 Convert an AmpBox to a dictionary of python objects, converting through a
2178 given arglist.
2179
2180 @param strings: an AmpBox (or dict of strings)
2181
2182 @param arglist: a list of 2-tuples of strings and Argument objects, as
2183 described in L{Command.arguments}.
2184
2185 @param proto: an L{AMP} instance.
2186
2187 @return: the converted dictionary mapping names to argument objects.
2188 """
2189 objects = {}
2190 myStrings = strings.copy()
2191 for argname, argparser in arglist:
2192 argparser.fromBox(argname, myStrings, objects, proto)
2193 return objects
2194
2195
2196
2197 def _objectsToStrings(objects, arglist, strings, proto):
2198 """
2199 Convert a dictionary of python objects to an AmpBox, converting through a
2200 given arglist.
2201
2202 @param objects: a dict mapping names to python objects
2203
2204 @param arglist: a list of 2-tuples of strings and Argument objects, as
2205 described in L{Command.arguments}.
2206
2207 @param strings: [OUT PARAMETER] An object providing the L{dict}
2208 interface which will be populated with serialized data.
2209
2210 @param proto: an L{AMP} instance.
2211
2212 @return: The converted dictionary mapping names to encoded argument
2213 strings (identical to C{strings}).
2214 """
2215 myObjects = {}
2216 for (k, v) in objects.items():
2217 myObjects[k] = v
2218
2219 for argname, argparser in arglist:
2220 argparser.toBox(argname, strings, myObjects, proto)
2221 return strings
2222
2223
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/protocols/_c_urlarg.c ('k') | third_party/twisted_8_1/twisted/protocols/basic.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698