1# 2ping - A bi-directional ping utility
2# Copyright (C) 2010-2021 Ryan Finnie
3# SPDX-License-Identifier: GPL-2.0+
4
5import gettext
6import platform
7import random as _pyrandom
8
9try:
10    import distro
11except ImportError as e:
12    distro = e
13
14try:
15    from Cryptodome.Cipher import AES
16except ImportError as e:
17    try:
18        from Crypto.Cipher import AES
19    except ImportError:
20        AES = e
21
22
23_ = gettext.translation("2ping", fallback=True).gettext
24_pl = gettext.translation("2ping", fallback=True).ngettext
25
26try:
27    random = _pyrandom.SystemRandom()
28    random_is_systemrandom = True
29except AttributeError:
30    random = _pyrandom
31    random_is_systemrandom = False
32
33
34def twoping_checksum(packet):
35    """Calculate 2ping checksum on a 2ping packet
36
37    Packet may be bytes or bytearray, return is a 32-bit int.
38    Checksum is calculated as described by the 2ping protocol
39    specification.
40    """
41    checksum = 0
42
43    if (len(packet) % 2) == 1:
44        # Convert from (possible) bytearray to bytes before appending
45        packet = bytes(packet) + b"\x00"
46
47    for i in range(0, len(packet), 2):
48        checksum = checksum + (packet[i] << 8) + packet[i + 1]
49        checksum = (checksum & 0xFFFF) + (checksum >> 16)
50
51    checksum = ~checksum & 0xFFFF
52
53    if checksum == 0:
54        checksum = 0xFFFF
55
56    return checksum
57
58
59def div0(n, d):
60    """Pretend we live in a world where n / 0 == 0"""
61    return 0 if d == 0 else n / d
62
63
64def npack(i, minimum=1):
65    """Pack int to network bytes
66
67    Takes an int and packs it to a network (big-endian) bytes.
68    If minimum is specified, bytes output is padded with zero'd bytes.
69    Minimum does not need to be aligned to 32-bits, 64-bits, etc.
70    """
71    out = bytearray()
72    while i >= 256:
73        out.insert(0, i & 0xFF)
74        i = i >> 8
75    out.insert(0, i)
76    out_len = len(out)
77    if out_len < minimum:
78        out = bytearray(minimum - out_len) + out
79    return bytes(out)
80
81
82def nunpack(b):
83    """Unpack network bytes to int
84
85    Takes arbitrary length network (big-endian) bytes, and returns an
86    int.
87    """
88    out = 0
89    for x in b:
90        out = (out << 8) + x
91    return out
92
93
94def platform_info():
95    """Return a string containing platform/OS information"""
96    platform_name = platform.system()
97    platform_machine = platform.machine()
98    platform_info = "{} {}".format(platform_name, platform_machine)
99    distro_name = ""
100    distro_version = ""
101    distro_info = ""
102    if not isinstance(distro, ImportError):
103        if hasattr(distro, "name"):
104            distro_name = distro.name()
105        if hasattr(distro, "version"):
106            distro_version = distro.version()
107    if distro_name:
108        distro_info = distro_name
109        if distro_version:
110            distro_info += " " + distro_version
111        return "{} ({})".format(platform_info, distro_info)
112    else:
113        return platform_info
114
115
116def fuzz_bytearray(data, percent, start=0, end=None):
117    """Fuzz a bytearray in-place
118
119    Each bit has a <percent> chance of being flipped.
120    """
121    if end is None:
122        end = len(data)
123    for p in range(start, end):
124        xor_byte = 0
125        for i in range(8):
126            if random.random() < (percent / 100.0):
127                xor_byte = xor_byte + (2 ** i)
128        data[p] = data[p] ^ xor_byte
129
130
131def fuzz_packet(packet, percent):
132    """Fuzz a dumped 2ping packet
133
134    Each bit in the opcode areas has a <percent> chance of being flipped.
135    Each bit in the magic number and checksum have a <percent> / 10
136    chance of being flipped.
137
138    Returns the fuzzed packet.
139    """
140    packet = bytearray(packet)
141
142    # Fuzz the entire packet
143    fuzz_bytearray(packet, percent, 4)
144
145    # Fuzz the magic number, at a lower probability
146    fuzz_bytearray(packet, percent / 10.0, 0, 2)
147
148    # Recalculate the checksum
149    packet[2:4] = b"\x00\x00"
150    packet[2:4] = npack(twoping_checksum(packet), 2)
151
152    # Fuzz the recalculated checksum itself, at a lower probability
153    fuzz_bytearray(packet, percent / 10.0, 2, 4)
154
155    return bytes(packet)
156
157
158def stats_time(seconds):
159    """Convert seconds to ms/s/m/h/d/y time string"""
160
161    conversion = (
162        (1000, "ms"),
163        (60, "s"),
164        (60, "m"),
165        (24, "h"),
166        (365, "d"),
167        (None, "y"),
168    )
169    out = ""
170    rest = int(seconds * 1000)
171    for (div, suffix) in conversion:
172        if div is None:
173            if out:
174                out = " " + out
175            out = "{}{}{}".format(rest, suffix, out)
176            break
177        p = rest % div
178        rest = int(rest / div)
179        if p > 0:
180            if out:
181                out = " " + out
182            out = "{}{}{}".format(p, suffix, out)
183        if rest == 0:
184            break
185    return out
186