File Coverage

blib/lib/Hex/Record.pm
Criterion Covered Total %
statement 169 205 82.4
branch 58 68 85.2
condition 11 11 100.0
subroutine 14 16 87.5
pod 7 8 87.5
total 259 308 84.0


line stmt bran cond sub pod time code
1             package Hex::Record;
2              
3 7     7   105244 use strict;
  7         11  
  7         187  
4 7     7   23 use warnings;
  7         11  
  7         163  
5 7     7   25 use Carp;
  7         15  
  7         12752  
6              
7             our $VERSION = '0.07';
8              
9             sub new {
10 17     17 0 9702 my ($class, %args) = @_;
11              
12 17 100       53 $args{parts} = [] unless exists $args{parts};
13              
14 17         38 return bless \%args, $class;
15             }
16              
17             sub import_intel_hex {
18 6     6 1 18 my ($self, $hex_string) = @_;
19              
20 6         4 my $addr_high_dec = 0;
21              
22 6         38 for my $line (split m{\n\r?}, $hex_string) {
23 34 100       148 my ($addr, $type, $bytes_str) = $line =~ m{
24             : # intel hex start
25             [[:xdigit:]]{2} # bytecount
26             ([[:xdigit:]]{4}) # addr
27             ([[:xdigit:]]{2}) # type
28             ([[:xdigit:]] * ) # databytes
29             [[:xdigit:]]{2} # checksum
30             }ix or next;
31              
32 33         115 my @bytes = unpack('(A2)*', $bytes_str);
33              
34             # data line?
35 33 100       77 if ($type == 0) {
    100          
    100          
36 24         48 $self->write($addr_high_dec + hex $addr, \@bytes);
37             }
38             # extended linear address type?
39             elsif ($type == 4) {
40 3         8 $addr_high_dec = hex( join '', @bytes ) << 16;
41             }
42             # extended segment address type?
43             elsif ($type == 2) {
44 3         7 $addr_high_dec = hex( join '', @bytes ) << 4;
45             }
46             }
47              
48 6         11 return;
49             }
50              
51             sub import_srec_hex {
52 6     6 1 14 my ($self, $hex_string) = @_;
53              
54 6         24 my %address_length_of_srec_type = (
55             0 => 4,
56             1 => 4,
57             2 => 6,
58             3 => 8,
59             4 => undef,
60             5 => 4,
61             6 => 6,
62             7 => 8,
63             8 => 6,
64             9 => 4,
65             );
66              
67 6         3 my @parts;
68 6         29 for my $line (split m{\n\r?}, $hex_string) {
69 22 100       55 next unless substr( $line, 0, 1 ) =~ m{s}i;
70              
71 21         22 my $type = substr $line, 1, 1;
72              
73 21   100     30 my $addr_length = $address_length_of_srec_type{$type} || next;
74              
75 20 100       147 my ($addr, $bytes_str) = $line =~ m{
76             s #srec hex start
77             [[:xdigit:]]{1} #type
78             [[:xdigit:]]{2} #bytecount
79             ([[:xdigit:]]{$addr_length}) #addr
80             ([[:xdigit:]] * ) #databytes
81             [[:xdigit:]]{2} #checksum
82             }ix or next;
83              
84             # data line?
85 19 100 100     60 if ($type == 1 || $type == 2 || $type == 3) {
      100        
86 18         91 $self->write(hex $addr, [ unpack '(A2)*', $bytes_str ]);
87             }
88             }
89              
90 6         15 return;
91             }
92              
93             sub write {
94 55     55 1 277 my ($self, $from, $bytes_hex_ref) = @_;
95              
96 55         77 $self->remove($from, scalar @$bytes_hex_ref);
97              
98 55         46 my $to = $from + @$bytes_hex_ref;
99              
100 55         41 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  107         166  
101 80         51 my $part = $self->{parts}->[$part_i];
102              
103 80         55 my $start_addr = $part->{start};
104 80         48 my $end_addr = $part->{start} + $#{ $part->{bytes} };
  80         65  
105              
106             # merge with this part
107 80 100       160 if ($to == $start_addr) {
    100          
    100          
108 4         5 $part->{start} = $from;
109 4         4 unshift @{ $part->{bytes} }, @$bytes_hex_ref;
  4         25  
110 4         9 return;
111             }
112             elsif ($from == $end_addr + 1) {
113 20         14 push @{ $part->{bytes} }, @$bytes_hex_ref;
  20         69  
114              
115 20 100       17 return if $part_i+1 == @{ $self->{parts} };
  20         62  
116              
117 3         5 my $next_part = $self->{parts}->[$part_i+1];
118             # merge with next part
119 3 100       6 if ($to == $next_part->{start}) {
120 1         1 push @{ $part->{bytes} }, @{ $next_part->{bytes} };
  1         1  
  1         3  
121 1         1 splice @{ $self->{parts} }, $part_i+1, 1;
  1         2  
122             }
123 3         7 return;
124             }
125             elsif ($from < $start_addr) {
126 4         4 splice @{ $self->{parts} }, $part_i, 0, {
  4         10  
127             start => $from,
128             bytes => $bytes_hex_ref
129             };
130 4         8 return;
131             }
132             }
133              
134 27         20 push @{ $self->{parts} }, {
  27         52  
135             start => $from,
136             bytes => $bytes_hex_ref
137             };
138              
139 27         47 return;
140             }
141              
142             sub get {
143 8     8 1 3372 my ($self, $from, $length) = @_;
144              
145 8         8 my @bytes;
146 8         7 my $end_last = $from;
147             my $get = sub {
148 8     8   8 my ($part, $part_i_ref, $overlap) = @_;
149              
150 8         9 my $gap = $part->{start} - $end_last;
151 8 100       13 push @bytes, (undef) x $gap if $gap > 0;
152              
153 8 100       13 if ($overlap eq 'a') {
    100          
    100          
154 5         4 push @bytes, @{ $part->{bytes} };
  5         16  
155             }
156             elsif ($overlap eq 'l') {
157 1         2 push @bytes, @{ $part->{bytes} }[ 0 .. $length - 1 ];
  1         2  
158             }
159             elsif ($overlap eq 'r') {
160 1         1 push @bytes, @{ $part->{bytes} }[ $from - $part->{start} .. $#{ $part->{bytes} } ];
  1         3  
  1         14  
161             }
162             else {
163 1         2 my $start_i = $from - $part->{start};
164 1         2 push @bytes, @{ $part->{bytes} }[ $start_i .. $start_i + $length - 1];
  1         2  
165             }
166              
167 8         7 $end_last = $part->{start} + @{ $part->{bytes} };
  8         12  
168 8         43 };
169              
170 8         15 $self->_traverse($from, $length, $get);
171              
172 8         80 return [ @bytes, (undef) x ($length - @bytes) ];
173             }
174              
175             sub remove {
176 67     67 1 14274 my ($self, $from, $length) = @_;
177              
178 67         60 my $to = $from + $length;
179              
180             my $remove = sub {
181 33     33   69 my ($part, $part_i_ref, $overlap) = @_;
182 33 100       61 if ($overlap eq 'a') {
    100          
    100          
183 22         16 splice @{ $self->{parts} }, $$part_i_ref, 1;
  22         33  
184 22         61 --$$part_i_ref;
185             }
186             elsif ($overlap eq 'l') {
187 2         3 my $to_remove = $to - $part->{start};
188 2         3 splice @{ $part->{bytes} }, 0, $to_remove;
  2         3  
189 2         4 $part->{start} += $to_remove;
190             }
191             elsif ($overlap eq 'r') {
192 7         9 splice @{ $part->{bytes} }, $from - $part->{start};
  7         30  
193             }
194             else {
195 2         6 splice @{ $self->{parts} }, $$part_i_ref, 1,
196             {
197             start => $part->{start},
198 2         39 bytes => [ @{ $part->{bytes} }[ 0 .. $from - $part->{start} - 1 ] ],
199             },
200             {
201             start => $from + $length,
202 2         18 bytes => [ @{ $part->{bytes} }[
203 2         2 $from - $part->{start} + $length .. $#{ $part->{bytes} }
  2         5  
204             ] ],
205             };
206             };
207 67         210 };
208              
209 67         99 $self->_traverse($from, $length, $remove);
210              
211 67         310 return;
212             }
213              
214             sub _traverse {
215 75     75   74 my ($self, $from, $length, $process) = @_;
216              
217 75         54 my $to = $from + $length;
218              
219 75         75 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  181         312  
220 131         94 my $part = $self->{parts}->[$part_i];
221              
222 131         94 my $start_addr = $part->{start};
223 131         90 my $end_addr = $part->{start} + @{ $part->{bytes} };
  131         105  
224              
225 131 100 100     297 if ($from < $end_addr && $to > $start_addr) {
226 25 100       56 if ($from <= $start_addr) {
    100          
227 14 100       26 if ($to < $end_addr -1) {
228 2         6 $process->($part, \$part_i, 'l');
229 2         2 return;
230             }
231 12         26 $process->($part, \$part_i, 'a');
232             }
233             elsif ($to < $end_addr - 1) {
234 3         7 $process->($part, \$part_i, 'm');
235 3         13 return;
236             }
237             else {
238 8         40 $process->($part, $part_i, 'r');
239             }
240              
241             # found start -> search for end
242 20         50 while ( defined (my $part = $self->{parts}->[++$part_i]) ) {
243 25         19 my $start_addr = $part->{start};
244 25 100       42 return if $start_addr > $to;
245              
246 16         17 my $end_addr = $part->{start} + @{ $part->{bytes} };
  16         15  
247              
248 16 100       29 if ($to >= $end_addr) {
249 15         19 $process->($part, \$part_i, 'a');
250             }
251             else {
252 1         3 $process->($part, \$part_i, 'l');
253 1         2 return;
254             }
255             }
256 10         15 return;
257             }
258             }
259             }
260              
261             sub as_intel_hex {
262 2     2 1 172 my ($self, $bytes_hex_a_line) = @_;
263              
264 2         9 my $intel_hex_string = '';
265 2         4 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  9         19  
266 7         7 my $part = $self->{parts}->[$part_i];
267              
268 7         6 my $start_addr = $part->{start};
269 7         6 my $end_addr = $part->{start} + $#{ $part->{bytes} };
  7         10  
270              
271 7         8 my $cur_high_addr_hex = '0000';
272              
273 7         9 for (my $slice_i = 0; $slice_i * $bytes_hex_a_line < @{ $part->{bytes} }; $slice_i++) {
  19         37  
274 12         10 my $total_addr = $start_addr + $slice_i*$bytes_hex_a_line;
275              
276 12         44 my ($addr_high_hex, $addr_low_hex) = unpack '(A4)*', sprintf('%08X', $total_addr);
277              
278 12 100       22 if ($cur_high_addr_hex ne $addr_high_hex) {
279 3         3 $cur_high_addr_hex = $addr_high_hex;
280 3         8 $intel_hex_string .= _intel_hex_line_of( '0000', 4, [unpack '(A2)*', $cur_high_addr_hex]);
281             }
282              
283 12 100       15 if ( ($slice_i + 1) * $bytes_hex_a_line <= $#{ $part->{bytes} } ) {
  12         20  
284             $intel_hex_string .= _intel_hex_line_of(
285             $addr_low_hex, 0,
286             [
287 5         8 @{ $part->{bytes} }[
  5         16  
288             $slice_i * $bytes_hex_a_line .. ($slice_i + 1) * $bytes_hex_a_line - 1
289             ]
290             ]
291             );
292             }
293             else {
294             $intel_hex_string .= _intel_hex_line_of(
295             $addr_low_hex, 0, [
296 7         21 @{ $part->{bytes} }[
297 7         9 $slice_i * $bytes_hex_a_line .. $#{ $part->{bytes} }
  7         9  
298             ]
299             ]
300             );
301             }
302             }
303             }
304             # intel hex eof
305 2         6 return $intel_hex_string . ":00000001FF\n";
306             }
307              
308             sub _intel_hex_line_of {
309 15     15   17 my ($addr_low_hex, $type, $bytes_hex_ref) = @_;
310              
311 15         9 my $byte_count = @$bytes_hex_ref;
312              
313 15         14 my $sum = 0;
314 15         22 $sum += $_ for ( $byte_count, (map { hex $_ } unpack '(A2)*', $addr_low_hex),
  30         34  
315 121         135 $type, (map { hex $_ } @$bytes_hex_ref) );
316              
317             #convert to hex, take lsb
318 15         33 $sum = substr(sprintf( '%02X', $sum ), -2);
319              
320 15         19 my $checksum_hex = sprintf '%02X', (hex $sum ^ 255) + 1;
321 15 100       21 $checksum_hex = '00' if length $checksum_hex != 2;
322              
323 15         59 return join '',
324             (
325             ':',
326             sprintf( '%02X', $byte_count ),
327             $addr_low_hex,
328             sprintf( '%02X', $type ),
329             @$bytes_hex_ref,
330             $checksum_hex,
331             "\n"
332             );
333             }
334              
335             sub as_srec_hex {
336 0     0 1   my ($self, $bytes_hex_a_line) = @_;
337              
338 0           my $srec_hex_string = '';
339 0           for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  0            
340 0           my $part = $self->{parts}->[$part_i];
341              
342 0           my $start_addr = $part->{start};
343 0           my $end_addr = $part->{start} + $#{ $part->{bytes} };
  0            
344              
345 0           for (my $slice_i = 0; $slice_i * $bytes_hex_a_line < @{ $part->{bytes} }; $slice_i++) {
  0            
346 0           my $total_addr = $start_addr + $slice_i*$bytes_hex_a_line;
347              
348 0 0         if ( ($slice_i + 1) * $bytes_hex_a_line <= $#{ $part->{bytes} } ) {
  0            
349             $srec_hex_string .= _srec_hex_line_of(
350             $total_addr,
351 0           [@{ $part->{bytes} }[$slice_i * $bytes_hex_a_line .. ($slice_i + 1) * $bytes_hex_a_line - 1]]
  0            
352             );
353             }
354             else {
355             $srec_hex_string .= _srec_hex_line_of(
356             $total_addr,
357 0           [@{ $part->{bytes} }[$slice_i * $bytes_hex_a_line .. $#{ $part->{bytes} }]]
  0            
  0            
358             );
359             }
360             }
361             }
362              
363 0           return $srec_hex_string;
364             }
365              
366             sub _srec_hex_line_of {
367 0     0     my ($total_addr, $bytes_hex_ref) = @_;
368              
369 0           my $total_addr_hex = sprintf '%04X', $total_addr;
370              
371 0           my $type;
372             # 16 bit addr
373 0 0         if (length $total_addr_hex == 4) {
    0          
374 0           $type = 1;
375             }
376             # 24 bit addr
377             elsif (length $total_addr_hex <= 6) {
378 0 0         $total_addr_hex = "0$total_addr_hex" if length $total_addr_hex == 5;
379 0           $type = 2;
380             }
381             # 32 bit addr
382             else {
383 0 0         $total_addr_hex = "0$total_addr_hex" if length $total_addr_hex == 7;
384 0           $type = 3;
385             }
386              
387             # count of data bytes + address bytes
388 0           my $byte_count = @$bytes_hex_ref + length($total_addr_hex) / 2;
389              
390 0           my $sum = 0;
391 0           $sum += $_ for ( $byte_count,
392 0           (map { hex $_ } unpack '(A2)*', $total_addr_hex),
393 0           (map { hex $_ } @$bytes_hex_ref) );
394              
395             #convert to hex, take lsb
396 0           $sum = substr(sprintf( '%02X', $sum ), -2);
397              
398 0           my $checksum_hex = sprintf '%02X', (hex $sum ^ 255);
399              
400 0           return join '',
401             (
402             "S$type",
403             sprintf('%02X', $byte_count),
404             $total_addr_hex,
405             @$bytes_hex_ref,
406             $checksum_hex,
407             "\n"
408             );
409             }
410              
411             1;
412              
413             =head1 NAME
414              
415             Hex::Record - manipulate intel and srec hex records
416              
417             =head1 SYNOPSIS
418              
419             use Hex::Record;
420              
421             my $hex_record = Hex::Record->new;
422              
423             $hex_record->import_intel_hex($intel_hex_str);
424             $hex_record->import_srec_hex($srec_hex_str);
425              
426             $hex_record->write(0x100, [qw(AA BB CC)]);
427              
428             # get 10 bytes (hex format) starting at address 0x100
429             # every single byte that is not found is returned as undef
430             my $bytes_ref = $hex_record->get(0x100, 10);
431              
432             # remove 10 bytes starting at address 0x100
433             $hex_record->remove(0x100, 10);
434              
435             # dump as intel hex (will use extended linear addresses for offset)
436             # maximum of 10 bytes in data field
437             my $intel_hex_string = $hex_record->as_intel_hex(10);
438              
439             # dump as srec hex (always tries to use smallest address, 16 bit, 24 bit, 32 bit)
440             # maximum of 10 bytes in data field
441             my $srec_hex_string = $hex_record->as_srec_hex(10);
442              
443             =head1 DESCRIPTION
444              
445             Manipulate intel/srec hex files.
446              
447             =head1 Methods
448              
449             =head2 import_intel_hex($intel_hex_str)
450              
451             Imports hex bytes from a string containing intel hex formatted data.
452             Ignores unknown lines, does not check if the checksum at the end is correct.
453              
454             $hex_record->import_intel_hex($intel_hex_str);
455              
456             =head2 import_srec_hex($srec_hex_str)
457              
458             Imports hex bytes from a string containing srec hex formatted data.
459             Ignores unknown lines, does not check if the checksum at the end is correct.
460              
461             $hex_record->import_srec_hex($srec_hex_str);
462              
463             =head2 get($from, $count)
464              
465             Returns $count hex bytes in array reference starting at address $from.
466             If hex byte is not found, undef instead. For example:
467              
468             my $bytes_ref = $hex_record->get(0x0, 6); # ['AA', '00', undef, undef, 'BC', undef]
469              
470             =head2 remove($from, $count)
471              
472             Removes $count bytes starting at address $from.
473              
474             $hex_record->remove(0x123, 10);
475              
476             =head2 write($from, $bytes_ref)
477              
478             (Over)writes bytes starting at address $from with bytes in $bytes_ref.
479              
480             $hex_record->write(0x10, [qw(AA BB CC DD EE FF 11)]);
481              
482             =head2 as_intel_hex($bytes_hex_a_line)
483              
484             Returns a string containing hex bytes formatted as intel hex.
485             Maximum of $bytes_hex_a_line in data field.
486             Extended linear addresses as offset are used if needed.
487             Extended segment addresses are not supported. (yet? let me know!)
488              
489             =head2 as_srec_hex($bytes_hex_a_line)
490              
491             Returns a string containing hex bytes formatted as srec hex.
492             Maximum of $bytes_hex_a_line in data field.
493             Tries to use the smallest address field. (16 bit, 24 bit, 32 bit)
494              
495             =head1 LICENSE
496              
497             This is released under the Artistic License.
498              
499             =head1 AUTHOR
500              
501             spebern
502              
503             =cut