Feature/client binary api update (#306)
* Split text and binary client apis * Very initial attempt at new serialization * Defined ser+de for Recipient and ReplySURB * Response errors * builds with changes * Working WS API + moved to separate crate * updated python examples * Fixed parsing bug * Updated go examples * Updated rust examples * formatting * Removed unused imports * Removed accidentally left panic call
This commit is contained in:
committed by
GitHub
parent
eec6034684
commit
7d2d512405
@@ -0,0 +1,139 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
from pathlib import Path
|
||||
import struct
|
||||
|
||||
# request tags
|
||||
SEND_REQUEST_TAG = 0x00
|
||||
REPLY_REQUEST_TAG = 0x01
|
||||
SELF_ADDRESS_REQUEST_TAG = 0x02
|
||||
|
||||
# response tags
|
||||
ERROR_RESPONSE_TAG = 0x00
|
||||
RECEIVED_RESPONSE_TAG = 0x01
|
||||
SELF_ADDRESS_RESPONSE_TAG = 0x02
|
||||
|
||||
|
||||
def make_self_address_request() -> bytes:
|
||||
return bytes([SELF_ADDRESS_REQUEST_TAG])
|
||||
|
||||
|
||||
def parse_self_address_response(raw_response: bytes) -> bytes:
|
||||
if len(raw_response) != 97 or raw_response[0] != SELF_ADDRESS_RESPONSE_TAG:
|
||||
print('Received invalid response!')
|
||||
raise
|
||||
|
||||
return raw_response[1:]
|
||||
|
||||
|
||||
def make_send_request(recipient: bytes, message: bytes, with_reply_surb: bool) -> bytes:
|
||||
# a big endian uint64
|
||||
message_len = len(message).to_bytes(length=8, byteorder='big', signed=False)
|
||||
|
||||
return bytes([SEND_REQUEST_TAG]) + bytes([with_reply_surb]) + recipient + message_len + message
|
||||
|
||||
|
||||
def make_reply_request(message: bytes, reply_surb: bytes) -> bytes:
|
||||
message_len = len(message).to_bytes(length=8, byteorder='big', signed=False)
|
||||
surb_len = len(reply_surb).to_bytes(length=8, byteorder='big', signed=False)
|
||||
|
||||
return bytes([REPLY_REQUEST_TAG]) + surb_len + reply_surb + message_len + message
|
||||
|
||||
|
||||
# it should have structure of RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
|
||||
# where surb_len || surb is only present if 'with_reply' is true
|
||||
def parse_received(raw_response: bytes) -> (bytes, bytes):
|
||||
if raw_response[0] != RECEIVED_RESPONSE_TAG:
|
||||
print('Received invalid response!')
|
||||
raise
|
||||
|
||||
if raw_response[1] == 1:
|
||||
has_surb = True
|
||||
elif raw_response[1] == 0:
|
||||
has_surb = False
|
||||
else:
|
||||
print("malformed received response!")
|
||||
raise
|
||||
|
||||
data = raw_response[2:]
|
||||
if has_surb:
|
||||
(surb_len,), other = struct.unpack(">Q", data[:8]), data[8:]
|
||||
surb = other[:surb_len]
|
||||
(msg_len,) = struct.unpack(">Q", other[surb_len:surb_len + 8])
|
||||
|
||||
if len(other[surb_len + 8:]) != msg_len:
|
||||
print("invalid msg len")
|
||||
raise
|
||||
|
||||
msg = other[surb_len + 8:]
|
||||
return msg, surb
|
||||
else:
|
||||
(msg_len,), other = struct.unpack(">Q", data[:8]), data[8:]
|
||||
if len(other) != msg_len:
|
||||
print("invalid msg len")
|
||||
raise
|
||||
|
||||
msg = other[:msg_len]
|
||||
return msg, None
|
||||
|
||||
|
||||
async def send_file_with_reply():
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
self_address_req = make_self_address_request()
|
||||
await websocket.send(self_address_req)
|
||||
|
||||
self_address = parse_self_address_response(await websocket.recv())
|
||||
file_data = Path('dummy_file').read_bytes()
|
||||
|
||||
send_request = make_send_request(self_address, file_data, True)
|
||||
print("sending content of 'dummy_file' over the mix network...")
|
||||
await websocket.send(send_request)
|
||||
|
||||
print("waiting to receive the 'dummy_file' from the mix network...")
|
||||
received_response = await websocket.recv()
|
||||
received_file, surb = parse_received(received_response)
|
||||
|
||||
with open("received_file_withreply", "wb") as output_file:
|
||||
print("writing the file back to the disk!")
|
||||
output_file.write(received_file)
|
||||
|
||||
reply_message = b"hello from reply SURB! - thanks for sending me the file!"
|
||||
reply_request = make_reply_request(reply_message, surb)
|
||||
|
||||
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
|
||||
await websocket.send(reply_request)
|
||||
|
||||
print("waiting to receive a message from the mix network...")
|
||||
received_response = await websocket.recv()
|
||||
received_msg, surb = parse_received(received_response)
|
||||
assert surb is None # no surbs in replies!
|
||||
|
||||
print("received '{}' from the mix network".format(received_msg))
|
||||
|
||||
|
||||
async def send_file_without_reply():
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
self_address_req = make_self_address_request()
|
||||
await websocket.send(self_address_req)
|
||||
|
||||
self_address = parse_self_address_response(await websocket.recv())
|
||||
file_data = Path('dummy_file').read_bytes()
|
||||
|
||||
send_request = make_send_request(self_address, file_data, False)
|
||||
print("sending content of 'dummy_file' over the mix network...")
|
||||
await websocket.send(send_request)
|
||||
|
||||
print("waiting to receive the 'dummy_file' from the mix network...")
|
||||
received_response = await websocket.recv()
|
||||
received_file, surb = parse_received(received_response)
|
||||
assert surb is None # we didn't attach a surb so we expect a None here!
|
||||
|
||||
with open("received_file_noreply", "wb") as output_file:
|
||||
print("writing the file back to the disk!")
|
||||
output_file.write(received_file)
|
||||
|
||||
|
||||
# asyncio.get_event_loop().run_until_complete(send_file_without_reply())
|
||||
asyncio.get_event_loop().run_until_complete(send_file_with_reply())
|
||||
@@ -1,111 +0,0 @@
|
||||
import asyncio
|
||||
import base58
|
||||
import json
|
||||
import websockets
|
||||
|
||||
self_address_request = json.dumps({
|
||||
"type": "selfAddress"
|
||||
})
|
||||
|
||||
WITHOUT_REPLY: int = 0
|
||||
WITH_REPLY: int = 1
|
||||
REPLY: int = 2
|
||||
|
||||
# this is really flaky so ideally will be replaced by some proper serialization
|
||||
# 16 - length of encryption key
|
||||
# 32 - length of first hop
|
||||
# 348 - current sphinx header size
|
||||
# 192 - size of single payload key
|
||||
# 4 - number of mix hops (gateway + 3 nodes)
|
||||
REPLY_SURB_LEN: int = 16 + 32 + 348 + 4 * 192
|
||||
|
||||
async def send_file_with_reply():
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
await websocket.send(self_address_request)
|
||||
self_address = json.loads(await websocket.recv())
|
||||
print("our address is: {}".format(self_address["address"]))
|
||||
# we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
|
||||
# all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
|
||||
split_address = self_address["address"].split("@")
|
||||
client_half = split_address[0]
|
||||
gateway_half = split_address[1]
|
||||
|
||||
# to DH: I think at this point this should be replaced with some sort of protobuf / flatbuffers / Cap'n Proto, etc
|
||||
split_client_address = client_half.split(".")
|
||||
|
||||
bin_payload = bytearray([WITH_REPLY])
|
||||
bin_payload += base58.b58decode(split_client_address[0])
|
||||
bin_payload += base58.b58decode(split_client_address[1])
|
||||
bin_payload += base58.b58decode(gateway_half)
|
||||
|
||||
with open("dummy_file", "rb") as input_file:
|
||||
read_data = input_file.read()
|
||||
bin_payload += read_data
|
||||
|
||||
print("sending content of 'dummy_file' over the mix network...")
|
||||
await websocket.send(bin_payload)
|
||||
|
||||
print("waiting to receive the 'dummy_file' from the mix network...")
|
||||
received_data = await websocket.recv()
|
||||
assert received_data[0] == WITH_REPLY
|
||||
reply_surb = received_data[1:1 +REPLY_SURB_LEN]
|
||||
output_file_data = received_data[1 + REPLY_SURB_LEN:]
|
||||
|
||||
with open("received_file_withreply", "wb") as output_file:
|
||||
print("writing the file back to the disk!")
|
||||
output_file.write(output_file_data)
|
||||
|
||||
|
||||
reply_message = b"hello from reply SURB! - thanks for sending me the file!"
|
||||
binary_reply = bytearray([REPLY])
|
||||
binary_reply += reply_surb
|
||||
binary_reply += reply_message
|
||||
|
||||
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
|
||||
await websocket.send(binary_reply)
|
||||
|
||||
print("waiting to receive a message from the mix network...")
|
||||
received_reply = await websocket.recv()
|
||||
assert received_reply[0] == WITHOUT_REPLY
|
||||
|
||||
print("received '{}' from the mix network".format(received_reply[1:]))
|
||||
|
||||
|
||||
|
||||
async def send_file_without_reply():
|
||||
uri = "ws://localhost:1977"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
await websocket.send(self_address_request)
|
||||
self_address = json.loads(await websocket.recv())
|
||||
print("our address is: {}".format(self_address["address"]))
|
||||
# we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
|
||||
# all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
|
||||
split_address = self_address["address"].split("@")
|
||||
client_half = split_address[0]
|
||||
gateway_half = split_address[1]
|
||||
|
||||
# to DH: I think at this point this should be replaced with some sort of protobuf / flatbuffers / Cap'n Proto, etc
|
||||
split_client_address = client_half.split(".")
|
||||
|
||||
bin_payload = bytearray([WITHOUT_REPLY])
|
||||
bin_payload += base58.b58decode(split_client_address[0])
|
||||
bin_payload += base58.b58decode(split_client_address[1])
|
||||
bin_payload += base58.b58decode(gateway_half)
|
||||
|
||||
with open("dummy_file", "rb") as input_file:
|
||||
read_data = input_file.read()
|
||||
bin_payload += read_data
|
||||
|
||||
print("sending content of 'dummy_file' over the mix network...")
|
||||
await websocket.send(bin_payload)
|
||||
|
||||
print("waiting to receive the 'dummy_file' from the mix network...")
|
||||
received_data = await websocket.recv()
|
||||
assert received_data[0] == WITHOUT_REPLY
|
||||
with open("received_file_noreply", "wb") as output_file:
|
||||
print("writing the file back to the disk!")
|
||||
output_file.write(received_data[1:])
|
||||
|
||||
# asyncio.get_event_loop().run_until_complete(send_file_without_reply())
|
||||
asyncio.get_event_loop().run_until_complete(send_file_with_reply())
|
||||
@@ -55,13 +55,13 @@ async def send_text_with_reply():
|
||||
print("received '{}' from the mix network".format(received_message))
|
||||
|
||||
# use the received surb to send an anonymous reply!
|
||||
reply_surb = received_message["replySurb"]
|
||||
reply_surb = received_message["replySURB"]
|
||||
|
||||
reply_message = "hello from reply SURB!"
|
||||
reply = json.dumps({
|
||||
"type": "reply",
|
||||
"message": reply_message,
|
||||
"replySurb": reply_surb
|
||||
"replySURB": reply_surb
|
||||
})
|
||||
|
||||
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
|
||||
|
||||
Reference in New Issue
Block a user