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

Gallopsled / pwntools / 39eb5b9f9fc3ac2301be5a7a982096b3568d76e1

01 Nov 2023 10:20PM UTC coverage: 73.405% (+1.9%) from 71.525%
39eb5b9f9fc3ac2301be5a7a982096b3568d76e1

push

github-actions

Arusekk
Update CHANGELOG.md

3902 of 6416 branches covered (0.0%)

12255 of 16695 relevant lines covered (73.41%)

0.73 hits per line

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

97.0
/pwnlib/util/iters.py
1
"""
2
This module includes and extends the standard module :mod:`itertools`.
3
"""
4
from __future__ import absolute_import
1✔
5
from __future__ import division
1✔
6

7
import collections
1✔
8
import copy
1✔
9
import multiprocessing
1✔
10
import operator
1✔
11
import random
1✔
12
import time
1✔
13
from itertools import *
1✔
14
from six.moves import map, filter, filterfalse, range, zip, zip_longest
1✔
15

16
from pwnlib.context import context
1✔
17
from pwnlib.log import getLogger
1✔
18

19
__all__ = [
1✔
20
    'bruteforce'                             ,
21
    'mbruteforce'                            ,
22
    'chained'                                ,
23
    'consume'                                ,
24
    'cyclen'                                 ,
25
    'dotproduct'                             ,
26
    'flatten'                                ,
27
    'group'                                  ,
28
    'iter_except'                            ,
29
    'lexicographic'                          ,
30
    'lookahead'                              ,
31
    'nth'                                    ,
32
    'pad'                                    ,
33
    'pairwise'                               ,
34
    'powerset'                               ,
35
    'quantify'                               ,
36
    'random_combination'                     ,
37
    'random_combination_with_replacement'    ,
38
    'random_permutation'                     ,
39
    'random_product'                         ,
40
    'repeat_func'                            ,
41
    'roundrobin'                             ,
42
    'tabulate'                               ,
43
    'take'                                   ,
44
    'unique_everseen'                        ,
45
    'unique_justseen'                        ,
46
    'unique_window'                          ,
47
    # these are re-exported from itertools
48
    'chain'                                  ,
49
    'combinations'                           ,
50
    'combinations_with_replacement'          ,
51
    'compress'                               ,
52
    'count'                                  ,
53
    'cycle'                                  ,
54
    'dropwhile'                              ,
55
    'groupby'                                ,
56
    'filter'                                 ,
57
    'filterfalse'                            ,
58
    'map'                                    ,
59
    'islice'                                 ,
60
    'zip'                                    ,
61
    'zip_longest'                            ,
62
    'permutations'                           ,
63
    'product'                                ,
64
    'repeat'                                 ,
65
    'starmap'                                ,
66
    'takewhile'                              ,
67
    'tee'
68
]
69

70
log = getLogger(__name__)
1✔
71

72
def take(n, iterable):
1✔
73
    """take(n, iterable) -> list
74

75
    Returns first `n` elements of `iterable`.  If `iterable` is a iterator it
76
    will be advanced.
77

78
    Arguments:
79
      n(int):  Number of elements to take.
80
      iterable:  An iterable.
81

82
    Returns:
83
      A list of the first `n` elements of `iterable`.  If there are fewer than
84
      `n` elements in `iterable` they will all be returned.
85

86
    Examples:
87
      >>> take(2, range(10))
88
      [0, 1]
89
      >>> i = count()
90
      >>> take(2, i)
91
      [0, 1]
92
      >>> take(2, i)
93
      [2, 3]
94
      >>> take(9001, [1, 2, 3])
95
      [1, 2, 3]
96
    """
97
    return list(islice(iterable, n))
1✔
98

99
def tabulate(func, start = 0):
1✔
100
    """tabulate(func, start = 0) -> iterator
101

102
    Arguments:
103
      func(function):  The function to tabulate over.
104
      start(int):  Number to start on.
105

106
    Returns:
107
      An iterator with the elements ``func(start), func(start + 1), ...``.
108

109
    Examples:
110
      >>> take(2, tabulate(str))
111
      ['0', '1']
112
      >>> take(5, tabulate(lambda x: x**2, start = 1))
113
      [1, 4, 9, 16, 25]
114
    """
115
    return map(func, count(start))
1✔
116

