summaryrefslogtreecommitdiff
path: root/chip/roun
blob: fb1624a79ed03f42affac50f130041c14dc483a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/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))