Compare commits

...

13 Commits

Author SHA1 Message Date
Lucas
f953c73076
Merge acd893a6e6 into 4edacf82f3 2024-05-03 16:28:26 +01:00
Mark Qvist
4edacf82f3 Merge branch 'master' of github.com:markqvist/Reticulum 2024-05-03 16:22:37 +02:00
Mark Qvist
0b0a3313c5 Multicast address type modifications 2024-05-01 15:49:48 +02:00
nothingbutlucas
acd893a6e6
refactor: Replace string concatenation with joins
Same as previous commits. "".join() is better that "string" + "another_string" + "another_string_2", because strings are immutable.
Also, changed for loops with list comprehension. This is much faster and would improve performance.

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 15:37:10 -03:00
nothingbutlucas
c032a2e438
refactor(if/elif): Add elif's on if "questions"
The variable sorting can have only 1 value at a time. So does not make sense to ask on every if statement the value of sorting. If one statement is True, then, the other would be False.

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 12:11:43 -03:00
nothingbutlucas
1f6a494e14
refactor: Change loop for dictionary comprehension
This will improve code execution because nested loops are slower than list/dictionary comprehensions.

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 12:08:23 -03:00
nothingbutlucas
059c2381b5
refactor(if/else/return): Remove indentation
This may seems like a lot, but the only think that I change here was the if/else block. I move the else statement at the beginning and if that condition is true, will exit the function. This is just for code clarity, I think that this change should not improve the speed or performance of the code.

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 11:52:02 -03:00
nothingbutlucas
541d3cde48
style: Format the code a little bit
This will remove the unnecesary import of time, and a little bit of format

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 11:28:00 -03:00
nothingbutlucas
87ff1808a2
refactor(main): Reduce complexity of main function
This will make it easier for us to maintain and understand the main function. Also deleting duplicated code

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 11:04:26 -03:00
nothingbutlucas
8d1a000378
refactor: Change method for string concatenations
Strings in Python are immutable so, the "+" operation means that we need to create the string again and attaching the old content. Per "+". We can replace this by using join() or built-in functions or arguments of functions, like "sep" in print()

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 10:30:07 -03:00
nothingbutlucas
794a5f4401
refactor: Remove unused code
Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 00:34:08 -03:00
nothingbutlucas
7159e6a523
style: Change comparations according PEP8
According PEP8 this is the recommended way to do it. I'm 99% sure that this does not speed up the code, but we are comparing identity, not equality, so "is" and "is not" must be used.

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 00:32:11 -03:00
nothingbutlucas
cf41187e2f
refactor: Change for loop to list comprehensions
Not only more concise and shorter, it's notably faster

Signed-off-by: nothingbutlucas <69118979+nothingbutlucas@users.noreply.github.com>
2024-04-24 00:24:22 -03:00
6 changed files with 283 additions and 298 deletions

View File

@ -137,6 +137,8 @@ class AutoInterface(Interface):
self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE
elif str(multicast_address_type).lower() == "permanent": elif str(multicast_address_type).lower() == "permanent":
self.multicast_address_type = AutoInterface.MULTICAST_PERMANENT_ADDRESS_TYPE self.multicast_address_type = AutoInterface.MULTICAST_PERMANENT_ADDRESS_TYPE
else:
self.multicast_address_type = AutoInterface.MULTICAST_TEMPORARY_ADDRESS_TYPE
if data_port == None: if data_port == None:
self.data_port = AutoInterface.DEFAULT_DATA_PORT self.data_port = AutoInterface.DEFAULT_DATA_PORT

View File

