2022-10-21 11:58:11 -04:00
#!/usr/bin/env python3
##############################################################################################################
#
# Copyright (c) 2022 Sebastian Obele / obele.eu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# This software uses the following software-parts:
# Reticulum, LXMF, NomadNet / Copyright (c) 2016-2022 Mark Qvist / unsigned.io / MIT License
#
##############################################################################################################
##############################################################################################################
# Include
#### System ####
import sys
import os
import time
import argparse
#### JSON ####
import json
import pickle
#### String ####
import string
#### Other ####
import random
import secrets
#### Process ####
import signal
import threading
#### Reticulum, LXMF ####
# Install: pip3 install rns lxmf
# Source: https://markqvist.github.io
import RNS
import LXMF
import RNS . vendor . umsgpack as umsgpack
##############################################################################################################
# Globals
#### Global Variables - Configuration ####
NAME = " LXMF Ping "
DESCRIPTION = " Periodically sends pings/messages and evaluates the status "
VERSION = " 0.0.1 (2022-10-21) "
COPYRIGHT = " (c) 2022 Sebastian Obele / obele.eu "
PATH = os . path . expanduser ( " ~ " ) + " /. " + os . path . splitext ( os . path . basename ( __file__ ) ) [ 0 ]
PATH_RNS = None
#### Global Variables - System (Not changeable) ####
DATA = None
RNS_CONNECTION = None
LXMF_CONNECTION = None
##############################################################################################################
# LXMF Class
class lxmf_connection :
message_received_callback = None
message_notification_callback = None
message_notification_success_callback = None
message_notification_failed_callback = None
def __init__ ( self , storage_path = None , identity_file = " identity " , identity = None , destination_name = " lxmf " , destination_type = " delivery " , display_name = " " , 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 ) :
self . storage_path = storage_path
self . identity_file = identity_file
self . identity = identity
self . destination_name = destination_name
self . destination_type = destination_type
self . aspect_filter = self . destination_name + " . " + self . destination_type
self . display_name = display_name
self . send_delay = int ( send_delay )
if desired_method == " propagated " or desired_method == " PROPAGATED " :
self . desired_method_direct = False
else :
self . desired_method_direct = True
self . propagation_node = propagation_node
self . try_propagation_on_fail = try_propagation_on_fail
self . announce_startup = announce_startup
self . announce_startup_delay = int ( announce_startup_delay )
self . announce_periodic = announce_periodic
self . announce_periodic_interval = int ( announce_periodic_interval )
self . sync_startup = sync_startup
self . sync_startup_delay = int ( sync_startup_delay )
self . sync_limit = int ( sync_limit )
self . sync_periodic = sync_periodic
self . sync_periodic_interval = int ( sync_periodic_interval )
if not os . path . isdir ( self . storage_path ) :
os . makedirs ( self . storage_path )
log ( " LXMF - Storage path was created " , LOG_NOTICE )
log ( " LXMF - Storage path: " + self . storage_path , LOG_INFO )
if self . identity :
log ( " LXMF - Using existing Primary Identity %s " % ( str ( self . identity ) ) )
else :
if not self . identity_file :
self . identity_file = " identity "
self . identity_path = self . storage_path + " / " + self . identity_file
if os . path . isfile ( self . identity_path ) :
try :
self . identity = RNS . Identity . from_file ( self . identity_path )
if self . identity != None :
log ( " LXMF - Loaded Primary Identity %s from %s " % ( str ( self . identity ) , self . identity_path ) )
else :
log ( " LXMF - Could not load the Primary Identity from " + self . identity_path , LOG_ERROR )
except Exception as e :
log ( " LXMF - Could not load the Primary Identity from " + self . identity_path , LOG_ERROR )
log ( " LXMF - The contained exception was: %s " % ( str ( e ) ) , LOG_ERROR )
else :
try :
log ( " LXMF - No Primary Identity file found, creating new... " )
self . identity = RNS . Identity ( )
self . identity . to_file ( self . identity_path )
log ( " LXMF - Created new Primary Identity %s " % ( str ( self . identity ) ) )
except Exception as e :
log ( " LXMF - Could not create and save a new Primary Identity " , LOG_ERROR )
log ( " LXMF - The contained exception was: %s " % ( str ( e ) ) , LOG_ERROR )
self . message_router = LXMF . LXMRouter ( identity = self . identity , storagepath = self . storage_path )
self . destination = self . message_router . register_delivery_identity ( self . identity , display_name = self . display_name )
self . message_router . register_delivery_callback ( self . process_lxmf_message_propagated )
if self . display_name == " " :
self . display_name = RNS . prettyhexrep ( self . destination_hash ( ) )
self . destination . set_default_app_data ( self . display_name . encode ( " utf-8 " ) )
self . destination . set_proof_strategy ( RNS . Destination . PROVE_ALL )
RNS . Identity . remember ( packet_hash = None , destination_hash = self . destination . hash , public_key = self . identity . get_public_key ( ) , app_data = None )
log ( " LXMF - Identity: " + str ( self . identity ) , LOG_INFO )
log ( " LXMF - Destination: " + str ( self . destination ) , LOG_INFO )
log ( " LXMF - Hash: " + RNS . prettyhexrep ( self . destination_hash ( ) ) , LOG_INFO )
self . destination . set_link_established_callback ( self . client_connected )
self . autoselect_propagation_node ( )
if self . announce_startup or self . announce_periodic :
self . announce ( True )
if self . sync_startup or self . sync_periodic :
self . sync ( True )
def register_announce_callback ( self , handler_function ) :
self . announce_callback = handler_function ( self . aspect_filter )
RNS . Transport . register_announce_handler ( self . announce_callback )
def register_message_received_callback ( self , handler_function ) :
self . message_received_callback = handler_function
def register_message_notification_callback ( self , handler_function ) :
self . message_notification_callback = handler_function
def register_message_notification_success_callback ( self , handler_function ) :
self . message_notification_success_callback = handler_function
def register_message_notification_failed_callback ( self , handler_function ) :
self . message_notification_failed_callback = handler_function
def destination_hash ( self ) :
return self . destination . hash
def destination_hash_str ( self ) :
return RNS . hexrep ( self . destination . hash , False )
def destination_check ( self , destination ) :
if type ( destination ) is not bytes :
if len ( destination ) == ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) + 2 :
destination = destination [ 1 : - 1 ]
if len ( destination ) != ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) :
log ( " LXMF - Destination length is invalid " , LOG_ERROR )
return False
try :
destination = bytes . fromhex ( destination )
except Exception as e :
log ( " LXMF - Destination is invalid " , LOG_ERROR )
return False
return True
def destination_correct ( self , destination ) :
if type ( destination ) is not bytes :
if len ( destination ) == ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) + 2 :
destination = destination [ 1 : - 1 ]
if len ( destination ) != ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) :
return " "
try :
destination_bytes = bytes . fromhex ( destination )
return destination
except Exception as e :
return " "
return " "
2022-10-25 03:02:42 -04:00
def send ( self , destination , content = " " , title = " " , fields = None , timestamp = None , app_data = " " ) :
2022-10-21 11:58:11 -04:00
if type ( destination ) is not bytes :
if len ( destination ) == ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) + 2 :
destination = destination [ 1 : - 1 ]
if len ( destination ) != ( ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2 ) :
log ( " LXMF - Destination length is invalid " , LOG_ERROR )
return
try :
destination = bytes . fromhex ( destination )
except Exception as e :
log ( " LXMF - Destination is invalid " , LOG_ERROR )
return
destination_identity = RNS . Identity . recall ( destination )
destination = RNS . Destination ( destination_identity , RNS . Destination . OUT , RNS . Destination . SINGLE , self . destination_name , self . destination_type )
self . send_message ( destination , self . destination , content , title , fields , timestamp , app_data )
2022-10-25 03:02:42 -04:00
def send_message ( self , destination , source , content = " " , title = " " , fields = None , timestamp = None , app_data = " " ) :
2022-10-21 11:58:11 -04:00
if self . desired_method_direct :
desired_method = LXMF . LXMessage . DIRECT
else :
desired_method = LXMF . LXMessage . PROPAGATED
message = LXMF . LXMessage ( destination , source , content , title = title , desired_method = desired_method )
if fields is not None :
message . fields = fields
if timestamp is not None :
message . timestamp = timestamp
message . app_data = app_data
self . message_method ( message )
self . log_message ( message , " LXMF - Message send " )
message . register_delivery_callback ( self . message_notification )
message . register_failed_callback ( self . message_notification )
if self . message_router . get_outbound_propagation_node ( ) != None :
message . try_propagation_on_fail = self . try_propagation_on_fail
try :
self . message_router . handle_outbound ( message )
time . sleep ( self . send_delay )
except Exception as e :
log ( " LXMF - Could not send message " + str ( message ) , LOG_ERROR )
log ( " LXMF - The contained exception was: " + str ( e ) , LOG_ERROR )
return
def message_notification ( self , message ) :
self . message_method ( message )
if self . message_notification_callback is not None :
self . message_notification_callback ( message )
if message . state == LXMF . LXMessage . FAILED and hasattr ( message , " try_propagation_on_fail " ) and message . try_propagation_on_fail :
self . log_message ( message , " LXMF - Delivery receipt (failed) Retrying as propagated message " )
message . try_propagation_on_fail = None
message . delivery_attempts = 0
del message . next_delivery_attempt
message . packed = None
message . desired_method = LXMF . LXMessage . PROPAGATED
self . message_router . handle_outbound ( message )
elif message . state == LXMF . LXMessage . FAILED :
self . log_message ( message , " LXMF - Delivery receipt (failed) " )
if self . message_notification_failed_callback is not None :
self . message_notification_failed_callback ( message )
else :
self . log_message ( message , " LXMF - Delivery receipt (success) " )
if self . message_notification_success_callback is not None :
self . message_notification_success_callback ( message )
def message_method ( self , message ) :
if message . desired_method == LXMF . LXMessage . DIRECT :
message . desired_method_str = " direct "
elif message . desired_method == LXMF . LXMessage . PROPAGATED :
message . desired_method_str = " propagated "
def announce ( self , initial = False ) :
announce_timer = None
if self . announce_periodic and self . announce_periodic_interval > 0 :
announce_timer = threading . Timer ( self . announce_periodic_interval * 60 , self . announce )
announce_timer . daemon = True
announce_timer . start ( )
if initial :
if self . announce_startup :
if self . announce_startup_delay > 0 :
if announce_timer is not None :
announce_timer . cancel ( )
announce_timer = threading . Timer ( self . announce_startup_delay , self . announce )
announce_timer . daemon = True
announce_timer . start ( )
else :
self . announce_now ( )
return
self . announce_now ( )
def announce_now ( self , app_data = None ) :
if app_data :
self . destination . announce ( app_data . encode ( " utf-8 " ) )
log ( " LXMF - Announced: " + RNS . prettyhexrep ( self . destination_hash ( ) ) + " : " + app_data , LOG_DEBUG )
else :
self . destination . announce ( )
log ( " LXMF - Announced: " + RNS . prettyhexrep ( self . destination_hash ( ) ) + " : " + self . display_name , LOG_DEBUG )
def sync ( self , initial = False ) :
sync_timer = None
if self . sync_periodic and self . sync_periodic_interval > 0 :
sync_timer = threading . Timer ( self . sync_periodic_interval * 60 , self . sync )
sync_timer . daemon = True
sync_timer . start ( )
if initial :
if self . sync_startup :
if self . sync_startup_delay > 0 :
if sync_timer is not None :
sync_timer . cancel ( )
sync_timer = threading . Timer ( self . sync_startup_delay , self . sync )
sync_timer . daemon = True
sync_timer . start ( )
else :
self . sync_now ( self . sync_limit )
return
self . sync_now ( self . sync_limit )
def sync_now ( self , limit = None ) :
if self . message_router . get_outbound_propagation_node ( ) is not None :
if self . message_router . propagation_transfer_state == LXMF . LXMRouter . PR_IDLE or self . message_router . propagation_transfer_state == LXMF . LXMRouter . PR_COMPLETE :
log ( " LXMF - Message sync requested from propagation node " + RNS . prettyhexrep ( self . message_router . get_outbound_propagation_node ( ) ) + " for " + str ( self . identity ) )
self . message_router . request_messages_from_propagation_node ( self . identity , max_messages = limit )
return True
else :
return False
else :
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
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 )
def client_connected ( self , link ) :
log ( " LXMF - Client connected " + str ( link ) , LOG_EXTREME )
link . set_resource_strategy ( RNS . Link . ACCEPT_ALL )
link . set_resource_concluded_callback ( self . resource_concluded )
link . set_packet_callback ( self . packet_received )
def packet_received ( self , lxmf_bytes , packet ) :
log ( " LXMF - Single packet delivered " + str ( packet ) , LOG_EXTREME )
self . process_lxmf_message_bytes ( lxmf_bytes )
def resource_concluded ( self , resource ) :
log ( " LXMF - Resource data transfer (multi packet) delivered " + str ( resource . file ) , LOG_EXTREME )
if resource . status == RNS . Resource . COMPLETE :
lxmf_bytes = resource . data . read ( )
self . process_lxmf_message_bytes ( lxmf_bytes )
else :
log ( " LXMF - Received resource message is not complete " , LOG_EXTREME )
def process_lxmf_message_bytes ( self , lxmf_bytes ) :
try :
message = LXMF . LXMessage . unpack_from_bytes ( lxmf_bytes )
except Exception as e :
log ( " LXMF - Could not assemble LXMF message from received data " , LOG_ERROR )
log ( " LXMF - The contained exception was: " + str ( e ) , LOG_ERROR )
return
message . desired_method = LXMF . LXMessage . DIRECT
self . message_method ( message )
self . log_message ( message , " LXMF - Message received " )
if self . message_received_callback is not None :
log ( " LXMF - Call to registered message received callback " , LOG_DEBUG )
self . message_received_callback ( message )
else :
log ( " LXMF - No message received callback registered " , LOG_DEBUG )
def process_lxmf_message_propagated ( self , message ) :
message . desired_method = LXMF . LXMessage . PROPAGATED
self . message_method ( message )
self . log_message ( message , " LXMF - Message received " )
if self . message_received_callback is not None :
log ( " LXMF - Call to registered message received callback " , LOG_DEBUG )
self . message_received_callback ( message )
else :
log ( " LXMF - No message received callback registered " , LOG_DEBUG )
def log_message ( self , message , message_tag = " LXMF - Message log " ) :
if message . signature_validated :
signature_string = " Validated "
else :
if message . unverified_reason == LXMF . LXMessage . SIGNATURE_INVALID :
signature_string = " Invalid signature "
elif message . unverified_reason == LXMF . LXMessage . SOURCE_UNKNOWN :
signature_string = " Cannot verify, source is unknown "
else :
signature_string = " Signature is invalid, reason undetermined "
title = message . title . decode ( ' utf-8 ' )
content = message . content . decode ( ' utf-8 ' )
fields = message . fields
log ( message_tag + " : " , LOG_DEBUG )
log ( " - Date/Time: " + time . strftime ( " % Y- % m- %d % H: % M: % S " , time . localtime ( message . timestamp ) ) , LOG_DEBUG )
log ( " - Title: " + title , LOG_DEBUG )
log ( " - Content: " + content , LOG_DEBUG )
log ( " - Fields: " + str ( fields ) , LOG_DEBUG )
log ( " - Size: " + str ( len ( title ) + len ( content ) + len ( title ) + len ( pickle . dumps ( fields ) ) ) + " bytes " , LOG_DEBUG )
log ( " - Source: " + RNS . prettyhexrep ( message . source_hash ) , LOG_DEBUG )
log ( " - Destination: " + RNS . prettyhexrep ( message . destination_hash ) , LOG_DEBUG )
log ( " - Signature: " + signature_string , LOG_DEBUG )
log ( " - Attempts: " + str ( message . delivery_attempts ) , LOG_DEBUG )
if hasattr ( message , " desired_method_str " ) :
log ( " - Method: " + message . desired_method_str + " ( " + str ( message . desired_method ) + " ) " , LOG_DEBUG )
else :
log ( " - Method: " + str ( message . desired_method ) , LOG_DEBUG )
if hasattr ( message , " app_data " ) :
log ( " - App Data: " + message . app_data , LOG_DEBUG )
##############################################################################################################
# LXMF Functions
#### LXMF - Success ####
def lxmf_success ( message ) :
global DATA
key = RNS . hexrep ( message . destination_hash , False )
if not key in DATA :
key = " . "
if not key in DATA :
return
DATA [ key ] [ " count_success " ] = DATA [ key ] [ " count_success " ] + 1
timestamp = round ( float ( time . time ( ) ) - float ( message . timestamp ) , 4 )
if DATA [ key ] [ " time_min " ] == 0 or DATA [ key ] [ " time_min " ] > timestamp :
DATA [ key ] [ " time_min " ] = timestamp
if DATA [ key ] [ " time_max " ] == 0 or DATA [ key ] [ " time_max " ] < timestamp :
DATA [ key ] [ " time_max " ] = timestamp
DATA [ key ] [ " time " ] = DATA [ key ] [ " time " ] + timestamp
DATA [ key ] [ " time_avg " ] = round ( DATA [ key ] [ " time " ] / DATA [ key ] [ " count_success " ] , 4 )
count = str ( message . content )
if " # " in count :
count = count . split ( " # " , 1 ) [ 1 ]
count = count . split ( " " , 1 ) [ 0 ]
else :
count = " "
print ( " Destination: " + str ( key ) + " | #: " + str ( count ) + " | Messages delivered: " + str ( DATA [ key ] [ " count_success " ] ) + " / " + str ( DATA [ key ] [ " count " ] ) + " ( " + str ( round ( 100 / DATA [ key ] [ " count " ] * DATA [ key ] [ " count_success " ] , 2 ) ) + " % ) | Time (min / max / avg): " + str ( DATA [ key ] [ " time_min " ] ) + " / " + str ( DATA [ key ] [ " time_max " ] ) + " / " + str ( DATA [ key ] [ " time_avg " ] ) + " | Info: Success " )
#### LXMF - Failed ####
def lxmf_failed ( message ) :
global DATA
key = RNS . hexrep ( message . destination_hash , False )
if not key in DATA :
key = " . "
if not key in DATA :
return
DATA [ key ] [ " count_failed " ] = DATA [ key ] [ " count_failed " ] + 1
count = str ( message . content )
if " # " in count :
count = count . split ( " # " , 1 ) [ 1 ]
count = count . split ( " " , 1 ) [ 0 ]
else :
count = " "
print ( " Destination: " + str ( key ) + " | #: " + str ( count ) + " | Messages delivered: " + str ( DATA [ key ] [ " count_success " ] ) + " / " + str ( DATA [ key ] [ " count " ] ) + " ( " + str ( round ( 100 / DATA [ key ] [ " count " ] * DATA [ key ] [ " count_success " ] , 2 ) ) + " % ) | Time (min / max / avg): " + str ( DATA [ key ] [ " time_min " ] ) + " / " + str ( DATA [ key ] [ " time_max " ] ) + " / " + str ( DATA [ key ] [ " time_avg " ] ) + " | Info: Failed " )
##############################################################################################################
# Value convert
def val_to_bool ( val ) :
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
else :
return False
##############################################################################################################
# Log
LOG_FORCE = - 1
LOG_CRITICAL = 0
LOG_ERROR = 1
LOG_WARNING = 2
LOG_NOTICE = 3
LOG_INFO = 4
LOG_VERBOSE = 5
LOG_DEBUG = 6
LOG_EXTREME = 7
LOG_LEVEL = LOG_NOTICE
LOG_LEVEL_SERVICE = LOG_NOTICE
LOG_TIMEFMT = " % Y- % m- %d % H: % M: % S "
LOG_MAXSIZE = 5 * 1024 * 1024
LOG_PREFIX = " "
LOG_SUFFIX = " "
LOG_FILE = " "
def log ( text , level = 3 , file = None ) :
if not LOG_LEVEL :
return
if LOG_LEVEL > = level :
name = " Unknown "
if ( level == LOG_FORCE ) :
name = " "
if ( level == LOG_CRITICAL ) :
name = " Critical "
if ( level == LOG_ERROR ) :
name = " Error "
if ( level == LOG_WARNING ) :
name = " Warning "
if ( level == LOG_NOTICE ) :
name = " Notice "
if ( level == LOG_INFO ) :
name = " Info "
if ( level == LOG_VERBOSE ) :
name = " Verbose "
if ( level == LOG_DEBUG ) :
name = " Debug "
if ( level == LOG_EXTREME ) :
name = " Extra "
if not isinstance ( text , str ) :
text = str ( text )
text = " [ " + time . strftime ( LOG_TIMEFMT , time . localtime ( time . time ( ) ) ) + " ] [ " + name + " ] " + LOG_PREFIX + text + LOG_SUFFIX
if file == None and LOG_FILE != " " :
file = LOG_FILE
if file == None :
print ( text )
else :
try :
file_handle = open ( file , " a " )
file_handle . write ( text + " \n " )
file_handle . close ( )
if os . path . getsize ( file ) > LOG_MAXSIZE :
file_prev = file + " .1 "
if os . path . isfile ( file_prev ) :
os . unlink ( file_prev )
os . rename ( file , file_prev )
except :
return
##############################################################################################################
# System
#### Panic #####
def panic ( ) :
sys . exit ( 255 )
#### Exit #####
def exit ( ) :
sys . exit ( 0 )
##############################################################################################################
# Setup/Start
#### Setup #####
def setup ( path = None , path_rns = None , path_log = None , loglevel = None , dest = " " , interval = 1 , size = 128 , count = 0 , inst = 1 ) :
global DATA
global PATH
global PATH_RNS
global LOG_LEVEL
global LOG_FILE
global RNS_CONNECTION
global LXMF_CONNECTION
if path is not None :
if path . endswith ( " / " ) :
path = path [ : - 1 ]
PATH = path
if path_rns is not None :
if path_rns . endswith ( " / " ) :
path_rns = path_rns [ : - 1 ]
PATH_RNS = path_rns
if loglevel is not None :
LOG_LEVEL = loglevel
rns_loglevel = loglevel
else :
rns_loglevel = None
RNS_CONNECTION = RNS . Reticulum ( configdir = PATH_RNS , loglevel = rns_loglevel )
print ( " ............................................................................... " )
print ( " Name: " + NAME + " - " + DESCRIPTION )
print ( " Program File: " + __file__ )
print ( " Version: " + VERSION )
print ( " Copyright: " + COPYRIGHT )
print ( " ............................................................................... " )
log ( " LXMF - Connecting ... " , LOG_DEBUG )
if path is None :
path = PATH
LXMF_CONNECTION = lxmf_connection ( storage_path = path )
LXMF_CONNECTION . register_message_notification_success_callback ( lxmf_success )
LXMF_CONNECTION . register_message_notification_failed_callback ( lxmf_failed )
log ( " LXMF - Connected " , LOG_DEBUG )
log ( " ............................................................................... " , LOG_FORCE )
log ( " LXMF - Address: " + RNS . prettyhexrep ( LXMF_CONNECTION . destination_hash ( ) ) , LOG_FORCE )
log ( " ............................................................................... " , LOG_FORCE )
DATA = { }
destinations = dest . split ( " , " )
for key in destinations :
DATA [ key ] = { }
DATA [ key ] [ " count " ] = 0
DATA [ key ] [ " count_success " ] = 0
DATA [ key ] [ " count_failed " ] = 0
DATA [ key ] [ " time " ] = 0
DATA [ key ] [ " time_min " ] = 0
DATA [ key ] [ " time_max " ] = 0
DATA [ key ] [ " time_avg " ] = 0
count_current = 0
while True :
if count == 0 or count_current < count :
count_current = count_current + 1
letters = string . ascii_lowercase
content = ' ' . join ( random . choice ( letters ) for i in range ( size ) )
for key in DATA :
DATA [ key ] [ " count " ] = DATA [ key ] [ " count " ] + 1
content = " # " + str ( DATA [ key ] [ " count " ] ) + " " + content
content = content [ : size ]
if key == " . " :
2022-10-22 03:04:32 -04:00
LXMF_CONNECTION . send ( secrets . token_hex ( nbytes = 10 ) , content )
2022-10-21 11:58:11 -04:00
else :
2022-10-22 03:04:32 -04:00
LXMF_CONNECTION . send ( key , content )
2022-10-21 11:58:11 -04:00
print ( " Destination: " + str ( key ) + " | #: " + str ( DATA [ key ] [ " count " ] ) + " | Messages delivered: " + str ( DATA [ key ] [ " count_success " ] ) + " / " + str ( DATA [ key ] [ " count " ] ) + " ( " + str ( round ( 100 / DATA [ key ] [ " count " ] * DATA [ key ] [ " count_success " ] , 2 ) ) + " % ) | Time (min / max / avg): " + str ( DATA [ key ] [ " time_min " ] ) + " / " + str ( DATA [ key ] [ " time_max " ] ) + " / " + str ( DATA [ key ] [ " time_avg " ] ) + " | Info: Sending/Queued " )
time . sleep ( interval )
#### Start ####
def main ( ) :
try :
description = NAME + " - " + DESCRIPTION
parser = argparse . ArgumentParser ( description = description )
parser . add_argument ( " -p " , " --path " , action = " store " , type = str , default = None , help = " Path to alternative config directory " )
parser . add_argument ( " -pr " , " --path_rns " , action = " store " , type = str , default = None , help = " Path to alternative Reticulum config directory " )
parser . add_argument ( " -pl " , " --path_log " , action = " store " , type = str , default = None , help = " Path to alternative log directory " )
parser . add_argument ( " -l " , " --loglevel " , action = " store " , type = int , default = LOG_LEVEL )
parser . add_argument ( " -d " , " --dest " , action = " store " , required = True , type = str , default = None , help = " Single destination hash or ,-separated list with destination hashs or . for random destination " )
parser . add_argument ( " -t " , " --time " , action = " store " , type = float , default = 1 , help = " Time between messages in seconds " )
parser . add_argument ( " -s " , " --size " , action = " store " , type = int , default = 128 , help = " Size (lenght) of the message content " )
parser . add_argument ( " -c " , " --count " , action = " store " , type = float , default = 0 , help = " Maximum message send count (0=no end) " )
parser . add_argument ( " -i " , " --inst " , action = " store " , type = int , default = 1 , help = " Parallel instances (different sender addresses) " )
params = parser . parse_args ( )
setup ( path = params . path , path_rns = params . path_rns , path_log = params . path_log , loglevel = params . loglevel , dest = params . dest , interval = params . time , size = params . size , count = params . count , inst = params . inst )
except KeyboardInterrupt :
print ( " Terminated by CTRL-C " )
exit ( )
##############################################################################################################
# Init
if __name__ == " __main__ " :
main ( )