From: Brian Warner <warner@allmydata.com> Date: Wed, 16 May 2007 00:33:52 +0000 (-0700) Subject: update to foolscap-0.1.4 X-Git-Tag: allmydata-tahoe-0.3.0~81 X-Git-Url: https://git.rkrishnan.org/%5B/%5D%20/uri/vdrive/index.php?a=commitdiff_plain;h=267a068fe4d45bffe5f38e93498b80066e837e8c;p=tahoe-lafs%2Ftahoe-lafs.git update to foolscap-0.1.4 --- diff --git a/src/foolscap/ChangeLog b/src/foolscap/ChangeLog index 06af6258..bade5f83 100644 --- a/src/foolscap/ChangeLog +++ b/src/foolscap/ChangeLog @@ -1,3 +1,112 @@ +2007-05-14 Brian Warner <warner@lothar.com> + + * foolscap/__init__.py: release Foolscap-0.1.4 + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same, also + remove a bunch of old between-release version numbers + +2007-05-14 Brian Warner <warner@lothar.com> + + * NEWS: update for the upcoming release + + * doc/using-foolscap.xhtml: rename from doc/using-pb.xhtml + + * doc/using-pb.xhtml: replace all uses of 'PB URL' with 'FURL' + + * foolscap/pb.py (Tub.getReference): if getReference() is called + before Tub.startService(), queue the request until startup. + (Tub.connectTo): same for connectTo(). + (Tub.startService): launch pending getReference() and connectTo() + requests. There are all fired with eventual-sends. + * foolscap/reconnector.py (Reconnector): don't automatically start + the Reconnector in __init__, rather wait for the Tub to start it. + * foolscap/test/test_tub.py (QueuedStartup): test it + * doc/using-pb.xhtml: update docs to match + + * foolscap/test/test_call.py (TestCall.testCall1): replace an + arbitrary delay with a polling loop, to make the test more + reliable under load + + * foolscap/referenceable.py (SturdyRef.asLiveRef): remove a method + that was never used, didn't work, and is of dubious utility + anyways. + (_AsLiveRef): remove this too + + * misc/testutils/figleaf.py (CodeTracer.start): remove leftover + debug logging + + * foolscap/remoteinterface.py (RemoteInterfaceConstraint): accept + gifts too: allow sending of RemoteReferences on the outbound side, + and accept their-reference sequences on the inbound side. + * foolscap/test/test_gifts.py (Gifts.test_constraint): test it + * foolscap/test/test_schema.py (Interfaces.test_remotereference): + update test, since now we allow RemoteReferences to be sent on the + outbound side + + * foolscap/remoteinterface.py (getRemoteInterface): improve the + error message reported when a Referenceable class implements + multiple RemoteInterfaces + + * foolscap/remoteinterface.py (RemoteMethodSchema.initFromMethod): + properly handle methods like 'def foo(nodefault)' that are missing + *all* default values. Previously this resulted in an unhelpful + exception (since typeList==None), now it gives a sensible + InvalidRemoteInterface exception. + * foolscap/test/test_schema.py (Arguments.test_bad_arguments): + test it + +2007-05-11 Brian Warner <warner@lothar.com> + + * foolscap/slicers/set.py (FrozenSetSlicer): finally acknowledge + our dependence on python2.4 or newer, by using the built-in 'set' + and 'frozenset' types by default. We'll serialize the old sets.Set + and sets.ImmutableSet too, but they'll emerge as a set/frozenset. + This will cause problems for code that was written to be + compatible with python2.3 (by using sets.Set) and wasn't changed + when moved to 2.4, if it tries to mingle sets.Set with the data + coming out of Foolscap. Unfortunate, but doing it this way + preserves both sanity and good behavior for modern 2.4-or-later + apps. + (SetUnslicer): fix handling of children that were unreferenceable + during construction, fix handling of children that are not ready + for use (i.e. gifts). + (FrozenSetUnslicer): base this off of TupleUnslicer, since + previously the cycle-handling logic was completely broken. I'm not + entirely sure this is necessary, since I think the contents of + sets must be transitively immutable (or at least transitively + hashable), but it good to review and clean it up anyways. + * foolscap/slicers/allslicers.py: match name change + + * foolscap/slicers/tuple.py (TupleUnslicer.receiveClose): fix + handling of unready children (i.e. gifts), previously gifts inside + containers were completely broken. + * foolscap/slicers/list.py (ListUnslicer.receiveClose): same + * foolscap/slicers/dict.py (DictUnslicer.receiveClose): same + + * foolscap/call.py: add debug log messages (disabled) + + * foolscap/referenceable.py (TheirReferenceUnslicer.receiveClose): + gifts must declare themselves 'unready' until the RemoteReference + resolves, since we might be inside a container of some sort. + Without this fix, methods would be invoked too early, before the + RemoteReference was really available. + + * foolscap/test/test_banana.py (ThereAndBackAgain.test_set): match + new set/sets.Set behavior + (ThereAndBackAgain.test_cycles_1): test some of the cycles + (ThereAndBackAgain.test_cycles_3): add (disabled) test for + checking cycles that involve sets. I think these tests are + non-sensical, since sets can't really participate in the sorts of + cycles we worry about, but I left the (disabled) test code in + place in case it becomes useful again. + + * foolscap/test/test_gifts.py (Gifts.testContainers): validate + that gifts can appear in all sorts of containers successfully. + +2007-05-11 Brian Warner <warner@lothar.com.com> + + * foolscap/__init__.py: bump revision to 0.1.3+ while between releases + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same + 2007-05-02 Brian Warner <warner@lothar.com> * foolscap/__init__.py: release Foolscap-0.1.3 diff --git a/src/foolscap/NEWS b/src/foolscap/NEWS index 8ef246fb..f45ecb20 100644 --- a/src/foolscap/NEWS +++ b/src/foolscap/NEWS @@ -1,5 +1,69 @@ User visible changes in Foolscap (aka newpb/pb2). -*- outline -*- +* Release 0.1.4 (14 May 2007) + +** Compatibility + +This release is fully compatible with 0.1.3 . + +** getReference/connectTo can be called before Tub.startService() + +The Tub.startService changes that were suggested in the 0.1.3 release notes +have been implemented. Calling getReference() or connectTo() before the Tub +has been started is now allowed, however no action will take place until the +Tub is running. Don't forget to start the Tub, or you'll be left wondering +why your Deferred or callback is never fired. (A log message is emitted when +these calls are made before the Tub is started, in the hopes of helping +developers find this mistake faster). + +** constraint improvements + +The RIFoo -style constraint now accepts gifts (third-party references). This +also means that using RIFoo on the outbound side will accept either a +Referenceable that implements the given RemoteInterface or a RemoteReference +that points to a Referenceable that implements the given RemoteInterface. +There is a situation (sending a RemoteReference back to its owner) that will +pass the outbound constraint but be rejected by the inbound constraint on the +other end. It remains to be seen how this will be fixed. + +** foolscap now deserializes into python2.4-native 'set' and 'frozenset' types + +Since Foolscap is dependent upon python2.4 or newer anyways, it now +unconditionally creates built-in 'set' and 'frozenset' instances when +deserializing 'set'/'immutable-set' banana sequences. The pre-python2.4 +'sets' module has non-built-in set classes named sets.Set and +sets.ImmutableSet, and these are serialized just like the built-in forms. + +Unfortunately this means that Set and ImmutableSet will not survive a +round-trip: they'll be turned into set and frozenset, respectively. Worse +yet, 'set' and 'sets.Set' are not entirely compatible. This may cause a +problem for older applications that were written to be compatible with both +python-2.3 and python-2.4 (by using sets.Set/sets.ImmutableSet), for which +the compatibility code is still in place (i.e. they are not using +set/frozenset). These applications may experience problems when set objects +that traverse the wire via Foolscap are brought into close proximity with set +objects that remained local. This is unfortunate, but it's the cleanest way +to support modern applications that use the native types exclusively. + +** bug fixes + +Gifts inside containers (lists, tuples, dicts, sets) were broken: the target +method was frequently invoked before the gift had properly resolved into a +RemoteReference. Constraints involving gifts inside containers were broken +too. The constraints may be too loose right now, but I don't think they +should cause false negatives. + +The unused SturdyRef.asLiveRef method was removed, since it didn't work +anyways. + +** terminology shift: FURL + +The preferred name for the sort of URL that you get back from +registerReference (and hand to getReference or connectTo) has changed from +"PB URL" to "FURL" (short for Foolscap URL). They still start with 'pb:', +however. Documentation is slowly being changed to use this term. + + * Release 0.1.3 (02 May 2007) ** Incompatibility Warning diff --git a/src/foolscap/doc/using-foolscap.xhtml b/src/foolscap/doc/using-foolscap.xhtml new file mode 100644 index 00000000..9ddc88ce --- /dev/null +++ b/src/foolscap/doc/using-foolscap.xhtml @@ -0,0 +1,978 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>Introduction to Foolscap</title> +<style src="stylesheet-unprocessed.css"></style> +</head> + +<body> +<h1>Introduction to Foolscap</h1> + +<h2>Introduction</h2> + +<p>Suppose you find yourself in control of both ends of the wire: you have +two programs that need to talk to each other, and you get to use any protocol +you want. If you can think of your problem in terms of objects that need to +make method calls on each other, then chances are good that you can use the +Foolscap protocol rather than trying to shoehorn your needs into something +like HTTP, or implementing yet another RPC mechanism.</p> + +<p>Foolscap is based upon a few central concepts:</p> + +<ul> + + <li><em>serialization</em>: taking fairly arbitrary objects and types, + turning them into a chunk of bytes, sending them over a wire, then + reconstituting them on the other end. By keeping careful track of object + ids, the serialized objects can contain references to other objects and the + remote copy will still be useful. </li> + + <li><em>remote method calls</em>: doing something to a local proxy and + causing a method to get run on a distant object. The local proxy is called + a <code class="API" base="foolscap.referenceable">RemoteReference</code>, + and you <q>do something</q> by running its <code>.callRemote</code> method. + The distant object is called a <code class="API" + base="foolscap.referenceable">Referenceable</code>, and it has methods like + <code>remote_foo</code> that will be invoked.</li> + +</ul> + +<p>Foolscap is the descendant of Perspective Broker (which lived in the +twisted.spread package). For many years it was known as "newpb". A lot of the +API still has the name "PB" in it somewhere. These will probably go away +sooner or later.</p> + +<p>A "foolscap" is a size of paper, probably measuring 17 by 13.5 inches. A +twisted foolscap of paper makes a good fool's cap. Also, "cap" makes me think +of capabilities, and Foolscap is a protocol to implement a distributed +object-capabilities model in python.</p> + + +<h2>Getting Started</h2> + +<p>Any Foolscap application has at least two sides: one which hosts a +remotely-callable object, and another which calls (remotely) the methods of +that object. We'll start with a simple example that demostrates both ends. +Later, we'll add more features like RemoteInterface declarations, and +transferring object references.</p> + +<p>The most common way to make an object with remotely-callable methods is to +subclass <code class="API" +base="foolscap.referenceable">Referenceable</code>. Let's create a simple +server which does basic arithmetic. You might use such a service to perform +difficult mathematical operations, like addition, on a remote machine which +is faster and more capable than your own<span class="footnote">although +really, if your client machine is too slow to perform this kind of math, it +is probably too slow to run python or use a network, so you should seriously +consider a hardware upgrade</span>.</p> + +<pre class="python"> +from foolscap import Referenceable + +class MathServer(Referenceable): + def remote_add(self, a, b): + return a+b + def remote_subtract(self, a, b): + return a-b + def remote_sum(self, args): + total = 0 + for a in args: total += a + return total + +myserver = MathServer() +</pre> + +<p>On the other end of the wire (which you might call the <q>client</q> +side), the code will have a <code class="API" +base="foolscap.referenceable">RemoteReference</code> to this object. The +<code>RemoteReference</code> has a method named <code class="API" +base="foolscap.referenceable.RemoteReference">callRemote</code> which you +will use to invoke the method. It always returns a Deferred, which will fire +with the result of the method. Assuming you've already acquired the +<code>RemoteReference</code>, you would invoke the method like this:</p> + +<pre class="python"> +def gotAnswer(result): + print "result is", result +def gotError(err): + print "error:", err +d = remote.callRemote("add", 1, 2) +d.addCallbacks(gotAnswer, gotError) +</pre> + +<p>Ok, now how do you acquire that <code>RemoteReference</code>? How do you +make the <code>Referenceable</code> available to the outside world? For this, +we'll need to discuss the <q>Tub</q>, and the concept of a <q>FURL +URL</q>.</p> + +<h2>Tubs: The Foolscap Service</h2> + +<p>The <code class="API" base="foolscap.pb">Tub</code> is the container that +you use to publish <code>Referenceable</code>s, and is the middle-man you use +to access <code>Referenceable</code>s on other systems. It is known as the +<q>Tub</q>, since it provides similar naming and identification properties as +the <a href="http://www.erights.org/">E language</a>'s <q>Vat</q><span +class="footnote">but they do not provide quite the same insulation against +other objects as E's Vats do. In this sense, Tubs are leaky Vats.</span>. If +you want to make a <code>Referenceable</code> available to the world, you +create a Tub, tell it to listen on a TCP port, and then register the +<code>Referenceable</code> with it under a name of your choosing. If you want +to access a remote <code>Referenceable</code>, you create a Tub and ask it to +acquire a <code>RemoteReference</code> using that same name.</p> + +<p>The <code>Tub</code> is a Twisted <code class="API" +base="twisted.application.service">Service</code> subclass, so you use it in +the same way: once you've created one, you attach it to a parent Service or +Application object. Once the top-level Application object has been started, +the Tub will start listening on any network ports you've requested. When the +Tub is shut down, it will stop listening and drop any connections it had +established since last startup. If you have no parent to attach it to, you +can use <code>startService</code> and <code>stopService</code> on the Tub +directly.</p> + +<p>Note that no network activity will occur until the Tub's +<code>startService</code> method has been called. This means that any +<code>getReference</code> or <code>connectTo</code> requests that occur +before the Tub is started will be deferred until startup. If the program +forgets to start the Tub, these requests will never be serviced. A message to +this effect is added to the twistd.log file to help developers discover this +kind of problem.</p> + +<h3>Making your Tub remotely accessible</h3> + +<p>To make any of your <code>Referenceable</code>s available, you must make +your Tub available. There are three parts: give it an identity, have it +listen on a port, and tell it the protocol/hostname/portnumber at which that +port is accessibly to the outside world.</p> + +<p>In general, the Tub will generate its own identity, the <em>TubID</em>, by +creating an SSL private key certificate and hashing it into a suitably-long +random-looking string. This is the primary identifier of the Tub: everything +else is just a <em>location hint</em> that suggests how the Tub might be +reached. The fact that the TubID is tied to the private key allows FURLs to +be <q>secure</q> references (meaning that no third party can cause you to +connect to the wrong reference). You can also create a Tub with a +pre-existing certificate, which is how Tubs can retain a persistent identity +over multiple executions.</p> + +<p>You can also create an <code>UnauthenticatedTub</code>, which has an empty +TubID. Hosting and connecting to unauthenticated Tubs do not require the +pyOpenSSL library, but do not provide privacy, authentication, connection +redirection, or shared listening ports. The FURLs that point to +unauthenticated Tubs have a distinct form (starting with <code>pbu:</code> +instead of <code>pb:</code>) to make sure they are not mistaken for +authenticated Tubs. Foolscap uses authenticated Tubs by default.</p> + +<p>Having the Tub listen on a TCP port is as simple as calling <code +class="API" base="foolscap.pb.Tub">listenOn</code> with a <code class="API" +base="twisted.application">strports</code>-formatted port specification +string. The simplest such string would be <q>tcp:12345</q>, to listen on port +12345 on all interfaces. Using <q>tcp:12345:interface=127.0.0.1</q> would +cause it to only listen on the localhost interface, making it available only +to other processes on the same host. The <code>strports</code> module +provides many other possibilities.</p> + +<p>The Tub needs to be told how it can be reached, so it knows what host and +port to put into the URLs it creates. This location is simply a string in the +format <q>host:port</q>, using the host name by which that TCP port you've +just opened can be reached. Foolscap cannot, in general, guess what this name +is, especially if there are NAT boxes or port-forwarding devices in the way. +If your machine is reachable directly over the internet as +<q>myhost.example.com</q>, then you could use something like this:</p> + +<pre class="python"> +from foolscap import Tub + +tub = Tub() +tub.listenOn("tcp:12345") # start listening on TCP port 12345 +tub.setLocation("myhost.example.com:12345") +</pre> + +<h3>Registering the Referenceable</h3> + +<p>Once the Tub has a Listener and a location, you can publish your +<code>Referenceable</code> to the entire world by picking a name and +registering it:</p> + +<pre class="python"> +url = tub.registerReference(myserver, "math-service") +</pre> + +<p>This returns the <q>FURL</q> for your <code>Referenceable</code>. Remote +systems will use this URL to access your newly-published object. The +registration just maps a per-Tub name to the <code>Referenceable</code>: +technically the same <code>Referenceable</code> could be published multiple +times, under different names, or even be published by multiple Tubs in the +same application. But in general, each program will have exactly one Tub, and +each object will be registered under only one name.</p> + +<p>In this example (if we pretend the generated TubID was <q>ABCD</q>), the +URL returned by <code>registerReference</code> would be +<code>"pb://ABCD@myhost.example.com:12345/math-service"</code>.</p> + +<p>If you do not provide a name, a random (and unguessable) name will be +generated for you. This is useful when you want to give access to your +<code>Referenceable</code> to someone specific, but do not want to make it +possible for someone else to acquire it by guessing the name.</p> + +<p>To use an unauthenticated Tub instead, you would do the following:</p> +<pre class="python"> +from foolscap import UnauthenticatedTub + +tub = UnauthenticatedTub() +tub.listenOn("tcp:12345") # start listening on TCP port 12345 +tub.setLocation("myhost.example.com:12345") +url = tub.registerReference(myserver, "math-service") +</pre> + +<p>In this case, the URL would be +<code>"pbu://myhost.example.com:12345/math-service"</code>. The deterministic +nature of this form makes it slightly easier to throw together +quick-and-dirty Foolscap applications, since you only need to hard-code the +target host and port into the client side program. However any serious +application should just used the default authenticated form and use a full +URL as their starting point. Note that the URL can come from anywhere: typed +in by the user, retrieved from a web page, or hardcoded into the +application.</p> + +<h4>Using a persistent certificate</h4> + +<p>The Tub uses a TLS private-key certificate as the base of all its +cryptographic operations. If you don't give it one when you create the Tub, +it will generate a brand-new one.</p> + +<p>The TubID is simply the hash of this certificate, so if you are writing an +application that should have a stable long-term identity, you will need to +insure that the Tub uses the same certificate every time your app starts. The +easiest way to do this is to pass the <code>certFile=</code> argument into +your <code>Tub()</code> constructor call. This argument provides a filename +where you want the Tub to store its certificate. The first time the Tub is +started (when this file does not exist), the Tub will generate a new +certificate and store it here. On subsequent invocations, the Tub will read +the earlier certificate from this location. Make sure this filename points to +a writable location, and that you pass the same filename to +<code>Tub()</code> each time.</p> + +<h3>Retrieving a RemoteReference</h3> + +<p>On the <q>client</q> side, you also need to create a Tub, although you +don't need to perform the (<code>listenOn</code>, <code>setLocation</code>, +<code>registerReference</code>) sequence unless you are also publishing +<code>Referenceable</code>s to the world. To acquire a reference to somebody +else's object, just use <code class="API" +base="foolscap.pb.Tub">getReference</code>:</p> + +<pre class="python"> +from foolscap import Tub + +tub = Tub() +tub.startService() +d = tub.getReference("pb://ABCD@myhost.example.com:12345/math-service") +def gotReference(remote): + print "Got the RemoteReference:", remote +def gotError(err): + print "error:", err +d.addCallbacks(gotReference, gotError) +</pre> + +<p><code>getReference</code> returns a Deferred which will fire with a +<code>RemoteReference</code> that is connected to the remote +<code>Referenceable</code> named by the URL. It will use an existing +connection, if one is available, and it will return an existing +<code>RemoteReference</code>, it one has already been acquired.</p> + +<p>Since <code>getReference</code> requests are queued until the Tub starts, +the following will work too. But don't forget to call +<code>tub.startService()</code> eventually, otherwise your program will hang +forever.</p> + +<pre class="python"> +from foolscap import Tub + +tub = Tub() +d = tub.getReference("pb://ABCD@myhost.example.com:12345/math-service") +def gotReference(remote): + print "Got the RemoteReference:", remote +def gotError(err): + print "error:", err +d.addCallbacks(gotReference, gotError) +tub.startService() +</pre> + + +<h3>Complete example</h3> + +<p>Here are two programs, one implementing the server side of our +remote-addition protocol, the other behaving as a client. This first example +uses an unauthenticated Tub so you don't have to manually copy a URL from the +server to the client. Both of these are standalone programs (you just run +them), but normally you would create an <code class="API" +base="twisted.application.service">Application</code> object and pass the +file to <code>twistd -noy</code>. An example of that usage will be provided +later.</p> + +<a href="listings/pb1server.py" class="py-listing" +skipLines="2">pb1server.py</a> + +<a href="listings/pb1client.py" class="py-listing" +skipLines="2">pb1client.py</a> + +<pre class="shell"> +% doc/listings/pb1server.py +the object is available at: pbu://localhost:12345/math-service +</pre> + +<pre class="shell"> +% doc/listings/pb1client.py +got a RemoteReference +asking it to add 1+2 +the answer is 3 +% +</pre> + +<p>The second example uses authenticated Tubs. When running this example, you +must copy the URL printed by the server and provide it as an argument to the +client.</p> + +<a href="listings/pb2server.py" class="py-listing" +skipLines="2">pb2server.py</a> + +<a href="listings/pb2client.py" class="py-listing" +skipLines="2">pb2client.py</a> + +<pre class="shell"> +% doc/listings/pb2server.py +the object is available at: pb://abcd123@localhost:12345/math-service +</pre> + +<pre class="shell"> +% doc/listings/pb2client.py pb://abcd123@localhost:12345/math-service +got a RemoteReference +asking it to add 1+2 +the answer is 3 +% +</pre> + + +<h3>FURLs</h3> + +<p>In Foolscap, each world-accessible Referenceable has one or more URLs +which are <q>secure</q>, where we use the capability-security definition of +the term, meaning those URLs have the following properties:</p> + +<ul> + <li>The only way to acquire the URL is either to get it from someone else + who already has it, or to be the person who published it in the first + place.</li> + + <li>Only that original creator of the URL gets to determine which + Referenceable it will connect to. If your + <code>tub.getReference(url)</code> call succeeds, the Referenceable you + will be connected to will be the right one.</li> +</ul> + +<p>To accomplish the first goal, FURLs must be unguessable. You can register +the reference with a human-readable name if your intention is to make it +available to the world, but in general you will let +<code>tub.registerReference</code> generate a random name for you, preserving +the unguessability property.</p> + +<p>To accomplish the second goal, the cryptographically-secure TubID is used +as the primary identifier, and the <q>location hints</q> are just that: +hints. If DNS has been subverted to point the hostname at a different +machine, or if a man-in-the-middle attack causes you to connect to the wrong +box, the TubID will not match the remote end, and the connection will be +dropped. These attacks can cause a denial-of-service, but they cannot cause +you to mistakenly connect to the wrong target.</p> + +<p>Obviously this second property only holds if you use SSL. If you choose to +use unauthenticated Tubs, all security properties are lost.</p> + +<p>The format of a FURL, like +<code>pb://abcd123@example.com:5901,backup.example.com:8800/math-server</code>, +is as follows<span class="footnote">note that the FURL uses the same format +as an <a href="http://www.waterken.com/dev/YURL/httpsy/">HTTPSY</a> +URL</span>:</p> + +<ol> + <li>The literal string <code>pb://</code></li> + <li>The TubID (as a base32-encoded hash of the SSL certificate)</li> + <li>A literal <code>@</code> sign</li> + + <li>A comma-separated list of <q>location hints</q>. Each is one of the + following: + <ul> + <li>TCP over IPv4 via DNS: <code>HOSTNAME:PORTNUM</code></li> + <li>TCP over IPv4 without DNS: <code>A.B.C.D:PORTNUM</code></li> + <li>TCP over IPv6: (TODO, maybe <code>tcp6:HOSTNAME:PORTNUM</code> ?</li> + <li>TCP over IPv6 w/o DNS: (TODO, + maybe <code>tcp6:[X:Y::Z]:PORTNUM</code></li> + <li>Unix-domain socket: (TODO)</li> + </ul> + + Each location hint is attempted in turn. Servers can return a + <q>redirect</q>, which will cause the client to insert the provided + redirect targets into the hint list and start trying them before continuing + with the original list.</li> + + <li>A literal <code>/</code> character</li> + <li>The reference's name</li> +</ol> + +<p>(Unix-domain sockets are represented with only a single location hint, in +the format <code>pb://ABCD@unix/path/to/socket/NAME</code>, but this needs +some work)</p> + +<p>FURLs for unauthenticated Tubs, like +<code>pbu://example.com:8700/math-server</code>, are formatted as +follows:</p> + +<ol> + <li>The literal string <code>pbu://</code></li> + <li>A single location hint</li> + <li>A literal <code>/</code> character</li> + <li>The reference's name</li> +</ol> + +<h2>Clients vs Servers, Names and Capabilities</h2> + +<p>It is worthwhile to point out that Foolscap is a symmetric protocol. +<code>Referenceable</code> instances can live on either side of a wire, and +the only difference between <q>client</q> and <q>server</q> is who publishes +the object and who initiates the network connection.</p> + +<p>In any Foolscap-using system, the very first object exchanged must be +acquired with a <code>tub.getReference(url)</code> call<span +class="footnote">in fact, the very <em>very</em> first object exchanged is a +special implicit RemoteReference to the remote Tub itself, which implements +an internal protocol that includes a method named +<code>remote_getReference</code>. The <code>tub.getReference(url)</code> call +is turned into one step that connects to the remote Tub, and a second step +which invokes remotetub.callRemote("getReference", refname) on the +result</span>, which means it must have been published with a call to +<code>tub.registerReference(ref, name)</code>. After that, other objects can +be passed as an argument to (or a return value from) a remotely-invoked +method of that first object. Any suitable <code>Referenceable</code> object +that is passed over the wire will appear on the other side as a corresponding +<code>RemoteReference</code>. It is not necessary to +<code>registerReference</code> something to let it pass over the wire.</p> + +<p>The converse of this property is thus: if you do <em>not</em> +<code>registerReference</code> a particular <code>Referenceable</code>, and +you do <em>not</em> give it to anyone else (by passing it in an argument to +somebody's remote method, or return it from one of your own), then nobody +else will be able to get access to that <code>Referenceable</code>. This +property means the <code>Referenceable</code> is a <q>capability</q>, as +holding a corresponding <code>RemoteReference</code> gives someone a power +that they cannot acquire in any other way<span class="footnote">of course, +the Foolscap connections must be secured with SSL (otherwise an eavesdropper +or man-in-the-middle could get access), and the registered name must be +unguessable (or someone else could acquire a reference), but both of these +are the default.</span></p> + +<p>In the following example, the first program creates an RPN-style +<code>Calculator</code> object which responds to <q>push</q>, <q>pop</q>, +<q>add</q>, and <q>subtract</q> messages from the user. The user can also +register an <code>Observer</code>, to which the Calculator sends an +<code>event</code> message each time something happens to the calculator's +state. When you consider the <code>Calculator</code> object, the first +program is the server and the second program is the client. When you think +about the <code>Observer</code> object, the first program is a client and the +second program is the server. It also happens that the first program is +listening on a socket, while the second program initiated a network +connection to the first. It <em>also</em> happens that the first program +published an object under some well-known name, while the second program has +not published any objects. These are all independent properties.</p> + +<p>Also note that the Calculator side of the example is implemented using a +<code class="API" base="twisted.application.service">Application</code> +object, which is the way you'd normally build a real-world application. You +therefore use <code>twistd</code> to launch the program. The User side is +written with the same <code>reactor.run()</code> style as the earlier +example.</p> + +<p>The server registers the Calculator instance and prints the FURL at which +it is listening. You need to pass this FURL to the client program so it knows +how to contact the servre. If you have a modern version of Twisted (2.5 or +later) and the right encryption libraries installed, you'll get an +authenticated Tub (for which the FURL will start with "pb:" and will be +fairly long). If you don't, you'll get an unauthenticated Tub (with a +relatively short FURL that starts with "pbu:").</p> + +<a href="listings/pb3calculator.py" class="py-listing" +skipLines="2">pb3calculator.py</a> + +<a href="listings/pb3user.py" class="py-listing" +skipLines="2">pb3user.py</a> + +<pre class="shell"> +% twistd -noy doc/listings/pb3calculator.py +15:46 PDT [-] Log opened. +15:46 PDT [-] twistd 2.4.0 (/usr/bin/python 2.4.4) starting up +15:46 PDT [-] reactor class: twisted.internet.selectreactor.SelectReactor +15:46 PDT [-] Loading doc/listings/pb3calculator.py... +15:46 PDT [-] the object is available at: + pb://5ojw4cv4u4d5cenxxekjukrogzytnhop@localhost:12345/calculator +15:46 PDT [-] Loaded. +15:46 PDT [-] foolscap.pb.Listener starting on 12345 +15:46 PDT [-] Starting factory <Listener at 0x4869c0f4 on tcp:12345 + with tubs None> +</pre> + +<pre class="shell"> +% doc/listings/pb3user.py \ + pb://5ojw4cv4u4d5cenxxekjukrogzytnhop@localhost:12345/calculator +event: push(2) +event: push(3) +event: add +event: pop +the result is 5 +% +</pre> + + +<h2>Invoking Methods, Method Arguments</h2> + +<p>As you've probably already guessed, all the methods with names that begin +with <code>remote_</code> will be available to anyone who manages to acquire +a corresponding <code>RemoteReference</code>. <code>remote_foo</code> matches +a <code>ref.callRemote("foo")</code>, etc. This name lookup can be changed by +overriding <code>Referenceable</code> (or, perhaps more usefully, +implementing an <code class="API" +base="foolscap.ipb">IRemotelyCallable</code> adapter).</p> + +<p>The arguments of a remote method may be passed as either positional +parameters (<code>foo(1,2)</code>), or as keyword args +(<code>foo(a=1,b=2)</code>), or a mixture of both. The usual python rules +about not duplicating parameters apply.</p> + +<p>You can pass all sorts of normal objects to a remote method: strings, +numbers, tuples, lists, and dictionaries. The serialization of these objects +is handled by <a href="specifications/banana.xhtml">Banana</a>, which knows +how to convey arbitrary object graphs over the wire. Things like containers +which contain multiple references to the same object, and recursive +references (cycles in the object graph) are all handled correctly<span +class="footnote">you may not want to accept shared objects in your method +arguments, as it could lead to surprising behavior depending upon how you +have written your method. The <code class="API" +base="foolscap.schema">Shared</code> constraint will let you express this, +and is described in the <a href="#constraints">Constraints</a> section of +this document</span>.</p> + +<p>Passing instances is handled specially. Foolscap will not send anything +over the wire that it does not know how to serialize, and (unlike the +standard <code>pickle</code> module) it will not make assumptions about how +to handle classes that that have not been explicitly marked as serializable. +This is for security, both for the sender (making you you don't pass anything +over the wire that you didn't intend to let out of your security perimeter), +and for the recipient (making sure outsiders aren't allowed to create +arbitrary instances inside your memory space, and therefore letting them run +somewhat arbitrary code inside <em>your</em> perimeter).</p> + +<p>Sending <code>Referenceable</code>s is straightforward: they always appear +as a corresponding <code>RemoteReference</code> on the other side. You can +send the same <code>Referenceable</code> as many times as you like, and it +will always show up as the same <code>RemoteReference</code> instance. A +distributed reference count is maintained, so as long as the remote side +hasn't forgotten about the <code>RemoteReference</code>, the original +<code>Referenceable</code> will be kept alive.</p> + +<p>Sending <code>RemoteReference</code>s fall into two categories. If you are +sending a <code>RemoteReference</code> back to the Tub that you got it from, +they will see their original <code>Referenceable</code>. If you send it to +some other Tub, they will (eventually) see a <code>RemoteReference</code> of +their own. This last feature is called an <q>introduction</q>, and has a few +additional requirements: see the <a href="#introductions">Introductions</a> +section of this document for details.</p> + +<p>Sending instances of other classes requires that you tell Banana how they +should be serialized. <code>Referenceable</code> is good for +copy-by-reference semantics<span class="footnote">In fact, if all you want is +referenceability (and not callability), you can use <code class="API" +base="foolscap.referenceable">OnlyReferenceable</code>. Strictly speaking, +<code>Referenceable</code> is both <q>Referenceable</q> (meaning it is sent +over the wire using pass-by-reference semantics, and it survives a round +trip) and <q>Callable</q> (meaning you can invoke remote methods on it). +<code>Referenceable</code> should really be named <code>Callable</code>, but +the existing name has a lot of historical weight behind it.</span>. For +copy-by-value semantics, the easiest route is to subclass <code class="API" +base="foolscap.copyable">Copyable</code>. See the <a +href="#copyable">Copyable</a> section for details. Note that you can also +register an <code class="API" base="foolscap.copyable">ICopyable</code> +adapter on third-party classes to avoid subclassing. You will need to +register the <code>Copyable</code>'s name on the receiving end too, otherwise +Banana will not know how to unserialize the incoming data stream.</p> + +<p>When returning a value from a remote method, you can do all these things, +plus two more. If you raise an exception, the caller's Deferred will have the +errback fired instead of the callback, with a <code class="API" +base="foolscap.call">CopiedFailure</code> instance that describes what went +wrong. The <code>CopiedFailure</code> is not quite as useful as a local <code +class="API" base="twisted.python.failure">Failure</code> object would be: to +send it over the wire, some contents are replaced with strings, and the +actual Exception object (<code>f.value</code>) is replaced with its string +representation. But you can still use it to find out what went wrong. The +<code>CopiedFailure</code> may reveal more information about the internals of +your program than you want: you can set the <code>unsafeTracebacks</code> +flag on the Tub to limit outgoing <code>CopiedFailure</code>s to contain only +the exception type (and none of the stack trace information that would reveal +lines of your source code to the remote end).</p> + +<p>The other alternative is for your method to return a <code class="API" +base="twisted.internet.defer">Deferred</code>. If this happens, the caller +will not actually get a response until you fire that Deferred. This is useful +when the remote operation being requested cannot complete right away. The +caller's Deferred with fire with whatever value you eventually fire your own +Deferred with. If your Deferred is errbacked, their Deferred will be +errbacked with a <code>CopiedFailure</code>.</p> + + +<h2>Constraints and RemoteInterfaces</h2><a name="constraints" /> + +<p>One major feature introduced by Foolscap (relative to oldpb) is the +serialization <code class="API" base="foolscap.schema">Constraint</code>. +This lets you place limits on what kind of data you are willing to accept, +which enables safer distributed programming. Typically python uses <q>duck +typing</q>, wherein you usually just throw some arguments at the method and +see what happens. When you are less sure of the origin of those arguments, +you may want to be more circumspect. Enforcing type checking at the boundary +between your code and the outside world may make it safer to use duck typing +inside those boundaries. The type specifications also form a convenient +remote API reference you can publish for prospective clients of your +remotely-invokable service.</p> + +<p>In addition, these Constraints are enforced on each token as it arrives +over the wire. This means that you can calculate a (small) upper bound on how +much received data your program will store before it decides to hang up on +the violator, minimizing your exposure to DoS attacks that involve sending +random junk at you.</p> + +<p>There are three pieces you need to know about: Tokens, Constraints, and +RemoteInterfaces.</p> + +<h3>Tokens</h3> + +<p>The fundamental unit of serialization is the Banana Token. These are +thoroughly documented in the <a href="specifications/banana.xhtml">Banana +Specification</a>, but what you need to know here is that each piece of +non-container data, like a string or a number, is represented by a single +token. Containers (like lists and dictionaries) are represented by a special +OPEN token, followed by tokens for everything that is in the container, +followed by the CLOSE token. Everything Banana does is in terms of these +nested OPEN/stuff/stuff/CLOSE sequences of tokens.</p> + +<p>Each token consists of a header, a type byte, and an optional body. The +header is always a base-128 number with a maximum of 64 digits, and the type +byte is always a single byte. The body (if present) has a length dictated by +the magnitude of the header.</p> + +<p>The length-first token format means that the receiving system never has to +accept more than 65 bytes before it knows the type and size of the token, at +which point it can make a decision about accepting or rejecting the rest of +it.</p> + +<h3>Constraints</h3> + +<p>The schema <code>foolscap.schema</code> module has a variety of <code +class="API" base="foolscap.schema">Constraint</code> classes that can be +applied to incoming data. Most of them correspond to typical Python types, +e.g. <code class="API" base="foolscap.schema">ListOf</code> matches a list, +with a certain maximum length, and a child <code>Constraint</code> that gets +applied to the contents of the list. You can nest <code>Constraint</code>s in +this way to describe the <q>shape</q> of the object graph that you are +willing to accept.</p> + +<p>At any given time, the receiving Banana protocol has single +<code>Constraint</code> object that it enforces against the inbound data +stream<span class="footnote">to be precise, each <code>Unslicer</code> on the +receive stack has a <code>Constraint</code>, and the idea is that all of them +get to pass judgement on the inbound token. A useful syntax to describe this +sort of thing is still being worked out.</span>.</p> + +<h3>RemoteInterfaces</h3> + +<p>The <code class="API" +base="foolscap.remoteinterface">RemoteInterface</code> is how you describe +your constraints. You can provide a constraint for each argument of each +method, as well as one for the return value. You can also specify addtional +flags on the methods. The convention (which is actually enforced by the code) +is to name <code>RemoteInterface</code> objects with an <q>RI</q> prefix, +like <code>RIFoo</code>.</p> + +<p><code>RemoteInterfaces</code> are created and used a lot like the usual +<code>zope.interface</code>-style <code>Interface</code>. They look like +class definitions, inheriting from <code>RemoteInterface</code>. For each +method, the default value of each argument is used to create a +<code>Constraint</code> for that argument. Basic types (<code>int</code>, +<code>str</code>, <code>bool</code>) are converted into a +<code>Constraint</code> subclass (<code class="API" +base="foolscap.schema">IntegerConstraint</code>, <code class="API" +base="foolscap.schema">StringConstraint</code>, <code class="API" +base="foolscap.schema">BooleanConstraint</code>). You can also use +instances of other <code>Constraint</code> subclasses, like <code class="API" +base="foolscap.schema">ListOf</code> and <code class="API" +base="foolscap.schema">DictOf</code>. This <code>Constraint</code> will be +enforced against the value for the given argument. Unless you specify +otherwise, remote callers must match all the <code>Constraint</code>s you +specify, all arguments listed in the RemoteInterface must be present, and no +arguments outside that list will be accepted.</p> + +<p>Note that, like zope.interface, these methods should <b>not</b> include +<q><code>self</code></q> in their argument list. This is because you are +documenting how <em>other</em> people invoke your methods. <code>self</code> +is an implementation detail. <code>RemoteInterface</code> will complain if +you forget.</p> + +<p>The <q>methods</q> in a <code>RemoteInterface</code> should return a +single value with the same format as the default arguments: either a basic +type (<code>int</code>, <code>str</code>, etc) or a <code>Constraint</code> +subclass. This <code>Constraint</code> is enforced on the return value of the +method. If you are calling a method in somebody else's process, the argument +constraints will be applied as a courtesy (<q>be conservative in what you +send</q>), and the return value constraint will be applied to prevent the +server from doing evil things to you. If you are running a method on behalf +of a remote client, the argument constraints will be enforced to protect +<em>you</em>, while the return value constraint will be applied as a +courtesy.</p> + +<p>Attempting to send a value that does not satisfy the Constraint will +result in a <code class="API" base="foolscap">Violation</code> exception +being raised.</p> + +<p>You can also specify methods by defining attributes of the same name in +the <code>RemoteInterface</code> object. Each attribute value should be an +instance of <code class="API" +base="foolscap.schema">RemoteMethodSchema</code><span +class="footnote">although technically it can be any object which implements +the <code class="API" base="foolscap.schema">IRemoteMethodConstraint</code> +interface</span>. This approach is more flexible: there are some constraints +that are not easy to express with the default-argument syntax, and this is +the only way to set per-method flags. Note that all such method-defining +attributes must be set in the <code>RemoteInterface</code> body itself, +rather than being set on it after the fact (i.e. <code>RIFoo.doBar = +stuff</code>). This is required because the <code>RemoteInterface</code> +metaclass magic processes all of these attributes only once, immediately +after the <code>RemoteInterface</code> body has been evaluated.</p> + +<p>The <code>RemoteInterface</code> <q>class</q> has a name. Normally this is +the (short) classname<span +class="footnote"><code>RIFoo.__class__.__name__</code>, if +<code>RemoteInterface</code>s were actually classes, which they're +not</span>. You can override this +name by setting a special <code>__remote_name__</code> attribute on the +<code>RemoteInterface</code> (again, in the body). This name is important +because it is externally visible: all <code>RemoteReference</code>s that +point at your <code>Referenceable</code>s will remember the name of the +<code>RemoteInterface</code>s it implements. This is what enables the +type-checking to be performed on both ends of the wire.</p> + +<p>In the future, this ought to default to the <b>fully-qualified</b> +classname (like <code>package.module.RIFoo</code>), so that two +RemoteInterfaces with the same name in different modules can co-exist. In the +current release, these two RemoteInterfaces will collide (and provoke an +import-time error message complaining about the duplicate name). As a result, +if you have such classes (e.g. <code>foo.RIBar</code> and +<code>baz.RIBar</code>), you <b>must</b> use <code>__remote_name__</code> to +distinguish them (by naming one of them something other than +<code>RIBar</code> to avoid this error. + +Hopefully this will be improved in a future version, but it looks like a +difficult change to implement, so the standing recommendation is to use +<code>__remote_name__</code> on all your RemoteInterfaces, and set it to a +suitably unique string (like a URI).</p> + +<p>Here's an example:</p> + +<pre class="python"> +from foolscap import RemoteInterface, schema + +class RIMath(RemoteInterface): + __remote_name__ = "RIMath.using-foolscap.docs.foolscap.twistedmatrix.com" + def add(a=int, b=int): + return int + # declare it with an attribute instead of a function definition + subtract = schema.RemoteMethodSchema(a=int, b=int, _response=int) + def sum(args=schema.ListOf(int)): + return int +</pre> + + +<h3>Using RemoteInterface</h3> + +<p>To declare that your <code>Referenceable</code> responds to a particular +<code>RemoteInterface</code>, use the normal <code>implements()</code> +annotation:</p> + +<pre class="python"> +class MathServer(foolscap.Referenceable): + implements(RIMath) + + def remote_add(self, a, b): + return a+b + def remote_subtract(self, a, b): + return a-b + def remote_sum(self, args): + total = 0 + for a in args: total += a + return total +</pre> + +<p>To enforce constraints everywhere, both sides will need to know about the +<code>RemoteInterface</code>, and both must know it by the same name. It is a +good idea to put the <code>RemoteInterface</code> in a common file that is +imported into the programs running on both sides. It is up to you to make +sure that both sides agree on the interface. Future versions of Foolscap may +implement some sort of checksum-verification or Interface-serialization as a +failsafe, but fundamentally the <code>RemoteInterface</code> that +<em>you</em> are using defines what <em>your</em> program is prepared to +handle. There is no difference between an old client accidentaly using a +different version of the RemoteInterface by mistake, and a malicious attacker +actively trying to confuse your code. The only promise that Foolscap can make +is that the constraints you provide in the RemoteInterface will be faithfully +applied to the incoming data stream, so that you don't need to do the type +checking yourself inside the method.</p> + +<p>When making a remote method call, you use the <code>RemoteInterface</code> +to identify the method instead of a string. This scopes the method name to +the RemoteInterface:</p> + +<pre class="python"> +d = remote.callRemote(RIMath["add"], a=1, b=2) +# or +d = remote.callRemote(RIMath["add"], 1, 2) +</pre> + +<h2>Pass-By-Copy</h2> + +<p>You can pass (nearly) arbitrary instances over the wire. Foolscap knows +how to serialize all of Python's native data types already: numbers, strings, +unicode strings, booleans, lists, tuples, dictionaries, sets, and the None +object. You can teach it how to serialize instances of other types too. +Foolscap will not serialize (or deserialize) any class that you haven't +taught it about, both for security and because it refuses the temptation to +guess your intentions about how these unknown classes ought to be +serialized.</p> + +<p>The simplest possible way to pass things by copy is demonstrated in the +following code fragment:</p> + +<pre class="python"> +from foolscap import Copyable, RemoteCopy + +class MyPassByCopy(Copyable, RemoteCopy): + typeToCopy = copytype = "MyPassByCopy" + def __init__(self): + # RemoteCopy subclasses may not accept any __init__ arguments + pass + def setCopyableState(self, state): + self.__dict__ = state +</pre> + +<p>If the code on both sides of the wire import this class, then any +instances of <code>MyPassByCopy</code> that are present in the arguments of a +remote method call (or returned as the result of a remote method call) will +be serialized and reconstituted into an equivalent instance on the other +side.</p> + +<p>For more complicated things to do with pass-by-copy, see the documentation +on <a href="copyable.html">Copyable</a>. This explains the difference between +<code>Copyable</code> and <code>RemoteCopy</code>, how to control the +serialization and deserialization process, and how to arrange for +serialization of third-party classes that are not subclasses of +<code>Copyable</code>.</p> + + +<h2>Third-party References</h2><a name="introductions" /> + +<p>Another new feature of Foolscap is the ability to send +<code>RemoteReference</code>s to third parties. The classic scenario for this +is illustrated by the <a +href="http://www.erights.org/elib/capability/overview.html">three-party +Granovetter diagram</a>. One party (Alice) has RemoteReferences to two other +objects named Bob and Carol. She wants to share her reference to Carol with +Bob, by including it in a message she sends to Bob (i.e. by using it as an +argument when she invokes one of Bob's remote methods). The Foolscap code for +doing this would look like:</p> + +<pre class="python"> +bobref.callRemote("foo", intro=carolref) +</pre> + +<p>When Bob receives this message (i.e. when his <code>remote_foo</code> +method is invoked), he will discover that he's holding a fully-functional +<code>RemoteReference</code> to the object named Carol<span +class="footnote">and if everyone involved is using authenticated Tubs, then +Foolscap offers a guarantee, in the cryptographic sense, that Bob will wind +up with a reference to the same object that Alice intended. The authenticated +FURLs prevent DNS-spoofing and man-in-the-middle attacks.</span>. He can +start using this RemoteReference right away:</p> + +<pre class="python"> +class Bob(foolscap.Referenceable): + def remote_foo(self, intro): + self.carol = intro + carol.callRemote("howdy", msg="Pleased to meet you", you=intro) + return carol +</pre> + +<p>If Bob sends this <code>RemoteReference</code> back to Alice, her method +will see the same <code>RemoteReference</code> that she sent to Bob. In this +example, Bob sends the reference by returning it from the original +<code>remote_foo</code> method call, but he could almost as easily send it in +a separate method call.</p> + +<pre class="python"> +class Alice(foolscap.Referenceable): + def start(self, carol): + self.carol = carol + d = self.bob.callRemote("foo", intro=carol) + d.addCallback(self.didFoo) + def didFoo(self, result): + assert result is self.carol # this will be true +</pre> + +<p>Moreover, if Bob sends it back to <em>Carol</em> (completing the +three-party round trip), Carol will see it as her original +<code>Referenceable</code>.</p> + +<pre class="python"> +class Carol(foolscap.Referenceable): + def remote_howdy(self, msg, you): + assert you is self # this will be true +</pre> + +<p>In addition to this, in the four-party introduction sequence as used by +the <a +href="http://www.erights.org/elib/equality/grant-matcher/index.html">Grant +Matcher Puzzle</a>, when a Referenceable is sent to the same destination +through multiple paths, the recipient will receive the same +<code>RemoteReference</code> object from both sides.</p> + +<p>For a <code>RemoteReference</code> to be transferrable to third-parties in +this fashion, the original <code>Referenceable</code> must live in a Tub +which has a working listening port, and an established base URL. It is not +necessary for the Referenceable to have been published with +<code>registerReference</code> first: if it is sent over the wire before a +name has been associated with it, it will be registered under a new random +and unguessable name. The <code>RemoteReference</code> will contain the +resulting URL, enabling it to be sent to third parties.</p> + +<p>When this introduction is made, the receiving system must establish a +connection with the Tub that holds the original Referenceable, and acquire +its own RemoteReference. These steps must take place before the remote method +can be invoked, and other method calls might arrive before they do. All +subsequent method calls are queued until the one that involved the +introduction is performed. Foolscap guarantees (by default) that the messages +sent to a given Referenceable will be delivered in the same order. In the +future there may be options to relax this guarantee, in exchange for higher +performance, reduced memory consumption, multiple priority queues, limited +latency, or other features. There might even be an option to turn off +introductions altogether.</p> + +<p>Also note that enabling this capability means any of your communication +peers can make you create TCP connections to hosts and port numbers of their +choosing. The fact that those connections can only speak the Foolscap +protocol may reduce the security risk presented, but it still lets other +people be annoying.</p> + + +</body></html> diff --git a/src/foolscap/doc/using-pb.xhtml b/src/foolscap/doc/using-pb.xhtml deleted file mode 100644 index 97005d01..00000000 --- a/src/foolscap/doc/using-pb.xhtml +++ /dev/null @@ -1,958 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<title>Introduction to Foolscap</title> -<style src="stylesheet-unprocessed.css"></style> -</head> - -<body> -<h1>Introduction to Foolscap</h1> - -<h2>Introduction</h2> - -<p>Suppose you find yourself in control of both ends of the wire: you have -two programs that need to talk to each other, and you get to use any protocol -you want. If you can think of your problem in terms of objects that need to -make method calls on each other, then chances are good that you can use the -Foolscap protocol rather than trying to shoehorn your needs into something -like HTTP, or implementing yet another RPC mechanism.</p> - -<p>Foolscap is based upon a few central concepts:</p> - -<ul> - - <li><em>serialization</em>: taking fairly arbitrary objects and types, - turning them into a chunk of bytes, sending them over a wire, then - reconstituting them on the other end. By keeping careful track of object - ids, the serialized objects can contain references to other objects and the - remote copy will still be useful. </li> - - <li><em>remote method calls</em>: doing something to a local proxy and - causing a method to get run on a distant object. The local proxy is called - a <code class="API" base="foolscap.referenceable">RemoteReference</code>, - and you <q>do something</q> by running its <code>.callRemote</code> method. - The distant object is called a <code class="API" - base="foolscap.referenceable">Referenceable</code>, and it has methods like - <code>remote_foo</code> that will be invoked.</li> - -</ul> - -<p>Foolscap is the descendant of Perspective Broker (which lived in the -twisted.spread package). For many years it was known as "newpb". A lot of the -API still has the name "PB" in it somewhere. These will probably go away -sooner or later.</p> - -<p>A "foolscap" is a size of paper, probably measuring 17 by 13.5 inches. A -twisted foolscap of paper makes a good fool's cap. Also, "cap" makes me think -of capabilities, and Foolscap is a protocol to implement a distributed -object-capabilities model in python.</p> - - -<h2>Getting Started</h2> - -<p>Any Foolscap application has at least two sides: one which hosts a -remotely-callable object, and another which calls (remotely) the methods of -that object. We'll start with a simple example that demostrates both ends. -Later, we'll add more features like RemoteInterface declarations, and -transferring object references.</p> - -<p>The most common way to make an object with remotely-callable methods is to -subclass <code class="API" -base="foolscap.referenceable">Referenceable</code>. Let's create a simple -server which does basic arithmetic. You might use such a service to perform -difficult mathematical operations, like addition, on a remote machine which -is faster and more capable than your own<span class="footnote">although -really, if your client machine is too slow to perform this kind of math, it -is probably too slow to run python or use a network, so you should seriously -consider a hardware upgrade</span>.</p> - -<pre class="python"> -from foolscap import Referenceable - -class MathServer(Referenceable): - def remote_add(self, a, b): - return a+b - def remote_subtract(self, a, b): - return a-b - def remote_sum(self, args): - total = 0 - for a in args: total += a - return total - -myserver = MathServer() -</pre> - -<p>On the other end of the wire (which you might call the <q>client</q> -side), the code will have a <code class="API" -base="foolscap.referenceable">RemoteReference</code> to this object. The -<code>RemoteReference</code> has a method named <code class="API" -base="foolscap.referenceable.RemoteReference">callRemote</code> which you -will use to invoke the method. It always returns a Deferred, which will fire -with the result of the method. Assuming you've already acquired the -<code>RemoteReference</code>, you would invoke the method like this:</p> - -<pre class="python"> -def gotAnswer(result): - print "result is", result -def gotError(err): - print "error:", err -d = remote.callRemote("add", 1, 2) -d.addCallbacks(gotAnswer, gotError) -</pre> - -<p>Ok, now how do you acquire that <code>RemoteReference</code>? How do you -make the <code>Referenceable</code> available to the outside world? For this, -we'll need to discuss the <q>Tub</q>, and the concept of a <q>PB URL</q>.</p> - -<h2>Tubs: The Foolscap Service</h2> - -<p>The <code class="API" base="foolscap.pb">Tub</code> is the container that -you use to publish <code>Referenceable</code>s, and is the middle-man you use -to access <code>Referenceable</code>s on other systems. It is known as the -<q>Tub</q>, since it provides similar naming and identification properties as -the <a href="http://www.erights.org/">E language</a>'s <q>Vat</q><span -class="footnote">but they do not provide quite the same insulation against -other objects as E's Vats do. In this sense, Tubs are leaky Vats.</span>. If -you want to make a <code>Referenceable</code> available to the world, you -create a Tub, tell it to listen on a TCP port, and then register the -<code>Referenceable</code> with it under a name of your choosing. If you want -to access a remote <code>Referenceable</code>, you create a Tub and ask it to -acquire a <code>RemoteReference</code> using that same name.</p> - -<p>The <code>Tub</code> is a Twisted <code class="API" -base="twisted.application.service">Service</code> subclass, so you use it in -the same way: once you've created one, you attach it to a parent Service or -Application object. Once the top-level Application object has been started, -the Tub will start listening on any network ports you've requested. When the -Tub is shut down, it will stop listening and drop any connections it had -established since last startup. If you have no parent to attach it to, you -can use <code>startService</code> and <code>stopService</code> on the Tub -directly.</p> - -<p>Note that you must start the Tub before calling <code>getReference</code> -or <code>connectTo</code>, since both of these trigger network activity, and -Tubs are supposed to be silent until they are started. In a future release -this requirement may be relaxed, but the principle of "no network activity -until the Tub is started" will be maintained, probably by queueing the -<code>getReference</code> calls and handling them after the Tub been -started.</p> - -<h3>Making your Tub remotely accessible</h3> - -<p>To make any of your <code>Referenceable</code>s available, you must make -your Tub available. There are three parts: give it an identity, have it -listen on a port, and tell it the protocol/hostname/portnumber at which that -port is accessibly to the outside world.</p> - -<p>In general, the Tub will generate its own identity, the <em>TubID</em>, by -creating an SSL private key certificate and hashing it into a suitably-long -random-looking string. This is the primary identifier of the Tub: everything -else is just a <em>location hint</em> that suggests how the Tub might be -reached. The fact that the TubID is tied to the private key allows PB URLs to -be <q>secure</q> references (meaning that no third party can cause you to -connect to the wrong reference). You can also create a Tub with a -pre-existing certificate, which is how Tubs can retain a persistent identity -over multiple executions.</p> - -<p>You can also create an <code>UnauthenticatedTub</code>, which has an empty -TubID. Hosting and connecting to unauthenticated Tubs do not require the -pyOpenSSL library, but do not provide privacy, authentication, connection -redirection, or shared listening ports. The PB-URLs that point to -unauthenticated Tubs have a distinct form (starting with <code>pbu:</code> -instead of <code>pb:</code>) to make sure they are not mistaken for -authenticated Tubs. PB uses authenticated Tubs by default.</p> - -<p>Having the Tub listen on a TCP port is as simple as calling <code -class="API" base="foolscap.pb.Tub">listenOn</code> with a <code class="API" -base="twisted.application">strports</code>-formatted port specification -string. The simplest such string would be <q>tcp:12345</q>, to listen on port -12345 on all interfaces. Using <q>tcp:12345:interface=127.0.0.1</q> would -cause it to only listen on the localhost interface, making it available only -to other processes on the same host. The <code>strports</code> module -provides many other possibilities.</p> - -<p>The Tub needs to be told how it can be reached, so it knows what host and -port to put into the URLs it creates. This location is simply a string in the -format <q>host:port</q>, using the host name by which that TCP port you've -just opened can be reached. Foolscap cannot, in general, guess what this name -is, especially if there are NAT boxes or port-forwarding devices in the way. -If your machine is reachable directly over the internet as -<q>myhost.example.com</q>, then you could use something like this:</p> - -<pre class="python"> -from foolscap import Tub - -tub = Tub() -tub.listenOn("tcp:12345") # start listening on TCP port 12345 -tub.setLocation("myhost.example.com:12345") -</pre> - -<h3>Registering the Referenceable</h3> - -<p>Once the Tub has a Listener and a location, you can publish your -<code>Referenceable</code> to the entire world by picking a name and -registering it:</p> - -<pre class="python"> -url = tub.registerReference(myserver, "math-service") -</pre> - -<p>This returns the <q>PB URL</q> for your <code>Referenceable</code>. Remote -systems will use this URL to access your newly-published object. The -registration just maps a per-Tub name to the <code>Referenceable</code>: -technically the same <code>Referenceable</code> could be published multiple -times, under different names, or even be published by multiple Tubs in the -same application. But in general, each program will have exactly one Tub, and -each object will be registered under only one name.</p> - -<p>In this example (if we pretend the generated TubID was <q>ABCD</q>), the -URL returned by <code>registerReference</code> would be -<code>"pb://ABCD@myhost.example.com:12345/math-service"</code>.</p> - -<p>If you do not provide a name, a random (and unguessable) name will be -generated for you. This is useful when you want to give access to your -<code>Referenceable</code> to someone specific, but do not want to make it -possible for someone else to acquire it by guessing the name.</p> - -<p>To use an unauthenticated Tub instead, you would do the following:</p> -<pre class="python"> -from foolscap import UnauthenticatedTub - -tub = UnauthenticatedTub() -tub.listenOn("tcp:12345") # start listening on TCP port 12345 -tub.setLocation("myhost.example.com:12345") -url = tub.registerReference(myserver, "math-service") -</pre> - -<p>In this case, the URL would be -<code>"pbu://myhost.example.com:12345/math-service"</code>. The deterministic -nature of this form makes it slightly easier to throw together -quick-and-dirty Foolscap applications, since you only need to hard-code the -target host and port into the client side program. However any serious -application should just used the default authenticated form and use a full -URL as their starting point. Note that the URL can come from anywhere: typed -in by the user, retrieved from a web page, or hardcoded into the -application.</p> - -<h4>Using a persistent certificate</h4> - -<p>The Tub uses a TLS private-key certificate as the base of all its -cryptographic operations. If you don't give it one when you create the Tub, -it will generate a brand-new one.</p> - -<p>The TubID is simply the hash of this certificate, so if you are writing an -application that should have a stable long-term identity, you will need to -insure that the Tub uses the same certificate every time your app starts. The -easiest way to do this is to pass the <code>certFile=</code> argument into -your <code>Tub()</code> constructor call. This argument provides a filename -where you want the Tub to store its certificate. The first time the Tub is -started (when this file does not exist), the Tub will generate a new -certificate and store it here. On subsequent invocations, the Tub will read -the earlier certificate from this location. Make sure this filename points to -a writable location, and that you pass the same filename to -<code>Tub()</code> each time.</p> - -<h3>Retrieving a RemoteReference</h3> - -<p>On the <q>client</q> side, you also need to create a Tub, although you -don't need to perform the (<code>listenOn</code>, <code>setLocation</code>, -<code>registerReference</code>) sequence unless you are also publishing -<code>Referenceable</code>s to the world. To acquire a reference to somebody -else's object, just use <code class="API" -base="foolscap.pb.Tub">getReference</code>:</p> - -<pre class="python"> -from foolscap import Tub - -tub = Tub() -tub.startService() -d = tub.getReference("pb://ABCD@myhost.example.com:12345/math-service") -def gotReference(remote): - print "Got the RemoteReference:", remote -def gotError(err): - print "error:", err -d.addCallbacks(gotReference, gotError) -</pre> - -<p><code>getReference</code> returns a Deferred which will fire with a -<code>RemoteReference</code> that is connected to the remote -<code>Referenceable</code> named by the URL. It will use an existing -connection, if one is available, and it will return an existing -<code>RemoteReference</code>, it one has already been acquired.</p> - -<h3>Complete example</h3> - -<p>Here are two programs, one implementing the server side of our -remote-addition protocol, the other behaving as a client. This first example -uses an unauthenticated Tub so you don't have to manually copy a URL from the -server to the client. Both of these are standalone programs (you just run -them), but normally you would create an <code class="API" -base="twisted.application.service">Application</code> object and pass the -file to <code>twistd -noy</code>. An example of that usage will be provided -later.</p> - -<a href="listings/pb1server.py" class="py-listing" -skipLines="2">pb1server.py</a> - -<a href="listings/pb1client.py" class="py-listing" -skipLines="2">pb1client.py</a> - -<pre class="shell"> -% doc/listings/pb1server.py -the object is available at: pbu://localhost:12345/math-service -</pre> - -<pre class="shell"> -% doc/listings/pb1client.py -got a RemoteReference -asking it to add 1+2 -the answer is 3 -% -</pre> - -<p>The second example uses authenticated Tubs. When running this example, you -must copy the URL printed by the server and provide it as an argument to the -client.</p> - -<a href="listings/pb2server.py" class="py-listing" -skipLines="2">pb2server.py</a> - -<a href="listings/pb2client.py" class="py-listing" -skipLines="2">pb2client.py</a> - -<pre class="shell"> -% doc/listings/pb2server.py -the object is available at: pb://abcd123@localhost:12345/math-service -</pre> - -<pre class="shell"> -% doc/listings/pb2client.py pb://abcd123@localhost:12345/math-service -got a RemoteReference -asking it to add 1+2 -the answer is 3 -% -</pre> - - -<h3>PB URLs</h3> - -<p>In Foolscap, each world-accessible Referenceable has one or more URLs -which are <q>secure</q>, where we use the capability-security definition of -the term, meaning those URLs have the following properties:</p> - -<ul> - <li>The only way to acquire the URL is either to get it from someone else - who already has it, or to be the person who published it in the first - place.</li> - - <li>Only that original creator of the URL gets to determine which - Referenceable it will connect to. If your - <code>tub.getReference(url)</code> call succeeds, the Referenceable you - will be connected to will be the right one.</li> -</ul> - -<p>To accomplish the first goal, PB URLs must be unguessable. You can -register the reference with a human-readable name if your intention is to -make it available to the world, but in general you will let -<code>tub.registerReference</code> generate a random name for you, preserving -the unguessability property.</p> - -<p>To accomplish the second goal, the cryptographically-secure TubID is used -as the primary identifier, and the <q>location hints</q> are just that: -hints. If DNS has been subverted to point the hostname at a different -machine, or if a man-in-the-middle attack causes you to connect to the wrong -box, the TubID will not match the remote end, and the connection will be -dropped. These attacks can cause a denial-of-service, but they cannot cause -you to mistakenly connect to the wrong target.</p> - -<p>Obviously this second property only holds if you use SSL. If you choose to -use unauthenticated Tubs, all security properties are lost.</p> - -<p>The format of a PB URL, like -<code>pb://abcd123@example.com:5901,backup.example.com:8800/math-server</code>, -is as follows<span class="footnote">note that the PB URL uses the same format -as an <a href="http://www.waterken.com/dev/YURL/httpsy/">HTTPSY</a> -URL</span>:</p> - -<ol> - <li>The literal string <code>pb://</code></li> - <li>The TubID (as a base32-encoded hash of the SSL certificate)</li> - <li>A literal <code>@</code> sign</li> - - <li>A comma-separated list of <q>location hints</q>. Each is one of the - following: - <ul> - <li>TCP over IPv4 via DNS: <code>HOSTNAME:PORTNUM</code></li> - <li>TCP over IPv4 without DNS: <code>A.B.C.D:PORTNUM</code></li> - <li>TCP over IPv6: (TODO, maybe <code>tcp6:HOSTNAME:PORTNUM</code> ?</li> - <li>TCP over IPv6 w/o DNS: (TODO, - maybe <code>tcp6:[X:Y::Z]:PORTNUM</code></li> - <li>Unix-domain socket: (TODO)</li> - </ul> - - Each location hint is attempted in turn. Servers can return a - <q>redirect</q>, which will cause the client to insert the provided - redirect targets into the hint list and start trying them before continuing - with the original list.</li> - - <li>A literal <code>/</code> character</li> - <li>The reference's name</li> -</ol> - -<p>(Unix-domain sockets are represented with only a single location hint, in -the format <code>pb://ABCD@unix/path/to/socket/NAME</code>, but this needs -some work)</p> - -<p>PB URLs for unauthenticated Tubs, like -<code>pbu://example.com:8700/math-server</code>, are formatted as -follows:</p> - -<ol> - <li>The literal string <code>pbu://</code></li> - <li>A single location hint</li> - <li>A literal <code>/</code> character</li> - <li>The reference's name</li> -</ol> - -<h2>Clients vs Servers, Names and Capabilities</h2> - -<p>It is worthwhile to point out that Foolscap is a symmetric protocol. -<code>Referenceable</code> instances can live on either side of a wire, and -the only difference between <q>client</q> and <q>server</q> is who publishes -the object and who initiates the network connection.</p> - -<p>In any Foolscap-using system, the very first object exchanged must be -acquired with a <code>tub.getReference(url)</code> call<span -class="footnote">in fact, the very <em>very</em> first object exchanged is a -special implicit RemoteReference to the remote Tub itself, which implements -an internal protocol that includes a method named -<code>remote_getReference</code>. The <code>tub.getReference(url)</code> call -is turned into one step that connects to the remote Tub, and a second step -which invokes remotetub.callRemote("getReference", refname) on the -result</span>, which means it must have been published with a call to -<code>tub.registerReference(ref, name)</code>. After that, other objects can -be passed as an argument to (or a return value from) a remotely-invoked -method of that first object. Any suitable <code>Referenceable</code> object -that is passed over the wire will appear on the other side as a corresponding -<code>RemoteReference</code>. It is not necessary to -<code>registerReference</code> something to let it pass over the wire.</p> - -<p>The converse of this property is thus: if you do <em>not</em> -<code>registerReference</code> a particular <code>Referenceable</code>, and -you do <em>not</em> give it to anyone else (by passing it in an argument to -somebody's remote method, or return it from one of your own), then nobody -else will be able to get access to that <code>Referenceable</code>. This -property means the <code>Referenceable</code> is a <q>capability</q>, as -holding a corresponding <code>RemoteReference</code> gives someone a power -that they cannot acquire in any other way<span class="footnote">of course, -the Foolscap connections must be secured with SSL (otherwise an eavesdropper -or man-in-the-middle could get access), and the registered name must be -unguessable (or someone else could acquire a reference), but both of these -are the default.</span></p> - -<p>In the following example, the first program creates an RPN-style -<code>Calculator</code> object which responds to <q>push</q>, <q>pop</q>, -<q>add</q>, and <q>subtract</q> messages from the user. The user can also -register an <code>Observer</code>, to which the Calculator sends an -<code>event</code> message each time something happens to the calculator's -state. When you consider the <code>Calculator</code> object, the first -program is the server and the second program is the client. When you think -about the <code>Observer</code> object, the first program is a client and the -second program is the server. It also happens that the first program is -listening on a socket, while the second program initiated a network -connection to the first. It <em>also</em> happens that the first program -published an object under some well-known name, while the second program has -not published any objects. These are all independent properties.</p> - -<p>Also note that the Calculator side of the example is implemented using a -<code class="API" base="twisted.application.service">Application</code> -object, which is the way you'd normally build a real-world application. You -therefore use <code>twistd</code> to launch the program. The User side is -written with the same <code>reactor.run()</code> style as the earlier -example.</p> - -<p>The server registers the Calculator instance and prints the PBURL at which -it is listening. You need to pass this PBURL to the client program so it -knows how to contact the servre. If you have a modern version of Twisted (2.5 -or later) and the right encryption libraries installed, you'll get an -authenticated Tub (for which the PBURL will start with "pb:" and will be -fairly long). If you don't, you'll get an unauthenticated Tub (with a -relatively short PBURL that starts with "pbu:").</p> - -<a href="listings/pb3calculator.py" class="py-listing" -skipLines="2">pb3calculator.py</a> - -<a href="listings/pb3user.py" class="py-listing" -skipLines="2">pb3user.py</a> - -<pre class="shell"> -% twistd -noy doc/listings/pb3calculator.py -15:46 PDT [-] Log opened. -15:46 PDT [-] twistd 2.4.0 (/usr/bin/python 2.4.4) starting up -15:46 PDT [-] reactor class: twisted.internet.selectreactor.SelectReactor -15:46 PDT [-] Loading doc/listings/pb3calculator.py... -15:46 PDT [-] the object is available at: - pb://5ojw4cv4u4d5cenxxekjukrogzytnhop@localhost:12345/calculator -15:46 PDT [-] Loaded. -15:46 PDT [-] foolscap.pb.Listener starting on 12345 -15:46 PDT [-] Starting factory <Listener at 0x4869c0f4 on tcp:12345 - with tubs None> -</pre> - -<pre class="shell"> -% doc/listings/pb3user.py \ - pb://5ojw4cv4u4d5cenxxekjukrogzytnhop@localhost:12345/calculator -event: push(2) -event: push(3) -event: add -event: pop -the result is 5 -% -</pre> - - -<h2>Invoking Methods, Method Arguments</h2> - -<p>As you've probably already guessed, all the methods with names that begin -with <code>remote_</code> will be available to anyone who manages to acquire -a corresponding <code>RemoteReference</code>. <code>remote_foo</code> matches -a <code>ref.callRemote("foo")</code>, etc. This name lookup can be changed by -overriding <code>Referenceable</code> (or, perhaps more usefully, -implementing an <code class="API" -base="foolscap.ipb">IRemotelyCallable</code> adapter).</p> - -<p>The arguments of a remote method may be passed as either positional -parameters (<code>foo(1,2)</code>), or as keyword args -(<code>foo(a=1,b=2)</code>), or a mixture of both. The usual python rules -about not duplicating parameters apply.</p> - -<p>You can pass all sorts of normal objects to a remote method: strings, -numbers, tuples, lists, and dictionaries. The serialization of these objects -is handled by <a href="specifications/banana.xhtml">Banana</a>, which knows -how to convey arbitrary object graphs over the wire. Things like containers -which contain multiple references to the same object, and recursive -references (cycles in the object graph) are all handled correctly<span -class="footnote">you may not want to accept shared objects in your method -arguments, as it could lead to surprising behavior depending upon how you -have written your method. The <code class="API" -base="foolscap.schema">Shared</code> constraint will let you express this, -and is described in the <a href="#constraints">Constraints</a> section of -this document</span>.</p> - -<p>Passing instances is handled specially. Foolscap will not send anything -over the wire that it does not know how to serialize, and (unlike the -standard <code>pickle</code> module) it will not make assumptions about how -to handle classes that that have not been explicitly marked as serializable. -This is for security, both for the sender (making you you don't pass anything -over the wire that you didn't intend to let out of your security perimeter), -and for the recipient (making sure outsiders aren't allowed to create -arbitrary instances inside your memory space, and therefore letting them run -somewhat arbitrary code inside <em>your</em> perimeter).</p> - -<p>Sending <code>Referenceable</code>s is straightforward: they always appear -as a corresponding <code>RemoteReference</code> on the other side. You can -send the same <code>Referenceable</code> as many times as you like, and it -will always show up as the same <code>RemoteReference</code> instance. A -distributed reference count is maintained, so as long as the remote side -hasn't forgotten about the <code>RemoteReference</code>, the original -<code>Referenceable</code> will be kept alive.</p> - -<p>Sending <code>RemoteReference</code>s fall into two categories. If you are -sending a <code>RemoteReference</code> back to the Tub that you got it from, -they will see their original <code>Referenceable</code>. If you send it to -some other Tub, they will (eventually) see a <code>RemoteReference</code> of -their own. This last feature is called an <q>introduction</q>, and has a few -additional requirements: see the <a href="#introductions">Introductions</a> -section of this document for details.</p> - -<p>Sending instances of other classes requires that you tell Banana how they -should be serialized. <code>Referenceable</code> is good for -copy-by-reference semantics<span class="footnote">In fact, if all you want is -referenceability (and not callability), you can use <code class="API" -base="foolscap.referenceable">OnlyReferenceable</code>. Strictly speaking, -<code>Referenceable</code> is both <q>Referenceable</q> (meaning it is sent -over the wire using pass-by-reference semantics, and it survives a round -trip) and <q>Callable</q> (meaning you can invoke remote methods on it). -<code>Referenceable</code> should really be named <code>Callable</code>, but -the existing name has a lot of historical weight behind it.</span>. For -copy-by-value semantics, the easiest route is to subclass <code class="API" -base="foolscap.copyable">Copyable</code>. See the <a -href="#copyable">Copyable</a> section for details. Note that you can also -register an <code class="API" base="foolscap.copyable">ICopyable</code> -adapter on third-party classes to avoid subclassing. You will need to -register the <code>Copyable</code>'s name on the receiving end too, otherwise -Banana will not know how to unserialize the incoming data stream.</p> - -<p>When returning a value from a remote method, you can do all these things, -plus two more. If you raise an exception, the caller's Deferred will have the -errback fired instead of the callback, with a <code class="API" -base="foolscap.call">CopiedFailure</code> instance that describes what went -wrong. The <code>CopiedFailure</code> is not quite as useful as a local <code -class="API" base="twisted.python.failure">Failure</code> object would be: to -send it over the wire, some contents are replaced with strings, and the -actual Exception object (<code>f.value</code>) is replaced with its string -representation. But you can still use it to find out what went wrong. The -<code>CopiedFailure</code> may reveal more information about the internals of -your program than you want: you can set the <code>unsafeTracebacks</code> -flag on the Tub to limit outgoing <code>CopiedFailure</code>s to contain only -the exception type (and none of the stack trace information that would reveal -lines of your source code to the remote end).</p> - -<p>The other alternative is for your method to return a <code class="API" -base="twisted.internet.defer">Deferred</code>. If this happens, the caller -will not actually get a response until you fire that Deferred. This is useful -when the remote operation being requested cannot complete right away. The -caller's Deferred with fire with whatever value you eventually fire your own -Deferred with. If your Deferred is errbacked, their Deferred will be -errbacked with a <code>CopiedFailure</code>.</p> - - -<h2>Constraints and RemoteInterfaces</h2><a name="constraints" /> - -<p>One major feature introduced by Foolscap (relative to oldpb) is the -serialization <code class="API" base="foolscap.schema">Constraint</code>. -This lets you place limits on what kind of data you are willing to accept, -which enables safer distributed programming. Typically python uses <q>duck -typing</q>, wherein you usually just throw some arguments at the method and -see what happens. When you are less sure of the origin of those arguments, -you may want to be more circumspect. Enforcing type checking at the boundary -between your code and the outside world may make it safer to use duck typing -inside those boundaries. The type specifications also form a convenient -remote API reference you can publish for prospective clients of your -remotely-invokable service.</p> - -<p>In addition, these Constraints are enforced on each token as it arrives -over the wire. This means that you can calculate a (small) upper bound on how -much received data your program will store before it decides to hang up on -the violator, minimizing your exposure to DoS attacks that involve sending -random junk at you.</p> - -<p>There are three pieces you need to know about: Tokens, Constraints, and -RemoteInterfaces.</p> - -<h3>Tokens</h3> - -<p>The fundamental unit of serialization is the Banana Token. These are -thoroughly documented in the <a href="specifications/banana.xhtml">Banana -Specification</a>, but what you need to know here is that each piece of -non-container data, like a string or a number, is represented by a single -token. Containers (like lists and dictionaries) are represented by a special -OPEN token, followed by tokens for everything that is in the container, -followed by the CLOSE token. Everything Banana does is in terms of these -nested OPEN/stuff/stuff/CLOSE sequences of tokens.</p> - -<p>Each token consists of a header, a type byte, and an optional body. The -header is always a base-128 number with a maximum of 64 digits, and the type -byte is always a single byte. The body (if present) has a length dictated by -the magnitude of the header.</p> - -<p>The length-first token format means that the receiving system never has to -accept more than 65 bytes before it knows the type and size of the token, at -which point it can make a decision about accepting or rejecting the rest of -it.</p> - -<h3>Constraints</h3> - -<p>The schema <code>foolscap.schema</code> module has a variety of <code -class="API" base="foolscap.schema">Constraint</code> classes that can be -applied to incoming data. Most of them correspond to typical Python types, -e.g. <code class="API" base="foolscap.schema">ListOf</code> matches a list, -with a certain maximum length, and a child <code>Constraint</code> that gets -applied to the contents of the list. You can nest <code>Constraint</code>s in -this way to describe the <q>shape</q> of the object graph that you are -willing to accept.</p> - -<p>At any given time, the receiving Banana protocol has single -<code>Constraint</code> object that it enforces against the inbound data -stream<span class="footnote">to be precise, each <code>Unslicer</code> on the -receive stack has a <code>Constraint</code>, and the idea is that all of them -get to pass judgement on the inbound token. A useful syntax to describe this -sort of thing is still being worked out.</span>.</p> - -<h3>RemoteInterfaces</h3> - -<p>The <code class="API" -base="foolscap.remoteinterface">RemoteInterface</code> is how you describe -your constraints. You can provide a constraint for each argument of each -method, as well as one for the return value. You can also specify addtional -flags on the methods. The convention (which is actually enforced by the code) -is to name <code>RemoteInterface</code> objects with an <q>RI</q> prefix, -like <code>RIFoo</code>.</p> - -<p><code>RemoteInterfaces</code> are created and used a lot like the usual -<code>zope.interface</code>-style <code>Interface</code>. They look like -class definitions, inheriting from <code>RemoteInterface</code>. For each -method, the default value of each argument is used to create a -<code>Constraint</code> for that argument. Basic types (<code>int</code>, -<code>str</code>, <code>bool</code>) are converted into a -<code>Constraint</code> subclass (<code class="API" -base="foolscap.schema">IntegerConstraint</code>, <code class="API" -base="foolscap.schema">StringConstraint</code>, <code class="API" -base="foolscap.schema">BooleanConstraint</code>). You can also use -instances of other <code>Constraint</code> subclasses, like <code class="API" -base="foolscap.schema">ListOf</code> and <code class="API" -base="foolscap.schema">DictOf</code>. This <code>Constraint</code> will be -enforced against the value for the given argument. Unless you specify -otherwise, remote callers must match all the <code>Constraint</code>s you -specify, all arguments listed in the RemoteInterface must be present, and no -arguments outside that list will be accepted.</p> - -<p>Note that, like zope.interface, these methods should <b>not</b> include -<q><code>self</code></q> in their argument list. This is because you are -documenting how <em>other</em> people invoke your methods. <code>self</code> -is an implementation detail. <code>RemoteInterface</code> will complain if -you forget.</p> - -<p>The <q>methods</q> in a <code>RemoteInterface</code> should return a -single value with the same format as the default arguments: either a basic -type (<code>int</code>, <code>str</code>, etc) or a <code>Constraint</code> -subclass. This <code>Constraint</code> is enforced on the return value of the -method. If you are calling a method in somebody else's process, the argument -constraints will be applied as a courtesy (<q>be conservative in what you -send</q>), and the return value constraint will be applied to prevent the -server from doing evil things to you. If you are running a method on behalf -of a remote client, the argument constraints will be enforced to protect -<em>you</em>, while the return value constraint will be applied as a -courtesy.</p> - -<p>Attempting to send a value that does not satisfy the Constraint will -result in a <code class="API" base="foolscap">Violation</code> exception -being raised.</p> - -<p>You can also specify methods by defining attributes of the same name in -the <code>RemoteInterface</code> object. Each attribute value should be an -instance of <code class="API" -base="foolscap.schema">RemoteMethodSchema</code><span -class="footnote">although technically it can be any object which implements -the <code class="API" base="foolscap.schema">IRemoteMethodConstraint</code> -interface</span>. This approach is more flexible: there are some constraints -that are not easy to express with the default-argument syntax, and this is -the only way to set per-method flags. Note that all such method-defining -attributes must be set in the <code>RemoteInterface</code> body itself, -rather than being set on it after the fact (i.e. <code>RIFoo.doBar = -stuff</code>). This is required because the <code>RemoteInterface</code> -metaclass magic processes all of these attributes only once, immediately -after the <code>RemoteInterface</code> body has been evaluated.</p> - -<p>The <code>RemoteInterface</code> <q>class</q> has a name. Normally this is -the (short) classname<span -class="footnote"><code>RIFoo.__class__.__name__</code>, if -<code>RemoteInterface</code>s were actually classes, which they're -not</span>. You can override this -name by setting a special <code>__remote_name__</code> attribute on the -<code>RemoteInterface</code> (again, in the body). This name is important -because it is externally visible: all <code>RemoteReference</code>s that -point at your <code>Referenceable</code>s will remember the name of the -<code>RemoteInterface</code>s it implements. This is what enables the -type-checking to be performed on both ends of the wire.</p> - -<p>In the future, this ought to default to the <b>fully-qualified</b> -classname (like <code>package.module.RIFoo</code>), so that two -RemoteInterfaces with the same name in different modules can co-exist. In the -current release, these two RemoteInterfaces will collide (and provoke an -import-time error message complaining about the duplicate name). As a result, -if you have such classes (e.g. <code>foo.RIBar</code> and -<code>baz.RIBar</code>), you <b>must</b> use <code>__remote_name__</code> to -distinguish them (by naming one of them something other than -<code>RIBar</code> to avoid this error. - -Hopefully this will be improved in a future version, but it looks like a -difficult change to implement, so the standing recommendation is to use -<code>__remote_name__</code> on all your RemoteInterfaces, and set it to a -suitably unique string (like a URI).</p> - -<p>Here's an example:</p> - -<pre class="python"> -from foolscap import RemoteInterface, schema - -class RIMath(RemoteInterface): - __remote_name__ = "RIMath.using-pb.docs.foolscap.twistedmatrix.com" - def add(a=int, b=int): - return int - # declare it with an attribute instead of a function definition - subtract = schema.RemoteMethodSchema(a=int, b=int, _response=int) - def sum(args=schema.ListOf(int)): - return int -</pre> - - -<h3>Using RemoteInterface</h3> - -<p>To declare that your <code>Referenceable</code> responds to a particular -<code>RemoteInterface</code>, use the normal <code>implements()</code> -annotation:</p> - -<pre class="python"> -class MathServer(foolscap.Referenceable): - implements(RIMath) - - def remote_add(self, a, b): - return a+b - def remote_subtract(self, a, b): - return a-b - def remote_sum(self, args): - total = 0 - for a in args: total += a - return total -</pre> - -<p>To enforce constraints everywhere, both sides will need to know about the -<code>RemoteInterface</code>, and both must know it by the same name. It is a -good idea to put the <code>RemoteInterface</code> in a common file that is -imported into the programs running on both sides. It is up to you to make -sure that both sides agree on the interface. Future versions of Foolscap may -implement some sort of checksum-verification or Interface-serialization as a -failsafe, but fundamentally the <code>RemoteInterface</code> that -<em>you</em> are using defines what <em>your</em> program is prepared to -handle. There is no difference between an old client accidentaly using a -different version of the RemoteInterface by mistake, and a malicious attacker -actively trying to confuse your code. The only promise that Foolscap can make -is that the constraints you provide in the RemoteInterface will be faithfully -applied to the incoming data stream, so that you don't need to do the type -checking yourself inside the method.</p> - -<p>When making a remote method call, you use the <code>RemoteInterface</code> -to identify the method instead of a string. This scopes the method name to -the RemoteInterface:</p> - -<pre class="python"> -d = remote.callRemote(RIMath["add"], a=1, b=2) -# or -d = remote.callRemote(RIMath["add"], 1, 2) -</pre> - -<h2>Pass-By-Copy</h2> - -<p>You can pass (nearly) arbitrary instances over the wire. Foolscap knows -how to serialize all of Python's native data types already: numbers, strings, -unicode strings, booleans, lists, tuples, dictionaries, sets, and the None -object. You can teach it how to serialize instances of other types too. -Foolscap will not serialize (or deserialize) any class that you haven't -taught it about, both for security and because it refuses the temptation to -guess your intentions about how these unknown classes ought to be -serialized.</p> - -<p>The simplest possible way to pass things by copy is demonstrated in the -following code fragment:</p> - -<pre class="python"> -from foolscap import Copyable, RemoteCopy - -class MyPassByCopy(Copyable, RemoteCopy): - typeToCopy = copytype = "MyPassByCopy" - def __init__(self): - # RemoteCopy subclasses may not accept any __init__ arguments - pass - def setCopyableState(self, state): - self.__dict__ = state -</pre> - -<p>If the code on both sides of the wire import this class, then any -instances of <code>MyPassByCopy</code> that are present in the arguments of a -remote method call (or returned as the result of a remote method call) will -be serialized and reconstituted into an equivalent instance on the other -side.</p> - -<p>For more complicated things to do with pass-by-copy, see the documentation -on <a href="copyable.html">Copyable</a>. This explains the difference between -<code>Copyable</code> and <code>RemoteCopy</code>, how to control the -serialization and deserialization process, and how to arrange for -serialization of third-party classes that are not subclasses of -<code>Copyable</code>.</p> - - -<h2>Third-party References</h2><a name="introductions" /> - -<p>Another new feature of Foolscap is the ability to send -<code>RemoteReference</code>s to third parties. The classic scenario for this -is illustrated by the <a -href="http://www.erights.org/elib/capability/overview.html">three-party -Granovetter diagram</a>. One party (Alice) has RemoteReferences to two other -objects named Bob and Carol. She wants to share her reference to Carol with -Bob, by including it in a message she sends to Bob (i.e. by using it as an -argument when she invokes one of Bob's remote methods). The Foolscap code for -doing this would look like:</p> - -<pre class="python"> -bobref.callRemote("foo", intro=carolref) -</pre> - -<p>When Bob receives this message (i.e. when his <code>remote_foo</code> -method is invoked), he will discover that he's holding a fully-functional -<code>RemoteReference</code> to the object named Carol<span -class="footnote">and if everyone involved is using authenticated Tubs, then -Foolscap offers a guarantee, in the cryptographic sense, that Bob will wind -up with a reference to the same object that Alice intended. The authenticated -PBURLs prevent DNS-spoofing and man-in-the-middle attacks.</span>. He can -start using this RemoteReference right away:</p> - -<pre class="python"> -class Bob(foolscap.Referenceable): - def remote_foo(self, intro): - self.carol = intro - carol.callRemote("howdy", msg="Pleased to meet you", you=intro) - return carol -</pre> - -<p>If Bob sends this <code>RemoteReference</code> back to Alice, her method -will see the same <code>RemoteReference</code> that she sent to Bob. In this -example, Bob sends the reference by returning it from the original -<code>remote_foo</code> method call, but he could almost as easily send it in -a separate method call.</p> - -<pre class="python"> -class Alice(foolscap.Referenceable): - def start(self, carol): - self.carol = carol - d = self.bob.callRemote("foo", intro=carol) - d.addCallback(self.didFoo) - def didFoo(self, result): - assert result is self.carol # this will be true -</pre> - -<p>Moreover, if Bob sends it back to <em>Carol</em> (completing the -three-party round trip), Carol will see it as her original -<code>Referenceable</code>.</p> - -<pre class="python"> -class Carol(foolscap.Referenceable): - def remote_howdy(self, msg, you): - assert you is self # this will be true -</pre> - -<p>In addition to this, in the four-party introduction sequence as used by -the <a -href="http://www.erights.org/elib/equality/grant-matcher/index.html">Grant -Matcher Puzzle</a>, when a Referenceable is sent to the same destination -through multiple paths, the recipient will receive the same -<code>RemoteReference</code> object from both sides.</p> - -<p>For a <code>RemoteReference</code> to be transferrable to third-parties in -this fashion, the original <code>Referenceable</code> must live in a Tub -which has a working listening port, and an established base URL. It is not -necessary for the Referenceable to have been published with -<code>registerReference</code> first: if it is sent over the wire before a -name has been associated with it, it will be registered under a new random -and unguessable name. The <code>RemoteReference</code> will contain the -resulting URL, enabling it to be sent to third parties.</p> - -<p>When this introduction is made, the receiving system must establish a -connection with the Tub that holds the original Referenceable, and acquire -its own RemoteReference. These steps must take place before the remote method -can be invoked, and other method calls might arrive before they do. All -subsequent method calls are queued until the one that involved the -introduction is performed. Foolscap guarantees (by default) that the messages -sent to a given Referenceable will be delivered in the same order. In the -future there may be options to relax this guarantee, in exchange for higher -performance, reduced memory consumption, multiple priority queues, limited -latency, or other features. There might even be an option to turn off -introductions altogether.</p> - -<p>Also note that enabling this capability means any of your communication -peers can make you create TCP connections to hosts and port numbers of their -choosing. The fact that those connections can only speak the Foolscap -protocol may reduce the security risk presented, but it still lets other -people be annoying.</p> - - -</body></html> diff --git a/src/foolscap/foolscap/__init__.py b/src/foolscap/foolscap/__init__.py index e2243f42..ab58d559 100644 --- a/src/foolscap/foolscap/__init__.py +++ b/src/foolscap/foolscap/__init__.py @@ -1,6 +1,6 @@ """Foolscap""" -__version__ = "0.1.3" +__version__ = "0.1.4" # here are the primary entry points from foolscap.pb import Tub, UnauthenticatedTub, getRemoteURL_TCP diff --git a/src/foolscap/foolscap/call.py b/src/foolscap/foolscap/call.py index 8ed3af4b..2f1ccd6a 100644 --- a/src/foolscap/foolscap/call.py +++ b/src/foolscap/foolscap/call.py @@ -197,11 +197,14 @@ class InboundDelivery: class ArgumentUnslicer(slicer.ScopedUnslicer): methodSchema = None + debug = False def setConstraint(self, methodSchema): self.methodSchema = methodSchema def start(self, count): + if self.debug: + log.msg("%s.start: %s" % (self, count)) self.numargs = None self.args = [] self.kwargs = {} @@ -242,6 +245,11 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): return unslicer def receiveChild(self, token, ready_deferred=None): + if self.debug: + log.msg("%s.receiveChild: %s %s %s %s %s args=%s kwargs=%s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children, token, ready_deferred, + self.args, self.kwargs)) if self.numargs is None: # this token is the number of positional arguments assert isinstance(token, int) @@ -261,10 +269,14 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): argpos = len(self.args) self.args.append(argvalue) if isinstance(argvalue, defer.Deferred): + # this may occur if the child is a gift which has not + # resolved yet. self.num_unreferenceable_children += 1 argvalue.addCallback(self.updateChild, argpos) argvalue.addErrback(self.explode) if ready_deferred: + if self.debug: + log.msg("%s.receiveChild got an unready posarg" % self) self.num_unready_children += 1 ready_deferred.addCallback(self.childReady) if len(self.args) < self.numargs: @@ -298,11 +310,13 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): argvalue.addCallback(self.updateChild, self.argname) argvalue.addErrback(self.explode) if ready_deferred: + if self.debug: + log.msg("%s.receiveChild got an unready kwarg" % self) self.num_unready_children += 1 ready_deferred.addCallback(self.childReady) self.argname = None return - + def updateChild(self, obj, which): # one of our arguments has just now become referenceable. Normal # types can't trigger this (since the arguments to a method form a @@ -311,6 +325,9 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): # RemoteReference, but for now all we get is a Deferred as a # placeholder. + if self.debug: + log.msg("%s.updateChild, [%s] became referenceable: %s" % + (self, which, obj)) if isinstance(which, int): self.args[which] = obj else: @@ -321,12 +338,22 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): def childReady(self, obj): self.num_unready_children -= 1 + if self.debug: + log.msg("%s.childReady, now %d left" % + (self, self.num_unready_children)) + log.msg(" obj=%s, args=%s, kwargs=%s" % + (obj, self.args, self.kwargs)) self.checkComplete() return obj def checkComplete(self): # this is called each time one of our children gets updated or # becomes ready (like when a Gift is finally resolved) + if self.debug: + log.msg("%s.checkComplete: %s %s %s args=%s kwargs=%s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children, self.args, self.kwargs)) + if not self.closed: return if self.num_unreferenceable_children: @@ -334,17 +361,25 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): if self.num_unready_children: return # yup, we're done. Notify anyone who is still waiting + if self.debug: + log.msg(" we are ready") for d in self.watchers: eventually(d.callback, self) del self.watchers def receiveClose(self): + if self.debug: + log.msg("%s.receiveClose: %s %s %s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children)) if (self.numargs is None or len(self.args) < self.numargs or self.argname is not None): raise BananaError("'arguments' sequence ended too early") self.closed = True self.watchers = [] + # we don't return a ready_deferred. Instead, the InboundDelivery + # object queries our isReady() method directly. return self, None def isReady(self): @@ -385,6 +420,8 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): class CallUnslicer(slicer.ScopedUnslicer): + debug = False + def start(self, count): # start=0:reqID, 1:objID, 2:methodname, 3: arguments self.stage = 0 @@ -436,7 +473,9 @@ class CallUnslicer(slicer.ScopedUnslicer): def receiveChild(self, token, ready_deferred=None): assert not isinstance(token, defer.Deferred) assert ready_deferred is None - #print "CallUnslicer.receiveChild [s%d]" % self.stage, repr(token) + if self.debug: + log.msg("%s.receiveChild [s%d]: %s" % + (self, self.stage, repr(token))) if self.stage == 0: # reqID # we don't yet know which reqID to send any failure to diff --git a/src/foolscap/foolscap/pb.py b/src/foolscap/foolscap/pb.py index 9f7cfb9d..735f1dbc 100644 --- a/src/foolscap/foolscap/pb.py +++ b/src/foolscap/foolscap/pb.py @@ -6,7 +6,7 @@ from twisted.internet import defer, protocol from twisted.application import service, strports from twisted.python import log -from foolscap import ipb, base32, negotiate, broker, observer +from foolscap import ipb, base32, negotiate, broker, observer, eventual from foolscap.referenceable import SturdyRef from foolscap.tokens import PBError, BananaError from foolscap.reconnector import Reconnector @@ -242,6 +242,8 @@ class Tub(service.MultiService): self._activeConnectors = [] self._allConnectorsAreFinished = observer.OneShotObserverList() + self._pending_getReferences = [] # list of (d, furl) pairs + def setOption(self, name, value): if name == "logLocalFailures": # log (with log.err) any exceptions that occur during the @@ -376,6 +378,15 @@ class Tub(service.MultiService): if not self.running and not self._activeConnectors: self._allConnectorsAreFinished.fire(self) + def startService(self): + service.MultiService.startService(self) + for d,sturdy in self._pending_getReferences: + d1 = eventual.fireEventually(sturdy) + d1.addCallback(self.getReference) + d1.addBoth(lambda res, d=d: d.callback(res)) + del self._pending_getReferences + for rc in self.reconnectors: + eventual.eventually(rc.startConnecting, self) def _tubsAreNotRestartable(self): raise RuntimeError("Sorry, but Tubs cannot be restarted.") @@ -386,6 +397,7 @@ class Tub(service.MultiService): # note that once you stopService a Tub, I cannot be restarted. (at # least this code is not designed to make that possible.. it might be # doable in the future). + assert self.running self.startService = self._tubsAreNotRestartable self.getReference = self._tubHasBeenShutDown self.connectTo = self._tubHasBeenShutDown @@ -523,8 +535,6 @@ class Tub(service.MultiService): @return: a Deferred that fires with the RemoteReference """ - assert self.running - if isinstance(sturdyOrURL, SturdyRef): sturdy = sturdyOrURL else: @@ -538,12 +548,21 @@ class Tub(service.MultiService): "we cannot handle encrypted PB-URLs like %s" % sturdy.getURL()) return defer.fail(e) + + if not self.running: + # queue their request for service once the Tub actually starts + log.msg("Tub.getReference(%s) queued until Tub.startService called" + % sturdy) + d = defer.Deferred() + self._pending_getReferences.append((d, sturdy)) + return d + name = sturdy.name d = self.getBrokerForTubRef(sturdy.getTubRef()) d.addCallback(lambda b: b.getYourReferenceByName(name)) return d - def connectTo(self, sturdyOrURL, cb, *args, **kwargs): + def connectTo(self, _sturdyOrURL, _cb, *args, **kwargs): """Establish (and maintain) a connection to a given PBURL. I establish a connection to the PBURL and run a callback to inform @@ -582,8 +601,12 @@ class Tub(service.MultiService): rc.stopConnecting() # later """ - assert self.running - rc = Reconnector(self, sturdyOrURL, cb, *args, **kwargs) + rc = Reconnector(_sturdyOrURL, _cb, args, kwargs) + if self.running: + rc.startConnecting(self) + else: + log.msg("Tub.connectTo(%s) queued until Tub.startService called" + % _sturdyOrURL) self.reconnectors.append(rc) return rc diff --git a/src/foolscap/foolscap/reconnector.py b/src/foolscap/foolscap/reconnector.py index d22ab3e8..eace104e 100644 --- a/src/foolscap/foolscap/reconnector.py +++ b/src/foolscap/foolscap/reconnector.py @@ -42,17 +42,17 @@ class Reconnector: jitter = 0.11962656492 # molar Planck constant times c, Joule meter/mole verbose = False - def __init__(self, tub, url, cb, *args, **kwargs): - self._tub = tub + def __init__(self, url, cb, args, kwargs): self._url = url self._active = False self._observer = (cb, args, kwargs) self._delay = self.initialDelay self._retries = 0 self._timer = None - self.startConnecting() + self._tub = None - def startConnecting(self): + def startConnecting(self, tub): + self._tub = tub if self.verbose: log.msg("Reconnector starting for %s" % self._url) self._active = True @@ -65,7 +65,8 @@ class Reconnector: if self._timer: self._timer.cancel() self._timer = False - self._tub._removeReconnector(self) + if self._tub: + self._tub._removeReconnector(self) def _connect(self): d = self._tub.getReference(self._url) diff --git a/src/foolscap/foolscap/referenceable.py b/src/foolscap/foolscap/referenceable.py index 283705a7..d940429f 100644 --- a/src/foolscap/foolscap/referenceable.py +++ b/src/foolscap/foolscap/referenceable.py @@ -630,8 +630,18 @@ class TheirReferenceUnslicer(slicer.LeafUnslicer): d.addBoth(self.ackGift) # we return a Deferred that will fire with the RemoteReference when # it becomes available. The RemoteReference is not even referenceable - # until then. - return d,None + # until then. In addition, we provide a ready_deferred, since any + # mutable container which holds the gift will be referenceable early + # but the message delivery must still wait for the getReference to + # complete. See to it that we fire the object deferred before we fire + # the ready_deferred. + obj_deferred, ready_deferred = defer.Deferred(), defer.Deferred() + def _ready(rref): + obj_deferred.callback(rref) + ready_deferred.callback(rref) + d.addCallback(_ready) + + return obj_deferred, ready_deferred def ackGift(self, rref): rb = self.broker.remote_broker @@ -727,26 +737,6 @@ class SturdyRef(Copyable, RemoteCopy): cmp(self.__class__, them.__class__) or cmp(self._distinguishers(), them._distinguishers())) - def asLiveRef(self): - """Return an object that can be sent over the wire and unserialized - as a live RemoteReference on the far end. Use this when you have a - SturdyRef and want to give someone a reference to its target, but - when you haven't bothered to acquire your own live reference to it.""" - - return _AsLiveRef(self) - -class _AsLiveRef: - implements(ipb.ISlicer) - - def __init__(self, sturdy): - self.target = sturdy - - def slice(self, streamable, banana): - yield 'their-reference' - yield giftID - yield self.target.getURL() - yield [] # interfacenames - class TubRef: """This is a little helper class which provides a comparable identifier diff --git a/src/foolscap/foolscap/remoteinterface.py b/src/foolscap/foolscap/remoteinterface.py index 46df3616..f3fbdc09 100644 --- a/src/foolscap/foolscap/remoteinterface.py +++ b/src/foolscap/foolscap/remoteinterface.py @@ -105,7 +105,8 @@ def getRemoteInterface(obj): if isinstance(i, RemoteInterfaceClass): if i not in ilist: ilist.append(i) - assert len(ilist) <= 1, "don't use multiple RemoteInterfaces! %s" % (obj,) + assert len(ilist) <= 1, ("don't use multiple RemoteInterfaces! %s uses %s" + % (obj, ilist)) if ilist: return ilist[0] return None @@ -199,7 +200,8 @@ class RemoteMethodSchema: raise InvalidRemoteInterface(why) if not names: typeList = [] - if len(names) != len(typeList): + # 'def foo(oops)' results in typeList==None + if typeList is None or len(names) != len(typeList): # TODO: relax this, use schema=Any for the args that don't have # default values. This would make: # def foo(a, b=int): return None @@ -361,9 +363,18 @@ class RemoteInterfaceConstraint(OpenerConstraint): associated with a remote Referenceable that implements the given RemoteInterface. If 'interface' is None, just assert that it is a RemoteReference at all. + + On the inbound side, this will only accept a suitably-implementing + RemoteReference, or a gift that resolves to such a RemoteReference. On + the outbound side, this will accept either a Referenceable or a + RemoteReference (which might be a your-reference or a their-reference). + + Sending your-references will result in the recipient getting a local + Referenceable, which will not pass the constraint. TODO: think about if + we want this behavior or not. """ - opentypes = [("my-reference",)] - # TODO: accept their-references too + + opentypes = [("my-reference",), ("their-reference",)] name = "RemoteInterfaceConstraint" def __init__(self, interface): @@ -387,7 +398,17 @@ class RemoteInterfaceConstraint(OpenerConstraint): % (obj, self.interface)) else: # this ought to be a Referenceable which implements the desired - # interface + # interface. Or, it might be a RemoteReference which points to + # one. + if ipb.IRemoteReference.providedBy(obj): + # it's a RemoteReference + if not self.interface: + return + iface = obj.tracker.interface + if not iface or iface != self.interface: + raise Violation("'%s' does not provide RemoteInterface %s" + % (obj, self.interface)) + return if not ipb.IReferenceable.providedBy(obj): # TODO: maybe distinguish between OnlyReferenceable and # Referenceable? which is more useful here? diff --git a/src/foolscap/foolscap/slicers/allslicers.py b/src/foolscap/foolscap/slicers/allslicers.py index aa05f177..afb3b470 100644 --- a/src/foolscap/foolscap/slicers/allslicers.py +++ b/src/foolscap/foolscap/slicers/allslicers.py @@ -10,7 +10,7 @@ from foolscap.slicers.unicode import UnicodeSlicer, UnicodeUnslicer from foolscap.slicers.list import ListSlicer, ListUnslicer from foolscap.slicers.tuple import TupleSlicer, TupleUnslicer from foolscap.slicers.set import SetSlicer, SetUnslicer -from foolscap.slicers.set import ImmutableSetSlicer, ImmutableSetUnslicer +from foolscap.slicers.set import FrozenSetSlicer, FrozenSetUnslicer #from foolscap.slicers.set import BuiltinSetSlicer from foolscap.slicers.dict import DictSlicer, DictUnslicer, OrderedDictSlicer from foolscap.slicers.vocab import ReplaceVocabSlicer, ReplaceVocabUnslicer @@ -26,7 +26,7 @@ unused = [ ListSlicer, ListUnslicer, TupleSlicer, TupleUnslicer, SetSlicer, SetUnslicer, - ImmutableSetSlicer, ImmutableSetUnslicer, + FrozenSetSlicer, FrozenSetUnslicer, #from foolscap.slicers.set import BuiltinSetSlicer DictSlicer, DictUnslicer, OrderedDictSlicer, ReplaceVocabSlicer, ReplaceVocabUnslicer, diff --git a/src/foolscap/foolscap/slicers/dict.py b/src/foolscap/foolscap/slicers/dict.py index 6cd30c29..23a69ca3 100644 --- a/src/foolscap/foolscap/slicers/dict.py +++ b/src/foolscap/foolscap/slicers/dict.py @@ -1,7 +1,7 @@ # -*- test-case-name: foolscap.test.test_banana -*- from twisted.python import log -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation, BananaError from foolscap.slicer import BaseSlicer, BaseUnslicer from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint @@ -35,6 +35,7 @@ class DictUnslicer(BaseUnslicer): self.d = {} self.protocol.setObject(count, self.d) self.key = None + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.maxKeys != None: @@ -72,8 +73,8 @@ class DictUnslicer(BaseUnslicer): self.d[key] = value def receiveChild(self, obj, ready_deferred=None): - assert not isinstance(obj, Deferred) - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if self.gettingKey: self.receiveKey(obj) else: @@ -102,7 +103,10 @@ class DictUnslicer(BaseUnslicer): self.d[self.key] = value # placeholder def receiveClose(self): - return self.d, None + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.d, ready_deferred def describe(self): if self.gettingKey: diff --git a/src/foolscap/foolscap/slicers/list.py b/src/foolscap/foolscap/slicers/list.py index 4e8a0db2..70ad55f8 100644 --- a/src/foolscap/foolscap/slicers/list.py +++ b/src/foolscap/foolscap/slicers/list.py @@ -1,7 +1,7 @@ # -*- test-case-name: foolscap.test.test_banana -*- from twisted.python import log -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation from foolscap.slicer import BaseSlicer, BaseUnslicer from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint @@ -35,8 +35,9 @@ class ListUnslicer(BaseUnslicer): self.list = [] self.count = count if self.debug: - print "%s[%d].start with %s" % (self, self.count, self.list) + log.msg("%s[%d].start with %s" % (self, self.count, self.list)) self.protocol.setObject(count, self.list) + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.maxLength != None and len(self.list) >= self.maxLength: @@ -65,15 +66,16 @@ class ListUnslicer(BaseUnslicer): def update(self, obj, index): # obj has already passed typechecking if self.debug: - print "%s[%d].update: [%d]=%s" % (self, self.count, index, obj) + log.msg("%s[%d].update: [%d]=%s" % (self, self.count, index, obj)) assert isinstance(index, int) self.list[index] = obj return obj def receiveChild(self, obj, ready_deferred=None): - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if self.debug: - print "%s[%d].receiveChild(%s)" % (self, self.count, obj) + log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj)) # obj could be a primitive type, a Deferred, or a complex type like # those returned from an InstanceUnslicer. However, the individual # object has already been through the schema validation process. The @@ -86,10 +88,12 @@ class ListUnslicer(BaseUnslicer): raise Violation("the list is full") if isinstance(obj, Deferred): if self.debug: - print " adding my update[%d] to %s" % (len(self.list), obj) + log.msg(" adding my update[%d] to %s" % (len(self.list), obj)) obj.addCallback(self.update, len(self.list)) obj.addErrback(self.printErr) - self.list.append("placeholder") + placeholder = "list placeholder for arg[%d], rd=%s" % \ + (len(self.list), ready_deferred) + self.list.append(placeholder) else: self.list.append(obj) @@ -99,7 +103,10 @@ class ListUnslicer(BaseUnslicer): log.err(why) def receiveClose(self): - return self.list, None + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.list, ready_deferred def describe(self): return "[%d]" % len(self.list) diff --git a/src/foolscap/foolscap/slicers/set.py b/src/foolscap/foolscap/slicers/set.py index 206507f2..f2afa09c 100644 --- a/src/foolscap/foolscap/slicers/set.py +++ b/src/foolscap/foolscap/slicers/set.py @@ -1,7 +1,11 @@ # -*- test-case-name: foolscap.test.test_banana -*- import sets -from foolscap.slicers.list import ListSlicer, ListUnslicer +from twisted.internet import defer +from twisted.python import log +from foolscap.slicers.list import ListSlicer +from foolscap.slicers.tuple import TupleUnslicer +from foolscap.slicer import BaseUnslicer from foolscap.tokens import Violation from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \ IConstraint @@ -9,34 +13,41 @@ from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \ class SetSlicer(ListSlicer): opentype = ("set",) trackReferences = True - slices = sets.Set + slices = set def sliceBody(self, streamable, banana): for i in self.obj: yield i -class ImmutableSetSlicer(SetSlicer): +class FrozenSetSlicer(SetSlicer): opentype = ("immutable-set",) trackReferences = False + slices = frozenset + +# python2.4 has a builtin 'set' type, which is mutable, and we require +# python2.4 or newer. Code which was written to be compatible with python2.3, +# however, may use the 'sets' module. We will serialize old sets.Set and +# sets.ImmutableSet the same as we serialize new set and frozenset. +# Unfortunately this means that these objects will be deserialized as modern +# 'set' and 'frozenset' objects, which are not entirely compatible. Therefore +# code that is compatible with python2.3 might not work with foolscap. + +class OldSetSlicer(SetSlicer): + slices = sets.Set +class OldImmutableSetSlicer(FrozenSetSlicer): slices = sets.ImmutableSet -have_builtin_set = False -try: - set - # python2.4 has a builtin 'set' type, which is mutable - have_builtin_set = True - class BuiltinSetSlicer(SetSlicer): - slices = set - class BuiltinFrozenSetSlicer(ImmutableSetSlicer): - slices = frozenset -except NameError: - # oh well, I guess we don't have 'set' +class _Placeholder: pass -class SetUnslicer(ListUnslicer): +class SetUnslicer(BaseUnslicer): + # this is a lot like a list, but sufficiently different to make it not + # worth subclassing opentype = ("set",) - def receiveClose(self): - return sets.Set(self.list), None + + debug = False + maxLength = None + itemConstraint = None def setConstraint(self, constraint): if isinstance(constraint, Any): @@ -45,10 +56,101 @@ class SetUnslicer(ListUnslicer): self.maxLength = constraint.maxLength self.itemConstraint = constraint.constraint -class ImmutableSetUnslicer(SetUnslicer): + def start(self, count): + #self.opener = foo # could replace it if we wanted to + self.set = set() + self.count = count + if self.debug: + log.msg("%s[%d].start with %s" % (self, self.count, self.set)) + self.protocol.setObject(count, self.set) + self._ready_deferreds = [] + + def checkToken(self, typebyte, size): + if self.maxLength != None and len(self.set) >= self.maxLength: + # list is full, no more tokens accepted + # this is hit if the max+1 item is a primitive type + raise Violation("the set is full") + if self.itemConstraint: + self.itemConstraint.checkToken(typebyte, size) + + def doOpen(self, opentype): + # decide whether the given object type is acceptable here. Raise a + # Violation exception if not, otherwise give it to our opener (which + # will normally be the RootUnslicer). Apply a constraint to the new + # unslicer. + if self.maxLength != None and len(self.set) >= self.maxLength: + # this is hit if the max+1 item is a non-primitive type + raise Violation("the set is full") + if self.itemConstraint: + self.itemConstraint.checkOpentype(opentype) + unslicer = self.open(opentype) + if unslicer: + if self.itemConstraint: + unslicer.setConstraint(self.itemConstraint) + return unslicer + + def update(self, obj, placeholder): + # obj has already passed typechecking + if self.debug: + log.msg("%s[%d].update: [%s]=%s" % (self, self.count, + placeholder, obj)) + self.set.remove(placeholder) + self.set.add(obj) + return obj + + def receiveChild(self, obj, ready_deferred=None): + if ready_deferred: + self._ready_deferreds.append(ready_deferred) + if self.debug: + log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj)) + # obj could be a primitive type, a Deferred, or a complex type like + # those returned from an InstanceUnslicer. However, the individual + # object has already been through the schema validation process. The + # only remaining question is whether the larger schema will accept + # it. + if self.maxLength != None and len(self.set) >= self.maxLength: + # this is redundant + # (if it were a non-primitive one, it would be caught in doOpen) + # (if it were a primitive one, it would be caught in checkToken) + raise Violation("the set is full") + if isinstance(obj, defer.Deferred): + if self.debug: + log.msg(" adding my update[%d] to %s" % (len(self.set), obj)) + # note: the placeholder isn't strictly necessary, but it will + # help debugging to see a _Placeholder sitting in the set when it + # shouldn't rather than seeing a set that is smaller than it + # ought to be. If a remote method ever sees a _Placeholder, then + # something inside Foolscap has broken. + placeholder = _Placeholder() + obj.addCallback(self.update, placeholder) + obj.addErrback(self.printErr) + self.set.add(placeholder) + else: + self.set.add(obj) + + def printErr(self, why): + print "ERR!" + print why.getBriefTraceback() + log.err(why) + + def receiveClose(self): + ready_deferred = None + if self._ready_deferreds: + ready_deferred = defer.DeferredList(self._ready_deferreds) + return self.set, ready_deferred + +class FrozenSetUnslicer(TupleUnslicer): opentype = ("immutable-set",) + def receiveClose(self): - return sets.ImmutableSet(self.list), None + obj_or_deferred, ready_deferred = TupleUnslicer.receiveClose(self) + if isinstance(obj_or_deferred, defer.Deferred): + def _convert(the_tuple): + return frozenset(the_tuple) + obj_or_deferred.addCallback(_convert) + else: + obj_or_deferred = frozenset(obj_or_deferred) + return obj_or_deferred, ready_deferred class SetConstraint(OpenerConstraint): @@ -64,12 +166,8 @@ class SetConstraint(OpenerConstraint): opentypes = [("set",), ("immutable-set",)] name = "SetConstraint" - if have_builtin_set: - mutable_set_types = (set, sets.Set) - immutable_set_types = (frozenset, sets.ImmutableSet) - else: - mutable_set_types = (sets.Set,) - immutable_set_types = (sets.ImmutableSet,) + mutable_set_types = (set, sets.Set) + immutable_set_types = (frozenset, sets.ImmutableSet) all_set_types = mutable_set_types + immutable_set_types def __init__(self, constraint, maxLength=30, mutable=None): diff --git a/src/foolscap/foolscap/slicers/tuple.py b/src/foolscap/foolscap/slicers/tuple.py index f7ea5657..6469f822 100644 --- a/src/foolscap/foolscap/slicers/tuple.py +++ b/src/foolscap/foolscap/slicers/tuple.py @@ -1,6 +1,6 @@ # -*- test-case-name: foolscap.test.test_banana -*- -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation from foolscap.slicer import BaseUnslicer from foolscap.slicers.list import ListSlicer @@ -34,6 +34,7 @@ class TupleUnslicer(BaseUnslicer): self.finished = False self.deferred = Deferred() self.protocol.setObject(count, self.deferred) + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.constraints == None: @@ -64,7 +65,8 @@ class TupleUnslicer(BaseUnslicer): return obj def receiveChild(self, obj, ready_deferred=None): - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if isinstance(obj, Deferred): obj.addCallback(self.update, len(self.list)) obj.addErrback(self.explode) @@ -81,20 +83,39 @@ class TupleUnslicer(BaseUnslicer): # not finished yet, we'll fire our Deferred when we are if self.debug: print " not finished yet" - return self.deferred, None + return + # list is now complete. We can finish. + return self.complete() + + def complete(self): + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + t = tuple(self.list) if self.debug: print " finished! tuple:%s{%s}" % (t, id(t)) self.protocol.setObject(self.count, t) self.deferred.callback(t) - return t, None + return t, ready_deferred def receiveClose(self): if self.debug: print "%s[%d].receiveClose" % (self, self.count) self.finished = 1 - return self.checkComplete() + + if self.num_unreferenceable_children: + # not finished yet, we'll fire our Deferred when we are + if self.debug: + print " not finished yet" + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.deferred, ready_deferred + + # the list is already complete + return self.complete() def describe(self): return "[%d]" % len(self.list) diff --git a/src/foolscap/foolscap/test/test_banana.py b/src/foolscap/foolscap/test/test_banana.py index 5951cafe..2c95c3f7 100644 --- a/src/foolscap/foolscap/test/test_banana.py +++ b/src/foolscap/foolscap/test/test_banana.py @@ -1597,18 +1597,13 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase): def test_tuple(self): return self.looptest((1,2)) def test_set(self): - d = self.looptest(sets.Set([1,2])) - d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2]))) - # verify the python2.4 builtin 'set' type, which is mutable - try: - set - have_set = True - except NameError: - have_set = False - if have_set: - # we serialize builtin 'set' as a regular mutable sets.Set - d.addCallback(lambda res: - self.looptest(set([1,2]), sets.Set([1,2]))) + d = self.looptest(set([1,2])) + d.addCallback(lambda res: self.looptest(frozenset([1,2]))) + # and verify that old sets turn into modern ones, which is + # unfortunate but at least consistent + d.addCallback(lambda res: self.looptest(sets.Set([1,2]), set([1,2]))) + d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2]), + frozenset([1,2]))) return d def test_bool(self): @@ -1709,15 +1704,71 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase): self.assertIdentical(z[0]['list'], z[1]) self.assertIdentical(z[0]['list'][0], z) - def testMoreReferences(self): + def test_cycles_1(self): + # a list that contains a tuple that can't be referenced yet a = [] - t = (a,) - t2 = (t,) + t1 = (a,) + t2 = (t1,) a.append(t2) - d = self.loop(t) + d = self.loop(t1) d.addCallback(lambda z: self.assertIdentical(z[0][0][0], z)) return d + def test_cycles_2(self): + # a dict that contains a tuple that can't be referenced yet. + a = {} + t1 = (a,) + t2 = (t1,) + a['foo'] = t2 + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(z[0]['foo'][0], z)) + return d + + def test_cycles_3(self): + # sets seem to be transitively immutable: any mutable contents would + # be unhashable, and sets can only contain hashable objects. + # Therefore sets cannot participate in cycles the way that tuples + # can. + + # a set that contains a tuple that can't be referenced yet. You can't + # actually create this in python, because you can only create a set + # out of hashable objects, and sets aren't hashable, and a tuple that + # contains a set is not hashable. + a = set() + t1 = (a,) + t2 = (t1,) + a.add(t2) + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(list(z[0])[0][0], z)) + + # a list that contains a frozenset that can't be referenced yet + a = [] + t1 = frozenset([a]) + t2 = frozenset([t1]) + a.append(t2) + d = self.loop(t1) + d.addCallback(lambda z: + self.assertIdentical(list(list(z)[0][0])[0], z)) + + # a dict that contains a frozenset that can't be referenced yet. + a = {} + t1 = frozenset([a]) + t2 = frozenset([t1]) + a['foo'] = t2 + d = self.loop(t1) + d.addCallback(lambda z: + self.assertIdentical(list(list(z)[0]['foo'])[0], z)) + + # a set that contains a frozenset that can't be referenced yet. + a = set() + t1 = frozenset([a]) + t2 = frozenset([t1]) + a.add(t2) + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(list(list(list(z)[0])[0])[0], z)) + return d + del test_cycles_3 + class VocabTest1(unittest.TestCase): diff --git a/src/foolscap/foolscap/test/test_call.py b/src/foolscap/foolscap/test/test_call.py index 04dc4abc..79e19beb 100644 --- a/src/foolscap/foolscap/test/test_call.py +++ b/src/foolscap/foolscap/test/test_call.py @@ -9,7 +9,6 @@ if False: log.startLogging(sys.stderr) from twisted.python import failure, log -from twisted.internet import reactor, defer from twisted.trial import unittest from twisted.internet.main import CONNECTION_LOST @@ -36,7 +35,6 @@ class TestCall(TargetMixin, unittest.TestCase): d.addCallback(lambda res: self.failUnlessEqual(target.calls, [(1,2)])) d.addCallback(self._testCall1_1, rr) return d - testCall1.timeout = 3 def _testCall1_1(self, res, rr): # the caller still holds the RemoteReference self.failUnless(self.callingBroker.yourReferenceByCLID.has_key(1)) @@ -46,10 +44,14 @@ class TestCall(TargetMixin, unittest.TestCase): # the targetBroker so *they* can forget about it. del rr # this fires a DecRef gc.collect() # make sure + # we need to give it a moment to deliver the DecRef message and act - # on it - d = defer.Deferred() - reactor.callLater(0.1, d.callback, None) + # on it. Poll until the caller has received it. + def _check(): + if self.callingBroker.yourReferenceByCLID.has_key(1): + return False + return True + d = self.poll(_check) d.addCallback(self._testCall1_2) return d def _testCall1_2(self, res): diff --git a/src/foolscap/foolscap/test/test_gifts.py b/src/foolscap/foolscap/test/test_gifts.py index 3775e657..db6dfd78 100644 --- a/src/foolscap/foolscap/test/test_gifts.py +++ b/src/foolscap/foolscap/test/test_gifts.py @@ -1,10 +1,11 @@ +from zope.interface import implements from twisted.trial import unittest from twisted.internet import defer from twisted.internet.error import ConnectionDone, ConnectionLost -from foolscap import Tub, UnauthenticatedTub +from foolscap import Tub, UnauthenticatedTub, RemoteInterface, Referenceable from foolscap.referenceable import RemoteReference -from foolscap.test.common import HelperTarget +from foolscap.test.common import HelperTarget, RIHelper from foolscap.eventual import flushEventualQueue crypto_available = False @@ -24,6 +25,19 @@ def ignoreConnectionDone(f): f.trap(ConnectionDone, ConnectionLost) return None +class RIConstrainedHelper(RemoteInterface): + def set(obj=RIHelper): return None + + +class ConstrainedHelper(Referenceable): + implements(RIConstrainedHelper) + + def __init__(self, name="unnamed"): + self.name = name + + def remote_set(self, obj): + self.obj = obj + class Gifts(unittest.TestCase): # Here we test the three-party introduction process as depicted in the # classic Granovetter diagram. Alice has a reference to Bob and another @@ -52,9 +66,15 @@ class Gifts(unittest.TestCase): self.bob_url = self.tubB.registerReference(self.bob) self.carol = HelperTarget("carol") self.carol_url = self.tubC.registerReference(self.carol) - self.cindy = HelperTarget("cindy") # cindy is Carol's little sister. She doesn't have a phone, but # Carol might talk about her anyway. + self.cindy = HelperTarget("cindy") + # more sisters. Alice knows them, and she introduces Bob to them. + self.charlene = HelperTarget("charlene") + self.christine = HelperTarget("christine") + self.clarisse = HelperTarget("clarisse") + self.colette = HelperTarget("colette") + self.courtney = HelperTarget("courtney") def createInitialReferences(self): # we must start by giving Alice a reference to both Bob and Carol. @@ -73,6 +93,46 @@ class Gifts(unittest.TestCase): d.addCallback(_aliceGotCarol) return d + def createMoreReferences(self): + # give Alice references to Carol's sisters + dl = [] + + url = self.tubC.registerReference(self.charlene) + d = self.tubA.getReference(url) + def _got_charlene(rref): + self.acharlene = rref + d.addCallback(_got_charlene) + dl.append(d) + + url = self.tubC.registerReference(self.christine) + d = self.tubA.getReference(url) + def _got_christine(rref): + self.achristine = rref + d.addCallback(_got_christine) + dl.append(d) + + url = self.tubC.registerReference(self.clarisse) + d = self.tubA.getReference(url) + def _got_clarisse(rref): + self.aclarisse = rref + d.addCallback(_got_clarisse) + dl.append(d) + + url = self.tubC.registerReference(self.colette) + d = self.tubA.getReference(url) + def _got_colette(rref): + self.acolette = rref + d.addCallback(_got_colette) + dl.append(d) + + url = self.tubC.registerReference(self.courtney) + d = self.tubA.getReference(url) + def _got_courtney(rref): + self.acourtney = rref + d.addCallback(_got_courtney) + dl.append(d) + return defer.DeferredList(dl) + def testGift(self): #defer.setDebugging(True) self.createCharacters() @@ -189,6 +249,76 @@ class Gifts(unittest.TestCase): d.addCallback(_checkBob) return d + def testContainers(self): + self.createCharacters() + self.bob.calls = [] + d = self.createInitialReferences() + d.addCallback(lambda res: self.createMoreReferences()) + def _introduce(res): + # we send several messages to Bob, each of which has a container + # with a gift inside it. This exercises the ready_deferred + # handling inside containers. + dl = [] + cr = self.abob.callRemote + dl.append(cr("append", set([self.acharlene]))) + dl.append(cr("append", frozenset([self.achristine]))) + dl.append(cr("append", [self.aclarisse])) + dl.append(cr("append", obj=(self.acolette,))) + dl.append(cr("append", {'a': self.acourtney})) + # TODO: pass a gift as an attribute of a Copyable + return defer.DeferredList(dl) + d.addCallback(_introduce) + def _checkBob(res): + # this runs after all three messages have been acked by Bob + self.failUnlessEqual(len(self.bob.calls), 5) + + bcharlene = self.bob.calls.pop(0) + self.failUnless(isinstance(bcharlene, set)) + self.failUnlessEqual(len(bcharlene), 1) + self.failUnless(isinstance(list(bcharlene)[0], RemoteReference)) + + bchristine = self.bob.calls.pop(0) + self.failUnless(isinstance(bchristine, frozenset)) + self.failUnlessEqual(len(bchristine), 1) + self.failUnless(isinstance(list(bchristine)[0], RemoteReference)) + + bclarisse = self.bob.calls.pop(0) + self.failUnless(isinstance(bclarisse, list)) + self.failUnlessEqual(len(bclarisse), 1) + self.failUnless(isinstance(bclarisse[0], RemoteReference)) + + bcolette = self.bob.calls.pop(0) + self.failUnless(isinstance(bcolette, tuple)) + self.failUnlessEqual(len(bcolette), 1) + self.failUnless(isinstance(bcolette[0], RemoteReference)) + + bcourtney = self.bob.calls.pop(0) + self.failUnless(isinstance(bcourtney, dict)) + self.failUnlessEqual(len(bcourtney), 1) + self.failUnless(isinstance(bcourtney['a'], RemoteReference)) + + d.addCallback(_checkBob) + return d + + def create_constrained_characters(self): + self.alice = HelperTarget("alice") + self.bob = ConstrainedHelper("bob") + self.bob_url = self.tubB.registerReference(self.bob) + self.carol = HelperTarget("carol") + self.carol_url = self.tubC.registerReference(self.carol) + + def test_constraint(self): + self.create_constrained_characters() + self.bob.calls = [] + d = self.createInitialReferences() + def _introduce(res): + return self.abob.callRemote("set", self.acarol) + d.addCallback(_introduce) + def _checkBob(res): + self.failUnless(isinstance(self.bob.obj, RemoteReference)) + d.addCallback(_checkBob) + return d + # this was used to alice's reference to carol (self.acarol) appeared in # alice's gift table at the right time, to make sure that the # RemoteReference is kept alive while the gift is in transit. The whole diff --git a/src/foolscap/foolscap/test/test_schema.py b/src/foolscap/foolscap/test/test_schema.py index 671b7a73..0c052e78 100644 --- a/src/foolscap/foolscap/test/test_schema.py +++ b/src/foolscap/foolscap/test/test_schema.py @@ -2,7 +2,7 @@ import sets, re from twisted.trial import unittest from foolscap import schema, copyable -from foolscap.tokens import Violation +from foolscap.tokens import Violation, InvalidRemoteInterface from foolscap.constraint import IConstraint from foolscap.remoteinterface import RemoteMethodSchema, \ RemoteInterfaceConstraint, LocalInterfaceConstraint @@ -474,6 +474,13 @@ class Arguments(unittest.TestCase): self.failUnlessRaises(schema.Violation, r.checkResults, 12, False) + def test_bad_arguments(self): + def foo(nodefault): return str + self.failUnlessRaises(InvalidRemoteInterface, + RemoteMethodSchema, method=foo) + def bar(nodefault, a=int): return str + self.failUnlessRaises(InvalidRemoteInterface, + RemoteMethodSchema, method=bar) class Interfaces(unittest.TestCase): @@ -520,9 +527,11 @@ class Interfaces(unittest.TestCase): interfaceName = common.RIHelper.__remote_name__ tracker = RemoteReferenceTracker(parent, clid, url, interfaceName) rr = RemoteReference(tracker) + c1 = RemoteInterfaceConstraint(common.RIHelper) - c2 = RemoteInterfaceConstraint(common.RIMyTarget) self.check_inbound(rr, c1) - self.violates_outbound(rr, c1) + self.check_outbound(rr, c1) # gift + + c2 = RemoteInterfaceConstraint(common.RIMyTarget) self.violates_inbound(rr, c2) self.violates_outbound(rr, c2) diff --git a/src/foolscap/foolscap/test/test_tub.py b/src/foolscap/foolscap/test/test_tub.py index 4b0d31b1..8f36a524 100644 --- a/src/foolscap/foolscap/test/test_tub.py +++ b/src/foolscap/foolscap/test/test_tub.py @@ -2,6 +2,7 @@ import os.path from twisted.trial import unittest +from twisted.internet import defer crypto_available = False try: @@ -10,7 +11,16 @@ try: except ImportError: pass -from foolscap import Tub +from foolscap import Tub, UnauthenticatedTub +from foolscap.referenceable import RemoteReference +from foolscap.eventual import eventually, flushEventualQueue +from foolscap.test.common import HelperTarget, TargetMixin + +# we use authenticated tubs if possible. If crypto is not available, fall +# back to unauthenticated ones +GoodEnoughTub = UnauthenticatedTub +if crypto_available: + GoodEnoughTub = Tub class TestCertFile(unittest.TestCase): def test_generate(self): @@ -38,3 +48,72 @@ class TestCertFile(unittest.TestCase): if not crypto_available: del TestCertFile + +class QueuedStartup(TargetMixin, unittest.TestCase): + # calling getReference and connectTo before the Tub has started should + # put off network activity until the Tub is started. + + def setUp(self): + TargetMixin.setUp(self) + self.tubB = GoodEnoughTub() + self.services = [self.tubB] + for s in self.services: + s.startService() + l = s.listenOn("tcp:0:interface=127.0.0.1") + s.setLocation("127.0.0.1:%d" % l.getPortnum()) + + self.barry = HelperTarget("barry") + self.barry_url = self.tubB.registerReference(self.barry) + + self.bill = HelperTarget("bill") + self.bill_url = self.tubB.registerReference(self.bill) + + self.bob = HelperTarget("bob") + self.bob_url = self.tubB.registerReference(self.bob) + + def tearDown(self): + d = TargetMixin.tearDown(self) + def _more(res): + return defer.DeferredList([s.stopService() for s in self.services]) + d.addCallback(_more) + d.addCallback(flushEventualQueue) + return d + + def test_queued_getref(self): + t1 = GoodEnoughTub() + d1 = t1.getReference(self.barry_url) + d2 = t1.getReference(self.bill_url) + def _check(res): + ((barry_success, barry_rref), + (bill_success, bill_rref)) = res + self.failUnless(barry_success) + self.failUnless(bill_success) + self.failUnless(isinstance(barry_rref, RemoteReference)) + self.failUnless(isinstance(bill_rref, RemoteReference)) + self.failIf(barry_rref == bill_success) + dl = defer.DeferredList([d1, d2]) + dl.addCallback(_check) + self.services.append(t1) + eventually(t1.startService) + return dl + + def test_queued_reconnector(self): + t1 = GoodEnoughTub() + bill_connections = [] + barry_connections = [] + t1.connectTo(self.bill_url, bill_connections.append) + t1.connectTo(self.barry_url, barry_connections.append) + def _check(): + if len(bill_connections) >= 1 and len(barry_connections) >= 1: + return True + return False + d = self.poll(_check) + def _validate(res): + self.failUnless(isinstance(bill_connections[0], RemoteReference)) + self.failUnless(isinstance(barry_connections[0], RemoteReference)) + self.failIf(bill_connections[0] == barry_connections[0]) + d.addCallback(_validate) + self.services.append(t1) + eventually(t1.startService) + return d + diff --git a/src/foolscap/misc/dapper/debian/changelog b/src/foolscap/misc/dapper/debian/changelog index f3331be3..9699a6df 100644 --- a/src/foolscap/misc/dapper/debian/changelog +++ b/src/foolscap/misc/dapper/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:11 -0700 + -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/dapper/debian/rules b/src/foolscap/misc/dapper/debian/rules index eb2284d2..8ee04543 100644 --- a/src/foolscap/misc/dapper/debian/rules +++ b/src/foolscap/misc/dapper/debian/rules @@ -41,7 +41,7 @@ binary-indep: build install dh_testdir dh_testroot dh_installdocs -i -A NEWS README - dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications dh_installchangelogs -i dh_compress -i -X.py dh_fixperms diff --git a/src/foolscap/misc/edgy/debian/changelog b/src/foolscap/misc/edgy/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/edgy/debian/changelog +++ b/src/foolscap/misc/edgy/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/edgy/debian/rules b/src/foolscap/misc/edgy/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/edgy/debian/rules +++ b/src/foolscap/misc/edgy/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/feisty/debian/changelog b/src/foolscap/misc/feisty/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/feisty/debian/changelog +++ b/src/foolscap/misc/feisty/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/feisty/debian/rules b/src/foolscap/misc/feisty/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/feisty/debian/rules +++ b/src/foolscap/misc/feisty/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/sarge/debian/changelog b/src/foolscap/misc/sarge/debian/changelog index f3331be3..9699a6df 100644 --- a/src/foolscap/misc/sarge/debian/changelog +++ b/src/foolscap/misc/sarge/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:11 -0700 + -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/sarge/debian/rules b/src/foolscap/misc/sarge/debian/rules index eb2284d2..8ee04543 100644 --- a/src/foolscap/misc/sarge/debian/rules +++ b/src/foolscap/misc/sarge/debian/rules @@ -41,7 +41,7 @@ binary-indep: build install dh_testdir dh_testroot dh_installdocs -i -A NEWS README - dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications dh_installchangelogs -i dh_compress -i -X.py dh_fixperms diff --git a/src/foolscap/misc/sid/debian/changelog b/src/foolscap/misc/sid/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/sid/debian/changelog +++ b/src/foolscap/misc/sid/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner <warner@lothar.com> Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner <warner@lothar.com> Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner <warner@lothar.com> Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner <warner@lothar.com> Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner <warner@lothar.com> Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/sid/debian/rules b/src/foolscap/misc/sid/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/sid/debian/rules +++ b/src/foolscap/misc/sid/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/testutils/figleaf.py b/src/foolscap/misc/testutils/figleaf.py index a24d4fab..03d8c08d 100644 --- a/src/foolscap/misc/testutils/figleaf.py +++ b/src/foolscap/misc/testutils/figleaf.py @@ -168,7 +168,6 @@ class CodeTracer: Start recording. """ if not self.started: - self.LOG = open("/tmp/flog.out", "w") self.started = True sys.settrace(self.g)