117
def consume(n, iterator):
1✔
118
    """consume(n, iterator)
119

120
    Advance the iterator `n` steps ahead. If `n is :const:`None`, consume
121
    everything.
122

123
    Arguments:
124
      n(int):  Number of elements to consume.
125
      iterator(iterator):  An iterator.
126

127
    Returns:
128
      :const:`None`.
129

130
    Examples:
131
      >>> i = count()
132
      >>> consume(5, i)
133
      >>> next(i)
134
      5
135
      >>> i = iter([1, 2, 3, 4, 5])
136
      >>> consume(2, i)
137
      >>> list(i)
138
      [3, 4, 5]
139
      >>> def g():
140
      ...     for i in range(2):
141
      ...         yield i
142
      ...         print(i)
143
      >>> consume(None, g())
144
      0
145
      1
146
    """
147
    # Use functions that consume iterators at C speed.
148
    if n is None:
1✔
149
        # feed the entire iterator into a zero-length deque
150
        collections.deque(iterator, maxlen = 0)
1✔
151
    else:
152
        # advance to the empty slice starting at position n
153
        next(islice(iterator, n, n), None)
1✔
154

155
def nth(n, iterable, default = None):
1✔
156
    """nth(n, iterable, default = None) -> object
157

158
    Returns the element at index `n` in `iterable`.  If `iterable` is a
159
    iterator it will be advanced.
160

161
    Arguments:
162
      n(int):  Index of the element to return.
163
      iterable:  An iterable.
164
      default(objext):  A default value.
165

166
    Returns:
167
      The element at index `n` in `iterable` or `default` if `iterable` has too
168
      few elements.
169

170
    Examples:
171
      >>> nth(2, [0, 1, 2, 3])
172
      2
173
      >>> nth(2, [0, 1], 42)
174
      42
175
      >>> i = count()
176
      >>> nth(42, i)
177
      42
178
      >>> nth(42, i)
179
      85
180
    """
181
    return next(islice(iterable, n, None), default)
1✔
182

183
def quantify(iterable, pred = bool):
1✔
184
    """quantify(iterable, pred = bool) -> int
185

186
    Count how many times the predicate `pred` is :const:`True`.
187

188
    Arguments:
189
        iterable:  An iterable.
190
        pred:  A function that given an element from `iterable` returns either
191
               :const:`True` or :const:`False`.
192

193
    Returns:
194
      The number of elements in `iterable` for which `pred` returns
195
      :const:`True`.
196

197
    Examples:
198
      >>> quantify([1, 2, 3, 4], lambda x: x % 2 == 0)
199
      2
200
      >>> quantify(['1', 'two', '3', '42'], str.isdigit)
201
      3
202
    """
203
    return sum(map(pred, iterable))
1✔
204

205
def pad(iterable, value = None):
1✔
206
    """pad(iterable, value = None) -> iterator
207

208
    Pad an `iterable` with `value`, i.e. returns an iterator whoose elements are
209
    first the elements of `iterable` then `value` indefinitely.
210

211
    Arguments:
212
      iterable:  An iterable.
213
      value:  The value to pad with.
214

215
    Returns:
216
      An iterator whoose elements are first the elements of `iterable` then
217
      `value` indefinitely.
218

219
    Examples:
220
      >>> take(3, pad([1, 2]))
221
      [1, 2, None]
222
      >>> i = pad(iter([1, 2, 3]), 42)
223
      >>> take(2, i)
224
      [1, 2]
225
      >>> take(2, i)
226
      [3, 42]
227
      >>> take(2, i)
228
      [42, 42]
229
    """
230
    return chain(iterable, repeat(value))
1✔
231

232
def cyclen(n, iterable):
1✔
233
    """cyclen(n, iterable) -> iterator
234

235
    Repeats the elements of `iterable` `n` times.
236

237
    Arguments:
238
      n(int):  The number of times to repeat `iterable`.
239
      iterable:  An iterable.
240

241
    Returns:
242
      An iterator whoose elements are the elements of `iterator` repeated `n`
243
      times.
244

245
    Examples:
246
      >>> take(4, cyclen(2, [1, 2]))
247
      [1, 2, 1, 2]
248
      >>> list(cyclen(10, []))
249
      []
250
    """
251
    return chain.from_iterable(repeat(tuple(iterable), n))
