API updates, make most tests pass, disable others

* finally wire up "application versions"
* remove when_verifier (which used to fire after key establishment, but
  before the VERSION message was received or verified)
* fire when_verified and when_version at the same time (after VERSION is
  verified), but with different args
This commit is contained in:
Brian Warner 2017-03-07 12:34:36 +01:00
parent e518f2b799
commit 5f9894ca63
6 changed files with 164 additions and 129 deletions

View File

@ -274,24 +274,25 @@ functions on the delegate object. In Deferred mode, the application retrieves
Deferred objects from the wormhole, and event dispatch is performed by firing Deferred objects from the wormhole, and event dispatch is performed by firing
those Deferreds. those Deferreds.
* got_code (`yield w.when_code()` / `dg.wormhole_got_code(code)`): fired when the * got_code (`yield w.when_code()` / `dg.wormhole_code(code)`): fired when the
wormhole code is established, either after `w.generate_code()` finishes the wormhole code is established, either after `w.generate_code()` finishes the
generation process, or when the Input Helper returned by `w.type_code()` generation process, or when the Input Helper returned by `w.type_code()`
has been told `h.set_words()`, or immediately after `w.set_code(code)` is has been told `h.set_words()`, or immediately after `w.set_code(code)` is
called. This is most useful after calling `w.generate_code()`, to show the called. This is most useful after calling `w.generate_code()`, to show the
generated code to the user so they can transcribe it to their peer. generated code to the user so they can transcribe it to their peer.
* got_verifier (`yield w.when_verifier()` / `dg.wormhole_got_verifier(verf)`: * verified (`verifier = yield w.when_verified()` /
fired when the key-exchange process has completed, and this side has `dg.wormhole_verified(verifier)`: fired when the key-exchange process has
learned the shared key. The "verifier" is a byte string with a hash of the completed and a valid VERSION message has arrived. The "verifier" is a byte
shared session key; clients can compare them (probably as hex) to ensure string with a hash of the shared session key; clients can compare them
that they're really talking to each other, and not to a man-in-the-middle. (probably as hex) to ensure that they're really talking to each other, and
When `got_verifier` happens, this side has not yet seen evidence that the not to a man-in-the-middle. When `got_verifier` happens, this side knows
peer has used the correct wormhole code. that *someone* has used the correct wormhole code; if someone used the
* got_version (`yield w.when_version()` / `dg.wormhole_got_version(version)`: wrong code, the VERSION message cannot be decrypted, and the wormhole will
fired when the VERSION message arrives from the peer. This serves two be closed instead.
purposes. The first is that it provide confirmation that the peer (or a * version (`yield w.when_version()` / `dg.wormhole_version(version)`:
man-in-the-middle) has used the correct wormhole code. The second is fired when the VERSION message arrives from the peer. This fires at the
delivery of the "app_versions" data (passed into `wormhole.create`). same time as `verified`, but delivers the "app_versions" data (passed into
`wormhole.create`) instead of the verifier string.
* received (`yield w.when_received()` / `dg.wormhole_received(data)`: fired * received (`yield w.when_received()` / `dg.wormhole_received(data)`: fired
each time a data message arrives from the peer, with the bytestring that each time a data message arrives from the peer, with the bytestring that
the peer passed into `w.send(data)`. the peer passed into `w.send(data)`.
@ -391,19 +392,19 @@ in python3):
## Full API list ## Full API list
action | Deferred-Mode | Delegated-Mode action | Deferred-Mode | Delegated-Mode
-------------------------- | --------------------- | ---------------------------- -------------------------- | -------------------- | --------------
w.generate_code(length=2) | | w.generate_code(length=2) | |
w.set_code(code) | | w.set_code(code) | |
h=w.type_code() | | h=w.type_code() | |
| d=w.when_code() | dg.wormhole_got_code(code) | d=w.when_code() | dg.wormhole_code(code)
| d=w.when_verifier() | dg.wormhole_got_verifier(verf) | d=w.when_verified() | dg.wormhole_verified(verifier)
| d=w.when_version() | dg.wormhole_got_version(version) | d=w.when_version() | dg.wormhole_version(version)
w.send(data) | | w.send(data) | |
| d=w.when_received() | dg.wormhole_received(data) | d=w.when_received() | dg.wormhole_received(data)
key=w.derive_key(purpose, length) | | key=w.derive_key(purpose, length) | |
w.close() | | dg.wormhole_closed(result) w.close() | | dg.wormhole_closed(result)
| d=w.close() | | d=w.close() |
## Dilation ## Dilation

View File

@ -27,6 +27,7 @@ class Boss(object):
_side = attrib(validator=instance_of(type(u""))) _side = attrib(validator=instance_of(type(u"")))
_url = attrib(validator=instance_of(type(u""))) _url = attrib(validator=instance_of(type(u"")))
_appid = attrib(validator=instance_of(type(u""))) _appid = attrib(validator=instance_of(type(u"")))
_versions = attrib(validator=instance_of(dict))
_welcome_handler = attrib() # TODO: validator: callable _welcome_handler = attrib() # TODO: validator: callable
_reactor = attrib() _reactor = attrib()
_journal = attrib(validator=provides(_interfaces.IJournal)) _journal = attrib(validator=provides(_interfaces.IJournal))
@ -41,7 +42,7 @@ class Boss(object):
self._M = Mailbox(self._side) self._M = Mailbox(self._side)
self._S = Send(self._side, self._timing) self._S = Send(self._side, self._timing)
self._O = Order(self._side, self._timing) self._O = Order(self._side, self._timing)
self._K = Key(self._appid, self._side, self._timing) self._K = Key(self._appid, self._versions, self._side, self._timing)
self._R = Receive(self._side, self._timing) self._R = Receive(self._side, self._timing)
self._RC = RendezvousConnector(self._url, self._appid, self._side, self._RC = RendezvousConnector(self._url, self._appid, self._side,
self._reactor, self._journal, self._reactor, self._journal,
@ -188,7 +189,7 @@ class Boss(object):
self._their_versions = bytes_to_dict(plaintext) self._their_versions = bytes_to_dict(plaintext)
# but this part is app-to-app # but this part is app-to-app
app_versions = self._their_versions.get("app_versions", {}) app_versions = self._their_versions.get("app_versions", {})
self._W.got_version(app_versions) self._W.got_versions(app_versions)
@m.output() @m.output()
def S_send(self, plaintext): def S_send(self, plaintext):

View File

@ -56,6 +56,7 @@ def encrypt_data(key, plaintext):
@implementer(_interfaces.IKey) @implementer(_interfaces.IKey)
class Key(object): class Key(object):
_appid = attrib(validator=instance_of(type(u""))) _appid = attrib(validator=instance_of(type(u"")))
_versions = attrib(validator=instance_of(dict))
_side = attrib(validator=instance_of(type(u""))) _side = attrib(validator=instance_of(type(u"")))
_timing = attrib(validator=provides(_interfaces.ITiming)) _timing = attrib(validator=provides(_interfaces.ITiming))
m = MethodicalMachine() m = MethodicalMachine()
@ -114,8 +115,7 @@ class Key(object):
self._B.got_verifier(derive_key(key, b"wormhole:verifier")) self._B.got_verifier(derive_key(key, b"wormhole:verifier"))
phase = "version" phase = "version"
data_key = derive_phase_key(key, self._side, phase) data_key = derive_phase_key(key, self._side, phase)
my_versions = {} # TODO: get from Wormhole? plaintext = dict_to_bytes(self._versions)
plaintext = dict_to_bytes(my_versions)
encrypted = encrypt_data(data_key, plaintext) encrypted = encrypt_data(data_key, plaintext)
self._M.add_message(phase, encrypted) self._M.add_message(phase, encrypted)
self._R.got_key(key) self._R.got_key(key)

View File

@ -6,9 +6,9 @@ from twisted.trial import unittest
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet.defer import Deferred, gatherResults, inlineCallbacks from twisted.internet.defer import Deferred, gatherResults, inlineCallbacks
from .common import ServerBase from .common import ServerBase
from .. import wormhole, _order from .. import wormhole, _rendezvous
from ..errors import (WrongPasswordError, WelcomeError, InternalError, from ..errors import (WrongPasswordError, WelcomeError, InternalError,
KeyFormatError) KeyFormatError, WormholeClosed, LonelyError)
from spake2 import SPAKE2_Symmetric from spake2 import SPAKE2_Symmetric
from ..timing import DebugTiming from ..timing import DebugTiming
from ..util import (bytes_to_dict, dict_to_bytes, from ..util import (bytes_to_dict, dict_to_bytes,
@ -116,7 +116,7 @@ class InputCode(unittest.TestCase):
res = self.successResultOf(d) res = self.successResultOf(d)
self.assertEqual(res, ["123"]) self.assertEqual(res, ["123"])
self.assertEqual(stderr.getvalue(), "") self.assertEqual(stderr.getvalue(), "")
InputCode.skip = "not yet"
class GetCode(unittest.TestCase): class GetCode(unittest.TestCase):
def test_get(self): def test_get(self):
@ -134,6 +134,7 @@ class GetCode(unittest.TestCase):
pieces = code.split("-") pieces = code.split("-")
self.assertEqual(len(pieces), 3) # nameplate plus two words self.assertEqual(len(pieces), 3) # nameplate plus two words
self.assert_(re.search(r'^\d+-\w+-\w+$', code), code) self.assert_(re.search(r'^\d+-\w+-\w+$', code), code)
GetCode.skip = "not yet"
class Basic(unittest.TestCase): class Basic(unittest.TestCase):
def tearDown(self): def tearDown(self):
@ -714,7 +715,7 @@ class Basic(unittest.TestCase):
w.derive_key, "foo", SecretBox.KEY_SIZE) w.derive_key, "foo", SecretBox.KEY_SIZE)
self.failureResultOf(w.get(), WrongPasswordError) self.failureResultOf(w.get(), WrongPasswordError)
self.failureResultOf(w.verify(), WrongPasswordError) self.failureResultOf(w.verify(), WrongPasswordError)
Basic.skip = "being replaced by test_wormhole_new"
# event orderings to exercise: # event orderings to exercise:
# #
@ -735,14 +736,15 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_basic(self): def test_basic(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
w1.send(b"data1") w1.send(b"data1")
w2.send(b"data2") w2.send(b"data2")
dataX = yield w1.get() dataX = yield w1.when_received()
dataY = yield w2.get() dataY = yield w2.when_received()
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
yield w1.close() yield w1.close()
@ -753,14 +755,15 @@ class Wormholes(ServerBase, unittest.TestCase):
# the two sides use random nonces for their messages, so it's ok for # the two sides use random nonces for their messages, so it's ok for
# both to try and send the same body: they'll result in distinct # both to try and send the same body: they'll result in distinct
# encrypted messages # encrypted messages
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
w1.send(b"data") w1.send(b"data")
w2.send(b"data") w2.send(b"data")
dataX = yield w1.get() dataX = yield w1.when_received()
dataY = yield w2.get() dataY = yield w2.when_received()
self.assertEqual(dataX, b"data") self.assertEqual(dataX, b"data")
self.assertEqual(dataY, b"data") self.assertEqual(dataY, b"data")
yield w1.close() yield w1.close()
@ -768,14 +771,15 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_interleaved(self): def test_interleaved(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
w1.send(b"data1") w1.send(b"data1")
dataY = yield w2.get() dataY = yield w2.when_received()
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
d = w1.get() d = w1.when_received()
w2.send(b"data2") w2.send(b"data2")
dataX = yield d dataX = yield d
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
@ -784,22 +788,23 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_unidirectional(self): def test_unidirectional(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
w1.send(b"data1") w1.send(b"data1")
dataY = yield w2.get() dataY = yield w2.when_received()
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
yield w1.close() yield w1.close()
yield w2.close() yield w2.close()
@inlineCallbacks @inlineCallbacks
def test_early(self): def test_early(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w1.send(b"data1") w1.send(b"data1")
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
d = w2.get() d = w2.when_received()
w1.set_code("123-abc-def") w1.set_code("123-abc-def")
w2.set_code("123-abc-def") w2.set_code("123-abc-def")
dataY = yield d dataY = yield d
@ -809,12 +814,12 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_fixed_code(self): def test_fixed_code(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant") w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant") w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2") w1.send(b"data1"), w2.send(b"data2")
dl = yield self.doBoth(w1.get(), w2.get()) dl = yield self.doBoth(w1.when_received(), w2.when_received())
(dataX, dataY) = dl (dataX, dataY) = dl
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
@ -824,28 +829,52 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_multiple_messages(self): def test_multiple_messages(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant") w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant") w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2") w1.send(b"data1"), w2.send(b"data2")
w1.send(b"data3"), w2.send(b"data4") w1.send(b"data3"), w2.send(b"data4")
dl = yield self.doBoth(w1.get(), w2.get()) dl = yield self.doBoth(w1.when_received(), w2.when_received())
(dataX, dataY) = dl (dataX, dataY) = dl
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
dl = yield self.doBoth(w1.get(), w2.get()) dl = yield self.doBoth(w1.when_received(), w2.when_received())
(dataX, dataY) = dl (dataX, dataY) = dl
self.assertEqual(dataX, b"data4") self.assertEqual(dataX, b"data4")
self.assertEqual(dataY, b"data3") self.assertEqual(dataY, b"data3")
yield w1.close() yield w1.close()
yield w2.close() yield w2.close()
@inlineCallbacks
def test_closed(self):
w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-foo")
w2.set_code("123-foo")
# let it connect and become HAPPY
yield w1.when_version()
yield w2.when_version()
yield w1.close()
yield w2.close()
# once closed, all Deferred-yielding API calls get an error
e = yield self.assertFailure(w1.when_code(), WormholeClosed)
self.assertEqual(e.args[0], "happy")
yield self.assertFailure(w1.when_verified(), WormholeClosed)
yield self.assertFailure(w1.when_version(), WormholeClosed)
yield self.assertFailure(w1.when_received(), WormholeClosed)
@inlineCallbacks @inlineCallbacks
def test_wrong_password(self): def test_wrong_password(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code+"not") w2.set_code(code+"not")
# That's enough to allow both sides to discover the mismatch, but # That's enough to allow both sides to discover the mismatch, but
# only after the confirmation message gets through. API calls that # only after the confirmation message gets through. API calls that
@ -855,44 +884,41 @@ class Wormholes(ServerBase, unittest.TestCase):
w2.send(b"should still work") w2.send(b"should still work")
# API calls that wait (i.e. get) will errback # API calls that wait (i.e. get) will errback
yield self.assertFailure(w2.get(), WrongPasswordError) yield self.assertFailure(w2.when_received(), WrongPasswordError)
yield self.assertFailure(w1.get(), WrongPasswordError) yield self.assertFailure(w1.when_received(), WrongPasswordError)
yield self.assertFailure(w1.when_verified(), WrongPasswordError)
yield self.assertFailure(w1.when_version(), WrongPasswordError)
yield self.assertFailure(w1.close(), WrongPasswordError)
yield self.assertFailure(w2.close(), WrongPasswordError)
yield w1.close()
yield w2.close()
self.flushLoggedErrors(WrongPasswordError)
@inlineCallbacks @inlineCallbacks
def test_wrong_password_with_spaces(self): def test_wrong_password_with_spaces(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) badcode = "4 oops spaces"
code = yield w1.get_code()
code_no_dashes = code.replace('-', ' ')
with self.assertRaises(KeyFormatError) as ex: with self.assertRaises(KeyFormatError) as ex:
w2.set_code(code_no_dashes) w.set_code(badcode)
expected_msg = "code (%s) contains spaces." % (badcode,)
expected_msg = "code (%s) contains spaces." % (code_no_dashes,)
self.assertEqual(expected_msg, str(ex.exception)) self.assertEqual(expected_msg, str(ex.exception))
yield self.assertFailure(w.close(), LonelyError)
yield w1.close()
yield w2.close()
self.flushLoggedErrors(KeyFormatError)
@inlineCallbacks @inlineCallbacks
def test_verifier(self): def test_verifier(self):
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
v1 = yield w1.verify() v1 = yield w1.when_verified()
v2 = yield w2.verify() v2 = yield w2.when_verified()
self.failUnlessEqual(type(v1), type(b"")) self.failUnlessEqual(type(v1), type(b""))
self.failUnlessEqual(v1, v2) self.failUnlessEqual(v1, v2)
w1.send(b"data1") w1.send(b"data1")
w2.send(b"data2") w2.send(b"data2")
dataX = yield w1.get() dataX = yield w1.when_received()
dataY = yield w2.get() dataY = yield w2.when_received()
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
yield w1.close() yield w1.close()
@ -901,16 +927,17 @@ class Wormholes(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_versions(self): def test_versions(self):
# there's no API for this yet, but make sure the internals work # there's no API for this yet, but make sure the internals work
w1 = wormhole.wormhole(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor,
w1._my_versions = {"w1": 123} versions={"w1": 123})
w2 = wormhole.wormhole(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor,
w2._my_versions = {"w2": 456} versions={"w2": 456})
code = yield w1.get_code() w1.allocate_code()
code = yield w1.when_code()
w2.set_code(code) w2.set_code(code)
yield w1.verify() w1_versions = yield w2.when_version()
self.assertEqual(w1._their_versions, {"w2": 456}) self.assertEqual(w1_versions, {"w1": 123})
yield w2.verify() w2_versions = yield w1.when_version()
self.assertEqual(w2._their_versions, {"w1": 123}) self.assertEqual(w2_versions, {"w2": 456})
yield w1.close() yield w1.close()
yield w2.close() yield w2.close()
@ -923,50 +950,52 @@ class Wormholes(ServerBase, unittest.TestCase):
# incoming PAKE message was received, which would cause # incoming PAKE message was received, which would cause
# SPAKE2.finish() to be called a second time, which throws an error # SPAKE2.finish() to be called a second time, which throws an error
# (which, being somewhat unexpected, caused a hang rather than a # (which, being somewhat unexpected, caused a hang rather than a
# clear exception). # clear exception). The Mailbox object is responsible for
with mock.patch("wormhole.wormhole._order", MessageDoubler): # deduplication, so we must patch the RendezvousConnector to simulate
# duplicated messages.
with mock.patch("wormhole._boss.RendezvousConnector", MessageDoubler):
w1 = wormhole.create(APPID, self.relayurl, reactor) w1 = wormhole.create(APPID, self.relayurl, reactor)
w2 = wormhole.create(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
w1.set_code("123-purple-elephant") w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant") w2.set_code("123-purple-elephant")
w1.send(b"data1"), w2.send(b"data2") w1.send(b"data1"), w2.send(b"data2")
dl = yield self.doBoth(w1.get(), w2.get()) dl = yield self.doBoth(w1.when_received(), w2.when_received())
(dataX, dataY) = dl (dataX, dataY) = dl
self.assertEqual(dataX, b"data2") self.assertEqual(dataX, b"data2")
self.assertEqual(dataY, b"data1") self.assertEqual(dataY, b"data1")
yield w1.close() yield w1.close()
yield w2.close() yield w2.close()
class MessageDoubler(_order.Order): class MessageDoubler(_rendezvous.RendezvousConnector):
# we could double messages on the sending side, but a future server will # we could double messages on the sending side, but a future server will
# strip those duplicates, so to really exercise the receiver, we must # strip those duplicates, so to really exercise the receiver, we must
# double them on the inbound side instead # double them on the inbound side instead
#def _msg_send(self, phase, body): #def _msg_send(self, phase, body):
# wormhole._Wormhole._msg_send(self, phase, body) # wormhole._Wormhole._msg_send(self, phase, body)
# self._ws_send_command("add", phase=phase, body=bytes_to_hexstr(body)) # self._ws_send_command("add", phase=phase, body=bytes_to_hexstr(body))
def got_message(self, side, phase, body): def _response_handle_message(self, msg):
_order.Order.got_message(self, side, phase, body) _rendezvous.RendezvousConnector._response_handle_message(self, msg)
_order.Order.got_message(self, side, phase, body) _rendezvous.RendezvousConnector._response_handle_message(self, msg)
class Errors(ServerBase, unittest.TestCase): class Errors(ServerBase, unittest.TestCase):
@inlineCallbacks @inlineCallbacks
def test_codes_1(self): def test_codes_1(self):
w = wormhole.wormhole(APPID, self.relayurl, reactor) w = wormhole.create(APPID, self.relayurl, reactor)
# definitely too early # definitely too early
self.assertRaises(InternalError, w.derive_key, "purpose", 12) self.assertRaises(InternalError, w.derive_key, "purpose", 12)
w.set_code("123-purple-elephant") w.set_code("123-purple-elephant")
# code can only be set once # code can only be set once
self.assertRaises(InternalError, w.set_code, "123-nope") self.assertRaises(InternalError, w.set_code, "123-nope")
yield self.assertFailure(w.get_code(), InternalError) yield self.assertFailure(w.when_code(), InternalError)
yield self.assertFailure(w.input_code(), InternalError) yield self.assertFailure(w.input_code(), InternalError)
yield w.close() yield w.close()
@inlineCallbacks @inlineCallbacks
def test_codes_2(self): def test_codes_2(self):
w = wormhole.wormhole(APPID, self.relayurl, reactor) w = wormhole.create(APPID, self.relayurl, reactor)
yield w.get_code() yield w.when_code()
self.assertRaises(InternalError, w.set_code, "123-nope") self.assertRaises(InternalError, w.set_code, "123-nope")
yield self.assertFailure(w.get_code(), InternalError) yield self.assertFailure(w.when_code(), InternalError)
yield self.assertFailure(w.input_code(), InternalError) yield self.assertFailure(w.input_code(), InternalError)
yield w.close() yield w.close()

View File

@ -57,8 +57,8 @@ class New(ServerBase, unittest.TestCase):
code2 = yield w2.when_code() code2 = yield w2.when_code()
self.assertEqual(code, code2) self.assertEqual(code, code2)
verifier1 = yield w1.when_verifier() verifier1 = yield w1.when_verified()
verifier2 = yield w2.when_verifier() verifier2 = yield w2.when_verified()
self.assertEqual(verifier1, verifier2) self.assertEqual(verifier1, verifier2)
version1 = yield w1.when_version() version1 = yield w1.when_version()
@ -88,7 +88,7 @@ class New(ServerBase, unittest.TestCase):
w1.allocate_code(2) w1.allocate_code(2)
code = yield w1.when_code() code = yield w1.when_code()
w2 = wormhole.create(APPID, self.relayurl, reactor) w2 = wormhole.create(APPID, self.relayurl, reactor)
w2.set_code(code+", NOT") w2.set_code(code+"NOT")
code2 = yield w2.when_code() code2 = yield w2.when_code()
self.assertNotEqual(code, code2) self.assertNotEqual(code, code2)

View File

@ -25,7 +25,7 @@ from .util import to_bytes
# w.send(data) # w.send(data)
# app.wormhole_got_code(code) # app.wormhole_got_code(code)
# app.wormhole_got_verifier(verifier) # app.wormhole_got_verifier(verifier)
# app.wormhole_got_version(version) # app.wormhole_got_version(versions)
# app.wormhole_receive(data) # app.wormhole_receive(data)
# w.close() # w.close()
# app.wormhole_closed() # app.wormhole_closed()
@ -117,15 +117,15 @@ class _DelegatedWormhole(object):
# from below # from below
def got_code(self, code): def got_code(self, code):
self._delegate.wormhole_got_code(code) self._delegate.wormhole_code(code)
def got_welcome(self, welcome): def got_welcome(self, welcome):
pass # TODO pass # TODO
def got_key(self, key): def got_key(self, key):
self._key = key # for derive_key() self._key = key # for derive_key()
def got_verifier(self, verifier): def got_verifier(self, verifier):
self._delegate.wormhole_got_verifier(verifier) self._delegate.wormhole_verified(verifier)
def got_version(self, version): def got_versions(self, versions):
self._delegate.wormhole_got_version(version) self._delegate.wormhole_version(versions)
def received(self, plaintext): def received(self, plaintext):
self._delegate.wormhole_received(plaintext) self._delegate.wormhole_received(plaintext)
def closed(self, result): def closed(self, result):
@ -139,7 +139,7 @@ class _DeferredWormhole(object):
self._key = None self._key = None
self._verifier = None self._verifier = None
self._verifier_observers = [] self._verifier_observers = []
self._version = None self._versions = None
self._version_observers = [] self._version_observers = []
self._received_data = [] self._received_data = []
self._received_observers = [] self._received_observers = []
@ -162,7 +162,7 @@ class _DeferredWormhole(object):
self._code_observers.append(d) self._code_observers.append(d)
return d return d
def when_verifier(self): def when_verified(self):
if self._observer_result is not None: if self._observer_result is not None:
return defer.fail(self._observer_result) return defer.fail(self._observer_result)
if self._verifier is not None: if self._verifier is not None:
@ -174,8 +174,8 @@ class _DeferredWormhole(object):
def when_version(self): def when_version(self):
if self._observer_result is not None: if self._observer_result is not None:
return defer.fail(self._observer_result) return defer.fail(self._observer_result)
if self._version is not None: if self._versions is not None:
return defer.succeed(self._version) return defer.succeed(self._versions)
d = defer.Deferred() d = defer.Deferred()
self._version_observers.append(d) self._version_observers.append(d)
return d return d
@ -241,10 +241,10 @@ class _DeferredWormhole(object):
for d in self._verifier_observers: for d in self._verifier_observers:
d.callback(verifier) d.callback(verifier)
self._verifier_observers[:] = [] self._verifier_observers[:] = []
def got_version(self, version): def got_versions(self, versions):
self._version = version self._versions = versions
for d in self._version_observers: for d in self._version_observers:
d.callback(version) d.callback(versions)
self._version_observers[:] = [] self._version_observers[:] = []
def received(self, plaintext): def received(self, plaintext):
@ -272,8 +272,9 @@ class _DeferredWormhole(object):
d.callback(self._closed_result) d.callback(self._closed_result)
def create(appid, relay_url, reactor, delegate=None, journal=None, def create(appid, relay_url, reactor, versions={},
tor_manager=None, timing=None, welcome_handler=None, delegate=None, journal=None, tor_manager=None,
timing=None, welcome_handler=None,
stderr=sys.stderr): stderr=sys.stderr):
timing = timing or DebugTiming() timing = timing or DebugTiming()
side = bytes_to_hexstr(os.urandom(5)) side = bytes_to_hexstr(os.urandom(5))
@ -287,7 +288,10 @@ def create(appid, relay_url, reactor, delegate=None, journal=None,
w = _DelegatedWormhole(delegate) w = _DelegatedWormhole(delegate)
else: else:
w = _DeferredWormhole() w = _DeferredWormhole()
b = Boss(w, side, relay_url, appid, welcome_handler, reactor, journal, wormhole_versions = {} # will be used to indicate Wormhole capabilities
wormhole_versions["app_versions"] = versions # app-specific capabilities
b = Boss(w, side, relay_url, appid, wormhole_versions,
welcome_handler, reactor, journal,
tor_manager, timing) tor_manager, timing)
w._set_boss(b) w._set_boss(b)
b.start() b.start()