File: utils.py

package info (click to toggle)
2ping 4.5-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 492 kB
  • sloc: python: 3,304; makefile: 44; sh: 4
file content (199 lines) | stat: -rw-r--r-- 5,394 bytes parent folder | download | duplicates (4)
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
# 2ping - A bi-directional ping utility
# Copyright (C) 2010-2020 Ryan Finnie
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

import gettext
import platform
import random as _pyrandom

try:
    import distro
except ImportError as e:
    distro = e

try:
    from Cryptodome.Cipher import AES
except ImportError as e:
    try:
        from Crypto.Cipher import AES
    except ImportError:
        AES = e


_ = gettext.translation("2ping", fallback=True).gettext
_pl = gettext.translation("2ping", fallback=True).ngettext

try:
    random = _pyrandom.SystemRandom()
    random_is_systemrandom = True
except AttributeError:
    random = _pyrandom
    random_is_systemrandom = False


def twoping_checksum(packet):
    """Calculate 2ping checksum on a 2ping packet

    Packet may be bytes or bytearray, return is a 32-bit int.
    Checksum is calculated as described by the 2ping protocol
    specification.
    """
    checksum = 0

    if (len(packet) % 2) == 1:
        # Convert from (possible) bytearray to bytes before appending
        packet = bytes(packet) + b"\x00"

    for i in range(0, len(packet), 2):
        checksum = checksum + (packet[i] << 8) + packet[i + 1]
        checksum = (checksum & 0xFFFF) + (checksum >> 16)

    checksum = ~checksum & 0xFFFF

    if checksum == 0:
        checksum = 0xFFFF

    return checksum


def div0(n, d):
    """Pretend we live in a world where n / 0 == 0"""
    return 0 if d == 0 else n / d


def npack(i, minimum=1):
    """Pack int to network bytes

    Takes an int and packs it to a network (big-endian) bytes.
    If minimum is specified, bytes output is padded with zero'd bytes.
    Minimum does not need to be aligned to 32-bits, 64-bits, etc.
    """
    out = bytearray()
    while i >= 256:
        out.insert(0, i & 0xFF)
        i = i >> 8
    out.insert(0, i)
    out_len = len(out)
    if out_len < minimum:
        out = bytearray(minimum - out_len) + out
    return bytes(out)


def nunpack(b):
    """Unpack network bytes to int

    Takes arbitrary length network (big-endian) bytes, and returns an
    int.
    """
    out = 0
    for x in b:
        out = (out << 8) + x
    return out


def platform_info():
    """Return a string containing platform/OS information"""
    platform_name = platform.system()
    platform_machine = platform.machine()
    platform_info = "{} {}".format(platform_name, platform_machine)
    distro_name = ""
    distro_version = ""
    distro_info = ""
    if not isinstance(distro, ImportError):
        if hasattr(distro, "name"):
            distro_name = distro.name()
        if hasattr(distro, "version"):
            distro_version = distro.version()
    if distro_name:
        distro_info = distro_name
        if distro_version:
            distro_info += " " + distro_version
        return "{} ({})".format(platform_info, distro_info)
    else:
        return platform_info


def fuzz_bytearray(data, percent, start=0, end=None):
    """Fuzz a bytearray in-place

    Each bit has a <percent> chance of being flipped.
    """
    if end is None:
        end = len(data)
    for p in range(start, end):
        xor_byte = 0
        for i in range(8):
            if random.random() < (percent / 100.0):
                xor_byte = xor_byte + (2 ** i)
        data[p] = data[p] ^ xor_byte


def fuzz_packet(packet, percent):
    """Fuzz a dumped 2ping packet

    Each bit in the opcode areas has a <percent> chance of being flipped.
    Each bit in the magic number and checksum have a <percent> / 10
    chance of being flipped.

    Returns the fuzzed packet.
    """
    packet = bytearray(packet)

    # Fuzz the entire packet
    fuzz_bytearray(packet, percent, 4)

    # Fuzz the magic number, at a lower probability
    fuzz_bytearray(packet, percent / 10.0, 0, 2)

    # Recalculate the checksum
    packet[2:4] = b"\x00\x00"
    packet[2:4] = npack(twoping_checksum(packet), 2)

    # Fuzz the recalculated checksum itself, at a lower probability
    fuzz_bytearray(packet, percent / 10.0, 2, 4)

    return bytes(packet)


def stats_time(seconds):
    """Convert seconds to ms/s/m/h/d/y time string"""

    conversion = (
        (1000, "ms"),
        (60, "s"),
        (60, "m"),
        (24, "h"),
        (365, "d"),
        (None, "y"),
    )
    out = ""
    rest = int(seconds * 1000)
    for (div, suffix) in conversion:
        if div is None:
            if out:
                out = " " + out
            out = "{}{}{}".format(rest, suffix, out)
            break
        p = rest % div
        rest = int(rest / div)
        if p > 0:
            if out:
                out = " " + out
            out = "{}{}{}".format(p, suffix, out)
        if rest == 0:
            break
    return out