1✔
252

253
def dotproduct(x, y):
1✔
254
    """dotproduct(x, y) -> int
255

256
    Computes the dot product of `x` and `y`.
257

258
    Arguments:
259
      x(iterable):  An iterable.
260
      x(iterable):  An iterable.
261

262
    Returns:
263
      The dot product of `x` and `y`, i.e.: ``x[0] * y[0] + x[1] * y[1] + ...``.
264

265
    Example:
266
      >>> dotproduct([1, 2, 3], [4, 5, 6])
267
      ... # 1 * 4 + 2 * 5 + 3 * 6 == 32
268
      32
269
    """
270
    return sum(map(operator.mul, x, y))
1✔
271

272
def flatten(xss):
1✔
273
    """flatten(xss) -> iterator
274

275
    Flattens one level of nesting; when `xss` is an iterable of iterables,
276
    returns an iterator whoose elements is the concatenation of the elements of
277
    `xss`.
278

279
    Arguments:
280
      xss:  An iterable of iterables.
281

282
    Returns:
283
      An iterator whoose elements are the concatenation of the iterables in
284
      `xss`.
285

286
    Examples:
287
      >>> list(flatten([[1, 2], [3, 4]]))
288
      [1, 2, 3, 4]
289
      >>> take(6, flatten([[43, 42], [41, 40], count()]))
290
      [43, 42, 41, 40, 0, 1]
291
    """
292
    return chain.from_iterable(xss)
1✔
293

294
def repeat_func(func, *args, **kwargs):
1✔
295
    """repeat_func(func, *args, **kwargs) -> iterator
296

297
    Repeatedly calls `func` with positional arguments `args` and keyword
298
    arguments `kwargs`.  If no keyword arguments is given the resulting iterator
299
    will be computed using only functions from :mod:`itertools` which are very
300
    fast.
301

302
    Arguments:
303
      func(function):  The function to call.
304
      args:  Positional arguments.
305
      kwargs:  Keyword arguments.
306

307
    Returns:
308
      An iterator whoose elements are the results of calling ``func(*args,
309
      **kwargs)`` repeatedly.
310

311
    Examples:
312
      >>> def f(x):
313
      ...     x[0] += 1
314
      ...     return x[0]
315
      >>> i = repeat_func(f, [0])
316
      >>> take(2, i)
317
      [1, 2]
318
      >>> take(2, i)
319
      [3, 4]
320
      >>> def f(**kwargs):
321
      ...     return kwargs.get('x', 43)
322
      >>> i = repeat_func(f, x = 42)
323
      >>> take(2, i)
324
      [42, 42]
325
      >>> i = repeat_func(f, 42)
326
      >>> take(2, i)
327
      Traceback (most recent call last):
328
          ...
329
      TypeError: f() takes exactly 0 arguments (1 given)
330
    """
331
    if kwargs:
1✔
332
        return starmap(lambda args, kwargs: func(*args, **kwargs),
1✔
333
                       repeat((args, kwargs))
334
                       )
335
    else:
336
        return starmap(func, repeat(args))
1✔
337

338
def pairwise(iterable):
1✔
339
    """pairwise(iterable) -> iterator
340

341
    Arguments:
342
      iterable:  An iterable.
343

344
    Returns:
345
      An iterator whoose elements are pairs of neighbouring elements of
346
      `iterable`.
347

348
    Examples:
349
      >>> list(pairwise([1, 2, 3, 4]))
350
      [(1, 2), (2, 3), (3, 4)]
351
      >>> i = starmap(operator.add, pairwise(count()))
352
      >>> take(5, i)
353
      [1, 3, 5, 7, 9]
354
    """
355
    a, b = tee(iterable)
1✔
356
    next(b, None)
1✔
357
    return zip(a, b)
1✔
358

359
def group(n, iterable, fill_value = None):
1✔
360
    """group(n, iterable, fill_value = None) -> iterator
361

362
    Similar to :func:`pwnlib.util.lists.group`, but returns an iterator and uses
363
    :mod:`itertools` fast build-in functions.
364

365
    Arguments:
366
      n(int):  The group size.
367
      iterable:  An iterable.
368
      fill_value:  The value to fill into the remaining slots of the last group
369
        if the `n` does not divide the number of elements in `iterable`.
370

371
    Returns:
372
      An iterator whoose elements are `n`-tuples of the elements of `iterable`.
373

374
    Examples:
375
      >>> list(group(2, range(5)))
376
      [(0, 1), (2, 3), (4, None)]
377
      >>> take(3, group(2, count()))
378
      [(0, 1), (2, 3), (4, 5)]
379
      >>> [''.join(x) for x in group(3, 'ABCDEFG', 'x')]
380
      ['ABC', 'DEF', 'Gxx']
381
    """
