File Coverage

blib/lib/Radamsa.pm
Criterion Covered Total %
statement 34 45 75.5
branch 12 28 42.8
condition 7 17 41.1
subroutine 7 9 77.7
pod 2 2 100.0
total 62 101 61.3


line stmt bran cond sub pod time code
1             package Radamsa;
2              
3 1     1   261277 use 5.010;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         32  
5 1     1   3 use warnings;
  1         2  
  1         92  
6              
7 1     1   6 use Carp qw(croak);
  1         8  
  1         71  
8 1     1   4 use Exporter qw(import);
  1         1  
  1         478  
9              
10             our $VERSION = '0.02';
11             our @EXPORT_OK = qw(mutate);
12              
13             require XSLoader;
14             XSLoader::load(__PACKAGE__, $VERSION);
15              
16             sub new {
17 1     1 1 538 my ($class, %args) = @_;
18             my $self = bless {
19             seed => delete($args{seed}) // _random_seed(),
20             max_len => delete($args{max_len}),
21 1   33     9 max_scale => delete($args{max_scale}) // 4,
      50        
22             }, $class;
23              
24 1 50       4 croak 'unknown constructor arguments: ' . join(', ', sort keys %args)
25             if %args;
26              
27 1         2 return $self;
28             }
29              
30             sub mutate {
31 3     3 1 658 my ($thing, @rest) = @_;
32 3 100       11 my $self = ref($thing) ? $thing : undef;
33 3 100       10 my $input = $self ? shift(@rest) : $thing;
34 3         8 my %args = @rest;
35              
36 3 50       8 croak 'input must be defined' unless defined $input;
37              
38 3         5 my $seed = delete($args{seed});
39 3 100       6 if (defined $self) {
40 2   33     21 $seed //= $self->{seed}++;
41             }
42             else {
43 1   33     4 $seed //= _random_seed();
44             }
45              
46 3         4 my $max_len = delete $args{max_len};
47 3 100 66     11 if (!defined $max_len && defined $self) {
48 2         3 $max_len = $self->{max_len};
49             }
50 3 0 33     6 $max_len //= _default_max_len($input, $self ? $self->{max_scale} : 4);
51              
52 3 50       6 croak 'max_len must be a positive integer' if $max_len < 1;
53 3 50       5 croak 'unknown mutate arguments: ' . join(', ', sort keys %args)
54             if %args;
55              
56 3         2371 return _mutate_raw($input, int($max_len), int($seed));
57             }
58              
59             sub _random_seed {
60 0     0     return int(rand(4_294_967_296));
61             }
62              
63             sub _default_max_len {
64 0     0     my ($input, $scale) = @_;
65 0 0         $scale = 4 unless defined $scale;
66 0           my $len = length $input;
67 0           my $min = 1024;
68              
69 0 0         return $min if $len == 0;
70              
71 0 0         $scale = 1 if $scale < 1;
72 0           my $max_len = int($len * $scale);
73 0 0         $max_len = $len if $max_len < $len;
74 0 0         $max_len = $min if $max_len < $min;
75              
76 0           return $max_len;
77             }
78              
79             1;
80              
81             =head1 NAME
82              
83             Radamsa - Perl 5 bindings for the Radamsa mutational fuzzer
84              
85             =head1 SYNOPSIS
86              
87             use Radamsa qw(mutate);
88              
89             my $output = mutate("hello\n", seed => 1234, max_len => 4096);
90              
91             my $rad = Radamsa->new(seed => 1, max_len => 4096);
92             my $case1 = $rad->mutate("sample one");
93             my $case2 = $rad->mutate("sample two");
94              
95             =head1 DESCRIPTION
96              
97             This module wraps Radamsa's C library interface and exposes a simple Perl API
98             for generating fuzzed variants of byte strings. The CPAN distribution ships a
99             vendored generated C source for Radamsa, so installation does not need network
100             access or the original Owl Lisp toolchain.
101              
102             =head1 FUNCTIONS
103              
104             =head2 mutate
105              
106             my $output = mutate($input, %options);
107              
108             Mutates one byte string and returns the mutated output.
109              
110             Options:
111              
112             =over 4
113              
114             =item * seed
115              
116             Unsigned 32-bit seed passed to the Radamsa library entry point.
117              
118             =item * max_len
119              
120             Maximum output size in bytes. Defaults to a heuristic based on the input size.
121              
122             =back
123              
124             =head1 METHODS
125              
126             =head2 new
127              
128             my $rad = Radamsa->new(%options);
129              
130             Creates a stateful mutator object.
131              
132             =head1 NOTES
133              
134             Radamsa's library mode keeps internal mutation state between calls. A fixed
135             seed influences generation, but repeated calls with the same seed should not be
136             assumed to be byte-for-byte deterministic for the lifetime of the same process.
137              
138             =head1 LICENSE
139              
140             This distribution includes vendored Radamsa source code by Aki Helin under the
141             MIT license. See the top-level F file.
142              
143             =cut