Improvements, added example configuration

This commit is contained in:
SebastianObi 2023-04-25 07:15:17 +02:00
parent 4880b6f249
commit 6308192009
25 changed files with 2747 additions and 444 deletions

View file

@ -47,6 +47,9 @@ import pickle
#### String ####
import string
#### Regex ####
import re
#### Process ####
import signal
import threading
@ -89,9 +92,10 @@ class lxmf_connection:
message_notification_callback = None
message_notification_success_callback = None
message_notification_failed_callback = None
config_set_callback = None
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, announce_hidden=False, send_delay=0, desired_method="direct", propagation_node=None, propagation_node_auto=False, propagation_node_active=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360):
self.storage_path = storage_path
self.identity_file = identity_file
@ -104,6 +108,7 @@ class lxmf_connection:
self.display_name = display_name
self.announce_data = announce_data
self.announce_hidden = announce_hidden
self.send_delay = int(send_delay)
@ -112,6 +117,8 @@ class lxmf_connection:
else:
self.desired_method_direct = True
self.propagation_node = propagation_node
self.propagation_node_auto = propagation_node_auto
self.propagation_node_active = propagation_node_active
self.try_propagation_on_fail = try_propagation_on_fail
self.announce_startup = announce_startup
@ -126,6 +133,10 @@ class lxmf_connection:
self.sync_periodic = sync_periodic
self.sync_periodic_interval = int(sync_periodic_interval)
if not self.storage_path:
log("LXMF - No storage_path parameter", LOG_ERROR)
return
if not os.path.isdir(self.storage_path):
os.makedirs(self.storage_path)
log("LXMF - Storage path was created", LOG_NOTICE)
@ -180,10 +191,18 @@ class lxmf_connection:
self.destination.set_link_established_callback(self.client_connected)
self.autoselect_propagation_node()
if self.propagation_node_auto:
self.propagation_callback = lxmf_connection_propagation(self, "lxmf.propagation")
RNS.Transport.register_announce_handler(self.propagation_callback)
if self.propagation_node_active:
self.propagation_node_set(self.propagation_node_active)
elif self.propagation_node:
self.propagation_node_set(self.propagation_node)
else:
self.propagation_node_set(self.propagation_node)
if self.announce_startup or self.announce_periodic:
self.announce(True)
self.announce(initial=True)
if self.sync_startup or self.sync_periodic:
self.sync(True)
@ -210,6 +229,10 @@ class lxmf_connection:
self.message_notification_failed_callback = handler_function
def register_config_set_callback(self, handler_function):
self.config_set_callback = handler_function
def destination_hash(self):
return self.destination.hash
@ -253,7 +276,7 @@ class lxmf_connection:
return ""
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""):
def send(self, destination, content="", title="", fields=None, timestamp=None, app_data="", destination_name=None, destination_type=None):
if type(destination) is not bytes:
if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2:
destination = destination[1:-1]
@ -268,8 +291,13 @@ class lxmf_connection:
log("LXMF - Destination is invalid", LOG_ERROR)
return
if destination_name == None:
destination_name = self.destination_name
if destination_type == None:
destination_type = self.destination_type
destination_identity = RNS.Identity.recall(destination)
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type)
destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, destination_name, destination_type)
self.send_message(destination, self.destination, content, title, fields, timestamp, app_data)
@ -338,7 +366,7 @@ class lxmf_connection:
message.desired_method_str = "propagated"
def announce(self, initial=False):
def announce(self, app_data=None, attached_interface=None, initial=False):
announce_timer = None
if self.announce_periodic and self.announce_periodic_interval > 0:
@ -355,26 +383,29 @@ class lxmf_connection:
announce_timer.daemon = True
announce_timer.start()
else:
self.announce_now()
self.announce_now(app_data=app_data, attached_interface=attached_interface)
return
self.announce_now()
self.announce_now(app_data=app_data, attached_interface=attached_interface)
def announce_now(self, app_data=None):
if app_data:
def announce_now(self, app_data=None, attached_interface=None):
if self.announce_hidden:
self.destination.announce("".encode("utf-8"), attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +" (Hidden)", LOG_DEBUG)
elif app_data != None:
if isinstance(app_data, str):
self.destination.announce(app_data.encode("utf-8"))
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG)
self.destination.announce(app_data.encode("utf-8"), attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + app_data, LOG_DEBUG)
else:
self.destination.announce(app_data)
self.destination.announce(app_data, attached_interface=attached_interface)
log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
elif self.announce_data:
if isinstance(self.announce_data, str):
self.destination.announce(self.announce_data.encode("utf-8"))
self.destination.announce(self.announce_data.encode("utf-8"), attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG)
else:
self.destination.announce(self.announce_data)
self.destination.announce(self.announce_data, attached_interface=attached_interface)
log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG)
else:
self.destination.announce()
@ -416,24 +447,50 @@ class lxmf_connection:
return False
def autoselect_propagation_node(self):
if self.propagation_node is not None:
if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("LXMF - Propagation node length is invalid", LOG_ERROR)
else:
try:
propagation_hash = bytes.fromhex(self.propagation_node)
except Exception as e:
log("LXMF - Propagation node is invalid", LOG_ERROR)
return
def propagation_node_set(self, dest_str):
if not dest_str:
return False
node_identity = RNS.Identity.recall(propagation_hash)
if node_identity != None:
log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO)
propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
self.message_router.set_outbound_propagation_node(propagation_hash)
else:
log("LXMF - Propagation node identity not known", LOG_ERROR)
if len(dest_str) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2):
log("LXMF - Propagation node length is invalid", LOG_ERROR)
return False
try:
dest_hash = bytes.fromhex(dest_str)
except Exception as e:
log("LXMF - Propagation node is invalid", LOG_ERROR)
return False
node_identity = RNS.Identity.recall(dest_hash)
if node_identity != None:
log("LXMF - Propagation node: " + RNS.prettyhexrep(dest_hash), LOG_INFO)
dest_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity)
self.message_router.set_outbound_propagation_node(dest_hash)
self.propagation_node_active = dest_str
return True
else:
log("LXMF - Propagation node identity not known", LOG_ERROR)
return False
def propagation_node_update(self, dest_str):
if self.propagation_node_hash_str() != dest_str:
if self.propagation_node_set(dest_str) and self.config_set_callback is not None:
self.config_set_callback("propagation_node_active", dest_str)
def propagation_node_hash(self):
try:
return bytes.fromhex(self.propagation_node_active)
except:
return None
def propagation_node_hash_str(self):
if self.propagation_node_active:
return self.propagation_node_active
else:
return ""
def client_connected(self, link):
@ -521,6 +578,43 @@ class lxmf_connection:
log("- App Data: " + message.app_data, LOG_DEBUG)
class lxmf_connection_propagation():
def __init__(self, owner, aspect_filter=None):
self.owner = owner
self.aspect_filter = aspect_filter
EMITTED_DELTA_GRACE = 300
EMITTED_DELTA_IGNORE = 10
def received_announce(self, destination_hash, announced_identity, app_data):
if app_data == None:
return
if len(app_data) == 0:
return
try:
unpacked = umsgpack.unpackb(app_data)
node_active = unpacked[0]
emitted = unpacked[1]
hop_count = RNS.Transport.hops_to(destination_hash)
age = time.time() - emitted
if age < 0:
if age < -1*PropDetector.EMITTED_DELTA_GRACE:
return
log("LXMF - Received an propagation node announce from "+RNS.prettyhexrep(destination_hash)+": "+str(age)+" seconds ago, "+str(hop_count)+" hops away", LOG_INFO)
if self.owner.propagation_node_active == None:
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
else:
prev_hop_count = RNS.Transport.hops_to(self.owner.propagation_node_hash())
if hop_count <= prev_hop_count:
self.owner.propagation_node_update(RNS.hexrep(destination_hash, False))
except:
return
##############################################################################################################
# LXMF Functions
@ -669,6 +763,36 @@ def config_getoption(config, section, key, default=False, lng_key=""):
#### Config - Set #####
def config_set(key=None, value=""):
global PATH
try:
file = PATH + "/config.cfg.owr"
if os.path.isfile(file):
fh = open(file,'r')
data = fh.read()
fh.close()
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
fh = open(file,'w')
fh.write(data)
fh.close()
file = PATH + "/config.cfg"
if os.path.isfile(file):
fh = open(file,'r')
data = fh.read()
fh.close()
data = re.sub(r'^#?'+key+'( +)?=( +)?(\w+)?', key+" = "+value, data, count=1, flags=re.MULTILINE)
fh = open(file,'w')
fh.write(data)
fh.close()
except:
pass
#### Config - Read #####
def config_read(file=None, file_override=None):
global CONFIG
@ -762,15 +886,15 @@ def config_default(file=None, file_override=None):
# Value convert
def val_to_bool(val):
def val_to_bool(val, fallback_true=True, fallback_false=False):
if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up":
return True
elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down":
return False
elif val != "":
return True
return fallback_true
else:
return False
return fallback_false
##############################################################################################################
@ -935,6 +1059,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
else:
config_propagation_node = None
if CONFIG.has_option("lxmf", "propagation_node_active"):
config_propagation_node_active = CONFIG["lxmf"]["propagation_node_active"]
else:
config_propagation_node_active = None
if path is None:
path = PATH
@ -943,9 +1072,12 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
destination_name=CONFIG["lxmf"]["destination_name"],
destination_type=CONFIG["lxmf"]["destination_type"],
display_name=CONFIG["lxmf"]["display_name"],
announce_hidden=CONFIG["lxmf"].getboolean("announce_hidden"),
send_delay=CONFIG["lxmf"]["send_delay"],
desired_method=CONFIG["lxmf"]["desired_method"],
propagation_node=config_propagation_node,
propagation_node_auto=CONFIG["lxmf"].getboolean("propagation_node_auto"),
propagation_node_active=config_propagation_node_active,
try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"),
announce_startup=CONFIG["lxmf"].getboolean("announce_startup"),
announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"],
@ -959,6 +1091,7 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False)
LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback)
LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback)
LXMF_CONNECTION.register_config_set_callback(config_set)
log("LXMF - Connected", LOG_DEBUG)
@ -1053,7 +1186,13 @@ display_name = Echo Test
desired_method = direct #direct/propagated
# Propagation node address/hash.
#propagation_node =
propagation_node =
# Set propagation node automatically.
propagation_node_auto = True
# Current propagation node (Automatically set by the software).
propagation_node_active =
# Try to deliver a message via the LXMF propagation network,
# if a direct delivery to the recipient is not possible.
@ -1069,6 +1208,10 @@ announce_startup_delay = 0 #Seconds
announce_periodic = Yes
announce_periodic_interval = 360 #Minutes
# The announce is hidden for client applications
# but is still used for the routing tables.
announce_hidden = No
# Some waiting time after message send
# for LXMF/Reticulum processing.
send_delay = 0 #Seconds