382
    args = [iter(iterable)] * n
1✔
383
    return zip_longest(fillvalue = fill_value, *args)
1✔
384

385
def roundrobin(*iterables):
1✔
386
    """roundrobin(*iterables)
387

388
    Take elements from `iterables` in a round-robin fashion.
389

390
    Arguments:
391
      *iterables:  One or more iterables.
392

393
    Returns:
394
      An iterator whoose elements are taken from `iterables` in a round-robin
395
      fashion.
396

397
    Examples:
398
      >>> ''.join(roundrobin('ABC', 'D', 'EF'))
399
      'ADEBFC'
400
      >>> ''.join(take(10, roundrobin('ABC', 'DE', repeat('x'))))
401
      'ADxBExCxxx'
402
    """
403
    # Recipe credited to George Sakkis
404
    pending = len(iterables)
1✔
405
    nexts = cycle(iter(it) for it in iterables)
1✔
406
    while pending:
1✔
407
        try:
1✔
408
            for nxt in nexts:
1!
409
                yield next(nxt)
1✔
410
        except StopIteration:
1✔
411
            pending -= 1
1✔
412
            nexts = cycle(islice(nexts, pending))
1✔
413

414
def powerset(iterable, include_empty = True):
1✔
415
    """powerset(iterable, include_empty = True) -> iterator
416

417
    The powerset of an iterable.
418

419
    Arguments:
420
      iterable:  An iterable.
421
      include_empty(bool):  Whether to include the empty set.
422

423
    Returns:
424
      The powerset of `iterable` as an interator of tuples.
425

426
    Examples:
427
      >>> list(powerset(range(3)))
428
      [(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)]
429
      >>> list(powerset(range(2), include_empty = False))
430
      [(0,), (1,), (0, 1)]
431
    """
432
    s = list(iterable)
1✔
433
    i = chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
1✔
434
    if not include_empty:
1✔
435
        next(i)
1✔
436
    return i
1✔
437

438
def unique_everseen(iterable, key = None):
1✔
439
    """unique_everseen(iterable, key = None) -> iterator
440

441
    Get unique elements, preserving order. Remember all elements ever seen.  If
442
    `key` is not :const:`None` then for each element ``elm`` in `iterable` the
443
    element that will be rememberes is ``key(elm)``.  Otherwise ``elm`` is
444
    remembered.
445

446
    Arguments:
447
      iterable:  An iterable.
448
      key:  A function to map over each element in `iterable` before remembering
449
        it.  Setting to :const:`None` is equivalent to the identity function.
450

451
    Returns:
452
      An iterator of the unique elements in `iterable`.
453

454
    Examples:
455
      >>> ''.join(unique_everseen('AAAABBBCCDAABBB'))
456
      'ABCD'
457
      >>> ''.join(unique_everseen('ABBCcAD', str.lower))
458
      'ABCD'
459
    """
460
    seen = set()
1✔
461
    seen_add = seen.add
1✔
462
    if key is None:
1✔
463
        for element in filterfalse(seen.__contains__, iterable):
1✔
464
            seen_add(element)
1✔
465
            yield element
1✔
466
    else:
467
        for element in iterable:
1✔
468
            k = key(element)
1✔
469
            if k not in seen:
1✔
470
                seen_add(k)
1✔
471
                yield element
1✔
472

473
def unique_justseen(iterable, key = None):
1✔
474
    """unique_everseen(iterable, key = None) -> iterator
475

476
    Get unique elements, preserving order. Remember only the elements just seen.
477
    If `key` is not :const:`None` then for each element ``elm`` in `iterable`
478
    the element that will be rememberes is ``key(elm)``.  Otherwise ``elm`` is
479
    remembered.
480

481
    Arguments:
482
      iterable:  An iterable.
483
      key:  A function to map over each element in `iterable` before remembering
484
        it.  Setting to :const:`None` is equivalent to the identity function.
485

486
    Returns:
487
      An iterator of the unique elements in `iterable`.
488

489
    Examples:
490
      >>> ''.join(unique_justseen('AAAABBBCCDAABBB'))
491
      'ABCDAB'
492
      >>> ''.join(unique_justseen('ABBCcAD', str.lower))
493
      'ABCAD'
494
    """
