amp.py 95 KB

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