Add send message function
parent
34e73a020b
commit
ee12742fc0
|
@ -11,6 +11,17 @@ Address = str
|
|||
# Default timeout for socket operations (seconds)
|
||||
_DEFAULT_TIMEOUT = 10.0
|
||||
|
||||
# Return codes
|
||||
SUCCESS = 0
|
||||
ERR_TYPE = 1 # invalid data type (TypeError)
|
||||
ERR_PAYLOAD_TOO_LARGE = 2 # payload exceeds supported length (ValueError)
|
||||
ERR_SOCKET_ERROR = 3 # plain TCP connection error or timeout
|
||||
ERR_SEND_FAILED = 4 # failure while sending data
|
||||
ERR_SOCKS_MISSING = 5 # PySocks (socks) module not installed
|
||||
ERR_INVALID_SOCKS_PORT = 6 # I2P_SOCKS_PORT environment variable not an integer
|
||||
ERR_PROXY_CONNECTION = 7 # error connecting through SOCKS proxy (I2P)
|
||||
ERR_UNKNOWN = 8 # unknown/unexpected error
|
||||
|
||||
|
||||
def _prepare_payload(data: typing.Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
|
@ -37,7 +48,7 @@ def _pack_message(payload: bytes) -> bytes:
|
|||
return struct.pack("!I", length) + payload
|
||||
|
||||
|
||||
def send_data(data: typing.Union[bytes, str], addr: Address, port: int) -> None:
|
||||
def send_data(data: typing.Union[bytes, str], addr: Address, port: int) -> int:
|
||||
"""
|
||||
Send a single length-prefixed message over plain TCP to (addr, port).
|
||||
|
||||
|
@ -48,22 +59,37 @@ def send_data(data: typing.Union[bytes, str], addr: Address, port: int) -> None:
|
|||
|
||||
Behaviour:
|
||||
- Connects, sends the framed message, then closes the socket.
|
||||
- Raises built-in socket exceptions on failure.
|
||||
|
||||
Example:
|
||||
send_data(b'hello', '192.0.2.1', 12345)
|
||||
- Returns 0 on success, otherwise returns an error code.
|
||||
"""
|
||||
try:
|
||||
payload = _prepare_payload(data)
|
||||
except TypeError:
|
||||
return ERR_TYPE
|
||||
try:
|
||||
message = _pack_message(payload)
|
||||
except ValueError:
|
||||
return ERR_PAYLOAD_TOO_LARGE
|
||||
|
||||
# Create a TCP connection with a timeout
|
||||
try:
|
||||
with socket.create_connection((addr, port), timeout=_DEFAULT_TIMEOUT) as sock:
|
||||
# Ensure blocking mode for sendall
|
||||
sock.setblocking(True)
|
||||
try:
|
||||
sock.sendall(message)
|
||||
except (socket.timeout, OSError):
|
||||
# Sending failed after connection
|
||||
return ERR_SEND_FAILED
|
||||
except (socket.timeout, OSError):
|
||||
# Could not create connection (including DNS, connect, timeout)
|
||||
return ERR_SOCKET_ERROR
|
||||
except Exception:
|
||||
return ERR_UNKNOWN
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
def send_data_i2p(data: typing.Union[bytes, str], addr: Address, port: int) -> None:
|
||||
def send_data_i2p(data: typing.Union[bytes, str], addr: Address, port: int) -> int:
|
||||
"""
|
||||
Send a single length-prefixed message to an I2P destination via a local i2pd SOCKS5 proxy.
|
||||
|
||||
|
@ -76,33 +102,29 @@ def send_data_i2p(data: typing.Union[bytes, str], addr: Address, port: int) -> N
|
|||
- Uses PySocks (socks) to create a socket that speaks SOCKS5 to the local proxy.
|
||||
- Uses remote DNS resolution (rdns=True) so that the proxy resolves .i2p names.
|
||||
- Connects, sends the framed message, then closes the socket.
|
||||
- Raises ImportError if PySocks is not installed.
|
||||
- Raises socket or proxy-related exceptions on failure.
|
||||
|
||||
Environment variables:
|
||||
- I2P_SOCKS_HOST (default '127.0.0.1')
|
||||
- I2P_SOCKS_PORT (default '4447')
|
||||
|
||||
Example:
|
||||
send_data_i2p(b'hello', 'abcd1234xxxxxxx.b32.i2p', 12345)
|
||||
- Returns 0 on success, otherwise returns an error code.
|
||||
"""
|
||||
try:
|
||||
import socks # PySocks; license: MIT
|
||||
except Exception as e:
|
||||
raise ImportError(
|
||||
"PySocks is required for send_data_i2p. "
|
||||
"Install it with: pip install PySocks"
|
||||
) from e
|
||||
except Exception:
|
||||
return ERR_SOCKS_MISSING
|
||||
|
||||
try:
|
||||
payload = _prepare_payload(data)
|
||||
except TypeError:
|
||||
return ERR_TYPE
|
||||
|
||||
try:
|
||||
message = _pack_message(payload)
|
||||
except ValueError:
|
||||
return ERR_PAYLOAD_TOO_LARGE
|
||||
|
||||
socks_host = os.environ.get("I2P_SOCKS_HOST", "127.0.0.1")
|
||||
socks_port_str = os.environ.get("I2P_SOCKS_PORT", "4447")
|
||||
try:
|
||||
socks_port = int(socks_port_str)
|
||||
except ValueError:
|
||||
raise ValueError("I2P_SOCKS_PORT environment variable must be an integer")
|
||||
return ERR_INVALID_SOCKS_PORT
|
||||
|
||||
# Create a socksified socket. socks.socksocket has the same interface as socket.socket.
|
||||
s = socks.socksocket()
|
||||
|
@ -114,11 +136,27 @@ def send_data_i2p(data: typing.Union[bytes, str], addr: Address, port: int) -> N
|
|||
# Connect through the proxy to the I2P destination.
|
||||
# When rdns=True, the proxy will perform name resolution for .i2p addresses.
|
||||
s.connect((addr, port))
|
||||
except (socket.timeout, OSError, Exception):
|
||||
# Proxy connection / resolution failed
|
||||
try:
|
||||
s.close()
|
||||
except Exception:
|
||||
pass
|
||||
return ERR_PROXY_CONNECTION
|
||||
|
||||
try:
|
||||
# Ensure blocking mode for sendall
|
||||
s.setblocking(True)
|
||||
try:
|
||||
s.sendall(message)
|
||||
except (socket.timeout, OSError):
|
||||
return ERR_SEND_FAILED
|
||||
except Exception:
|
||||
return ERR_UNKNOWN
|
||||
finally:
|
||||
try:
|
||||
s.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return SUCCESS
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
import ressenger_cryptography, ressenger_exceptions
|
||||
import pickle, pathlib
|
||||
|
||||
# class Contact():
|
||||
# def __init__(self, nickname, b32address, pub_enc, pub_sig):
|
||||
import ressenger_cryptography, ressenger_exceptions, ressenger_client
|
||||
import pickle, pathlib, time
|
||||
|
||||
def load_user(password, username='default'):
|
||||
user_path=pathlib.Path(f'~/.ressenger/{username}').expanduser()
|
||||
|
@ -21,3 +18,19 @@ def dump_user(password, user, username='default'):
|
|||
else:
|
||||
with open(user_path, 'wb') as file:
|
||||
file.write(ressenger_cryptography.encrypt_bytes(pickle.dumps(user, protocol=pickle.HIGHEST_PROTOCOL), password))
|
||||
|
||||
def send_message(text, destination_addr, destination_port, enc_pub_key, sig_pri_key, sig_pub_key):
|
||||
data={'type':'text', 'sent_time'=time.time_ns(), 'data':{'content':text}}
|
||||
data_encrypted, data_signature=ressenger_cryptography.encrypt(pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL), enc_pub_key, sig_pri_key)
|
||||
packet={'data':data_encrypted, 'sig':data_signature, 'enc_pub':enc_pub_key, 'sig_pub':sig_pub_key}
|
||||
errorno=ressenger_client.send_data_i2p(pickle.dumps(packet, protocol=pickle.HIGHEST_PROTOCOL), destination_addr, destination_port)
|
||||
event={'type':'sent', 'data':data, 'dest':{'addr':destination_addr, 'port':destination_port}, 'errorno':errorno}
|
||||
return event
|
||||
|
||||
def send_file(filename, filebytes, destination_addr, destination_port, enc_pub_key, sig_pri_key, sig_pub_key)
|
||||
data={'type':'file', 'sent_time'=time.time_ns(), 'data':{'content':filebytes, 'filename':filename}}
|
||||
data_encrypted, data_signature=ressenger_cryptography.encrypt(pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL), enc_pub_key, sig_pri_key)
|
||||
packet={'data':data_encrypted, 'sig':data_signature, 'enc_pub':enc_pub_key, 'sig_pub':sig_pub_key}
|
||||
errorno=ressenger_client.send_data_i2p(pickle.dumps(packet, protocol=pickle.HIGHEST_PROTOCOL), destination_addr, destination_port)
|
||||
event={'type':'sent', 'data':data, 'dest':{'addr':destination_addr, 'port':destination_port}, 'errorno':errorno}
|
||||
return event
|
||||
|
|
|
@ -185,7 +185,7 @@ def decrypt(encrypted_bytes: bytes, decryption_private_key_pem: bytes, verificat
|
|||
# # generate sender signing keypair (sender)
|
||||
# priv_sig, pub_sig = generate_keypair()
|
||||
#
|
||||
# message = b"I love Ruby!"
|
||||
# message = b"I love C!"
|
||||
# out = encrypt(message, pub_enc, priv_sig)
|
||||
# if out is None:
|
||||
# raise SystemExit("Encryption failed")
|
||||
|
|
|
@ -23,4 +23,4 @@ def initialise(password, b32address, username='default', port=5273, nick='John D
|
|||
enc_pri, enc_pub=ressenger_cryptography.generate_keypair()
|
||||
sig_pri, sig_pub=ressenger_cryptography.generate_keypair()
|
||||
with open(user_path, 'wb') as file:
|
||||
file.write(ressenger_cryptography.encrypt_bytes(pickle.dumps({'port':port, 'b32address':b32address, 'enc_pri':enc_pri, 'enc_pub':enc_pub, 'sig_pri':sig_pri, 'sig_pub':sig_pub, 'keyring':[], 'events':{}, 'contacts':{}}, protocol=pickle.HIGHEST_PROTOCOL), password))
|
||||
file.write(ressenger_cryptography.encrypt_bytes(pickle.dumps({'port':port, 'b32address':b32address, 'enc_pri':enc_pri, 'enc_pub':enc_pub, 'sig_pri':sig_pri, 'sig_pub':sig_pub, 'contacts':{}, 'events':{}}, protocol=pickle.HIGHEST_PROTOCOL), password))
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
TCP server that receives binary blobs and persists them to a cache file.
|
||||
This variant ensures the cache file is removed on shutdown (where possible).
|
||||
|
||||
Behaviour:
|
||||
- Always start with a fresh, empty CACHE (do NOT load existing file).
|
||||
- Use a dedicated CacheWriter thread to debounce and atomically write CACHE.
|
||||
- On exit (signals, KeyboardInterrupt, normal interpreter exit) stop writer and remove cache file.
|
||||
Note: SIGKILL (kill -9) and sudden power loss cannot be trapped; cleanup cannot run then.
|
||||
"""
|
||||
|
||||
# English comments throughout (British English spelling).
|
||||
|
||||
#!/usr/bin/python3
|
||||
import socket
|
||||
import threading
|
||||
import struct
|
||||
|
@ -226,7 +213,7 @@ def run_server(port: int):
|
|||
CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with cache_lock:
|
||||
atomic_dump(CACHE, CACHE_PATH)
|
||||
print("Initialised fresh empty CACHE on disk (existing file not loaded).")
|
||||
print("Initialised fresh empty CACHE on disk.")
|
||||
except Exception:
|
||||
print("Failed to initialise cache file on disk")
|
||||
traceback.print_exc()
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
#!/usr/bin/python3
|
||||
import ressenger_common
|
||||
|
||||
|
|
Loading…
Reference in New Issue