1import six 2from twisted.python.failure import Failure 3from zope.interface import Attribute, Interface 4 5# delimiter characters. 6LIST = six.int2byte(0x80) # old 7INT = six.int2byte(0x81) 8STRING = six.int2byte(0x82) 9NEG = six.int2byte(0x83) 10FLOAT = six.int2byte(0x84) 11# "optional" -- these might be refused by a low-level implementation. 12LONGINT = six.int2byte(0x85) # old 13LONGNEG = six.int2byte(0x86) # old 14# really optional; this is is part of the 'pb' vocabulary 15VOCAB = six.int2byte(0x87) 16# newbanana tokens 17OPEN = six.int2byte(0x88) 18CLOSE = six.int2byte(0x89) 19ABORT = six.int2byte(0x8A) 20ERROR = six.int2byte(0x8D) 21PING = six.int2byte(0x8E) 22PONG = six.int2byte(0x8F) 23 24tokenNames = { 25 LIST: "LIST", 26 INT: "INT", 27 STRING: "STRING", 28 NEG: "NEG", 29 FLOAT: "FLOAT", 30 LONGINT: "LONGINT", 31 LONGNEG: "LONGNEG", 32 VOCAB: "VOCAB", 33 OPEN: "OPEN", 34 CLOSE: "CLOSE", 35 ABORT: "ABORT", 36 ERROR: "ERROR", 37 PING: "PING", 38 PONG: "PONG", 39 } 40 41SIZE_LIMIT = 1000 # default limit on the body length of long tokens (STRING, 42 # LONGINT, LONGNEG, ERROR) 43 44class InvalidRemoteInterface(Exception): 45 pass 46class UnknownSchemaType(Exception): 47 pass 48 49class Violation(Exception): 50 """This exception is raised in response to a schema violation. It 51 indicates that the incoming token stream has violated a constraint 52 imposed by the recipient. The current Unslicer is abandoned and the 53 error is propagated upwards to the enclosing Unslicer parent by 54 providing an BananaFailure object to the parent's .receiveChild method. 55 All remaining tokens for the current Unslicer are to be dropped. 56 """ 57 58 """.where: this string describes which node of the object graph was 59 being handled when the exception took place.""" 60 where = "" 61 62 def setLocation(self, where): 63 self.where = where 64 def getLocation(self): 65 return self.where 66 def prependLocation(self, prefix): 67 if self.where: 68 self.where = prefix + " " + self.where 69 else: 70 self.where = prefix 71 def appendLocation(self, suffix): 72 if self.where: 73 self.where = self.where + " " + suffix 74 else: 75 self.where = suffix 76 77 def __str__(self): 78 if self.where: 79 return "Violation (%s): %s" % (self.where, self.args) 80 else: 81 return "Violation: %s" % (self.args,) 82 83class RemoteException(Exception): 84 """When the Tub is in expose-remote-exception-types=False mode, this 85 exception is raised in response to any remote exception. It wraps a 86 CopiedFailure, which can be examined by callers who want to know more 87 than the fact that something failed on the remote end.""" 88 def __init__(self, failure): 89 self.failure = failure 90 def __str__(self): 91 return "<RemoteException around '%s'>" % str(self.failure) 92 93 94class BananaError(Exception): 95 """This exception is raised in response to a fundamental protocol 96 violation. The connection should be dropped immediately. 97 98 .where is an optional string that describes the node of the object graph 99 where the failure was noticed. 100 """ 101 where = None 102 103 def __str__(self): 104 if self.where: 105 return "BananaError(in %s): %s" % (self.where, self.args) 106 else: 107 return "BananaError: %s" % (self.args,) 108 109class NegotiationError(Exception): 110 pass 111class DuplicateConnection(NegotiationError): 112 pass 113 114class RemoteNegotiationError(Exception): 115 """The other end hung up on us because they had a NegotiationError on 116 their side.""" 117 pass 118 119class PBError(Exception): 120 pass 121 122class BananaFailure(Failure): 123 """This is a marker subclass of Failure, to let Unslicer.receiveChild 124 distinguish between an unserialized Failure instance and a a failure in 125 a child Unslicer""" 126 pass 127 128class WrongTubIdError(Exception): 129 """getReference(furlFile=) used a FURL with a different TubID""" 130class WrongNameError(Exception): 131 """getReference(furlFule=) used a FURL with a different name""" 132 133class NoLocationError(Exception): 134 """This Tub has no location set, so we cannot make references to it.""" 135 136class NoLocationHintsError(Exception): 137 """We cannot make a connection without some location hints""" 138 139class ISlicer(Interface): 140 """I know how to slice objects into tokens.""" 141 142 sendOpen = Attribute(\ 143"""True if an OPEN/CLOSE token pair should be sent around the Slicer's body 144tokens. Only special-purpose Slicers (like the RootSlicer) should use False. 145""") 146 147 trackReferences = Attribute(\ 148"""True if the object we slice is referenceable: i.e. it is useful or 149necessary to send multiple copies as a single instance and a bunch of 150References, rather than as separate copies. Instances are referenceable, as 151are mutable containers like lists.""") 152 153 streamable = Attribute(\ 154"""True if children of this object are allowed to use Deferreds to stall 155production of new tokens. This must be set in slice() before yielding each 156child object, and affects that child and all descendants. Streaming is only 157allowed if the parent also allows streaming: if slice() is called with 158streamable=False, then self.streamable must be False too. It can be changed 159from within the slice() generator at any time as long as this restriction is 160obeyed. 161 162This attribute is read when each child Slicer is started.""") 163 164 165 def slice(streamable, banana): 166 """Return an iterator which provides Index Tokens and the Body 167 Tokens of the object's serialized form. This is frequently 168 implemented with a generator (i.e. 'yield' appears in the body of 169 this function). Do not yield the OPEN or the CLOSE token, those will 170 be handled elsewhere. 171 172 If a Violation exception is raised, slicing will cease. An ABORT 173 token followed by a CLOSE token will be emitted. 174 175 If 'streamable' is True, the iterator may yield a Deferred to 176 indicate that slicing should wait until the Deferred is fired. If 177 the Deferred is errbacked, the connection will be dropped. TODO: it 178 should be possible to errback with a Violation.""" 179 180 def registerRefID(refid, obj): 181 """Register the relationship between 'refid' (a number taken from 182 the cumulative count of OPEN tokens sent over our connection: 0 is 183 the object described by the very first OPEN sent over the wire) and 184 the object. If the object is sent a second time, a Reference may be 185 used in its place. 186 187 Slicers usually delgate this function upwards to the RootSlicer, but 188 it can be handled at any level to allow local scoping of references 189 (they might only be valid within a single RPC invocation, for 190 example). 191 192 This method is *not* allowed to raise a Violation, as that will mess 193 up the transmit logic. If it raises any other exception, the 194 connection will be dropped.""" 195 196 def childAborted(f): 197 """Notify the Slicer that one of its child slicers (as produced by 198 its .slice iterator) has caused an error. If the slicer got started, 199 it has now emitted an ABORT token and terminated its token stream. 200 If it did not get started (usually because the child object was 201 unserializable), there has not yet been any trace of the object in 202 the token stream. 203 204 The corresponding Unslicer (receiving this token stream) will get an 205 BananaFailure and is likely to ignore any remaining tokens from us, 206 so it may be reasonable for the parent Slicer to give up as well. 207 208 If the Slicer wishes to abandon their own sequence, it should simply 209 return the failure object passed in. If it wants to absorb the 210 error, it should return None.""" 211 212 def slicerForObject(obj): 213 """Get a new Slicer for some child object. Slicers usually delegate 214 this method up to the RootSlicer. References are handled by 215 producing a ReferenceSlicer here. These references can have various 216 scopes. 217 218 If something on the stack does not want the object to be sent, it can 219 raise a Violation exception. This is the 'taster' function.""" 220 221 def describe(): 222 """Return a short string describing where in the object tree this 223 slicer is sitting, relative to its parent. These strings are 224 obtained from every slicer in the stack, and joined to describe 225 where any problems occurred.""" 226 227class IRootSlicer(Interface): 228 def allowStreaming(streamable): 229 """Specify whether or not child Slicers will be allowed to stream.""" 230 def connectionLost(why): 231 """Called when the transport is closed. The RootSlicer may choose to 232 abandon objects being sent here.""" 233 234class IUnslicer(Interface): 235 # .parent 236 237 # start/receiveChild/receiveClose/finish are 238 # the main "here are some tokens, make an object out of them" entry 239 # points used by Unbanana. 240 241 # start/receiveChild can call self.protocol.abandonUnslicer(failure, 242 # self) to tell the protocol that the unslicer has given up on life and 243 # all its remaining tokens should be discarded. The failure will be 244 # given to the late unslicer's parent in lieu of the object normally 245 # returned by receiveClose. 246 247 # start/receiveChild/receiveClose/finish may raise a Violation 248 # exception, which tells the protocol that this object is contaminated 249 # and should be abandoned. An BananaFailure will be passed to its 250 # parent. 251 252 # Note, however, that it is not valid to both call abandonUnslicer *and* 253 # raise a Violation. That would discard too much. 254 255 def setConstraint(constraint): 256 """Add a constraint for this unslicer. The unslicer will enforce 257 this constraint upon all incoming data. The constraint must be of an 258 appropriate type (a ListUnslicer will only accept a ListConstraint, 259 etc.). It must not be None. To leave us unconstrained, do not call 260 this method. 261 262 If this method is not called, the Unslicer will accept any valid 263 banana as input, which probably means there is no limit on the 264 number of bytes it will accept (and therefore on the memory it could 265 be made to consume) before it finally accepts or rejects the input. 266 """ 267 268 def start(count): 269 """Called to initialize the new slice. The 'count' argument is the 270 reference id: if this object might be shared (and therefore the 271 target of a 'reference' token), it should call 272 self.protocol.setObject(count, obj) with the object being created. 273 If this object is not available yet (tuples), it should save a 274 Deferred there instead. 275 """ 276 277 def checkToken(typebyte, size): 278 """Check to see if the given token is acceptable (does it conform to 279 the constraint?). It will not be asked about ABORT or CLOSE tokens, 280 but it *will* be asked about OPEN. It should enfore a length limit 281 for long tokens (STRING and LONGINT/LONGNEG types). If STRING is 282 acceptable, then VOCAB should be too. It should return None if the 283 token and the size are acceptable. Should raise Violation if the 284 schema indiates the token is not acceptable. Should raise 285 BananaError if the type byte violates the basic Banana protocol. (if 286 no schema is in effect, this should never raise Violation, but might 287 still raise BananaError). 288 """ 289 290 def openerCheckToken(typebyte, size, opentype): 291 """'typebyte' is the type of an incoming index token. 'size' is the 292 value of header associated with this typebyte. 'opentype' is a list 293 of open tokens that we've received so far, not including the one 294 that this token hopes to create. 295 296 This method should ask the current opener if this index token is 297 acceptable, and is used in lieu of checkToken() when the receiver is 298 in the index phase. Usually implemented by calling 299 self.opener.openerCheckToken, thus delegating the question to the 300 RootUnslicer. 301 """ 302 303 def doOpen(opentype): 304 """opentype is a tuple. Return None if more index tokens are 305 required. Check to see if this kind of child object conforms to the 306 constraint, raise Violation if not. Create a new Unslicer (usually 307 by delegating to self.parent.doOpen, up to the RootUnslicer). Set a 308 constraint on the child unslicer, if any. 309 """ 310 311 def receiveChild(childobject, 312 ready_deferred): 313 """'childobject' is being handed to this unslicer. It may be a 314 primitive type (number or string), or a composite type produced by 315 another Unslicer. It might also be a Deferred, which indicates that 316 the actual object is not ready (perhaps a tuple with an element that 317 is not yet referenceable), in which case you should add a callback 318 to it that will fill in the appropriate object later. This callback 319 is required to return the object when it is done, so multiple such 320 callbacks can be chained. The childobject/ready_deferred argument 321 pair is taken directly from the output of receiveClose(). If 322 ready_deferred is non-None, you should return a dependent Deferred 323 from your own receiveClose method.""" 324 325 def reportViolation(bf): 326 """You have received an error instead of a child object. If you wish 327 to give up and propagate the error upwards, return the BananaFailure 328 object you were just given. To absorb the error and keep going with 329 your sequence, return None.""" 330 331 def receiveClose(): 332 """Called when the Close token is received. Returns a tuple of 333 (object/referenceable-deferred, complete-deferred), or an 334 BananaFailure if something went wrong. There are four potential 335 cases:: 336 337 (obj, None): the object is complete and ready to go 338 (d1, None): the object cannot be referenced yet, probably 339 because it is an immutable container, and one of its 340 children cannot be referenced yet. The deferred will 341 fire by the time the cycle has been fully deserialized, 342 with the object as its argument. 343 (obj, d2): the object can be referenced, but it is not yet 344 complete, probably because some component of it is 345 'slow' (see below). The Deferred will fire (with an 346 argument of None) when the object is ready to be used. 347 It is not guaranteed to fire by the time the enclosing 348 top-level object has finished deserializing. 349 (d1, d2): the object cannot yet be referenced, and even if it could 350 be, it would not yet be ready for use. Any potential users 351 should wait until both deferreds fire before using it. 352 353 The first deferred (d1) is guaranteed to fire before the top-most 354 enclosing object (a CallUnslicer, for PB methods) is closed. (if it 355 does not fire, that indicates a broken cycle). It is present to 356 handle cycles that include immutable containers, like tuples. 357 Mutable containers *must* return a reference to an object (even if 358 it is not yet ready to be used, because it contains placeholders to 359 tuples that have not yet been created), otherwise those cycles 360 cannot be broken and the object graph will not reconstructable. 361 362 The second (d2) has no such guarantees about when it will fire. It 363 indicates a dependence upon 'slow' external events. The first use 364 case for such 'slow' objects is a globally-referenceable object 365 which requires a new Broker connection before it can be used, so the 366 Deferred will not fire until a TCP connection has been established 367 and the first stages of PB negotiation have been completed. 368 369 If necessary, unbanana.setObject should be called, then the Deferred 370 created in start() should be fired with the new object.""" 371 372 def finish(): 373 """Called when the unslicer is popped off the stack. This is called 374 even if the pop is because of an exception. The unslicer should 375 perform cleanup, including firing the Deferred with an 376 BananaFailure if the object it is creating could not be created. 377 378 TODO: can receiveClose and finish be merged? Or should the child 379 object be returned from finish() instead of receiveClose? 380 """ 381 382 def describe(): 383 """Return a short string describing where in the object tree this 384 unslicer is sitting, relative to its parent. These strings are 385 obtained from every unslicer in the stack, and joined to describe 386 where any problems occurred.""" 387 388 def where(): 389 """This returns a string that describes the location of this 390 unslicer, starting at the root of the object tree.""" 391