root/trunk/twisted/protocols/amp.py

Revision 34362, 88.5 KB (checked in by exarkun, 12 days ago)

Correct a typo in the epytext markup for twisted.protocols.amp.Descriptor.fromStringProto

Author: exarkun
Reviewer: itamar
Fixes: #5673

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