@ -37,26 +37,48 @@ SIG_EXT = "rsg"
ENCRYPT_EXT = "rfe" ENCRYPT_EXT = "rfe"
CHUNK_SIZE = 16*1024*1024 CHUNK_SIZE = 16*1024*1024
def spin(until=None, msg=None, timeout=None):
def spin(until=None, msg="", timeout=None):
i = 0 i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠" syms = "⢄⢂⢁⡁⡈⡐⡠"
if timeout != None: if timeout is not None:
timeout = time.time()+timeout timeout = time.time()+timeout
print(msg+" ", end=" ") print(msg, end=" ")
while (timeout == None or time.time()<timeout) and not until(): while (timeout is None or time.time()<timeout) and not until():
time.sleep(0.1) time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="") print(("\b\b", syms[i]), end=" ", sep="")
sys.stdout.flush() sys.stdout.flush()
i = (i+1)%len(syms) i = (i+1)%len(syms)
print("\r"+" "*len(msg)+" \r", end="") print("\r", *len(msg), "\r", end="", sep="")
if timeout != None and time.time() > timeout: if timeout is not None and time.time() > timeout:
return False return False
else: else:
return True return True
def get_keys(args: argparse.Namespace, identity) -> None:
"""Get public or/and private keys"""
if args.base64:
RNS.log("Public Key : " + base64.urlsafe_b64encode(identity.get_public_key()).decode("utf-8"))
elif args.base32:
RNS.log("Public Key : " + base64.b32encode(identity.get_public_key()).decode("utf-8"))
else:
RNS.log("Public Key : " + RNS.hexrep(identity.get_public_key(), delimit=False))
if identity.prv:
if args.print_private:
if args.base64:
RNS.log("Private Key : " + base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Private Key : " + base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Private Key : " + RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Private Key : Hidden")
def main(): def main():
try: try:
parser = argparse.ArgumentParser(description="Reticulum Identity & Encryption Utility") parser = argparse.ArgumentParser(description="Reticulum Identity & Encryption Utility")
@ -82,7 +104,7 @@ def main():
parser.add_argument("-w", "--write", metavar="file", action="store", default=None, help="output file path", type=str) parser.add_argument("-w", "--write", metavar="file", action="store", default=None, help="output file path", type=str)
parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files") parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files")
parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # "read input from STDIN instead of file" parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # "read input from STDIN instead of file"
parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file", parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file",
parser.add_argument("-R", "--request", action="store_true", default=False, help="request unknown Identities from the network") parser.add_argument("-R", "--request", action="store_true", default=False, help="request unknown Identities from the network")
parser.add_argument("-t", action="store", metavar="seconds", type=float, help="identity request timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT) parser.add_argument("-t", action="store", metavar="seconds", type=float, help="identity request timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
@ -93,14 +115,10 @@ def main():
parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output") parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output")
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__)) parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
args = parser.parse_args() args = parser.parse_args()
ops = 0; ops = sum(1 for t in [args.encrypt, args.decrypt, args.validate, args.sign] if t)
for t in [args.encrypt, args.decrypt, args.validate, args.sign]:
if t:
ops += 1
if ops > 1: if ops > 1:
RNS.log("This utility currently only supports one of the encrypt, decrypt, sign or verify operations per invocation", RNS.LOG_ERROR) RNS.log("This utility currently only supports one of the encrypt, decrypt, sign or verify operations per invocation", RNS.LOG_ERROR)
exit(1) exit(1)
@ -134,22 +152,8 @@ def main():
exit(42) exit(42)
RNS.log("Identity imported") RNS.log("Identity imported")
if args.base64:
RNS.log("Public Key : "+base64.urlsafe_b64encode(identity.get_public_key()).decode("utf-8")) get_keys(args, identity)
elif args.base32:
RNS.log("Public Key : "+base64.b32encode(identity.get_public_key()).decode("utf-8"))
else:
RNS.log("Public Key : "+RNS.hexrep(identity.get_public_key(), delimit=False))
if identity.prv:
if args.print_private:
if args.base64:
RNS.log("Private Key : "+base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Private Key : "+base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Private Key : "+RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Private Key : Hidden")
if args.write: if args.write:
try: try:
@ -179,7 +183,7 @@ def main():
quietness = args.quiet quietness = args.quiet
if verbosity != 0 or quietness != 0: if verbosity != 0 or quietness != 0:
targetloglevel = targetloglevel+verbosity-quietness targetloglevel = targetloglevel+verbosity-quietness
# Start Reticulum # Start Reticulum
reticulum = RNS.Reticulum(configdir=args.config, loglevel=targetloglevel) reticulum = RNS.Reticulum(configdir=args.config, loglevel=targetloglevel)
RNS.compact_log_fmt = True RNS.compact_log_fmt = True
@ -208,7 +212,7 @@ def main():
destination_hash = bytes.fromhex(identity_str) destination_hash = bytes.fromhex(identity_str)
identity = RNS.Identity.recall(destination_hash) identity = RNS.Identity.recall(destination_hash)
if identity == None: if identity is None:
if not args.request: if not args.request:
RNS.log("Could not recall Identity for "+RNS.prettyhexrep(destination_hash)+".", RNS.LOG_ERROR) RNS.log("Could not recall Identity for "+RNS.prettyhexrep(destination_hash)+".", RNS.LOG_ERROR)
RNS.log("You can query the network for unknown Identities with the -R option.", RNS.LOG_ERROR) RNS.log("You can query the network for unknown Identities with the -R option.", RNS.LOG_ERROR)
@ -216,7 +220,7 @@ def main():
else: else:
RNS.Transport.request_path(destination_hash) RNS.Transport.request_path(destination_hash)
def spincheck(): def spincheck():
return RNS.Identity.recall(destination_hash) != None return RNS.Identity.recall(destination_hash) is not None
spin(spincheck, "Requesting unknown Identity for "+RNS.prettyhexrep(destination_hash), args.t) spin(spincheck, "Requesting unknown Identity for "+RNS.prettyhexrep(destination_hash), args.t)
if not spincheck(): if not spincheck():
@ -224,17 +228,15 @@ def main():
exit(6) exit(6)
else: else:
identity = RNS.Identity.recall(destination_hash) identity = RNS.Identity.recall(destination_hash)
RNS.log("Received Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash)+" from the network") RNS.log(" ".join(["Received Identity", str(identity), "for destination", RNS.prettyhexrep(destination_hash), "from the network"]))
else: else:
RNS.log("Recalled Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash)) RNS.log(" ".join(["Recalled Identity", str(identity), "for destination", RNS.prettyhexrep(destination_hash)]))
except Exception as e: except Exception as e:
RNS.log("Invalid hexadecimal hash provided", RNS.LOG_ERROR) RNS.log("Invalid hexadecimal hash provided", RNS.LOG_ERROR)
exit(7) exit(7)
else: else:
# Try loading Identity from file # Try loading Identity from file
if not os.path.isfile(identity_str): if not os.path.isfile(identity_str):
@ -243,13 +245,13 @@ def main():
else: else:
try: try:
identity = RNS.Identity.from_file(identity_str) identity = RNS.Identity.from_file(identity_str)
RNS.log("Loaded Identity "+str(identity)+" from "+str(identity_str)) RNS.log(" ".join(["Loaded Identity", str(identity), "from", str(identity_str)]))
except Exception as e: except Exception as e:
RNS.log("Could not decode Identity from specified file") RNS.log("Could not decode Identity from specified file")
exit(9) exit(9)
if identity != None: if identity is not None:
if args.hash: if args.hash:
try: try:
aspects = args.hash.split(".") aspects = args.hash.split(".")
@ -259,9 +261,9 @@ def main():
else: else:
app_name = aspects[0] app_name = aspects[0]
aspects = aspects[1:] aspects = aspects[1:]
if identity.pub != None: if identity.pub is not None:
destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects) destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects)
RNS.log("The "+str(args.hash)+" destination for this Identity is "+RNS.prettyhexrep(destination.hash)) RNS.log(" ".join(["The", str(args.hash), "destination for this Identity is", RNS.prettyhexrep(destination.hash)]))
RNS.log("The full destination specifier is "+str(destination)) RNS.log("The full destination specifier is "+str(destination))
time.sleep(0.25) time.sleep(0.25)
exit(0) exit(0)
@ -282,7 +284,7 @@ def main():
else: else:
app_name = aspects[0] app_name = aspects[0]
aspects = aspects[1:] aspects = aspects[1:]
if identity.prv != None: if identity.prv is not None:
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, app_name, *aspects) destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, app_name, *aspects)
RNS.log("Created destination "+str(destination)) RNS.log("Created destination "+str(destination))
RNS.log("Announcing destination "+RNS.prettyhexrep(destination.hash)) RNS.log("Announcing destination "+RNS.prettyhexrep(destination.hash))
@ -291,7 +293,7 @@ def main():
exit(0) exit(0)
else: else:
destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects) destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects)
RNS.log("The "+str(args.announce)+" destination for this Identity is "+RNS.prettyhexrep(destination.hash)) RNS.log(" ".join(["The", str(args.announce), "destination for this Identity is", RNS.prettyhexrep(destination.hash)]))
RNS.log("The full destination specifier is "+str(destination)) RNS.log("The full destination specifier is "+str(destination))
RNS.log("Cannot announce this destination, since the private key is not held") RNS.log("Cannot announce this destination, since the private key is not held")
time.sleep(0.25) time.sleep(0.25)
@ -303,22 +305,7 @@ def main():
exit(0) exit(0)
if args.print_identity: if args.print_identity:
if args.base64: get_keys(args, identity)
RNS.log("Public Key : "+base64.urlsafe_b64encode(identity.get_public_key()).decode("utf-8"))
elif args.base32:
RNS.log("Public Key : "+base64.b32encode(identity.get_public_key()).decode("utf-8"))
else:
RNS.log("Public Key : "+RNS.hexrep(identity.get_public_key(), delimit=False))
if identity.prv:
if args.print_private:
if args.base64:
RNS.log("Private Key : "+base64.urlsafe_b64encode(identity.get_private_key()).decode("utf-8"))
elif args.base32:
RNS.log("Private Key : "+base64.b32encode(identity.get_private_key()).decode("utf-8"))
else:
RNS.log("Private Key : "+RNS.hexrep(identity.get_private_key(), delimit=False))
else:
RNS.log("Private Key : Hidden")
exit(0) exit(0)
if args.export: if args.export:
@ -373,7 +360,7 @@ def main():
if args.decrypt and not args.write and not args.stdout and args.read and args.read.lower().endswith("."+ENCRYPT_EXT): if args.decrypt and not args.write and not args.stdout and args.read and args.read.lower().endswith("."+ENCRYPT_EXT):
args.write = str(args.read).replace("."+ENCRYPT_EXT, "") args.write = str(args.read).replace("."+ENCRYPT_EXT, "")
if args.sign and identity.prv == None: if args.sign and identity.prv is None:
RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR) RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR)
exit(14) exit(14)
@ -391,7 +378,7 @@ def main():
RNS.log("Could not open output file for writing", RNS.LOG_ERROR) RNS.log("Could not open output file for writing", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
exit(15) exit(15)
# TODO: Actually expand this to a good solution # TODO: Actually expand this to a good solution
# probably need to create a wrapper that takes # probably need to create a wrapper that takes
# into account not closing stdout when done # into account not closing stdout when done
@ -399,7 +386,7 @@ def main():
# data_output = sys.stdout # data_output = sys.stdout
if args.sign: if args.sign:
if identity.prv == None: if identity.prv is None:
RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR) RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR)
exit(16) exit(16)
@ -415,15 +402,15 @@ def main():
if not args.stdout: if not args.stdout:
RNS.log("Signing "+str(args.read)) RNS.log("Signing "+str(args.read))
try: try:
data_output.write(identity.sign(data_input.read())) data_output.write(identity.sign(data_input.read()))
data_output.close() data_output.close()
data_input.close() data_input.close()
if not args.stdout: if not args.stdout:
if args.read: if args.read:
RNS.log("File "+str(args.read)+" signed with "+str(identity)+" to "+str(args.write)) RNS.log(" ".join([ "File", str(args.read), "signed with", str(identity), "to", str(args.write) ]))
exit(0) exit(0)
except Exception as e: except Exception as e:
@ -448,7 +435,7 @@ def main():
else: else:
# if not args.stdout: # if not args.stdout:
# RNS.log("Verifying "+str(args.validate)+" for "+str(args.read)) # RNS.log("Verifying "+str(args.validate)+" for "+str(args.read))
try: try:
try: try:
sig_input = open(args.validate, "rb") sig_input = open(args.validate, "rb")
@ -457,18 +444,17 @@ def main():
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
exit(21) exit(21)
validated = identity.validate(sig_input.read(), data_input.read()) validated = identity.validate(sig_input.read(), data_input.read())
sig_input.close() sig_input.close()
data_input.close() data_input.close()
if not validated: if not validated:
if not args.stdout: if not args.stdout:
RNS.log("Signature "+str(args.validate)+" for file "+str(args.read)+" is invalid", RNS.LOG_ERROR) RNS.log(" ".join(["Signature", str(args.validate), "for file", str(args.read), "is invalid"]), RNS.LOG_ERROR)
exit(22) exit(22)
else: else:
if not args.stdout: if not args.stdout:
RNS.log("Signature "+str(args.validate)+" for file "+str(args.read)+" made by Identity "+str(identity)+" is valid") RNS.log(" ".join(["Signature", str(args.validate), "for file", str(args.read), "made by Identity", str(identity), "is valid"]))
exit(0) exit(0)
except Exception as e: except Exception as e:
@ -498,7 +484,7 @@ def main():
if not args.stdout: if not args.stdout:
RNS.log("Encrypting "+str(args.read)) RNS.log("Encrypting "+str(args.read))
try: try:
more_data = True more_data = True
while more_data: while more_data:
@ -511,7 +497,7 @@ def main():
data_input.close() data_input.close()
if not args.stdout: if not args.stdout:
if args.read: if args.read:
RNS.log("File "+str(args.read)+" encrypted for "+str(identity)+" to "+str(args.write)) RNS.log(" ".join(["File", str(args.read), "encrypted for", str(identity), "to", str(args.write)]))
exit(0) exit(0)
except Exception as e: except Exception as e:
@ -529,7 +515,7 @@ def main():
exit(26) exit(26)
if args.decrypt: if args.decrypt:
if identity.prv == None: if identity.prv is None:
RNS.log("Specified Identity does not hold a private key. Cannot decrypt.", RNS.LOG_ERROR) RNS.log("Specified Identity does not hold a private key. Cannot decrypt.", RNS.LOG_ERROR)
exit(27) exit(27)
@ -544,15 +530,15 @@ def main():
exit(29) exit(29)
if not args.stdout: if not args.stdout:
RNS.log("Decrypting "+str(args.read)+"...") RNS.log("".join(["Decrypting ", str(args.read), "..."]))
try: try:
more_data = True more_data = True
while more_data: while more_data:
chunk = data_input.read(CHUNK_SIZE) chunk = data_input.read(CHUNK_SIZE)
if chunk: if chunk:
plaintext = identity.decrypt(chunk) plaintext = identity.decrypt(chunk)
if plaintext == None: if plaintext is None:
if not args.stdout: if not args.stdout:
RNS.log("Data could not be decrypted with the specified Identity") RNS.log("Data could not be decrypted with the specified Identity")
exit(30) exit(30)
@ -564,7 +550,7 @@ def main():
data_input.close() data_input.close()
if not args.stdout: if not args.stdout:
if args.read: if args.read:
RNS.log("File "+str(args.read)+" decrypted with "+str(identity)+" to "+str(args.write)) RNS.log(" ".join(["File", str(args.read), "decrypted with", str(identity), "to", str(args.write)]))
exit(0) exit(0)
except Exception as e: except Exception as e:
@ -581,20 +567,10 @@ def main():
pass pass
exit(31) exit(31)
if True:
pass
elif False:
pass
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt: except KeyboardInterrupt:
print("") print("")
exit(255) exit(255)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -24,23 +24,23 @@
import RNS import RNS
import argparse import argparse
import time
from RNS._version import __version__ from RNS._version import __version__
def program_setup(configdir, verbosity = 0, quietness = 0, service = False): def program_setup(configdir, verbosity=0, quietness=0, service=False):
targetverbosity = verbosity-quietness targetverbosity = verbosity - quietness
if service: if service:
targetlogdest = RNS.LOG_FILE targetlogdest = RNS.LOG_FILE
targetverbosity = None targetverbosity = None
else: else:
targetlogdest = RNS.LOG_STDOUT targetlogdest = RNS.LOG_STDOUT
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest) RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
exit(0) exit(0)
def main(): def main():
try: try:
parser = argparse.ArgumentParser(description="Reticulum Distributed Identity Resolver") parser = argparse.ArgumentParser(description="Reticulum Distributed Identity Resolver")
@ -49,24 +49,23 @@ def main():
parser.add_argument('-q', '--quiet', action='count', default=0) parser.add_argument('-q', '--quiet', action='count', default=0)
parser.add_argument("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit") parser.add_argument("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit")
parser.add_argument("--version", action="version", version="ir {version}".format(version=__version__)) parser.add_argument("--version", action="version", version="ir {version}".format(version=__version__))
args = parser.parse_args() args = parser.parse_args()
if args.exampleconfig: if args.exampleconfig:
print(__example_rns_config__) print(__example_rns_config__)
exit() exit()
configarg = None
if args.config: if args.config:
configarg = args.config configarg = args.config
else:
configarg = None
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet) program_setup(configdir=configarg, verbosity=args.verbose, quietness=args.quiet)
except KeyboardInterrupt: except KeyboardInterrupt:
print("") print("")
exit() exit()
__example_rns_config__ = '''# This is an example Identity Resolver file. __example_rns_config__ = '''# This is an example Identity Resolver file.
''' '''