495
    return map(next, map(operator.itemgetter(1), groupby(iterable, key)))
1✔
496

497
def unique_window(iterable, window, key = None):
1✔
498
    """unique_everseen(iterable, window, key = None) -> iterator
499

500
    Get unique elements, preserving order. Remember only the last `window`
501
    elements seen.  If `key` is not :const:`None` then for each element ``elm``
502
    in `iterable` the element that will be rememberes is ``key(elm)``.
503
    Otherwise ``elm`` is remembered.
504

505
    Arguments:
506
      iterable:  An iterable.
507
      window(int):  The number of elements to remember.
508
      key:  A function to map over each element in `iterable` before remembering
509
        it.  Setting to :const:`None` is equivalent to the identity function.
510

511
    Returns:
512
      An iterator of the unique elements in `iterable`.
513

514
    Examples:
515
      >>> ''.join(unique_window('AAAABBBCCDAABBB', 6))
516
      'ABCDA'
517
      >>> ''.join(unique_window('ABBCcAD', 5, str.lower))
518
      'ABCD'
519
      >>> ''.join(unique_window('ABBCcAD', 4, str.lower))
520
      'ABCAD'
521
    """
522
    seen = collections.deque(maxlen = window)
1✔
523
    seen_add = seen.append
1✔
524
    if key is None:
1✔
525
        for element in iterable:
1✔
526
            if element not in seen:
1✔
527
                yield element
1✔
528
            seen_add(element)
1✔
529
    else:
530
        for element in iterable:
1✔
531
            k = key(element)
1✔
532
            if k not in seen:
1✔
533
                yield element
1✔
534
            seen_add(k)
1✔
535

536
def iter_except(func, exception):
1✔
537
    """iter_except(func, exception)
538

539
    Calls `func` repeatedly until an exception is raised.  Works like the
540
    build-in :func:`iter` but uses an exception instead of a sentinel to signal
541
    the end.
542

543
    Arguments:
544
      func(callable): The function to call.
545
      exception(Exception):  The exception that signals the end.  Other
546
        exceptions will not be caught.
547

548
    Returns:
549
      An iterator whoose elements are the results of calling ``func()`` until an
550
      exception matching `exception` is raised.
551

552
    Examples:
553
      >>> s = {1, 2, 3}
554
      >>> i = iter_except(s.pop, KeyError)
555
      >>> next(i)
556
      1
557
      >>> next(i)
558
      2
559
      >>> next(i)
560
      3
561
      >>> next(i)
562
      Traceback (most recent call last):
563
          ...
564
      StopIteration
565
    """
566
    try:
1✔
567
        while True:
1✔
568
            yield func()
1✔
569
    except exception:
1✔
570
        pass
1✔
571

572
def random_product(*args, **kwargs):
1✔
573
    """random_product(*args, repeat = 1) -> tuple
574

575
    Arguments:
576
      args:  One or more iterables
577
      repeat(int):  Number of times to repeat `args`.
578

579
    Returns:
580
      A random element from ``itertools.product(*args, repeat = repeat)``.
581

582
    Examples:
583
      >>> args = (range(2), range(2))
584
      >>> random_product(*args) in {(0, 0), (0, 1), (1, 0), (1, 1)}
585
      True
586
      >>> args = (range(3), range(3), range(3))
587
      >>> random_product(*args, repeat = 2) in product(*args, repeat = 2)
588
      True
589
    """
590
    repeat = kwargs.pop('repeat', 1)
1✔
591

592
    if kwargs != {}:
1!
593
        raise TypeError('random_product() does not support argument %s' % kwargs.popitem())
×
594

595
    pools = list(map(tuple, args)) * repeat
1✔
596
    return tuple(random.choice(pool) for pool in pools)
1✔
597

