fill in initiator flow, define relay API
This commit is contained in:
parent
246e080c7c
commit
f5a0b3e5c6
55
docs/api.md
55
docs/api.md
|
@ -29,44 +29,53 @@ The synchronous+blocking flow looks like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from wormhole.transcribe import Initiator
|
from wormhole.transcribe import Initiator
|
||||||
blob = b"initiator's blob"
|
data = b"initiator's data"
|
||||||
i = Initiator("appid", blob)
|
i = Initiator("appid", data)
|
||||||
print("Invitation Code: %s" % i.start()
|
code = i.get_code()
|
||||||
theirblob = i.finish()
|
print("Invitation Code: %s" % code)
|
||||||
print("Their blob: %s" % theirblob.decode("ascii"))
|
theirdata = i.get_data()
|
||||||
|
print("Their data: %s" % theirdata.decode("ascii"))
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import sys
|
import sys
|
||||||
from wormhole.transcribe import Receiver
|
from wormhole.transcribe import Receiver
|
||||||
blob = b"receiver's blob"
|
data = b"receiver's data"
|
||||||
code = sys.argv[1]
|
code = sys.argv[1]
|
||||||
r = Receiver("appid", code, blob)
|
r = Receiver("appid", code, data)
|
||||||
theirblob = r.finish()
|
theirdata = r.get_data()
|
||||||
print("Their blob: %s" % theirblob.decode("ascii"))
|
print("Their data: %s" % theirdata.decode("ascii"))
|
||||||
```
|
```
|
||||||
|
|
||||||
The Twisted-friendly flow looks like this:
|
The Twisted-friendly flow looks like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from wormhole.transcribe import Initiator
|
from twisted.internet import reactor
|
||||||
blob = b"initiator's blob"
|
from wormhole.transcribe import TwistedInitiator
|
||||||
i = Initiator("appid", blob)
|
data = b"initiator's data"
|
||||||
d = i.start()
|
ti = TwistedInitiator("appid", data, reactor)
|
||||||
d.addCallback(lambda code: print("Invitation Code: %s" % code))
|
ti.startService()
|
||||||
d.addCallback(lambda _: i.finish())
|
d1 = ti.when_get_code()
|
||||||
d.addCallback(lambda theirblob:
|
d1.addCallback(lambda code: print("Invitation Code: %s" % code))
|
||||||
print("Their blob: %s" % theirblob.decode("ascii")))
|
d2 = ti.when_get_data()
|
||||||
|
d2.addCallback(lambda theirdata:
|
||||||
|
print("Their data: %s" % theirdata.decode("ascii")))
|
||||||
|
d2.addCallback(labmda _: reactor.stop())
|
||||||
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from wormhole.transcribe import Receiver
|
from twisted.internet import reactor
|
||||||
blob = b"receiver's blob"
|
from wormhole.transcribe import TwistedReceiver
|
||||||
|
data = b"receiver's data"
|
||||||
code = sys.argv[1]
|
code = sys.argv[1]
|
||||||
r = Receiver("appid", code, blob)
|
tr = TwistedReceiver("appid", code, data, reactor)
|
||||||
d = r.finish()
|
tr.startService()
|
||||||
d.addCallback(lambda theirblob:
|
d = tr.when_get_data()
|
||||||
print("Their blob: %s" % theirblob.decode("ascii")))
|
d.addCallback(lambda theirdata:
|
||||||
|
print("Their data: %s" % theirdata.decode("ascii")))
|
||||||
|
d.addCallback(lambda _: reactor.stop())
|
||||||
|
reactor.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Application Identifier
|
## Application Identifier
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -19,7 +19,7 @@ setup(name="wormhole-sync",
|
||||||
url="https://github.com/warner/wormhole-sync",
|
url="https://github.com/warner/wormhole-sync",
|
||||||
package_dir={"": "src"},
|
package_dir={"": "src"},
|
||||||
packages=["wormhole"],
|
packages=["wormhole"],
|
||||||
install_requires=["spake2"],
|
install_requires=["spake2", "requests"],
|
||||||
test_suite="wormhole.test",
|
test_suite="wormhole.test",
|
||||||
cmdclass=commands,
|
cmdclass=commands,
|
||||||
)
|
)
|
||||||
|
|
15
src/wormhole/codes.py
Normal file
15
src/wormhole/codes.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import os, random
|
||||||
|
|
||||||
|
WORDLIST = ["able", "baker", "charlie"] # TODO: 1024
|
||||||
|
|
||||||
|
def make_code(channel_id):
|
||||||
|
# TODO: confirm that random.choice() uses os.urandom properly and covers
|
||||||
|
# the entire range with minimal bias. Many random.py functions do not,
|
||||||
|
# but I think this one might. If not, build our own from os.urandom,
|
||||||
|
# convert-to-int, and modulo.
|
||||||
|
word = random.choice(WORDLIST)
|
||||||
|
return "%d-%s" % (channel_id, word)
|
||||||
|
|
||||||
|
def extract_channel_id(code):
|
||||||
|
channel_id = int(code.split("-")[0])
|
||||||
|
return channel_id
|
2
src/wormhole/const.py
Normal file
2
src/wormhole/const.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
RELAY = "baked in relay URL"
|
|
@ -1,7 +1,7 @@
|
||||||
import os, sys, json
|
import os, sys, json
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from nacl.secret import SecretBox
|
from nacl.secret import SecretBox
|
||||||
from . import api
|
from .transcribe import Receiver
|
||||||
|
|
||||||
APPID = "lothar.com/wormhole/file-xfer"
|
APPID = "lothar.com/wormhole/file-xfer"
|
||||||
RELAY = "example.com"
|
RELAY = "example.com"
|
||||||
|
@ -9,8 +9,8 @@ RELAY = "example.com"
|
||||||
# we're receiving
|
# we're receiving
|
||||||
code = sys.argv[1]
|
code = sys.argv[1]
|
||||||
blob = b""
|
blob = b""
|
||||||
r = api.Receiver(APPID, blob, code)
|
r = Receiver(APPID, blob, code)
|
||||||
them_bytes = r.finish()
|
them_bytes = r.get_data()
|
||||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||||
print("them: %r" % (them_d,))
|
print("them: %r" % (them_d,))
|
||||||
xfer_key = unhexlify(them_d["xfer_key"].encode("ascii"))
|
xfer_key = unhexlify(them_d["xfer_key"].encode("ascii"))
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import sys, json
|
import sys, json
|
||||||
from . import api
|
from .transcribe import Receiver
|
||||||
|
|
||||||
APPID = "lothar.com/wormhole/text-xfer"
|
APPID = "lothar.com/wormhole/text-xfer"
|
||||||
|
|
||||||
# we're receiving
|
# we're receiving
|
||||||
code = sys.argv[1]
|
code = sys.argv[1]
|
||||||
blob = b""
|
blob = b""
|
||||||
r = api.Receiver(APPID, blob, code)
|
r = Receiver(APPID, blob, code)
|
||||||
them_bytes = r.finish()
|
them_bytes = r.get_data()
|
||||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||||
print(them_d["message"])
|
print(them_d["message"])
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os, sys, json
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from nacl.secret import SecretBox
|
from nacl.secret import SecretBox
|
||||||
from nacl import utils
|
from nacl import utils
|
||||||
from . import api
|
from .transcribe import Initiator
|
||||||
|
|
||||||
APPID = "lothar.com/wormhole/file-xfer"
|
APPID = "lothar.com/wormhole/file-xfer"
|
||||||
RELAY = "example.com"
|
RELAY = "example.com"
|
||||||
|
@ -16,14 +16,14 @@ blob = json.dumps({"xfer_key": hexlify(xfer_key),
|
||||||
"filesize": os.stat(filename).st_size,
|
"filesize": os.stat(filename).st_size,
|
||||||
"relay": RELAY,
|
"relay": RELAY,
|
||||||
}).encode("utf-8")
|
}).encode("utf-8")
|
||||||
i = api.Initiator(APPID, blob)
|
i = Initiator(APPID, blob)
|
||||||
code = i.start()
|
code = i.get_code()
|
||||||
print("Wormhole code is '%s'" % code)
|
print("Wormhole code is '%s'" % code)
|
||||||
print("On the other computer, please run:")
|
print("On the other computer, please run:")
|
||||||
print()
|
print()
|
||||||
print(" wormhole-receive-file %s" % code)
|
print(" wormhole-receive-file %s" % code)
|
||||||
print()
|
print()
|
||||||
them_bytes = i.finish()
|
them_bytes = i.get_data()
|
||||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||||
print("them: %r" % (them_d,))
|
print("them: %r" % (them_d,))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import sys, json
|
import sys, json
|
||||||
from . import api
|
from .transcribe import Initiator
|
||||||
|
|
||||||
APPID = "lothar.com/wormhole/text-xfer"
|
APPID = "lothar.com/wormhole/text-xfer"
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@ APPID = "lothar.com/wormhole/text-xfer"
|
||||||
message = sys.argv[1]
|
message = sys.argv[1]
|
||||||
blob = json.dumps({"message": message,
|
blob = json.dumps({"message": message,
|
||||||
}).encode("utf-8")
|
}).encode("utf-8")
|
||||||
i = api.Initiator(APPID, blob)
|
i = Initiator(APPID, blob)
|
||||||
code = i.start()
|
code = i.get_code()
|
||||||
print("Wormhole code is '%s'" % code)
|
print("Wormhole code is '%s'" % code)
|
||||||
print("On the other computer, please run:")
|
print("On the other computer, please run:")
|
||||||
print()
|
print()
|
||||||
print(" wormhole-receive-text %s" % code)
|
print(" wormhole-receive-text %s" % code)
|
||||||
print()
|
print()
|
||||||
them_bytes = i.finish()
|
them_bytes = i.get_data()
|
||||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||||
print("them: %r" % (them_d,))
|
print("them: %r" % (them_d,))
|
||||||
|
|
106
src/wormhole/transcribe.py
Normal file
106
src/wormhole/transcribe.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import time, requests
|
||||||
|
from spake2 import SPAKE2_A, SPAKE2_B
|
||||||
|
from .const import RELAY
|
||||||
|
from .codes import make_code, extract_channel_id
|
||||||
|
|
||||||
|
SECOND = 1
|
||||||
|
MINUTE = 60*SECOND
|
||||||
|
|
||||||
|
class Timeout(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# POST /allocate -> {channel-id: INT}
|
||||||
|
# POST /pake/post/CHANNEL-ID {side: STR, message: STR} -> {messages: [STR..]}
|
||||||
|
# POST /pake/poll/CHANNEL-ID {side: STR} -> {messages: [STR..]}
|
||||||
|
# POST /data/post/CHANNEL-ID {side: STR, message: STR} -> {messages: [STR..]}
|
||||||
|
# POST /data/poll/CHANNEL-ID {side: STR} -> {messages: [STR..]}
|
||||||
|
# POST /deallocate/CHANNEL-ID {side: STR} -> waiting | ok
|
||||||
|
|
||||||
|
class Initiator:
|
||||||
|
def __init__(self, appid, data, relay=RELAY):
|
||||||
|
self.appid = appid
|
||||||
|
self.data = data
|
||||||
|
self.relay = relay
|
||||||
|
self.started = time.time()
|
||||||
|
self.wait = 2*SECOND
|
||||||
|
self.timeout = 3*MINUTE
|
||||||
|
self.side = "initiator"
|
||||||
|
|
||||||
|
def get_code(self):
|
||||||
|
# allocate channel
|
||||||
|
r = requests.post(self.relay + "allocate", data="{}")
|
||||||
|
r.raise_for_status()
|
||||||
|
self.channel_id = r.json()["channel-id"]
|
||||||
|
self.code = codes.make_code(self.channel_id)
|
||||||
|
self.sp = SPAKE2_A(self.code.encode("ascii"),
|
||||||
|
idA=self.appid+":Initiator",
|
||||||
|
idB=self.appid+":Receiver")
|
||||||
|
msg = self.sp.start()
|
||||||
|
post_url = self.relay + "pake/post/%d" % self.channel_id
|
||||||
|
post_data = {"side": self.side,
|
||||||
|
"message": hexlify(msg).decode("ascii")}
|
||||||
|
r = requests.post(post_url, data=json.dumps(post_data))
|
||||||
|
r.raise_for_status()
|
||||||
|
return self.code
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
# poll for PAKE response
|
||||||
|
pake_url = self.relay + "pake/poll/%d" % self.channel_id
|
||||||
|
post_data = json.dumps({"side": self.side})
|
||||||
|
while True:
|
||||||
|
r = requests.post(pake_url, data=post_data)
|
||||||
|
r.raise_for_status()
|
||||||
|
msgs = r.json()["messages"]
|
||||||
|
if msgs:
|
||||||
|
break
|
||||||
|
if time.time() > (self.started + self.timeout):
|
||||||
|
raise Timeout
|
||||||
|
time.sleep(self.wait)
|
||||||
|
pake_msg = unhexlify(msgs[0].encode("ascii"))
|
||||||
|
self.key = self.sp.finish(pake_msg)
|
||||||
|
|
||||||
|
# post encrypted data
|
||||||
|
post_url = self.relay + "data/post/%d" % self.channel_id
|
||||||
|
post_data = json.dumps({"side": self.side,
|
||||||
|
"message": hexlify(self.data).decode("ascii")})
|
||||||
|
r = requests.post(post_url, data=post_data)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
# poll for data message
|
||||||
|
data_url = self.relay + "data/poll/%d" % self.channel_id
|
||||||
|
post_data = json.dumps({"side": self.side})
|
||||||
|
while True:
|
||||||
|
r = requests.post(data_url, data=post_data)
|
||||||
|
r.raise_for_status()
|
||||||
|
msgs = r.json()["messages"]
|
||||||
|
if msgs:
|
||||||
|
break
|
||||||
|
if time.time() > (self.started + self.timeout):
|
||||||
|
raise Timeout
|
||||||
|
time.sleep(self.wait)
|
||||||
|
data = unhexlify(msgs[0].encode("ascii"))
|
||||||
|
|
||||||
|
# deallocate channel
|
||||||
|
deallocate_url = self.relay + "deallocate/%s" % self.channel_id
|
||||||
|
post_data = json.dumps({"side": self.side})
|
||||||
|
r = requests.post(deallocate, data=post_data)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
class Receiver:
|
||||||
|
def __init__(self, appid, data, code, relay=RELAY):
|
||||||
|
self.appid = appid
|
||||||
|
self.data = data
|
||||||
|
self.code = code
|
||||||
|
self.channel_id = extract_channel_id(code)
|
||||||
|
self.relay = relay
|
||||||
|
self.sp = SPAKE2_B(code.encode("ascii"),
|
||||||
|
idA=self.appid+":Initiator",
|
||||||
|
idB=self.appid+":Receiver")
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
# poll for PAKE response
|
||||||
|
# poll for data message
|
||||||
|
# deallocate channel
|
||||||
|
pass
|
27
src/wormhole/twisted/transcribe.py
Normal file
27
src/wormhole/twisted/transcribe.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from twisted.application import service
|
||||||
|
from ..const import RELAY
|
||||||
|
|
||||||
|
class TwistedInitiator(service.MultiService):
|
||||||
|
def __init__(self, appid, data, reactor, relay=RELAY):
|
||||||
|
self.appid = appid
|
||||||
|
self.data = data
|
||||||
|
self.reactor = reactor
|
||||||
|
self.relay = relay
|
||||||
|
|
||||||
|
def when_get_code(self):
|
||||||
|
pass # return Deferred
|
||||||
|
|
||||||
|
def when_get_data(self):
|
||||||
|
pass # return Deferred
|
||||||
|
|
||||||
|
class TwistedReceiver(service.MultiService):
|
||||||
|
def __init__(self, appid, data, code, reactor, relay=RELAY):
|
||||||
|
self.appid = appid
|
||||||
|
self.data = data
|
||||||
|
self.code = code
|
||||||
|
self.reactor = reactor
|
||||||
|
self.relay = relay
|
||||||
|
|
||||||
|
def when_get_data(self):
|
||||||
|
pass # return Deferred
|
||||||
|
|
Loading…
Reference in New Issue
Block a user