View File

@ -27,13 +27,14 @@ import argparse
from RNS._version import __version__ from RNS._version import __version__
def size_str(num, suffix='B'): def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z'] units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
last_unit = 'Y' last_unit = 'Y'
if suffix == 'b': if suffix == 'b':
num *= 8 num *= 8
units = ['','K','M','G','T','P','E','Z'] units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
last_unit = 'Y' last_unit = 'Y'
for unit in units: for unit in units:
@ -46,6 +47,14 @@ def size_str(num, suffix='B'):
return "%.2f%s%s" % (num, last_unit, suffix) return "%.2f%s%s" % (num, last_unit, suffix)
def convert_bytes_to_hex(value_obj):
if isinstance(value_obj, bytes):
return RNS.hexrep(value_obj, delimit=False)
elif isinstance(value_obj, dict):
return {key: convert_bytes_to_hex(value) for key, value in value_obj.items()}
return value_obj
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, sorting=None, sort_reverse=False): def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, sorting=None, sort_reverse=False):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
@ -55,169 +64,159 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
except Exception as e: except Exception as e:
pass pass
if stats != None: if stats is None:
if json:
import json
for s in stats:
if isinstance(stats[s], bytes):
stats[s] = RNS.hexrep(stats[s], delimit=False)
if isinstance(stats[s], dict):
for i in stats[s]:
if isinstance(i, dict):
for k in i:
if isinstance(i[k], bytes):
i[k] = RNS.hexrep(i[k], delimit=False)
print(json.dumps(stats))
exit()
interfaces = stats["interfaces"]
if sorting != None and isinstance(sorting, str):
sorting = sorting.lower()
if sorting == "rate" or sorting == "bitrate":
interfaces.sort(key=lambda i: i["bitrate"], reverse=not sort_reverse)
if sorting == "rx":
interfaces.sort(key=lambda i: i["rxb"], reverse=not sort_reverse)
if sorting == "tx":
interfaces.sort(key=lambda i: i["txb"], reverse=not sort_reverse)
if sorting == "traffic":
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
if sorting == "announces" or sorting == "announce":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"]+i["outgoing_announce_frequency"], reverse=not sort_reverse)
if sorting == "arx":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
if sorting == "atx":
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
if sorting == "held":
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
for ifstat in interfaces:
name = ifstat["name"]
if dispall or not (
name.startswith("LocalInterface[") or
name.startswith("TCPInterface[Client") or
name.startswith("I2PInterfacePeer[Connected peer") or
(name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False))
):
if not (name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False)):
if name_filter == None or name_filter.lower() in name.lower():
print("")
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY:
modestr = "Gateway"
else:
modestr = "Full"
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
else:
spec_str = " programs"
clients_string = "Serving : "+str(cnum)+spec_str
elif name.startswith("I2PInterface["):
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
cnum = clients
if cnum == 1:
spec_str = " connected I2P endpoint"
else:
spec_str = " connected I2P endpoints"
clients_string = "Peers : "+str(cnum)+spec_str
else:
clients_string = ""
else:
clients_string = "Clients : "+str(clients)
else:
clients = None
print(" {n}".format(n=ifstat["name"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
print(" Status : {ss}".format(ss=ss))
if clients != None and clients_string != "":
print(" "+clients_string)
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "airtime_short" in ifstat and "airtime_long" in ifstat:
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
if "channel_load_short" in ifstat and "channel_load_long" in ifstat:
print(" Ch.Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None:
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if astats and "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
if astats and "held_announces" in ifstat and ifstat["held_announces"] != None and ifstat["held_announces"] > 0:
aqn = ifstat["held_announces"]
if aqn == 1:
print(" Held : {np} announce".format(np=aqn))
else:
print(" Held : {np} announces".format(np=aqn))
if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
print(" Announces : {iaf}".format(iaf=RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])))
print(" {iaf}".format(iaf=RNS.prettyfrequency(ifstat["incoming_announce_frequency"])))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
if "probe_responder" in stats and stats["probe_responder"] != None:
print(" Probe responder at "+RNS.prettyhexrep(stats["probe_responder"])+ " active")
print(" Uptime is "+RNS.prettytime(stats["transport_uptime"]))
print("")
else:
print("Could not get RNS status") print("Could not get RNS status")
return
if json:
import json
for s, value in stats.items():
stats[s] = convert_bytes_to_hex(value)
print(json.dumps(stats))
exit()
interfaces = stats["interfaces"]
if sorting is not None and isinstance(sorting, str):
sorting = sorting.lower()
if sorting == "rate" or sorting == "bitrate":
interfaces.sort(key=lambda i: i["bitrate"], reverse=not sort_reverse)
elif sorting == "rx":
interfaces.sort(key=lambda i: i["rxb"], reverse=not sort_reverse)
elif sorting == "tx":
interfaces.sort(key=lambda i: i["txb"], reverse=not sort_reverse)
elif sorting == "traffic":
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
elif sorting == "announces" or sorting == "announce":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"]+i["outgoing_announce_frequency"], reverse=not sort_reverse)
elif sorting == "arx":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
elif sorting == "atx":
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
elif sorting == "held":
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
for ifstat in interfaces:
name = ifstat["name"]
if dispall or not (
name.startswith("LocalInterface[") or
name.startswith("TCPInterface[Client") or
name.startswith("I2PInterfacePeer[Connected peer") or
(name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False))
):
if not (name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False)):
if name_filter == None or name_filter.lower() in name.lower():
print("")
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY:
modestr = "Gateway"
else:
modestr = "Full"
if ifstat["clients"] is not None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
else:
spec_str = " programs"
clients_string = "Serving : "+str(cnum)+spec_str
elif name.startswith("I2PInterface["):
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
cnum = clients
if cnum == 1:
spec_str = " connected I2P endpoint"
else:
spec_str = " connected I2P endpoints"
clients_string = "Peers : "+str(cnum)+spec_str
else:
clients_string = ""
else:
clients_string = "Clients : "+str(clients)
else:
clients = None
print(" {n}".format(n=ifstat["name"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
print(" Status : {ss}".format(ss=ss))
if clients != None and clients_string != "":
print(" "+clients_string)
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "airtime_short" in ifstat and "airtime_long" in ifstat:
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
if "channel_load_short" in ifstat and "channel_load_long" in ifstat:
print(" Ch.Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None:
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if astats and "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
if astats and "held_announces" in ifstat and ifstat["held_announces"] != None and ifstat["held_announces"] > 0:
aqn = ifstat["held_announces"]
if aqn == 1:
print(" Held : {np} announce".format(np=aqn))
else:
print(" Held : {np} announces".format(np=aqn))
if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
print(" Announces : {iaf}".format(iaf=RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])))
print(" {iaf}".format(iaf=RNS.prettyfrequency(ifstat["incoming_announce_frequency"])))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
if "probe_responder" in stats and stats["probe_responder"] != None:
print(" Probe responder at "+RNS.prettyhexrep(stats["probe_responder"])+ " active")
print(" Uptime is "+RNS.prettytime(stats["transport_uptime"]))
print("")
def main(): def main():
try: try:
@ -232,7 +231,7 @@ def main():
help="show all interfaces", help="show all interfaces",
default=False default=False
) )
parser.add_argument( parser.add_argument(
"-A", "-A",
"--announce-stats", "--announce-stats",
@ -240,7 +239,7 @@ def main():
help="show announce stats", help="show announce stats",
default=False default=False
) )
parser.add_argument( parser.add_argument(
"-s", "-s",
"--sort", "--sort",
@ -249,7 +248,7 @@ def main():
default=None, default=None,
type=str type=str
) )
parser.add_argument( parser.add_argument(
"-r", "-r",
"--reverse", "--reverse",
@ -257,7 +256,7 @@ def main():
help="reverse sorting", help="reverse sorting",
default=False, default=False,
) )
parser.add_argument( parser.add_argument(
"-j", "-j",
"--json", "--json",
@ -269,7 +268,7 @@ def main():
parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument("filter", nargs="?", default=None, help="only display interfaces with names including filter", type=str) parser.add_argument("filter", nargs="?", default=None, help="only display interfaces with names including filter", type=str)
args = parser.parse_args() args = parser.parse_args()
if args.config: if args.config:
@ -292,6 +291,7 @@ def main():
print("") print("")
exit() exit()
def speed_str(num, suffix='bps'): def speed_str(num, suffix='bps'):
units = ['','k','M','G','T','P','E','Z'] units = ['','k','M','G','T','P','E','Z']
last_unit = 'Y' last_unit = 'Y'
@ -308,5 +308,6 @@ def speed_str(num, suffix='bps'):
return "%.2f %s%s" % (num, last_unit, suffix) return "%.2f %s%s" % (num, last_unit, suffix)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -28,7 +28,7 @@ import argparse
import shlex import shlex
import time import time
import sys import sys
import tty # import tty # Commented this because commented lines below use it
import os import os
from RNS._version import __version__ from RNS._version import __version__
@ -39,25 +39,27 @@ reticulum = None
allow_all = False allow_all = False
allowed_identity_hashes = [] allowed_identity_hashes = []
def prepare_identity(identity_path): def prepare_identity(identity_path):
global identity global identity
if identity_path == None: if identity_path is None:
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path): if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path) identity = RNS.Identity.from_file(identity_path)
if identity == None: if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO) RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity() identity = RNS.Identity()
identity.to_file(identity_path) identity.to_file(identity_path)
def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed = [], print_identity = False, disable_auth = None, disable_announce=False): def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed = [], print_identity = False, disable_auth = None, disable_announce=False):
global identity, allow_all, allowed_identity_hashes, reticulum global identity, allow_all, allowed_identity_hashes, reticulum
targetloglevel = 3+verbosity-quietness targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel) reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
prepare_identity(identitypath) prepare_identity(identitypath)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "execute") destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "execute")
@ -69,7 +71,7 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
if disable_auth: if disable_auth:
allow_all = True allow_all = True
else: else:
if allowed != None: if allowed is not None:
for a in allowed: for a in allowed:
try: try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
@ -107,7 +109,7 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
if not disable_announce: if not disable_announce:
destination.announce() destination.announce()
while True: while True:
time.sleep(1) time.sleep(1)
@ -297,6 +299,7 @@ def remote_execution_progress(request_receipt):
link = None link = None
listener_destination = None listener_destination = None
remote_exec_grace = 2.0 remote_exec_grace = 2.0
def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detailed = False, mirror = False, noid = False, destination = None, command = None, stdin = None, stdoutl = None, stderrl = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, result_timeout = None, interactive = False): def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detailed = False, mirror = False, noid = False, destination = None, command = None, stdin = None, stdoutl = None, stderrl = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, result_timeout = None, interactive = False):
global identity, reticulum, link, listener_destination, remote_exec_grace global identity, reticulum, link, listener_destination, remote_exec_grace
@ -338,7 +341,7 @@ def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detail
if link == None or link.status == RNS.Link.CLOSED or link.status == RNS.Link.PENDING: if link == None or link.status == RNS.Link.CLOSED or link.status == RNS.Link.PENDING:
link = RNS.Link(listener_destination) link = RNS.Link(listener_destination)
link.did_identify = False link.did_identify = False
if not spin(until=lambda: link.status == RNS.Link.ACTIVE, msg="Establishing link with "+RNS.prettyhexrep(destination_hash), timeout=timeout): if not spin(until=lambda: link.status == RNS.Link.ACTIVE, msg="Establishing link with "+RNS.prettyhexrep(destination_hash), timeout=timeout):
print("Could not establish link with "+RNS.prettyhexrep(destination_hash)) print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
exit(243) exit(243)
@ -467,7 +470,7 @@ def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detail
else: else:
tstr = "" tstr = ""
print("Remote wrote "+str(outlen)+" bytes to stdout"+tstr) print("Remote wrote "+str(outlen)+" bytes to stdout"+tstr)
if errlen != None and stderr != None: if errlen != None and stderr != None:
if len(stderr) < errlen: if len(stderr) < errlen:
tstr = ", "+str(len(stderr))+" bytes displayed" tstr = ", "+str(len(stderr))+" bytes displayed"
@ -548,7 +551,6 @@ def main():
parser.add_argument("--stdout", action='store', default=None, help="max size in bytes of returned stdout", type=int) parser.add_argument("--stdout", action='store', default=None, help="max size in bytes of returned stdout", type=int)
parser.add_argument("--stderr", action='store', default=None, help="max size in bytes of returned stderr", type=int) parser.add_argument("--stderr", action='store', default=None, help="max size in bytes of returned stderr", type=int)
parser.add_argument("--version", action="version", version="rnx {version}".format(version=__version__)) parser.add_argument("--version", action="version", version="rnx {version}".format(version=__version__))
args = parser.parse_args() args = parser.parse_args()
if args.listen or args.print_identity: if args.listen or args.print_identity:
@ -600,8 +602,8 @@ def main():
# while True: # while True:
# ch = sys.stdin.read(1) # ch = sys.stdin.read(1)
# cmdbuf += ch.encode("utf-8") # cmdbuf += ch.encode("utf-8")
# print("\r"+prompt+cmdbuf.decode("utf-8"), end="") # print("\r"+prompt+cmdbuf.decode("utf-8"), end="")
command = input() command = input()
if command.lower() == "exit" or command.lower() == "quit": if command.lower() == "exit" or command.lower() == "quit":
exit(0) exit(0)
@ -668,6 +670,7 @@ def size_str(num, suffix='B'):
return "%.2f%s%s" % (num, last_unit, suffix) return "%.2f%s%s" % (num, last_unit, suffix)
def pretty_time(time, verbose=False): def pretty_time(time, verbose=False):
days = int(time // (24 * 3600)) days = int(time // (24 * 3600))
time = time % (24 * 3600) time = time % (24 * 3600)
@ -676,7 +679,7 @@ def pretty_time(time, verbose=False):
minutes = int(time // 60) minutes = int(time // 60)
time %= 60 time %= 60
seconds = round(time, 2) seconds = round(time, 2)
ss = "" if seconds == 1 else "s" ss = "" if seconds == 1 else "s"
sm = "" if minutes == 1 else "s" sm = "" if minutes == 1 else "s"
sh = "" if hours == 1 else "s" sh = "" if hours == 1 else "s"
@ -684,31 +687,24 @@ def pretty_time(time, verbose=False):
components = [] components = []
if days > 0: if days > 0:
components.append(str(days)+" day"+sd if verbose else str(days)+"d") components.append("".join([str(days), " day", sd if verbose else str(days), "d"]))
if hours > 0: if hours > 0:
components.append(str(hours)+" hour"+sh if verbose else str(hours)+"h") components.append("".join([str(hours), " hour", sh if verbose else str(hours), "h"]))
if minutes > 0: if minutes > 0:
components.append(str(minutes)+" minute"+sm if verbose else str(minutes)+"m") components.append("".join([str(minutes), " minute", sm if verbose else str(minutes), "m"]))
if seconds > 0: if seconds > 0:
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s") components.append("".join([str(seconds), " second", ss if verbose else str(seconds), "s"]))
i = 0 if len(components) > 1:
tstr = "" time_string = "".join([", ".join(components[:-1]), " and ", components[-1]])
for c in components: else:
i += 1 time_string = components[0] if components else ""
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
tstr += c return time_string
return tstr
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -33,9 +33,20 @@ system, which should be enabled by default in almost all OSes.
.. code:: .. code::
# This example demonstrates a TCP server interface. # This example demonstrates a bare-minimum setup
# It will listen for incoming connections on the # of an Auto Interface. It will allow communica-
# specified IP address and port number. # tion with all other reachable devices on all
# usable physical ethernet-based devices that
# are available on the system.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# This example demonstrates an more specifically
# configured Auto Interface, that only uses spe-
# cific physical interfaces, and has a number of
# other configuration options set.
[[Default Interface]] [[Default Interface]]
type = AutoInterface type = AutoInterface