598
def random_permutation(iterable, r = None):
1✔
599
    """random_product(iterable, r = None) -> tuple
600

601
    Arguments:
602
      iterable:  An iterable.
603
      r(int):  Size of the permutation.  If :const:`None` select all elements in
604
        `iterable`.
605

606
    Returns:
607
      A random element from ``itertools.permutations(iterable, r = r)``.
608

609
    Examples:
610
      >>> random_permutation(range(2)) in {(0, 1), (1, 0)}
611
      True
612
      >>> random_permutation(range(10), r = 2) in permutations(range(10), r = 2)
613
      True
614
    """
615
    pool = tuple(iterable)
1✔
616
    r = len(pool) if r is None else r
1✔
617
    return tuple(random.sample(pool, r))
1✔
618

619
def random_combination(iterable, r):
1✔
620
    """random_combination(iterable, r) -> tuple
621

622
    Arguments:
623
      iterable:  An iterable.
624
      r(int):  Size of the combination.
625

626
    Returns:
627
      A random element from ``itertools.combinations(iterable, r = r)``.
628

629
    Examples:
630
      >>> random_combination(range(2), 2)
631
      (0, 1)
632
      >>> random_combination(range(10), r = 2) in combinations(range(10), r = 2)
633
      True
634
    """
635
    pool = tuple(iterable)
1✔
636
    n = len(pool)
1✔
637
    indices = sorted(random.sample(range(n), r))
1✔
638
    return tuple(pool[i] for i in indices)
1✔
639

640
def random_combination_with_replacement(iterable, r):
1✔
641
    """random_combination(iterable, r) -> tuple
642

643
    Arguments:
644
      iterable:  An iterable.
645
      r(int):  Size of the combination.
646

647
    Returns:
648
      A random element from ``itertools.combinations_with_replacement(iterable,
649
      r = r)``.
650

651
    Examples:
652
      >>> cs = {(0, 0), (0, 1), (1, 1)}
653
      >>> random_combination_with_replacement(range(2), 2) in cs
654
      True
655
      >>> i = combinations_with_replacement(range(10), r = 2)
656
      >>> random_combination_with_replacement(range(10), r = 2) in i
657
      True
658
    """
659
    pool = tuple(iterable)
1✔
660
    n = len(pool)
1✔
661
    indices = sorted(random.randrange(n) for i in range(r))
1✔
662
    return tuple(pool[i] for i in indices)
1✔
663

664
def lookahead(n, iterable):
1✔
665
    """lookahead(n, iterable) -> object
666

667
    Inspects the upcoming element at index `n` without advancing the iterator.
668
    Raises ``IndexError`` if `iterable` has too few elements.
669

670
    Arguments:
671
      n(int):  Index of the element to return.
672
      iterable:  An iterable.
673

674
    Returns:
675
      The element in `iterable` at index `n`.
676

677
    Examples:
678
      >>> i = count()
679
      >>> lookahead(4, i)
680
      4
681
      >>> next(i)
682
      0
683
      >>> i = count()
684
      >>> nth(4, i)
685
      4
686
      >>> next(i)
687
      5
688
      >>> lookahead(4, i)
689
      10
690
    """
691
    for value in islice(copy.copy(iterable), n, None):
1!
692
        return value
1✔
693
    raise IndexError(n)
×
694

695
def lexicographic(alphabet):
1✔
696
    """lexicographic(alphabet) -> iterator
697

698
    The words with symbols in `alphabet`, in lexicographic order (determined by
699
    the order of `alphabet`).
700

701
    Arguments:
702
      alphabet:  The alphabet to draw symbols from.
703

704
    Returns:
705
      An iterator of the words with symbols in `alphabet`, in lexicographic
706
      order.
707

708
    Example:
709
      >>> take(8, map(lambda x: ''.join(x), lexicographic('01')))
710
      ['', '0', '1', '00', '01', '10', '11', '000']
711
    """
712
    for n in count():
1!
713
        for e in product(alphabet, repeat = n):
1✔
714
            yield e
1✔
715

716
def chained(func):
1✔
717
    """chained(func)
718

719
    A decorator chaining the results of `func`.  Useful for generators.
720

721
    Arguments:
722
      func(function):  The function being decorated.
723

724
    Returns:
725
      A generator function whoose elements are the concatenation of the return
726
      values from ``func(*args, **kwargs)``.
727

728
    Example:
729
      >>> @chained
730
      ... def g():
731
      ...     for x in count():
732
      ...         yield (x, -x)
733
      >>> take(6, g())
734
      [0, 0, 1, -1, 2, -2]
735
      >>> @chained
736
      ... def g2():
737
      ...     for x in range(3):
738
      ...         yield (x, -x)
739
      >>> list(g2())
740
      [0, 0, 1, -1, 2, -2]
741
    """
742
    def wrapper(*args, **kwargs):
1✔
743
        for xs in func(*args, **kwargs):
1✔
744
            for x in xs:
1✔
745
                yield x
1✔
746
    return wrapper
1✔
747

748
def bruteforce(func, alphabet, length, method = 'upto', start = None, databag = None):
1✔
749
    """bruteforce(func, alphabet, length, method = 'upto', start = None)
750

751
    Bruteforce `func` to return :const:`True`.  `func` should take a string
752
    input and return a :func:`bool`.  `func` will be called with strings from
753
    `alphabet` until it returns :const:`True` or the search space has been
754
    exhausted.
755

756
    The argument `start` can be used to split the search space, which is useful
757
    if multiple CPU cores are available.
758

759
    Arguments:
760
      func(function):  The function to bruteforce.
761
      alphabet:  The alphabet to draw symbols from.
762
      length:  Longest string to try.
763
      method:  If 'upto' try strings of length ``1 .. length``, if 'fixed' only
764
        try strings of length ``length`` and if 'downfrom' try strings of length
765
        ``length .. 1``.
766
      start: a tuple ``(i, N)`` which splits the search space up into `N` pieces
767
        and starts at piece `i` (1..N). :const:`None` is equivalent to ``(1, 1)``.
768

769
    Returns:
770
      A string `s` such that ``func(s)`` returns :const:`True` or :const:`None`
771
      if the search space was exhausted.
772

773
    Example:
774
      >>> bruteforce(lambda x: x == 'yes', string.ascii_lowercase, length=5)
775
      'yes'
776
    """
777

778
    if   method == 'upto' and length > 1:
1✔
779
        iterator = product(alphabet, repeat = 1)
1✔
780
        for i in range(2, length + 1):
1✔
781
            iterator = chain(iterator, product(alphabet, repeat = i))
1✔
782

783
    elif method == 'downfrom' and length > 1:
1✔
784
        iterator = product(alphabet, repeat = length)
1✔
785
        for i in range(length - 1, 1, -1):
1✔
786
            iterator = chain(iterator, product(alphabet, repeat = i))
1✔
787

788
    elif method == 'fixed':
1!
789
        iterator = product(alphabet, repeat = length)
1✔
790

791
    else:
792
        raise TypeError('bruteforce(): unknown method')
×
793

794
    if method == 'fixed':
1✔
795
        total_iterations = len(alphabet) ** length
1✔
796
    else:
797
        total_iterations = (len(alphabet) ** (length + 1) // (len(alphabet) - 1)) - 1
1✔
798

799
    if start is not None:
1✔
800
        i, N = start
1✔
801
        if i > N:
1!
802
            raise ValueError('bruteforce(): invalid starting point')
×
803

804
        i -= 1
1✔
805
        chunk_size = total_iterations // N
1✔
806
        rest = total_iterations % N
1✔
807
        starting_point = 0
1✔
808

809
        for chunk in range(N):
1!
810
            if chunk >= i:
1✔
811
                break
1✔
812
            if chunk <= rest:
1!
813
                starting_point += chunk_size + 1
1✔
814
            else:
815
                starting_point += chunk_size
×
816

817
        if rest >= i:
1✔
818
            chunk_size += 1
1✔
819

820
        total_iterations = chunk_size
1✔
821

822
    h = log.waitfor('Bruteforcing')
1✔
823
    cur_iteration = 0
1✔
824
    if start is not None:
1✔
825
        consume(i, iterator)
1✔
826
    for e in iterator:
1✔
827
        cur = ''.join(e)
1✔
828
        cur_iteration += 1
1✔
829
        if cur_iteration % 2000 == 0:
1✔
830
            progress = 100.0 * cur_iteration / total_iterations
1✔
831
            h.status('Trying "%s", %0.3f%%' % (cur, progress))
1✔
832
            if databag:
1✔
833
                databag["current_item"] = cur
1✔
834
                databag["items_done"] = cur_iteration
1✔
835
                databag["items_total"] = total_iterations
1✔
836
        res = func(cur)
1✔
837
        if res:
1✔
838
            h.success('Found key: "%s"' % cur)
1✔
839
            return cur
1✔
840
        if start is not None:
1✔
841
            consume(N - 1, iterator)
1✔
842

843
    h.failure('No matches found')
1✔
844

845

846
def _mbruteforcewrap(func, alphabet, length, method, start, databag):
1✔
847
    oldloglevel = context.log_level
1✔
848
    context.log_level = 'critical'
1✔
849
    res = bruteforce(func, alphabet, length, method=method, start=start, databag=databag)
1✔
850
    context.log_level = oldloglevel
1✔
851
    databag["result"] = res
1✔
852

853

854
def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None):
1✔
855
    """mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None)
856

857
    Same functionality as bruteforce(), but multithreaded.
858

859
    Arguments:
860
      func, alphabet, length, method, start: same as for bruteforce()
861
      threads: Amount of threads to spawn, default is the amount of cores.
862

863
    Example:
864
      >>> mbruteforce(lambda x: x == 'hello', string.ascii_lowercase, length = 10)
865
      'hello'
866
      >>> mbruteforce(lambda x: x == 'hello', 'hlo', 5, 'downfrom') is None
867
      True
868
      >>> mbruteforce(lambda x: x == 'no', string.ascii_lowercase, length=2, method='fixed')
869
      'no'
870
      >>> mbruteforce(lambda x: x == '9999', string.digits, length=4, threads=1, start=(2, 2))
871
      '9999'
872
    """
873

874
    if start is None:
1✔
875
        start = (1, 1)
1✔
876

877
    if threads is None:
1✔
878
        try:
1✔
879
            threads = multiprocessing.cpu_count()
1✔
880
        except NotImplementedError:
×
881
            threads = 1
×
882

883
    h = log.waitfor('MBruteforcing')
1✔
884
    processes = [None] * threads
1✔
885
    shareddata = [None] * threads
1✔
886

887
    (i2, N2) = start
1✔
888
    totalchunks = threads * N2
1✔
889

890
    for i in range(threads):
1✔
891
        shareddata[i] = multiprocessing.Manager().dict()
1✔
892
        shareddata[i]['result'] = None
1✔
893
        shareddata[i]['current_item'] = ""
1✔
894
        shareddata[i]['items_done'] = 0
1✔
895
        shareddata[i]['items_total'] = 0
1✔
896

897
        chunkid = (i2-1) + (i * N2) + 1
1✔
898

899
        processes[i] = multiprocessing.Process(target=_mbruteforcewrap,
1✔
900
                args=(func, alphabet, length, method, (chunkid, totalchunks),
901
                        shareddata[i]))
902
        processes[i].start()
1✔
903

904
    done = False
1✔
905

906
    while not done:
1✔
907
        # log status
908
        current_item_list = ",".join(["\"%s\"" % x["current_item"]
1✔
909
                                for x in shareddata if x is not None])
910
        items_done = sum([x["items_done"] for x in shareddata if x is not None])
1✔
911
        items_total = sum([x["items_total"] for x in shareddata if x is not None])
1✔
912

913
        progress = 100.0 * items_done / items_total if items_total != 0 else 0.0
1✔
914

915
        h.status('Trying %s -- %0.3f%%' % (current_item_list, progress))
1✔
916

917
        # handle finished threads
918
        for i in range(threads):
1✔
919
            if processes[i] and processes[i].exitcode is not None:
1✔
920
                # thread has terminated
921
                res = shareddata[i]["result"]
1✔
922
                processes[i].join()
1✔
923
                processes[i] = None
1✔
924

925
                # if successful, kill all other threads and return success
926
                if res is not None:
1✔
927
                    for i in range(threads):
1✔
928
                        if processes[i] is not None:
1✔
929
                            processes[i].terminate()
1✔
930
                            processes[i].join()
1✔
931
                            processes[i] = None
1✔
932
                    h.success('Found key: "%s"' % res)
1✔
933
                    return res
1✔
934

935
                if all([x is None for x in processes]):
1✔
936
                    done = True
1✔
937
        time.sleep(0.3)
1✔
938
    h.failure('No matches found')
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