# Copyright 2007 Kevin Reid, under the terms of the MIT X license # found at http://www.opensource.org/licenses/mit-license.html ................ ? pragma.syntax("0.9") ? pragma.enable("accumulator") ? def makeFlexMap := ; null ? def deSubgraphKit := ; null ? def makeCycleBreaker := ; null ? def Throwable := ; null ? def once := ; null ? def makeVine := ; null ? def traceMessages := ; null In this file, we test the implementation of the CapTP operations and associated state. ? def makeCapTPConnection := # value: ? def makeSwissTable := # value: ? def nonceLocatorIndex := 0; null ? def nullSwissNumber := 0; null Test framework: An mock finalization interface, with operations to trigger finalization of obects created in a particular time period: ? def finalizers := [].asMap().diverge() > var fIndex := 0 > def pseudoWhenGarbage(ref, reactor) { > #traceln(`Adding finalizer for $ref -> $reactor`) > finalizers[fIndex += 1] := [ref, reactor] > } > def clearFinalizers() { finalizers.removeAll() } > def runFinalizers() { > for i => [_, reactor] in finalizers { > #traceln(`Running $reactor`) > reactor <- () > finalizers.removeKey(i) > } > } > null Objects to represent the descriptors used by CapTP for connection-spanning references: ? def descs { > match [v, a] { > def generic { > to __printOn(out :TextWriter) { > out.print(v, "Desc") > a.printOn("(", ", ", ")", out) > } > to __optUncall() { return [descs, v, a] } > } > } > }; null LocatorUnum presences: ? def locatorUnumLocal {} > def locatorUnumRemote {} > null Data-E setup: ? def testGraphRecognizer := { > def scope := deSubgraphKit.getDefaultScope() | [ > "CapTP_1_descs" => descs, > "CapTP_1_locatorUnum" => locatorUnumRemote, > => Ref, > => ] > deSubgraphKit.makeRecognizer(null, makeCycleBreaker.byInverting(scope)) > }; null buildIn takes an object in our test environment, containing objects made by 'descs' above, and transforms them as a CapTP wire protocol unserializer would. ? def buildIn(obj) { > return once(fn b { testGraphRecognizer.recognize(obj, b) }) > }; null Mock other-connections-of-this-comm-system for 3-vat introductions: ? def [otherConnProxySealer, otherConnProxyUnsealer] := ("otherConnProxy") > def otherConnProxyBrand := otherConnProxyUnsealer.getBrand() > null ? def otherPromiseGiftTable := (pseudoWhenGarbage, def notNonceLocator {to ignore(_) {}}) > def otherNearGiftTable := (pseudoWhenGarbage) > null ? def hub { > to amplifyFor3Desc(specimen, recipID) { > if (otherConnProxyUnsealer.amplify(specimen) =~ [wantsRecip]) { > return wantsRecip(recipID) > } > } > to isOurProxy(specimen) { > return otherConnProxyUnsealer.amplify(specimen) =~ [_] > } > to get3DescBrand() { return otherConnProxyBrand } > to get3DescSealer() { return otherConnProxySealer } > to get(searchPath, vatID) { > println(`hub.get($searchPath, ${E.toQuote(vatID)})`) > return def otherVat { > to nonceLocator() { > return def otherNonceLocator { > match msg { println(`.$msg`); `${msg[0]} result` } > } > } > to getNearGiftTable() { return otherNearGiftTable } > to getPromiseGiftTable() { return otherPromiseGiftTable } > match msg { println(`.$msg`) } > } > } > match msg { println(`hub.$msg`) } > }; null To make the tests' use of indexes and swiss numbers independent of each other, we set up a new connection repeatedly using this function. > def makeTestConnection() { Deterministic mock RNG. Its output will mostly appear in these tests hashed: 10000001.cryptoHash() == 382064925828890872342377433413917253333972024416 10000002.cryptoHash() == 1033912670282953026478972818820010722176128073325 10000003.cryptoHash() == 47255196895205160731987244561159478631887357377 > var i := 10000000 > def pseudoEntropy { > to nextSwiss() { return (i += 1) } > } > > def swissTable := makeSwissTable(pseudoEntropy, makeFlexMap, makeFlexMap) > > def [r, outgoingConnection, peerConnection] := makeCapTPConnection( > traceMessages(stdout.println, def sink { match _ {} }), > swissTable, > locatorUnumLocal, > pseudoWhenGarbage, > hub, > "tLocalVatID", > ["tRemoteSearchPath"], > "tRemoteVatID") > return [swissTable, r, outgoingConnection, peerConnection] > }; null --- Incoming DeliverOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] At this point, the only thing the remote side can possibly do is close the connection or talk to the NonceLocator, which is at the well-known position 0. We'll ask it for null, which has the well-known swiss number 0. The index provided is negative because it is chosen by the remote side. The 2-cycle delay on the redirector reports is because there is first a send of answer <- __whenMoreResolved(redirector) followed by the __whenMoreResolved's response send. ? receiver.Deliver(-1, > buildIn(descs.NewFar(1, 81479683)), > nonceLocatorIndex, > "lookupSwiss", > buildIn([nullSwissNumber, null])) ? # stdout: DeliverOnly(1, "run", [null]) # null should now be present in the connection's answers table at -1. ? receiver.Deliver(-2, > buildIn(descs.Import(1)), > -1, > "__getAllegedType", > buildIn([])) ? # stdout: DeliverOnly(1, "run", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # ? receiver.Deliver(-3, > buildIn(descs.Import(1)), > -2, > "getFQName", > buildIn([])) ? # stdout: DeliverOnly(1, "run", ["org.cubik.cle.prim.null"]) # Positive/zero answer index is an error: ? receiver.Deliver(0, buildIn(descs.Import(1)), -2, "getFQName", buildIn([])) # problem: 0 is not in the region -2147483648..!0 ? receiver.Deliver(1, buildIn(descs.Import(1)), -2, "getFQName", buildIn([])) # problem: 1 is not in the region -2147483648..!0 Missing recipient: ? receiver.Deliver(-1, buildIn(descs.Import(1)), -999, "getFQName", buildIn([])) # problem: 999 not alloced in AnswersTable__1 --- Incoming DeliverOnlyOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] 2-cycle delay on this test because the reactor invocation is itself a send. Delivery to the NonceLocator: ? receiver.DeliverOnly(0, > "__whenMoreResolved", > buildIn([descs.NewFar(1, 329849023489032)])) ? # stdout: DeliverOnly(1, "run", [ImportDesc(0)]) # Delivery to an answer, creating the answer first: ? receiver.Deliver(-1, buildIn(descs.NewFar(2, 78897982443232)), > 0, "__respondsTo", buildIn(["ignore", 1])) ? # stdout: DeliverOnly(2, "run", [true]) # ? receiver.DeliverOnly(-1, > "__whenMoreResolved", > buildIn([descs.Import(1)])) ? # stdout: DeliverOnly(1, "run", [true]) # Delivery to an export: ? E.sendOnly(outgoingConnection.nonceLocator(), "export", [def exported(x) {println(x)}]) ? # stdout: DeliverOnly(0, "export", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # ? receiver.DeliverOnly(1, > "run", > buildIn(["foo"])) # stdout: foo # --- Incoming GCExportOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] To test GCExportOp, we must first export something. ? E.sendOnly(outgoingConnection.nonceLocator(), "exportTest", [def exported(x) {println(x)}]) ? # stdout: DeliverOnly(0, "exportTest", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # wireDelta of 0 should have no effect. ? receiver.GCExport(1, 0) ? receiver.DeliverOnly(1, "run", buildIn(["still here"])) # stdout: still here # wireDelta of 1 should delete the export. ? receiver.GCExport(1, 1) ? receiver.DeliverOnly(1, "run", buildIn(["gone"])) # problem: 1 not alloced in ExportsTable__1 A reference is collected once the sum of wireDeltas in all received GCExport messages exceeds the number of times it is exported. Here's a multiple export: ? E.sendOnly(outgoingConnection.nonceLocator(), "exportTest2a", [exported]) ? # stdout: DeliverOnly(0, "exportTest2a", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # (The NewFarDesc's index is the same because 1 is at the head of the exports table's free list, not because of any deliberate relationship.) ? E.sendOnly(outgoingConnection.nonceLocator(), "exportTest2b", [exported]) > E.sendOnly(outgoingConnection.nonceLocator(), "exportTest2c", [exported]) ? # stdout: DeliverOnly(0, "exportTest2b", [ImportDesc(1)]) # DeliverOnly(0, "exportTest2c", [ImportDesc(1)]) # wireDelta of 1 should have no effect (export count now 2). ? receiver.GCExport(1, 1) ? receiver.DeliverOnly(1, "run", buildIn(["still here #2"])) # stdout: still here #2 # Additional wireDelta of 2 should balance the three previous references. ? receiver.GCExport(1, 2) ? receiver.DeliverOnly(1, "run", buildIn(["gone #2"])) # problem: 1 not alloced in ExportsTable__1 --- Incoming GCAnswerOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] Create an answer. ? receiver.Deliver(-1, buildIn(descs.Incoming(0)), 0, "__respondsTo", buildIn(["ignore", 1])) Deliveries to the answer are possible now: ? receiver.DeliverOnly(-1, > "__whenMoreResolved", > buildIn([descs.NewFar(1, 3124570689423516780)])) ? # stdout: DeliverOnly(1, "run", [true]) # but not after GCAnswerOp: ? receiver.GCAnswer(-1) ? receiver.DeliverOnly(-1, > "__whenMoreResolved", > buildIn([descs.Import(1)])) # problem: 1 not alloced in AnswersTable__1 --- Incoming ShutdownOp --- XXX do this (shut down connection iff no more messages than the specified count have been sent by the recipient). Must not be visible to code. Note that (IRC log from July 28, 2007) The funny case is messages to be sent to zero when a shutdown might happen These need to be buffered, so that if a shutdown does happen, that can be resent transparently on a newly formed connection. ow This is much of why it was never fully implemented. whereas in the current impls, it's essentially a connection-specific always-live-ref, yes? GCing connections seems to be hard yes --- Incoming TerminatedOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] There are several cases to check the behavior of: Imported far ref: ? def outgoingFar := outgoingConnection.nonceLocator() <- getTerminatedTestRef1() # value: ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "getTerminatedTestRef1", []) # ? receiver.DeliverOnly(1, "run", buildIn([descs.NewFar(1, 1234)])) Question (promise resulting from DeliverOp): ? def outgoingQuestion := outgoingConnection.nonceLocator() <- getTerminatedTestRef2() # value: ? # stdout: Deliver(-2, NewFarDesc(2, 1033912670282953026478972818820010722176128073325), 0, "getTerminatedTestRef2", []) # Imported remote promise: ? def outgoingPromise := outgoingConnection.nonceLocator() <- getTerminatedTestRef3() # value: ? # stdout: Deliver(-3, NewFarDesc(3, 47255196895205160731987244561159478631887357377), 0, "getTerminatedTestRef3", []) # ? receiver.DeliverOnly(3, "run", buildIn([descs.NewRemotePromise(2, -1, 4352160789)])) Export, which should receive a __reactToLostClient: ? E.sendOnly(outgoingConnection.nonceLocator(), "TerminatedExportTest", [ > def ttReactReceiver {to __reactToLostClient(p) { print(`rTLC expecter got $p`) }}]) ? # stdout: DeliverOnly(0, "TerminatedExportTest", [NewFarDesc(4, 412897248500657771667886264468852369692723684603)]) # The termination: ? receiver.Terminated(buildIn("foo" :Throwable)) # stdout: rTLC expecter got problem: foo Consequences: ? [outgoingFar, outgoingQuestion, outgoingPromise, outgoingConnection.nonceLocator()] # value: [, , , ] ? Ref.optProblem(outgoingFar) == Ref.optProblem(outgoingQuestion) # value: true ? Ref.optProblem(outgoingFar) == Ref.optProblem(outgoingPromise) # value: true Checking for robust termination: We don't need to test anything but the receiver, builder-maker, and builders, because every other access is an eventual ref that gets broken, as tested above. ? receiver.DeliverOnly(0, "", buildIn([])) # problem: this CapTP connection has been terminated (problem: foo) ? receiver.Deliver(-1, buildIn(null), 0, "", buildIn([])) # problem: this CapTP connection has been terminated (problem: foo) ? receiver.GCAnswer(-1) # problem: this CapTP connection has been terminated (problem: foo) ? receiver.GCExport(1, 0) # problem: this CapTP connection has been terminated (problem: foo) x ? receiver.Shutdown(0) x # problem: this CapTP connection has been terminated (problem: foo) ? receiver.Terminated(buildIn("bar")) # problem: this CapTP connection has been terminated (problem: foo) x ? receiver.Wormhole(-1, buildIn(null), 0, "", buildIn([])) x # problem: this CapTP connection has been terminated (problem: foo) XXX test these things' behavior after termination: proxy finalizer locally referenced redirector (gets exported then sent home) --- Incoming WormholeOp --- XXX we don't have VatID processing yet ------ Incoming descriptors ------ --- Incoming NewFarDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? receiver.DeliverOnly(0, "__whenMoreResolved", buildIn([descs.NewFar(1, 13904808385)])) ? # stdout: DeliverOnly(1, "run", [ImportDesc(0)]) # --- Incoming NewRemotePromiseDesc, and delayed redirection --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] First we set up an incoming reference to an object to catch the remote promise. ? clearFinalizers() > def remotePromise > def promiseReceiver(p) { > println(`got $p`) > bind remotePromise := p > } > def swiss :int := swissTable.getNewSwiss(promiseReceiver); null > receiver.Deliver(-3, buildIn(descs.NewFar(2, 928)), 0, "lookupSwiss", buildIn([swiss, null])) > receiver.GCAnswer(-3) ? # stdout: DeliverOnly(2, "run", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # Then we deliver a message containing the promise. ? receiver.DeliverOnly(1, "run", buildIn([descs.NewRemotePromise(3, -4, def remotePromiseRdrBase := 4290823709)])) > receiver.GCExport(1, 1) # stdout: got # Testing sends to the promise: ? E.sendOnly(remotePromise, "hello eventual", []) ? # stdout: DeliverOnly(3, "hello eventual", []) # Testing that the promise is present in the imports table: ? receiver.DeliverOnly(0, "__whenMoreResolved", buildIn([descs.Import(3)])) ? # stdout: DeliverOnly(3, "run", [ImportDesc(0)]) # The redirector should be accessible by our provided SwissBase: ? receiver.Deliver(-3, buildIn(descs.Import(2)), 0, "lookupSwiss", buildIn([remotePromiseRdrBase.cryptoHash(), null])) ? # stdout: DeliverOnly(2, "run", [NewFarDesc(1, 216307466466887903142951307717254789854244528344)]) # Export index 2 now holds the redirector. (remotePromiseRdrBase.cryptoHash().cryptoHash() == 216307466466887903142951307717254789854244528344) Invoking the redirector performs a delayed redirection, to ensure that messages sent to the resolution don't arrive before the messages previously sent to the promise. A delayed redirection is: __whenMoreResolved is sent to the ... XXX finish this explanation ? def unoptimizableRedirection implements pbc { > to __optUncall() { return [[], "diverge", []] }} > null ? receiver.DeliverOnly(-4, "run", buildIn([unoptimizableRedirection])) ? # stdout: DeliverOnly(3, "__whenMoreResolved", [NewFarDesc(2, 1033912670282953026478972818820010722176128073325)]) # Export 2 is the non-delayed redirector. ? receiver.DeliverOnly(2, "run", buildIn(["secondRedirValue"])) ? remotePromise # value: "secondRedirValue" XXX explain why the second redirection wins The remote promise proxy is now garbage, so CapTP should send GCExportOp: ? runFinalizers() ? # stdout: GCExport(3, 2) # GCExport(2, 2) # Index 3 is the remote promise (passed in once as NewRemotePromiseDesc and once as ImportDesc), and index 2 is the twice-used fake redirector for incoming deliveries. XXX further tests --- Incoming NewRemotePromiseDesc without delayed redirection --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] First we publish the resolver to catch the remote promise. ? def swiss :int := swissTable.getNewSwiss(def remotePromise); null > receiver.Deliver(-3, buildIn(descs.NewFar(2, 928)), 0, "lookupSwiss", buildIn([swiss, null])) > receiver.GCAnswer(-3) ? # stdout: DeliverOnly(2, "run", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # Then we deliver a message containing the promise. ? receiver.DeliverOnly(1, "resolve", buildIn([descs.NewRemotePromise(3, -4, def remotePromiseRdrBase := 4290823709)])) Invoking the redirector causes the remote promise to resolve immediately: ? receiver.DeliverOnly(-4, "run", buildIn([56])) ? remotePromise # value: 56 --- Incoming ImportDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] Setting up a proxy for testing: ? def swiss :int := swissTable.getNewSwiss(def _ { > to compare(x, y) { println(x == y) } > to compareNL(x) { println(x == outgoingConnection.nonceLocator()) } > }); null > receiver.Deliver(-1, buildIn(descs.NewFar(1, 2384978932)), 0, "lookupSwiss", buildIn([swiss, null])) > receiver.GCAnswer(-1) ? # stdout: DeliverOnly(1, "run", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # Actual testing: ? receiver.DeliverOnly(1, "compare", buildIn([descs.NewFar(1, 13904808385), > descs.Import(1)])) # stdout: true # ? receiver.DeliverOnly(1, "whatever", buildIn([descs.Import(999)])) # problem: 999 not alloced in ProxiesTable__1 ? receiver.DeliverOnly(1, "compareNL", buildIn([descs.Import(0)])) # stdout: true # An ImportDesc cannot have a negative index (receiver's questions table) because that would conflict with the receiver being able to GC its questions table entries. --- Incoming IncomingDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def swiss :int := swissTable.getNewSwiss(def f(x) { println(x == f) }); null ? receiver.Deliver(-1, buildIn(descs.Incoming(0)), 0, "lookupSwiss", buildIn([swiss, null])) ? receiver.DeliverOnly(-1, "run", buildIn([descs.Incoming(-1)]) ) # stdout: true # --- Incoming Promise3Desc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def swiss :int := swissTable.getNewSwiss(def f(x) { println(x); Ref.whenResolved(x, println) }); null ? receiver.Deliver(-1, buildIn(descs.Incoming(0)), 0, "lookupSwiss", buildIn([swiss, null])) ? receiver.DeliverOnly(-1, "run", buildIn([ > descs.Promise3(["a"], > "b", > 3241570689, > descs.NewFar(1, 39248071832))])) # stdout: hub.get(["a"], "b") # # stdout: # ? # stdout: .["acceptFrom", [["tRemoteSearchPath"], "tRemoteVatID", 3241570689, ]] # acceptFrom result # --- Incoming Far3Desc --- XXX ------ Outgoing operations ------ --- Outgoing DeliverOnlyOp --- XXX write this --- Outgoing DeliverOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def outgoingFar := outgoingConnection.nonceLocator() <- lookupSwiss(12345, null) # value: ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "lookupSwiss", [12345, null]) # The above is a delivery to the NonceLocator, which is a bit of an unusual case; so we will also test a Far ref. The remote side invokes the redirector for 'r', resolving it to a Far ref with import index 1. ? receiver.DeliverOnly(1, "run", buildIn([descs.NewFar(1, 2398478938902)])) ? outgoingFar # value: Here's a normal message to that Far ref. ? outgoingFar <- foo() # value: ? # stdout: Deliver(-2, NewFarDesc(2, 1033912670282953026478972818820010722176128073325), 1, "foo", []) # --- Outgoing GCExportOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] GCExportOp is sent when a proxy for an import-table entry has gone away; so to test it, we simulate an arriving import which is discarded. ? clearFinalizers() > receiver.DeliverOnly(0, "__optSealedDispatch", buildIn([descs.NewFar(2, 38249328)])) ? runFinalizers() ? # stdout: GCExport(2, 1) # Note that the sender of GCExportOp does not know whether the index was actually freed by the remote side; if it was, the index will be overwritten by a New*Desc; otherwise, an ImportDesc will arrive causing the creation of a new proxy for the existing table entry. ? clearFinalizers() > receiver.DeliverOnly(0, "__optSealedDispatch", buildIn([descs.NewFar(2, 38249328)])) > receiver.DeliverOnly(0, "__optSealedDispatch", buildIn([descs.Import(2)])) ? runFinalizers() ? # stdout: GCExport(2, 2) # --- Outgoing GCAnswerOp --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] We create a remote promise for an answer and tell the connection that it has been GCed. ? clearFinalizers() > E.send(outgoingConnection.nonceLocator(), "lookupSwiss", [93478934, null]) > null ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "lookupSwiss", [93478934, null]) # ? runFinalizers() ? # stdout: GCAnswer(-1) # After GC, the question table entry is free and will be reused. ? E.send(outgoingConnection.nonceLocator(), "lookupSwiss", [23948931413532, null]) > null ? # stdout: Deliver(-1, NewFarDesc(2, 1033912670282953026478972818820010722176128073325), 0, "lookupSwiss", [23948931413532, null]) # ? runFinalizers() ? # stdout: GCAnswer(-1) # --- Outgoing ShutdownOp --- XXX write this --- Outgoing TerminatedOp --- XXX write this --- Outgoing WormholeOp --- XXX write this ------ Outgoing descriptors ------ --- Outgoing NewFarDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? E.sendOnly(outgoingConnection.nonceLocator(), "newFarDescTest", [def newSelfish {}]) ? # stdout: DeliverOnly(0, "newFarDescTest", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # After an outgoing NewFarDesc, the object should be present in the exports table, so further outgoing messages use ImportDesc: ? E.sendOnly(outgoingConnection.nonceLocator(), "sameFarTest", [newSelfish]) ? # stdout: DeliverOnly(0, "sameFarTest", [ImportDesc(1)]) # --- Outgoing NewRemotePromiseDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? var wmrs := [] > def resolution > def exportedPromise := ( > def handler { > match [=="handleSendOnly", [=="__whenMoreResolved", [reactor]]] { > wmrs with= reactor > } > match msg { stderr.println(msg) }}, > resolution, > false) > E.sendOnly(outgoingConnection.nonceLocator(), "newRemotePromiseDescTest", [exportedPromise]) ? # stdout: DeliverOnly(0, "newRemotePromiseDescTest", [NewRemotePromiseDesc(1, -1, 10000001)]) # # stderr: ["handleOptSealedDispatch", [CapTP far ref's remote incomingPos]] # ["handleOptSealedDispatch", [otherConnProxy]] # The resolution must be delivered to the redirector even if it's a promise, to allow for promise shortening; in other words, it must be whenMoreResolved and not whenResolved. ? def anotherPromise > bind resolution := __makeFinalSlot(anotherPromise); null > for r in wmrs { E.sendOnly(r, "run", [anotherPromise]) } ? # stdout: DeliverOnly(-1, "run", [NewRemotePromiseDesc(2, -2, 10000002)]) # XXX if the answer to http://www.eros-os.org/pipermail/e-lang/2007-September/012219.html is local promises should invoke reactors, then we can get rid of using proxies for this test. (Yes, it is correct that the third argument isn't a hash. It's direct from entropy.nextSwiss(), and is a SwissBase, the authority to *be* an object (as seen from outside some vat).) XXX test properties of the identity of the redirector passed to exportedPromise <- __whenMoreResolved, and that if re-imported it is still good XXX test that index 1 delivers to the promise --- Outgoing ImportDesc --- See NewFarDesc above for testing of ordinary imports; this is the special NonceLocator case. XXX write this test --- Outgoing IncomingDesc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] IncomingDesc for the nonce locator (magic index 0) ? E.sendOnly(outgoingConnection.nonceLocator(), "incomingNonceLocatorTest", [outgoingConnection.nonceLocator()]) ? # stdout: DeliverOnly(0, "incomingNonceLocatorTest", [IncomingDesc(0)]) # IncomingDesc for an export ? def outgoing := outgoingConnection.nonceLocator() <- willBeFar(); null ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "willBeFar", []) # ? receiver.DeliverOnly(1, "run", buildIn([descs.NewFar(1, 398427908325834)])) ? E.sendOnly(outgoingConnection.nonceLocator(), "incomingFarTest", [outgoing]) ? # stdout: DeliverOnly(0, "incomingFarTest", [IncomingDesc(1)]) # IncomingDesc for an answer ? def outgoing := outgoingConnection.nonceLocator() <- willBePromise(); null ? # stdout: Deliver(-2, NewFarDesc(2, 1033912670282953026478972818820010722176128073325), 0, "willBePromise", []) # ? E.sendOnly(outgoingConnection.nonceLocator(), "incomingAnswerTest", [outgoing]) ? # stdout: DeliverOnly(0, "incomingAnswerTest", [IncomingDesc(-2)]) # --- Outgoing Promise3Desc --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def otherConnPromise := ( > def handler { > to handleOptSealedDispatch(brand) { > if (brand == otherConnProxyBrand) { > def nonce := 893921618639987 > return otherConnProxySealer.seal(fn recipID {[["otherSP"], "otherID", nonce, makeVine(otherConnPromise)]}) > } > } > to handleSendOnly(_, _) {} > to handleSend(_, _) {} > }, > Ref.promise()[0], > false) # value: ? E.sendOnly(outgoingConnection.nonceLocator(), "introduction", [otherConnPromise]) ? # stdout: DeliverOnly(0, "introduction", [Promise3Desc(["otherSP"], "otherID", 893921618639987, NewFarDesc(1, 382064925828890872342377433413917253333972024416))]) # Until WormholeOp, Far refs are also sent using Promise3Desc. ? def otherConnFar := ( > handler, > Ref.promise()[0], > true) # value: ? E.sendOnly(outgoingConnection.nonceLocator(), "introduction", [otherConnFar]) ? # stdout: DeliverOnly(0, "introduction", [Promise3Desc(["otherSP"], "otherID", 893921618639987, NewFarDesc(2, 1033912670282953026478972818820010722176128073325))]) # --- Outgoing Far3Desc --- XXX ------ Serialization ------ ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? E.sendOnly(outgoingConnection.nonceLocator(), "setupIncoming", [println]) ? # stdout: DeliverOnly(0, "setupIncoming", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # --- Disconnected ref --- ? E.sendOnly(outgoingConnection.nonceLocator(), "s", [Ref.broken("b")]) ? # stdout: DeliverOnly(0, "s", [Ref.broken(("b"))]) # ? receiver.DeliverOnly(1, "run", buildIn([Ref.broken("b")])) # stdout: # --- LocatorUnum --- ? E.sendOnly(outgoingConnection.nonceLocator(), "s", [locatorUnumLocal]) ? # stdout: DeliverOnly(0, "s", [CapTP_1_locatorUnum]) # ? receiver.DeliverOnly(1, "run", buildIn([locatorUnumRemote])) # stdout: # XXX write more serialization tests ------ Other ------ --- Being Carol --- At the sending side in a 3-vat introduction, the proxy for the passed far ref must amplify to the data for a Promise3Desc or Far3Desc, so that other connections can pass it as such. This includes making an entry in the promise gift table[?] for the generated nonce. ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def eventualRef := outgoingConnection.nonceLocator() <- eventualForPassTest() # value: ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "eventualForPassTest", []) # ? hub.amplifyFor3Desc(eventualRef, "recipID") # value: [["tRemoteSearchPath"], "tRemoteVatID", 10000002, ] ? # stdout: Deliver(-2, NewFarDesc(2, 47255196895205160731987244561159478631887357377), 0, "provideFor", [IncomingDesc(-1), "recipID", 10000002]) # --- outgoingConnection.nonceLocator() ref --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? E.sendOnly(outgoingConnection.nonceLocator(), "foo", []) ? # stdout: DeliverOnly(0, "foo", []) # --- Peer facets and gift tables --- ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] Access ? peerConnection.nonceLocator() == outgoingConnection.nonceLocator() # value: true ? def pgt := peerConnection.getPromiseGiftTable() # value: ? def ngt := peerConnection.getNearGiftTable() # value: Use of PromiseGiftTable ? receiver.DeliverOnly(0, "provideFor", buildIn(["gift1", "tPGTRecID", 346718905])) ? pgt.acceptFor("tPGTRecID", 346718905) # value: "gift1" Use of NearGiftTable ? E.sendOnly(outgoingConnection.nonceLocator(), "exporting", [println]) ? # stdout: DeliverOnly(0, "exporting", [NewFarDesc(1, 382064925828890872342377433413917253333972024416)]) # ? receiver.DeliverOnly(0, "provideFor", buildIn([descs.Incoming(1), "tPGTRecID", 346718905, 382064925828890872342377433413917253333972024416])) ? ngt.acceptFor("tPGTRecID", 346718905, 382064925828890872342377433413917253333972024416) # value: XXX review whether the full tables (vs. smaller facets of them) should be exposed to peers. NonceLocator#acceptFrom ? otherPromiseGiftTable.provideFor(println, "tRemoteVatID", 472938) > receiver.Deliver(-1, buildIn(null), 0, "acceptFrom", buildIn([[], "tAFDonorID", 472938, null])) > receiver.DeliverOnly(-1, "run", buildIn(["got promise gift"])) # stdout: hub.get([], "tAFDonorID") # ? # stdout: got promise gift # ? otherNearGiftTable.provideFor(println, "tRemoteVatID", 673563245, 382064925828890872342377433413917253333972024416) > receiver.Deliver(-2, buildIn(null), 0, "acceptFrom", buildIn([[], "tAFDonorID", 673563245, 382064925828890872342377433413917253333972024416, null])) > receiver.DeliverOnly(-2, "run", buildIn(["got near gift"])) # stdout: hub.get([], "tAFDonorID") # ? # stdout: got near gift # --- Reuse of builders --- This is prohibited because the timing and reoccurrence of unserialization operations affects the results and the state of the comm tables. ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def builder > receiver.DeliverOnly(0, "__getAllegedType", fn bind builder { testGraphRecognizer.recognize([], builder) }) ? builder.buildImport("CapTP_1_descs") # problem: this CapTP argument builder is no longer valid --- Outgoing __whenMoreResolved --- If a remote promise proxy receives a __whenMoreResolved where the reactor is a proxy for another connection, then instead of forwarding the message, it delivers itself to the reactor. This creates the opportunity for the promise to be serialized as a Promise3Desc, triggering 3-vat shortening. ? clearFinalizers() ? def [swissTable, receiver, outgoingConnection, peerConnection] := makeTestConnection() # value: [, , , ] ? def otherConnReactor := ( > def handler { > to handleOptSealedDispatch(brand) { > if (brand == otherConnProxyBrand) { > def nonce := 483853534795 > return otherConnProxySealer.seal(fn recipID {[["otherSP"], "otherID", nonce, makeVine(otherConnReactor)]}) > } > } > match msg { stderr.println(msg) } > }, > Ref.promise()[0], > true) # value: ? def someRemotePromise := outgoingConnection.nonceLocator() <- wmrTest() # value: ? # stdout: Deliver(-1, NewFarDesc(1, 382064925828890872342377433413917253333972024416), 0, "wmrTest", []) # In order to check for a particular refcount bug, the promise must be an import rather than a question. ? receiver.DeliverOnly(1, "run", buildIn([descs.NewRemotePromise(1, -1, def remotePromiseRdrBase := 23897893247)])) ? E.sendOnly(someRemotePromise, "__whenMoreResolved", [otherConnReactor]) ? # stderr: ["handleSendOnly", ["run", []]] # Bug test: the self-reference in __whenMoreResolved was being treated like an arriving reference and increasing the wire-count, resulting in this wire count being 2 rather than 1. ? runFinalizers() ? # stdout: GCAnswer(-1) # GCExport(1, 1) # --- Replay attacks on outgoing encoding --- Unfortunately, there is no way to test this at the moment. The potential problem is: if the __optSealedDispatch responses of a proxy are replayed after the proxy is GCed, can the CapTP system be fooled into presenting an invalid (or reused for an unrelated object) position to the remote side? The solution is to retain the proxy in the sealed box, so the position cannot have been GCed. (Revoking the box is not acceptable as this would allow the replayer to discover whether the remote reference has been GCed.) If this mechanism also checks, when amplifying the proxy, that the proxy in the box is the same as the proxy sealed-dispatch was performed on, then it would be possible to test that the check happens, and therefore that the proxy is in the box, but remote promise proxies do not currently have the sameness they should, so this check cannot be done yet. XXX fix this, write the test