• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 11996306614

24 Nov 2024 12:54PM UTC coverage: 73.582% (+0.1%) from 73.442%
11996306614

push

github

web-flow
Merge branch 'dev' into add_ko_file_search_support

3796 of 6420 branches covered (59.13%)

62 of 65 new or added lines in 3 files covered. (95.38%)

3 existing lines in 2 files now uncovered.

13311 of 18090 relevant lines covered (73.58%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.64
/pwnlib/util/packing.py
1
 # -*- coding: utf-8 -*-
2
r"""
3
Module for packing and unpacking integers.
4

5
Simplifies access to the standard ``struct.pack`` and ``struct.unpack``
6
functions, and also adds support for packing/unpacking arbitrary-width
7
integers.
8

9
The packers are all context-aware for ``endian`` and ``signed`` arguments,
10
though they can be overridden in the parameters.
11

12
Examples:
13

14
    >>> p8(0)
15
    b'\x00'
16
    >>> p32(0xdeadbeef)
17
    b'\xef\xbe\xad\xde'
18
    >>> p32(0xdeadbeef, endian='big')
19
    b'\xde\xad\xbe\xef'
20
    >>> with context.local(endian='big'): p32(0xdeadbeef)
21
    b'\xde\xad\xbe\xef'
22

23
    Make a frozen packer, which does not change with context.
24

25
    >>> p=make_packer('all')
26
    >>> p(0xff)
27
    b'\xff'
28
    >>> p(0x1ff)
29
    b'\xff\x01'
30
    >>> with context.local(endian='big'): print(repr(p(0x1ff)))
31
    b'\xff\x01'
32
"""
33
from __future__ import absolute_import
1✔
34
from __future__ import division
1✔
35

36
import collections
1✔
37
import six
1✔
38
import struct
1✔
39
import sys
1✔
40
import warnings
1✔
41

42
from six.moves import range
1✔
43

44
from pwnlib.context import LocalNoarchContext
1✔
45
from pwnlib.context import context
1✔
46
from pwnlib.log import getLogger
1✔
47

48
from pwnlib.util import iters
1✔
49

50
mod = sys.modules[__name__]
1✔
51
log = getLogger(__name__)
1✔
52

53
def pack(number, word_size = None, endianness = None, sign = None, **kwargs):
1✔
54
    r"""pack(number, word_size = None, endianness = None, sign = None, **kwargs) -> str
55

56
    Packs arbitrary-sized integer.
57

58
    Word-size, endianness and signedness is done according to context.
59

60
    `word_size` can be any positive number or the string "all". Choosing the
61
    string "all" will output a string long enough to contain all the significant
62
    bits and thus be decodable by :func:`unpack`.
63

64
    `word_size` can be any positive number. The output will contain word_size/8
65
    rounded up number of bytes. If word_size is not a multiple of 8, it will be
66
    padded with zeroes up to a byte boundary.
67

68
    Arguments:
69
        number (int): Number to convert
70
        word_size (int): Word size of the converted integer or the string 'all' (in bits).
71
        endianness (str): Endianness of the converted integer ("little"/"big")
72
        sign (str): Signedness of the converted integer (False/True)
73
        kwargs: Anything that can be passed to context.local
74

75
    Returns:
76
        The packed number as a string.
77

78
    Examples:
79

80
        >>> pack(0x414243, 24, 'big', True)
81
        b'ABC'
82
        >>> pack(0x414243, 24, 'little', True)
83
        b'CBA'
84
        >>> pack(0x814243, 24, 'big', False)
85
        b'\x81BC'
86
        >>> pack(0x814243, 24, 'big', True)
87
        Traceback (most recent call last):
88
           ...
89
        ValueError: pack(): number does not fit within word_size
90
        >>> pack(0x814243, 25, 'big', True)
91
        b'\x00\x81BC'
92
        >>> pack(-1, 'all', 'little', True)
93
        b'\xff'
94
        >>> pack(-256, 'all', 'big', True)
95
        b'\xff\x00'
96
        >>> pack(0x0102030405, 'all', 'little', True)
97
        b'\x05\x04\x03\x02\x01'
98
        >>> pack(-1)
99
        b'\xff\xff\xff\xff'
100
        >>> pack(0x80000000, 'all', 'big', True)
101
        b'\x00\x80\x00\x00\x00'
102
"""
103
    if sign is None and number < 0:
1✔
104
        sign = True
1✔
105

106
    if word_size != 'all':
1✔
107
        kwargs.setdefault('word_size', word_size)
1✔
108

109
    kwargs.setdefault('endianness', endianness)
1✔
110
    kwargs.setdefault('sign', sign)
1✔
111

112
    with context.local(**kwargs):
1✔
113
        # Lookup in context if not found
114
        word_size  = 'all' if word_size == 'all' else context.word_size
1✔
115
        endianness = context.endianness
1✔
116
        sign       = context.sign
1✔
117

118
        if not isinstance(number, six.integer_types):
1!
119
            raise ValueError("pack(): number must be of type (int,long) (got %r)" % type(number))
×
120

121
        if not isinstance(sign, bool):
1!
122
            raise ValueError("pack(): sign must be either True or False (got %r)" % sign)
×
123

124
        if endianness not in ['little', 'big']:
1!
125
            raise ValueError("pack(): endianness must be either 'little' or 'big' (got %r)" % endianness)
×
126

127
        # Verify that word_size make sense
128
        if word_size == 'all':
1✔
129
            if number == 0:
1!
130
                word_size = 8
×
131
            elif number > 0:
1✔
132
                if sign:
1✔
133
                    word_size = (number.bit_length() | 7) + 1
1✔
134
                else:
135
                    word_size = ((number.bit_length() - 1) | 7) + 1
1✔
136
            else:
137
                if not sign:
1!
138
                    raise ValueError("pack(): number does not fit within word_size")
×
139
                word_size = ((number + 1).bit_length() | 7) + 1
1✔
140
        elif not isinstance(word_size, six.integer_types) or word_size <= 0:
1!
141
            raise ValueError("pack(): word_size must be a positive integer or the string 'all'")
×
142

143
        if sign:
1✔
144
            limit = 1 << (word_size-1)
1✔
145
            if not -limit <= number < limit:
1✔
146
                raise ValueError("pack(): number does not fit within word_size")
1✔
147
        else:
148
            limit = 1 << word_size
1✔
149
            if not 0 <= number < limit:
1!
150
                raise ValueError("pack(): number does not fit within word_size [%i, %r, %r]" % (0, number, limit))
×
151

152
        # Normalize number and size now that we have verified them
153
        # From now on we can treat positive and negative numbers the same
154
        number = number & ((1 << word_size) - 1)
1✔
155
        byte_size = (word_size + 7) // 8
1✔
156

157
        out = []
1✔
158

159
        for _ in range(byte_size):
1✔
160
            out.append(_p8lu(number & 0xff))
1✔
161
            number = number >> 8
1✔
162

163
        if endianness == 'little':
1✔
164
            return b''.join(out)
1✔
165
        else:
166
            return b''.join(reversed(out))
1✔
167

168
@LocalNoarchContext
1✔
169
def unpack(data, word_size = None):
1✔
170
    r"""unpack(data, word_size = None, endianness = None, sign = None, **kwargs) -> int
171

172
    Unpacks arbitrary-sized integer.
173

174
    Word-size, endianness and signedness is done according to context.
175

176
    `word_size` can be any positive number or the string "all". Choosing the
177
    string "all" is equivalent to ``len(data)*8``.
178

179
    If `word_size` is not a multiple of 8, then the bits used for padding
180
    are discarded.
181

182
    Arguments:
183
        number (int): String to convert
184
        word_size (int): Word size of the converted integer or the string "all" (in bits).
185
        endianness (str): Endianness of the converted integer ("little"/"big")
186
        sign (str): Signedness of the converted integer (False/True)
187
        kwargs: Anything that can be passed to context.local
188

189
    Returns:
190
        The unpacked number.
191

192
    Examples:
193

194
        >>> hex(unpack(b'\xaa\x55', 16, endian='little', sign=False))
195
        '0x55aa'
196
        >>> hex(unpack(b'\xaa\x55', 16, endian='big', sign=False))
197
        '0xaa55'
198
        >>> hex(unpack(b'\xaa\x55', 16, endian='big', sign=True))
199
        '-0x55ab'
200
        >>> hex(unpack(b'\xaa\x55', 15, endian='big', sign=True))
201
        '0x2a55'
202
        >>> hex(unpack(b'\xff\x02\x03', 'all', endian='little', sign=True))
203
        '0x302ff'
204
        >>> hex(unpack(b'\xff\x02\x03', 'all', endian='big', sign=True))
205
        '-0xfdfd'
206
    """
207

208
    # Lookup in context if not found
209
    word_size  = word_size  or context.word_size
1✔
210
    endianness = context.endianness
1✔
211
    sign       = context.sign
1✔
212
    data = _need_bytes(data, 2)
1✔
213

214
    # Verify that word_size make sense
215
    if word_size == 'all':
1✔
216
        word_size = len(data) * 8
1✔
217
    elif not isinstance(word_size, six.integer_types) or word_size <= 0:
1!
218
        raise ValueError("unpack(): word_size must be a positive integer or the string 'all'")
×
219

220
    byte_size = (word_size + 7) // 8
1✔
221

222
    if byte_size != len(data):
1!
UNCOV
223
        raise ValueError("unpack(): data must have length %d, since word_size was %d" % (byte_size, word_size))
×
224

225
    number = 0
1✔
226

227
    if endianness == "little":
1✔
228
        data = reversed(data)
1✔
229
    data = bytearray(data)
1✔
230

231
    for c in data:
1✔
232
        number = (number << 8) + c
1✔
233

234
    number = number & ((1 << word_size) - 1)
1✔
235

236
    if not sign:
1✔
237
        return int(number)
1✔
238

239
    signbit = number & (1 << (word_size-1))
1✔
240
    return int(number - 2*signbit)
1✔
241

242
@LocalNoarchContext
1✔
243
def unpack_many(data, word_size = None):
1✔
244
    """unpack_many(data, word_size = None, endianness = None, sign = None) -> int list
245

246
    Splits `data` into groups of ``word_size//8`` bytes and calls :func:`unpack` on each group.  Returns a list of the results.
247

248
    `word_size` must be a multiple of `8` or the string "all".  In the latter case a singleton list will always be returned.
249

250
    Args
251
        number (int): String to convert
252
        word_size (int): Word size of the converted integers or the string "all" (in bits).
253
        endianness (str): Endianness of the converted integer ("little"/"big")
254
        sign (str): Signedness of the converted integer (False/True)
255
        kwargs: Anything that can be passed to context.local
256

257
    Returns:
258
        The unpacked numbers.
259

260
    Examples:
261

262
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='little', sign=False)))
263
        ['0x55aa', '0x33cc']
264
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=False)))
265
        ['0xaa55', '0xcc33']
266
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=True)))
267
        ['-0x55ab', '-0x33cd']
268
        >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='little', sign=True)))
269
        ['0x302ff']
270
        >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='big', sign=True)))
271
        ['-0xfdfd']
272
    """
273
    # Lookup in context if None
274
    word_size  = word_size  or context.word_size
1✔
275
    endianness = context.endianness
1✔
276
    sign       = context.sign
1✔
277

278
    if word_size == 'all':
1✔
279
        return [unpack(data, word_size)]
1✔
280

281
    # Currently we only group on byte boundaries
282
    if word_size % 8 != 0:
1!
283
        raise ValueError("unpack_many(): word_size must be a multiple of 8")
×
284

285
    out = []
1✔
286
    n = word_size // 8
1✔
287
    for i in range(0, len(data), n):
1✔
288
        out.append(unpack(data[i:i+n], word_size))
1✔
289

290
    return list(map(int, out))
1✔
291

292

293

294
#
295
# Make individual packers, e.g. _p8lu
296
#
297
ops   = ['p','u']
1✔
298
sizes = {8:'b', 16:'h', 32:'i', 64:'q'}
1✔
299
ends  = ['b','l']
1✔
300
signs = ['s','u']
1✔
301

302
op_verbs         = {'p': 'pack', 'u': 'unpack'}
1✔
303

304

305
def make_single(op,size,end,sign):
1✔
306
    name = '_%s%s%s%s' % (op, size, end, sign)
1✔
307
    fmt  = sizes[size]
1✔
308
    end = '>' if end == 'b' else '<'
1✔
309

310
    if sign == 'u':
1✔
311
        fmt = fmt.upper()
1✔
312
    fmt = end+fmt
1✔
313

314
    struct_op = getattr(struct.Struct(fmt), op_verbs[op])
1✔
315
    if op == 'u':
1✔
316
        def routine(data, stacklevel=1):
1✔
317
            data = _need_bytes(data, stacklevel)
1✔
318
            return struct_op(data)[0]
1✔
319
    else:
320
        def routine(data, stacklevel=None):
1✔
321
            return struct_op(data)
1✔
322
    routine.__name__ = routine.__qualname__ = name
1✔
323

324
    return name, routine
1✔
325

326

327
for op,size,end,sign in iters.product(ops, sizes, ends, signs):
1✔
328
    name, routine = make_single(op,size,end,sign)
1✔
329
    setattr(mod, name, routine)
1✔
330

331

332
#
333
# Make normal user-oriented packers, e.g. p8
334
#
335
def _do_packing(op, size, number):
1✔
336

337
    name = "%s%s" % (op,size)
1✔
338
    mod = sys.modules[__name__]
1✔
339

340
    ls = getattr(mod, "_%sls" % (name))
1✔
341
    lu = getattr(mod, "_%slu" % (name))
1✔
342
    bs = getattr(mod, "_%sbs" % (name))
1✔
343
    bu = getattr(mod, "_%sbu" % (name))
1✔
344

345
    endian = context.endian
1✔
346
    signed = context.signed
1✔
347
    return {("little", True ):  ls,
1✔
348
            ("little", False):  lu,
349
            ("big",    True ):  bs,
350
            ("big",    False):  bu}[endian, signed](number, 3)
351

352
@LocalNoarchContext
1✔
353
def p8(number, endianness = None, sign = None, **kwargs):
1✔
354
    """p8(number, endianness, sign, ...) -> bytes
355

356
    Packs an 8-bit integer
357

358
    Arguments:
359
        number (int): Number to convert
360
        endianness (str): Endianness of the converted integer ("little"/"big")
361
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
362
        kwargs (dict): Arguments passed to context.local(), such as
363
            ``endian`` or ``signed``.
364

365
    Returns:
366
        The packed number as a byte string
367
    """
368
    return _do_packing('p', 8, number)
1✔
369

370
@LocalNoarchContext
1✔
371
def p16(number, endianness = None, sign = None, **kwargs):
1✔
372
    """p16(number, endianness, sign, ...) -> bytes
373

374
    Packs an 16-bit integer
375

376
    Arguments:
377
        number (int): Number to convert
378
        endianness (str): Endianness of the converted integer ("little"/"big")
379
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
380
        kwargs (dict): Arguments passed to context.local(), such as
381
            ``endian`` or ``signed``.
382

383
    Returns:
384
        The packed number as a byte string
385
    """
386
    return _do_packing('p', 16, number)
1✔
387

388
@LocalNoarchContext
1✔
389
def p32(number, endianness = None, sign = None, **kwargs):
1✔
390
    """p32(number, endianness, sign, ...) -> bytes
391

392
    Packs an 32-bit integer
393

394
    Arguments:
395
        number (int): Number to convert
396
        endianness (str): Endianness of the converted integer ("little"/"big")
397
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
398
        kwargs (dict): Arguments passed to context.local(), such as
399
            ``endian`` or ``signed``.
400

401
    Returns:
402
        The packed number as a byte string
403
    """
404
    return _do_packing('p', 32, number)
1✔
405

406
@LocalNoarchContext
1✔
407
def p64(number, endianness = None, sign = None, **kwargs):
1✔
408
    """p64(number, endianness, sign, ...) -> bytes
409

410
    Packs an 64-bit integer
411

412
    Arguments:
413
        number (int): Number to convert
414
        endianness (str): Endianness of the converted integer ("little"/"big")
415
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
416
        kwargs (dict): Arguments passed to context.local(), such as
417
            ``endian`` or ``signed``.
418

419
    Returns:
420
        The packed number as a byte string
421
    """
422
    return _do_packing('p', 64, number)
1✔
423

424
@LocalNoarchContext
1✔
425
def u8(data, endianness = None, sign = None, **kwargs):
1✔
426
    """u8(data, endianness, sign, ...) -> int
427

428
    Unpacks an 8-bit integer
429

430
    Arguments:
431
        data (bytes): Byte string to convert
432
        endianness (str): Endianness of the converted integer ("little"/"big")
433
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
434
        kwargs (dict): Arguments passed to context.local(), such as
435
            ``endian`` or ``signed``.
436

437
    Returns:
438
        The unpacked number
439
    """
440
    return _do_packing('u', 8, data)
1✔
441

442
@LocalNoarchContext
1✔
443
def u16(data, endianness = None, sign = None, **kwargs):
1✔
444
    """u16(data, endianness, sign, ...) -> int
445

446
    Unpacks an 16-bit integer
447

448
    Arguments:
449
        data (bytes): Byte string to convert
450
        endianness (str): Endianness of the converted integer ("little"/"big")
451
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
452
        kwargs (dict): Arguments passed to context.local(), such as
453
            ``endian`` or ``signed``.
454

455
    Returns:
456
        The unpacked number
457
    """
458
    return _do_packing('u', 16, data)
1✔
459

460
@LocalNoarchContext
1✔
461
def u32(data, endianness = None, sign = None, **kwargs):
1✔
462
    """u32(data, endianness, sign, ...) -> int
463

464
    Unpacks an 32-bit integer
465

466
    Arguments:
467
        data (bytes): Byte string to convert
468
        endianness (str): Endianness of the converted integer ("little"/"big")
469
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
470
        kwargs (dict): Arguments passed to context.local(), such as
471
            ``endian`` or ``signed``.
472

473
    Returns:
474
        The unpacked number
475
    """
476
    return _do_packing('u', 32, data)
1✔
477

478
@LocalNoarchContext
1✔
479
def u64(data, endianness = None, sign = None, **kwargs):
1✔
480
    """u64(data, endianness, sign, ...) -> int
481

482
    Unpacks an 64-bit integer
483

484
    Arguments:
485
        data (bytes): Byte string to convert
486
        endianness (str): Endianness of the converted integer ("little"/"big")
487
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
488
        kwargs (dict): Arguments passed to context.local(), such as
489
            ``endian`` or ``signed``.
490

491
    Returns:
492
        The unpacked number
493
    """
494
    return _do_packing('u', 64, data)
1✔
495

496
def make_packer(word_size = None, sign = None, **kwargs):
1✔
497
    """make_packer(word_size = None, endianness = None, sign = None) -> number → str
498

499
    Creates a packer by "freezing" the given arguments.
500

501
    Semantically calling ``make_packer(w, e, s)(data)`` is equivalent to calling
502
    ``pack(data, w, e, s)``. If word_size is one of 8, 16, 32 or 64, it is however
503
    faster to call this function, since it will then use a specialized version.
504

505
    Arguments:
506
        word_size (int): The word size to be baked into the returned packer or the string all (in bits).
507
        endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
508
        sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
509
        kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
510

511
    Returns:
512
        A function, which takes a single argument in the form of a number and returns a string
513
        of that number in a packed form.
514

515
    Examples:
516

517
        >>> p = make_packer(32, endian='little', sign='unsigned')
518
        >>> p
519
        <function _p32lu at 0x...>
520
        >>> p(42)
521
        b'*\\x00\\x00\\x00'
522
        >>> p(-1)
523
        Traceback (most recent call last):
524
            ...
525
        error: integer out of range for 'I' format code
526
        >>> make_packer(33, endian='little', sign='unsigned')
527
        <function ...<lambda> at 0x...>
528
"""
529
    with context.local(sign=sign, **kwargs):
1✔
530
        word_size  = word_size or context.word_size
1✔
531
        endianness = context.endianness
1✔
532
        sign       = sign if sign is None else context.sign
1✔
533

534
        if word_size in [8, 16, 32, 64]:
1✔
535
            packer = {
1✔
536
                (8, 0, 0):  _p8lu,
537
                (8, 0, 1):  _p8ls,
538
                (8, 1, 0):  _p8bu,
539
                (8, 1, 1):  _p8bs,
540
                (16, 0, 0): _p16lu,
541
                (16, 0, 1): _p16ls,
542
                (16, 1, 0): _p16bu,
543
                (16, 1, 1): _p16bs,
544
                (32, 0, 0): _p32lu,
545
                (32, 0, 1): _p32ls,
546
                (32, 1, 0): _p32bu,
547
                (32, 1, 1): _p32bs,
548
                (64, 0, 0): _p64lu,
549
                (64, 0, 1): _p64ls,
550
                (64, 1, 0): _p64bu,
551
                (64, 1, 1): _p64bs,
552
            }.get((word_size, {'big': 1, 'little': 0}[endianness], sign))
553

554
            if packer:
1✔
555
                return packer
1✔
556

557
        return lambda number: pack(number, word_size, endianness, sign)
1✔
558

559
@LocalNoarchContext
1✔
560
def make_unpacker(word_size = None, endianness = None, sign = None, **kwargs):
1✔
561
    """make_unpacker(word_size = None, endianness = None, sign = None,  **kwargs) -> str → number
562

563
    Creates an unpacker by "freezing" the given arguments.
564

565
    Semantically calling ``make_unpacker(w, e, s)(data)`` is equivalent to calling
566
    ``unpack(data, w, e, s)``. If word_size is one of 8, 16, 32 or 64, it is however
567
    faster to call this function, since it will then use a specialized version.
568

569
    Arguments:
570
        word_size (int): The word size to be baked into the returned packer (in bits).
571
        endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
572
        sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
573
        kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
574

575
    Returns:
576
        A function, which takes a single argument in the form of a string and returns a number
577
        of that string in an unpacked form.
578

579
    Examples:
580

581
        >>> u = make_unpacker(32, endian='little', sign='unsigned')
582
        >>> u
583
        <function _u32lu at 0x...>
584
        >>> hex(u(b'/bin'))
585
        '0x6e69622f'
586
        >>> u(b'abcde')
587
        Traceback (most recent call last):
588
            ...
589
        error: unpack requires a string argument of length 4
590
        >>> make_unpacker(33, endian='little', sign='unsigned')
591
        <function ...<lambda> at 0x...>
592
"""
593
    word_size  = word_size or context.word_size
1✔
594
    endianness = context.endianness
1✔
595
    sign       = context.sign
1✔
596

597
    if word_size in [8, 16, 32, 64]:
1✔
598
        endianness = 1 if endianness == 'big'    else 0
1✔
599

600
        return {
1✔
601
            (8, 0, 0):  _u8lu,
602
            (8, 0, 1):  _u8ls,
603
            (8, 1, 0):  _u8bu,
604
            (8, 1, 1):  _u8bs,
605
            (16, 0, 0): _u16lu,
606
            (16, 0, 1): _u16ls,
607
            (16, 1, 0): _u16bu,
608
            (16, 1, 1): _u16bs,
609
            (32, 0, 0): _u32lu,
610
            (32, 0, 1): _u32ls,
611
            (32, 1, 0): _u32bu,
612
            (32, 1, 1): _u32bs,
613
            (64, 0, 0): _u64lu,
614
            (64, 0, 1): _u64ls,
615
            (64, 1, 0): _u64bu,
616
            (64, 1, 1): _u64bs,
617
        }[word_size, endianness, sign]
618
    else:
619
        return lambda number: unpack(number, word_size, endianness, sign)
1✔
620

621
def _fit(pieces, preprocessor, packer, filler, stacklevel=1):
1✔
622

623
    # Pulls bytes from `filler` and adds them to `pad` until it ends in `key`.
624
    # Returns the index of `key` in `pad`.
625
    pad = bytearray()
1✔
626
    def fill(key):
1✔
627
        key = bytearray(key)
1✔
628
        offset = pad.find(key)
1✔
629
        while offset == -1:
1✔
630
            pad.append(next(filler))
1✔
631
            offset = pad.find(key, -len(key))
1✔
632
        return offset
1✔
633

634
    # Key conversion:
635
    # - convert str/unicode keys to offsets
636
    # - convert large int (no null-bytes in a machine word) keys to offsets
637
    pieces_ = dict()
1✔
638
    large_key = 2**(context.word_size-8)
1✔
639
    for k, v in pieces.items():
1✔
640
        if isinstance(k, six.integer_types):
1✔
641
            if k >= large_key:
1✔
642
                k = fill(pack(k))
1✔
643
        elif isinstance(k, (six.text_type, bytearray, bytes)):
1!
644
            k = fill(_need_bytes(k, stacklevel, 0x80))
1✔
645
        else:
646
            raise TypeError("flat(): offset must be of type int or str, but got '%s'" % type(k))
×
647
        if k in pieces_:
1!
648
            raise ValueError("flag(): multiple values at offset %d" % k)
×
649
        pieces_[k] = v
1✔
650
    pieces = pieces_
1✔
651

652
    # We must "roll back" `filler` so each recursive call to `_flat` gets it in
653
    # the right position
654
    filler = iters.chain(pad, filler)
1✔
655

656
    # Build output
657
    out = b''
1✔
658

659
    # Negative indices need to be removed and then re-submitted
660
    negative = {k:v for k,v in pieces.items() if isinstance(k, int) and k<0}
1✔
661

662
    for k in negative:
1✔
663
        del pieces[k]
1✔
664

665
    # Positive output
666
    for k, v in sorted(pieces.items()):
1✔
667
        if k < len(out):
1!
668
            raise ValueError("flat(): data at offset %d overlaps with previous data which ends at offset %d" % (k, len(out)))
×
669

670
        # Fill up to offset
671
        while len(out) < k:
1✔
672
            out += p8(next(filler))
1✔
673

674
        # Recursively flatten data
675
        out += _flat([v], preprocessor, packer, filler, stacklevel + 1)
1✔
676

677
    # Now do negative indices
678
    out_negative = b''
1✔
679
    if negative:
1✔
680
        most_negative = min(negative.keys())
1✔
681
        for k, v in sorted(negative.items()):
1✔
682
            k += -most_negative
1✔
683

684
            if k < len(out_negative):
1!
685
                raise ValueError("flat(): data at offset %d overlaps with previous data which ends at offset %d" % (k, len(out)))
×
686

687
            # Fill up to offset
688
            while len(out_negative) < k:
1✔
689
                out_negative += p8(next(filler))
1✔
690

691
            # Recursively flatten data
692
            out_negative += _flat([v], preprocessor, packer, filler, stacklevel + 1)
1✔
693

694
    return filler, out_negative + out
1✔
695

696
def _flat(args, preprocessor, packer, filler, stacklevel=1):
1✔
697
    out = []
1✔
698
    for arg in args:
1✔
699

700
        if not isinstance(arg, (list, tuple, dict)):
1✔
701
            arg_ = preprocessor(arg)
1✔
702
            if arg_ is not None:
1✔
703
                arg = arg_
1✔
704

705
        if hasattr(arg, '__flat__'):
1✔
706
            val = arg.__flat__()
1✔
707
        elif isinstance(arg, (list, tuple)):
1✔
708
            val = _flat(arg, preprocessor, packer, filler, stacklevel + 1)
1✔
709
        elif isinstance(arg, dict):
1✔
710
            filler, val = _fit(arg, preprocessor, packer, filler, stacklevel + 1)
1✔
711
        elif isinstance(arg, bytes):
1✔
712
            val = arg
1✔
713
        elif isinstance(arg, six.text_type):
1!
714
            val = _need_bytes(arg, stacklevel + 1)
×
715
        elif isinstance(arg, six.integer_types):
1✔
716
            val = packer(arg)
1✔
717
        elif isinstance(arg, bytearray):
1!
718
            val = bytes(arg)
1✔
719
        else:
720
            raise ValueError("flat(): Flat does not support values of type %s" % type(arg))
×
721

722
        out.append(val)
1✔
723

724
        # Advance `filler` for "non-recursive" values
725
        if not isinstance(arg, (list, tuple, dict)):
1✔
726
            for _ in range(len(val)):
1✔
727
                next(filler)
1✔
728

729
    return b''.join(out)
1✔
730

731
@LocalNoarchContext
1✔
732
def flat(*args, **kwargs):
1✔
733
    r"""flat(\*args, preprocessor = None, length = None, filler = de_bruijn(),
734
     word_size = None, endianness = None, sign = None) -> str
735

736
    Flattens the arguments into a string.
737

738
    This function takes an arbitrary number of arbitrarily nested lists, tuples
739
    and dictionaries.  It will then find every string and number inside those
740
    and flatten them out.  Strings are inserted directly while numbers are
741
    packed using the :func:`pack` function.  Unicode strings are UTF-8 encoded.
742

743
    Dictionary keys give offsets at which to place the corresponding values
744
    (which are recursively flattened).  Offsets are relative to where the
745
    flattened dictionary occurs in the output (i.e. ``{0: 'foo'}`` is equivalent
746
    to ``'foo'``).  Offsets can be integers, unicode strings or regular strings.
747
    Integer offsets >= ``2**(word_size-8)`` are converted to a string using
748
    :func:`pack`.  Unicode strings are UTF-8 encoded.  After these conversions
749
    offsets are either integers or strings.  In the latter case, the offset will
750
    be the lowest index at which the string occurs in `filler`.  See examples
751
    below.
752

753
    Space between pieces of data is filled out using the iterable `filler`.  The
754
    `n`'th byte in the output will be byte at index ``n % len(iterable)`` byte
755
    in `filler` if it has finite length or the byte at index `n` otherwise.
756

757
    If `length` is given, the output will be padded with bytes from `filler` to
758
    be this size.  If the output is longer than `length`, a :py:exc:`ValueError`
759
    exception is raised.
760

761
    The three kwargs `word_size`, `endianness` and `sign` will default to using
762
    values in :mod:`pwnlib.context` if not specified as an argument.
763

764
    Arguments:
765
      args: Values to flatten
766
      preprocessor (function): Gets called on every element to optionally
767
         transform the element before flattening. If :const:`None` is
768
         returned, then the original value is used.
769
      length: The length of the output.
770
      filler: Iterable to use for padding.
771
      word_size (int): Word size of the converted integer.
772
      endianness (str): Endianness of the converted integer ("little"/"big").
773
      sign (str): Signedness of the converted integer (False/True)
774

775
    Examples:
776

777
        (Test setup, please ignore)
778
    
779
        >>> context.clear()
780

781
        Basic usage of :meth:`flat` works similar to the pack() routines.
782

783
        >>> flat(4)
784
        b'\x04\x00\x00\x00'
785

786
        :meth:`flat` works with strings, bytes, lists, and dictionaries.
787

788
        >>> flat(b'X')
789
        b'X'
790
        >>> flat([1,2,3])
791
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
792
        >>> flat({4:b'X'})
793
        b'aaaaX'
794

795
        :meth:`.flat` flattens all of the values provided, and allows nested lists
796
        and dictionaries.
797

798
        >>> flat([{4:b'X'}] * 2)
799
        b'aaaaXaaacX'
800
        >>> flat([[[[[[[[[1]]]], 2]]]]])
801
        b'\x01\x00\x00\x00\x02\x00\x00\x00'
802

803
        You can also provide additional arguments like endianness, word-size, and
804
        whether the values are treated as signed or not.
805

806
        >>> flat(1, b"test", [[[b"AB"]*2]*3], endianness = 'little', word_size = 16, sign = False)
807
        b'\x01\x00testABABABABABAB'
808

809
        A preprocessor function can be provided in order to modify the values in-flight.
810
        This example converts increments each value by 1, then converts to a byte string.
811

812
        >>> flat([1, [2, 3]], preprocessor = lambda x: str(x+1).encode())
813
        b'234'
814

815
        Using dictionaries is a fast way to get specific values at specific offsets,
816
        without having to do ``data += "foo"`` repeatedly.
817

818
        >>> flat({12: 0x41414141,
819
        ...       24: b'Hello',
820
        ...      })
821
        b'aaaabaaacaaaAAAAeaaafaaaHello'
822

823
        Dictionary usage permits directly using values derived from :func:`.cyclic`.
824
        See :func:`.cyclic`, :function:`pwnlib.context.context.cyclic_alphabet`, and :data:`.context.cyclic_size`
825
        for more options.  
826

827
        The cyclic pattern can be provided as either the text or hexadecimal offset.
828

829
        >>> flat({ 0x61616162: b'X'})
830
        b'aaaaX'
831
        >>> flat({'baaa': b'X'})
832
        b'aaaaX'
833

834
        Fields do not have to be in linear order, and can be freely mixed.
835
        This also works with cyclic offsets.
836

837
        >>> flat({2: b'A', 0:b'B'})
838
        b'BaA'
839
        >>> flat({0x61616161: b'x', 0x61616162: b'y'})
840
        b'xaaay'
841
        >>> flat({0x61616162: b'y', 0x61616161: b'x'})
842
        b'xaaay'
843

844
        Fields do not have to be in order, and can be freely mixed.
845

846
        >>> flat({'caaa': b'XXXX', 16: b'\x41', 20: 0xdeadbeef})
847
        b'aaaabaaaXXXXdaaaAaaa\xef\xbe\xad\xde'
848
        >>> flat({ 8: [0x41414141, 0x42424242], 20: b'CCCC'})
849
        b'aaaabaaaAAAABBBBeaaaCCCC'
850
        >>> fit({
851
        ...     0x61616161: b'a',
852
        ...     1: b'b',
853
        ...     0x61616161+2: b'c',
854
        ...     3: b'd',
855
        ... })
856
        b'abadbaaac'
857

858
        By default, gaps in the data are filled in with the :meth:`.cyclic` pattern.
859
        You can customize this by providing an iterable or method for the ``filler``
860
        argument.
861

862
        >>> flat({12: b'XXXX'}, filler = b'_', length = 20)
863
        b'____________XXXX____'
864
        >>> flat({12: b'XXXX'}, filler = b'AB', length = 20)
865
        b'ABABABABABABXXXXABAB'
866

867
        Nested dictionaries also work as expected.
868

869
        >>> flat({4: {0: b'X', 4: b'Y'}})
870
        b'aaaaXaaaY'
871
        >>> fit({4: {4: b'XXXX'}})
872
        b'aaaabaaaXXXX'
873

874
        Negative indices are also supported, though this only works for integer
875
        keys.
876
    
877
        >>> flat({-4: b'x', -1: b'A', 0: b'0', 4: b'y'})
878
        b'xaaA0aaay'
879
    """
880
    # HACK: To avoid circular imports we need to delay the import of `cyclic`
881
    from pwnlib.util import cyclic
1✔
882

883
    preprocessor = kwargs.pop('preprocessor', lambda x: None)
1✔
884
    filler       = kwargs.pop('filler', cyclic.de_bruijn())
1✔
885
    length       = kwargs.pop('length', None)
1✔
886
    stacklevel   = kwargs.pop('stacklevel', 0)
1✔
887

888
    if isinstance(filler, (str, six.text_type)):
1✔
889
        filler = bytearray(_need_bytes(filler))
1✔
890

891
    if kwargs != {}:
1!
892
        raise TypeError("flat() does not support argument %r" % kwargs.popitem()[0])
×
893

894
    filler = iters.cycle(filler)
1✔
895
    out = _flat(args, preprocessor, make_packer(), filler, stacklevel + 2)
1✔
896

897
    if length:
1✔
898
        if len(out) > length:
1!
899
            raise ValueError("flat(): Arguments does not fit within `length` (= %d) bytes" % length)
×
900
        out += b''.join(p8(next(filler)) for _ in range(length - len(out)))
1✔
901

902
    return out
1✔
903

904
def fit(*args, **kwargs):
1✔
905
    """Legacy alias for :func:`flat`"""
906
    kwargs['stacklevel'] = kwargs.get('stacklevel', 0) + 1
1✔
907
    return flat(*args, **kwargs)
1✔
908

909
"""
1✔
910
    Generates a string from a dictionary mapping offsets to data to place at
911
    that offset.
912

913
    For each key-value pair in `pieces`, the key is either an offset or a byte
914
    sequence.  In the latter case, the offset will be the lowest index at which
915
    the sequence occurs in `filler`.  See examples below.
916

917
    Each piece of data is passed to :meth:`flat` along with the keyword
918
    arguments `word_size`, `endianness` and `sign`.
919

920
    Space between pieces of data is filled out using the iterable `filler`.  The
921
    `n`'th byte in the output will be byte at index ``n % len(iterable)`` byte
922
    in `filler` if it has finite length or the byte at index `n` otherwise.
923

924
    If `length` is given, the output will padded with bytes from `filler` to be
925
    this size.  If the output is longer than `length`, a :py:exc:`ValueError`
926
    exception is raised.
927

928
    If entries in `pieces` overlap, a :py:exc:`ValueError` exception is
929
    raised.
930

931
    Arguments:
932
      pieces: Offsets and values to output.
933
      length: The length of the output.
934
      filler: Iterable to use for padding.
935
      preprocessor (function): Gets called on every element to optionally
936
         transform the element before flattening. If :const:`None` is
937
         returned, then the original value is used.
938
      word_size (int): Word size of the converted integer (in bits).
939
      endianness (str): Endianness of the converted integer ("little"/"big").
940
      sign (str): Signedness of the converted integer (False/True)
941

942
    Examples:
943

944
    """
945

946
def signed(integer):
1✔
947
    return unpack(pack(integer), signed=True)
×
948

949
def unsigned(integer):
1✔
950
    return unpack(pack(integer))
×
951

952
def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
1✔
953
    """dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False) -> dst
954

955
    Inspired by the command line tool ``dd``, this function copies `count` byte
956
    values from offset `seek` in `src` to offset `skip` in `dst`.  If `count` is
957
    0, all of ``src[seek:]`` is copied.
958

959
    If `dst` is a mutable type it will be updated.  Otherwise, a new instance of
960
    the same type will be created.  In either case the result is returned.
961

962
    `src` can be an iterable of characters or integers, a unicode string or a
963
    file object.  If it is an iterable of integers, each integer must be in the
964
    range [0;255].  If it is a unicode string, its UTF-8 encoding will be used.
965

966
    The seek offset of file objects will be preserved.
967

968
    Arguments:
969
        dst: Supported types are :class:`file`, :class:`list`, :class:`tuple`,
970
             :class:`str`, :class:`bytearray` and :class:`unicode`.
971
        src: An iterable of byte values (characters or integers), a unicode
972
             string or a file object.
973
        count (int): How many bytes to copy.  If `count` is 0 or larger than
974
                     ``len(src[seek:])``, all bytes until the end of `src` are
975
                     copied.
976
        skip (int): Offset in `dst` to copy to.
977
        seek (int): Offset in `src` to copy from.
978
        truncate (bool): If :const:`True`, `dst` is truncated at the last copied
979
                         byte.
980

981
    Returns:
982
        A modified version of `dst`.  If `dst` is a mutable type it will be
983
        modified in-place.
984

985
    Examples:
986

987
        >>> dd(tuple('Hello!'), b'?', skip = 5)
988
        ('H', 'e', 'l', 'l', 'o', b'?')
989
        >>> dd(list('Hello!'), (63,), skip = 5)
990
        ['H', 'e', 'l', 'l', 'o', b'?']
991
        >>> _ = open('/tmp/foo', 'w').write('A' * 10)
992
        >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4).read()
993
        'AAA\\x00\\x00\\x00\\x00AAA'
994
        >>> _ = open('/tmp/foo', 'w').write('A' * 10)
995
        >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4, truncate = True).read()
996
        'AAA\\x00\\x00\\x00\\x00'
997
    """
998

999
    # Re-open file objects to make sure we have the mode right
1000
    if hasattr(src, 'name'):
1✔
1001
        src = open(src.name, 'rb')
1✔
1002
    if hasattr(dst, 'name'):
1✔
1003
        real_dst = dst
1✔
1004
        dst = open(dst.name, 'rb+')
1✔
1005

1006
    # Special case: both `src` and `dst` are files, so we don't need to hold
1007
    # everything in memory
1008
    if hasattr(src, 'seek') and hasattr(dst, 'seek'):
1✔
1009
        src.seek(seek)
1✔
1010
        dst.seek(skip)
1✔
1011
        n = 0
1✔
1012
        if count:
1!
1013
            while n < count:
1✔
1014
                s = src.read(min(count - n, 0x1000))
1✔
1015
                if not s:
1!
1016
                    break
×
1017
                n += len(s)
1✔
1018
                dst.write(s)
1✔
1019
        else:
1020
            while True:
×
1021
                s = src.read(0x1000)
×
1022
                if not s:
×
1023
                    break
×
1024
                n += len(s)
×
1025
                dst.write(s)
×
1026
        if truncate:
1✔
1027
            dst.truncate(skip + n)
1✔
1028
        src.close()
1✔
1029
        dst.close()
1✔
1030
        return real_dst
1✔
1031

1032
    # Otherwise get `src` in canonical form, i.e. a string of at most `count`
1033
    # bytes
1034
    if isinstance(src, six.text_type):
1!
1035
        if count:
×
1036
            # The only way to know where the `seek`th byte is, is to decode, but
1037
            # we only need to decode up to the first `seek + count` code points
1038
            src = src[:seek + count].encode('utf8')
×
1039
            # The code points may result in more that `seek + count` bytes
1040
            src = src[seek : seek + count]
×
1041
        else:
1042
            src = src.encode('utf8')[seek:]
×
1043

1044
    elif hasattr(src, 'seek'):
1!
1045
        src.seek(seek)
×
1046
        src_ = b''
×
1047
        if count:
×
1048
            while len(src_) < count:
×
1049
                s = src.read(count - len(src_))
×
1050
                if not s:
×
1051
                    break
×
1052
                src_ += s
×
1053
        else:
1054
            while True:
×
1055
                s = src.read()
×
1056
                if not s:
×
1057
                    break
×
1058
                src_ += s
×
1059
        src.close()
×
1060
        src = src_
×
1061

1062
    elif isinstance(src, bytes):
1✔
1063
        if count:
1!
1064
            src = src[seek : seek + count]
×
1065
        else:
1066
            src = src[seek:]
1✔
1067

1068
    elif hasattr(src, '__iter__'):
1!
1069
        src = src[seek:]
1✔
1070
        src_ = b''
1✔
1071
        for i, b in enumerate(src, seek):
1✔
1072
            if count and i > count + seek:
1!
1073
                break
×
1074
            if isinstance(b, bytes):
1!
1075
                src_ += b
×
1076
            elif isinstance(b, six.integer_types):
1!
1077
                if b > 255 or b < 0:
1!
1078
                    raise ValueError("dd(): Source value %d at index %d is not in range [0;255]" % (b, i))
×
1079
                src_ += _p8lu(b)
1✔
1080
            else:
1081
                raise TypeError("dd(): Unsupported `src` element type: %r" % type(b))
×
1082
        src = src_
1✔
1083

1084
    else:
1085
        raise TypeError("dd(): Unsupported `src` type: %r" % type(src))
×
1086

1087
    # If truncate, then where?
1088
    if truncate:
1!
1089
        truncate = skip + len(src)
×
1090

1091
    # UTF-8 encode unicode `dst`
1092
    if isinstance(dst, six.text_type):
1!
1093
        dst = dst.encode('utf8')
×
1094
        utf8 = True
×
1095
    else:
1096
        utf8 = False
1✔
1097

1098
    # Match on the type of `dst`
1099
    if   hasattr(dst, 'seek'):
1!
1100
        dst.seek(skip)
×
1101
        dst.write(src)
×
1102
        if truncate:
×
1103
            dst.truncate(truncate)
×
1104
        dst.close()
×
1105
        dst = real_dst
×
1106

1107
    elif isinstance(dst, (list, bytearray)):
1✔
1108
        dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
1✔
1109
        if truncate:
1!
1110
            while len(dst) > truncate:
×
1111
                dst.pop()
×
1112

1113
    elif isinstance(dst, tuple):
1!
1114
        tail = dst[skip + len(src):]
1✔
1115
        dst = dst[:skip] + tuple(map(p8, bytearray(src)))
1✔
1116
        if not truncate:
1!
1117
            dst = dst + tail
1✔
1118

1119
    elif isinstance(dst, bytes):
×
1120
        tail = dst[skip + len(src):]
×
1121
        dst = dst[:skip] + src
×
1122
        if not truncate:
×
1123
            dst = dst + tail
×
1124

1125
    else:
1126
        raise TypeError("dd(): Unsupported `dst` type: %r" % type(dst))
×
1127

1128
    if utf8:
1!
1129
        dst = dst.decode('utf8')
×
1130

1131
    return dst
1✔
1132

1133
def _need_bytes(s, level=1, min_wrong=0):
1✔
1134
    if isinstance(s, (bytes, bytearray)):
1✔
1135
        return s   # already bytes
1✔
1136

1137
    encoding = context.encoding
1✔
1138
    errors = 'strict'
1✔
1139
    worst = -1
1✔
1140
    if encoding == 'auto':
1!
1141
        worst = s and max(map(ord, s)) or 0
1✔
1142
        if worst > 255:
1!
1143
            encoding = 'UTF-8'
×
1144
            errors = 'surrogateescape'
×
1145
        elif worst > 127:
1!
1146
            encoding = 'ISO-8859-1'
×
1147
        else:
1148
            encoding = 'ASCII'
1✔
1149

1150
    if worst >= min_wrong:
1!
1151
        warnings.warn("Text is not bytes; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes"
×
1152
                      .format(encoding), BytesWarning, level + 2)
1153
    return s.encode(encoding, errors)
1✔
1154

1155
def _need_text(s, level=1):
1✔
1156
    if isinstance(s, (str, six.text_type)):
1!
1157
        return s   # already text
1✔
1158

1159
    if not isinstance(s, (bytes, bytearray)):
×
1160
        return repr(s)
×
1161

1162
    encoding = context.encoding
×
1163
    errors = 'strict'
×
1164
    if encoding == 'auto':
×
1165
        for encoding in 'ASCII', 'UTF-8', 'ISO-8859-1':
×
1166
            try:
×
1167
                s.decode(encoding)
×
1168
            except UnicodeDecodeError:
×
1169
                pass
×
1170
            else:
1171
                break
×
1172

1173
    warnings.warn("Bytes is not text; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes"
×
1174
                  .format(encoding), BytesWarning, level + 2)
1175
    return s.decode(encoding, errors)
×
1176

1177
def _encode(s):
1✔
1178
    if isinstance(s, (bytes, bytearray)):
1✔
1179
        return s   # already bytes
1✔
1180

1181
    if context.encoding == 'auto':
1!
1182
        try:
1✔
1183
            return s.encode('latin1')
1✔
1184
        except UnicodeEncodeError:
1✔
1185
            return s.encode('utf-8', 'surrogateescape')
1✔
1186
    return s.encode(context.encoding)
×
1187

1188
def _decode(b):
1✔
1189
    if isinstance(b, (str, six.text_type)):
1✔
1190
        return b   # already text
1✔
1191

1192
    if context.encoding == 'auto':
1!
1193
        try:
1✔
1194
            return b.decode('utf-8')
1✔
1195
        except UnicodeDecodeError:
1✔
1196
            return b.decode('latin1')
1✔
1197
        except AttributeError:
×
1198
            return b
×
1199
    return b.decode(context.encoding)
×
1200

1201
del op, size, end, sign
1✔
1202
del name, routine, mod
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc