File Coverage

blib/lib/Blockchain/Ethereum/RLP.pm
Criterion Covered Total %
statement 90 92 97.8
branch 32 38 84.2
condition 17 24 70.8
subroutine 10 10 100.0
pod 2 2 100.0
total 151 166 90.9


line stmt bran cond sub pod time code
1 4     4   416701 use v5.26;
  4         50  
2 4     4   2453 use Object::Pad;
  4         44726  
  4         17  
3              
4             package Blockchain::Ethereum::RLP 0.007;
5             class Blockchain::Ethereum::RLP;
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             Blockchain::Ethereum::RLP - Ethereum RLP encoding/decoding utility
12              
13             =head1 SYNOPSIS
14              
15             Allow RLP encoding and decoding
16              
17             my $rlp = Blockchain::Ethereum::RLP->new();
18              
19             my $tx_params = ['0x9', '0x4a817c800', '0x5208', '0x3535353535353535353535353535353535353535', '0xde0b6b3a7640000', '0x', '0x1', '0x', '0x'];
20             my $encoded = $rlp->encode($params); #ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080
21              
22             my $encoded_tx_params = 'ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080';
23             my $decoded = $rlp->decode(pack "H*", $encoded_tx_params); #['0x9', '0x4a817c800', '0x5208', '0x3535353535353535353535353535353535353535', '0xde0b6b3a7640000', '0x', '0x1', '0x', '0x']
24              
25             =cut
26              
27 4     4   1628 use Carp;
  4         10  
  4         354  
28              
29             use constant {
30 4         9248 STRING => 'string',
31             LIST => 'list',
32             SINGLE_BYTE_MAX_LENGTH => 128,
33             SHORT_STRING_MAX_LENGTH => 183,
34             LONG_STRING_MAX_LENGTH => 192,
35             LIST_MAX_LENGTH => 247,
36             LONG_LIST_MAX_LENGTH => 255,
37             BYTE_LENGTH_DELIMITER => 55,
38             INPUT_LENGTH_DELIMITER => 256,
39 4     4   26 };
  4         8  
40              
41             =head2 encode
42              
43             Encodes the given input to RLP
44              
45             Usage:
46              
47             encode(hex string / hex array reference) -> encoded bytes
48              
49             =over 4
50              
51             =item * C<$input> hexadecimal string or reference to an hexadecimal string array
52              
53             =back
54              
55             Return the encoded bytes
56              
57             =cut
58              
59 65     65 1 10545 method encode ($input) {
  65         88  
  65         92  
  65         83  
60              
61 65 50       135 croak 'No input given' unless defined $input;
62              
63 65 100       138 if (ref $input eq 'ARRAY') {
64 16         27 my $output = '';
65 16         49 $output .= $self->encode($_) for $input->@*;
66              
67 16         33 return $self->_encode_length(length($output), LONG_STRING_MAX_LENGTH) . $output;
68             }
69              
70 49         160 my $hex = $input =~ s/^0x//r;
71              
72             # zero will be considered empty as per RLP specification
73 49 100       120 unless ($hex) {
74 11         20 $hex = chr(0x80);
75 11         33 return $hex;
76             }
77              
78             # pack will add a null character at the end if the length is odd
79             # RLP expects this to be added at the left instead.
80 38 100       112 $hex = "0$hex" if length($hex) % 2 != 0;
81 38         118 $hex = pack("H*", $hex);
82              
83 38         56 my $input_length = length $hex;
84              
85 38 100 100     116 return $hex if $input_length == 1 && ord $hex < SINGLE_BYTE_MAX_LENGTH;
86 31         63 return $self->_encode_length($input_length, SINGLE_BYTE_MAX_LENGTH) . $hex;
87             }
88              
89 47     47   84 method _encode_length ($length, $offset) {
  47         79  
  47         99  
  47         84  
  47         58  
90              
91 47 100       220 return chr($length + $offset) if $length <= BYTE_LENGTH_DELIMITER;
92              
93 5 50       32 if ($length < INPUT_LENGTH_DELIMITER**8) {
94 5         18 my $bl = $self->_to_binary($length);
95 5         38 return chr(length($bl) + $offset + BYTE_LENGTH_DELIMITER) . $bl;
96             }
97              
98 0         0 croak "Input too long";
99             }
100              
101 10     10   25 method _to_binary ($x) {
  10         14  
  10         18  
  10         15  
102              
103 10 100       37 return '' unless $x;
104 5         24 return $self->_to_binary(int($x / INPUT_LENGTH_DELIMITER)) . chr($x % INPUT_LENGTH_DELIMITER);
105             }
106              
107             =head2 decode
108              
109             Decode the given input from RLP to the specific return type
110              
111             Usage:
112              
113             decode(RLP encoded bytes) -> hexadecimal string / array reference
114              
115             =over 4
116              
117             =item * C<$input> RLP encoded bytes
118              
119             =back
120              
121             Returns an hexadecimals string or an array reference in case of multiple items
122              
123             =cut
124              
125 65     65 1 14971 method decode ($input) {
  65         98  
  65         114  
  65         116  
126              
127 65 50       132 return [] unless length $input;
128              
129 65         122 my ($offset, $data_length, $type) = $self->_decode_length($input);
130              
131             # string
132 65 100       158 if ($type eq STRING) {
133 49         137 my $hex = unpack("H*", substr($input, $offset, $data_length));
134             # same as for the encoding we do expect an prefixed 0 for
135             # odd length hexadecimal values, this just removes the 0 prefix.
136 49 100 66     183 $hex = substr($hex, 1) if $hex =~ /^0/ && (length($hex) - 1) % 2 != 0;
137 49         145 return '0x' . $hex;
138             }
139              
140             # list
141 16         25 my @output;
142 16         34 my $list_data = substr($input, $offset, $data_length);
143 16         25 my $list_offset = 0;
144             # recursive arrays
145 16         57 while ($list_offset < length($list_data)) {
146 51         124 my ($item_offset, $item_length, $item_type) = $self->_decode_length(substr($list_data, $list_offset));
147 51         163 my $list_item = $self->decode(substr($list_data, $list_offset, $item_offset + $item_length));
148 51         113 push @output, $list_item;
149 51         114 $list_offset += $item_offset + $item_length;
150             }
151              
152 16         46 return \@output;
153             }
154              
155 116     116   201 method _decode_length ($input) {
  116         149  
  116         207  
  116         161  
156              
157 116         163 my $length = length($input);
158 116 50       198 croak "Invalid empty input" unless $length;
159              
160 116         198 my $prefix = ord(substr($input, 0, 1));
161              
162 116         168 my $short_string = $prefix - SINGLE_BYTE_MAX_LENGTH;
163 116         153 my $long_string = $prefix - SHORT_STRING_MAX_LENGTH;
164 116         157 my $list = $prefix - LONG_STRING_MAX_LENGTH;
165 116         178 my $long_list = $prefix - LIST_MAX_LENGTH;
166              
167 116 100 66     454 if ($prefix < SINGLE_BYTE_MAX_LENGTH) {
    100 100        
    100 100        
    100 66        
    50 33        
      33        
168             # single byte
169 13         36 return (0, 1, STRING);
170             } elsif ($prefix <= SHORT_STRING_MAX_LENGTH && $length > $short_string) {
171             # short string
172 75         187 return (1, $short_string, STRING);
173             } elsif ($prefix <= LONG_STRING_MAX_LENGTH
174             && $length > $long_string
175             && $length > $long_string + $self->_to_integer(substr($input, 1, $long_string)))
176             {
177             # long string
178 3         10 my $str_length = $self->_to_integer(substr($input, 1, $long_string));
179 3         11 return (1 + $long_string, $str_length, STRING);
180             } elsif ($prefix < LIST_MAX_LENGTH && $length > $list) {
181             # list
182 22         65 return (1, $list, LIST);
183             } elsif ($prefix <= LONG_LIST_MAX_LENGTH
184             && $length > $long_list
185             && $length > $long_list + $self->_to_integer(substr($input, 1, $long_list)))
186             {
187             # long list
188 3         9 my $list_length = $self->_to_integer(substr($input, 1, $long_list));
189 3         15 return (1 + $long_list, $list_length, LIST);
190             }
191              
192 0         0 croak "Invalid RLP input";
193             }
194              
195 30     30   57 method _to_integer ($b) {
  30         44  
  30         59  
  30         40  
196              
197 30         48 my $length = length($b);
198 30 50       54 croak "Invalid empty input" unless $length;
199              
200 30 100       99 return ord($b) if $length == 1;
201              
202 16         63 return ord(substr($b, -1)) + $self->_to_integer(substr($b, 0, -1)) * INPUT_LENGTH_DELIMITER;
203             }
204              
205             1;
206              
207             __END__