root/tags/releases/twisted-8.2.0/twisted/protocols/amp.py

Revision 25346, 71.8 KB (checked in by exarkun, 22 months ago)

Merge amp-keys-doc-3216

Author: thijs, exarkun
Reviewer: therve
Fixes: #3216

Clarify the amp box key ordering rules in the amp module docstring.

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