diff options
Diffstat (limited to 'Com/Simatime/roun')
-rwxr-xr-x | Com/Simatime/roun | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/Com/Simatime/roun b/Com/Simatime/roun new file mode 100755 index 0000000..addbaf3 --- /dev/null +++ b/Com/Simatime/roun @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 + +import argparse + +DESC = """roun is a program that takes numbers and turns them into human-readable +names, and vice versa. + +A 'roun' is the human-readable name. For example: + + roun = hidor-kahih + ip = 68.107.97.20 + hex = 0x446b6114 + int = 1147887892 + +The names are inspired by proquints <https://arxiv.org/html/0901.4016>. + +Currently the algorithm is the same, but I would like to modify the algorithm at +some point to be less odd than what proquints currently is. When I get time. +""" + + +def _char_list_to_dict(char_list): + return {c: k for (c, k) in zip(char_list, range(len(char_list)))} + + +UINT_TO_CONSONANT = "bdfghjklmnprstvz" +UINT_TO_VOWEL = "aiou" + +CONSONANT_TO_UINT = _char_list_to_dict(UINT_TO_CONSONANT) +VOWEL_TO_UINT = _char_list_to_dict(UINT_TO_VOWEL) + +MASK_LAST4 = 0xF +MASK_LAST2 = 0x3 + +CHARS_PER_CHUNK = 5 + + +def _uint16_to_roun(uint16_val): + val = uint16_val + res = ["?"] * CHARS_PER_CHUNK + for i in range(CHARS_PER_CHUNK): + if i & 1: + res[-i - 1] = UINT_TO_VOWEL[val & MASK_LAST2] + val >>= 2 + else: + res[-i - 1] = UINT_TO_CONSONANT[val & MASK_LAST4] + val >>= 4 + return "".join(res) + + +def uint_to_roun(uint_val, separator="-"): + """Convert 32-bit integer value into corresponding roun string identifier. + + >>> uint_to_roun(0x7F000001, '-') + lusab-babad + + :param uint_val: 32-bit integer value to encode + :param separator: string to separate character rounets + :return: roun string identifier + """ + if uint_val < 0 or uint_val > 0xFFFFFFFF: + raise ValueError("uint_val should be in range 0-0xFFFFFFFF") + return _uint16_to_roun(uint_val >> 16) + separator + _uint16_to_roun(uint_val) + + +def roun_to_uint(roun): + """Convert roun string identifier into corresponding 32-bit integer value. + + >>> hex(roun_to_uint('lusab-babad')) + '0x7F000001' + + :param roun: roun string identifier to decode + :return: 32-bit integer representation of the roun encoded value + """ + nchar = len(roun) + if nchar < 10 or nchar > 11: + raise ValueError("roun should be in form of two rounets + optional separator") + + res = 0 + for i, c in enumerate(roun): + mag = CONSONANT_TO_UINT.get(c) + if mag is not None: + res <<= 4 + res += mag + else: + mag = VOWEL_TO_UINT.get(c) + if mag is not None: + res <<= 2 + res += mag + elif i != 5: + raise ValueError("bad roun format") + return res + + +def ip2uint_str(ipv4_str): + """Convert IPv4 string to 32-bit integer value""" + parts = ipv4_str.split(".") + if len(parts) != 4: + raise ValueError( + "Expected IPv4 address in form A.B.C.D, got {}".format(ipv4_str) + ) + ip = [0] * 4 + for i, part in enumerate(parts): + try: + int_part = int(part) + except ValueError: + raise ValueError("Part {} of IPv4 address is not an integer".format(i)) + if int_part < 0 or int_part > 255: + raise ValueError("Part {} of IPv4 address is not in range 0-255".format(i)) + ip[i] = int_part + return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3] + + +def uint_to_ip_str(uint_val): + "Covert 32-bit integer value to IPv4 string" + return "{}.{}.{}.{}".format( + (uint_val >> 24) & 0xFF, + (uint_val >> 16) & 0xFF, + (uint_val >> 8) & 0xFF, + uint_val & 0xFF, + ) + + +def uint_to_roun_str(uint_str, separator="-"): + return uint_to_roun(int(uint_str), separator) + + +def roun_to_uint_str(roun): + return str(roun_to_uint(roun)) + + +def hex2roun_str(hex_str, separator="-"): + return uint_to_roun(int(hex_str, 16), separator) + + +def roun2hex_str(roun): + return hex(roun_to_uint(roun)) + + +def convert(str_val, target=None): + """Convert between roun, integer, hex or IPv4 string representations. + Tries to guess the representation from input. + :param str_val: input representation (string) + :return: output representation (string) + """ + if target is not None and target not in {"uint", "hex", "ip"}: + raise ValueError("Convert target should be one of: uint, hex, ip") + + if target == "uint": + return roun_to_uint_str(str_val) + + if target == "hex": + return roun2hex_str(str_val) + + if target == "ip": + return uint_to_ip_str(roun_to_uint(str_val)) + + # try to guess the representation + try: + return roun_to_uint_str(str_val) + except ValueError: + pass + + try: + return uint_to_roun_str(str_val) + except ValueError: + pass + + try: + return hex2roun_str(str_val) + except ValueError: + pass + + try: + return uint_to_roun_str(ip2uint_str(str_val)) + except ValueError: + pass + + raise ValueError("Unrecognized input format: {}".format(str_val)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=DESC, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "-n", + "--uint", + action="store_true", + help="convert from roun to 32-bit integer", + required=False, + ) + parser.add_argument( + "-x", + "--hex", + action="store_true", + help="convert from roun to hexadecimal", + required=False, + ) + parser.add_argument( + "-i", + "--ip", + action="store_true", + help="convert from roun to IPv4", + required=False, + ) + parser.add_argument( + "val", + nargs="?", + type=str, + default=None, + help="value to convert (if not specified, " + "IP address of the current host is printed)", + ) + + args = parser.parse_args() + + target = None + if args.uint: + target = "uint" + elif args.hex: + target = "hex" + elif args.ip: + target = "ip" + + res = convert(args.val, target) + print("{}".format(res)) |