File Coverage

blib/lib/Hex/Record.pm
Criterion Covered Total %
statement 205 205 100.0
branch 68 68 100.0
condition 11 11 100.0
subroutine 16 16 100.0
pod 7 8 87.5
total 307 308 99.6


line stmt bran cond sub pod time code
1             package Hex::Record;
2              
3 8     8   110337 use strict;
  8         25  
  8         192  
4 8     8   26 use warnings;
  8         7  
  8         141  
5 8     8   23 use Carp;
  8         11  
  8         13152  
6              
7             our $VERSION = '0.08';
8              
9             sub new {
10 18     18 0 8620 my ($class, %args) = @_;
11              
12 18 100       52 $args{parts} = [] unless exists $args{parts};
13              
14 18         50 return bless \%args, $class;
15             }
16              
17             sub import_intel_hex {
18 6     6 1 15 my ($self, $hex_string) = @_;
19              
20 6         5 my $addr_high_dec = 0;
21              
22 6         32 for my $line (split m{\n\r?}, $hex_string) {
23 34 100       122 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         102 my @bytes = unpack('(A2)*', $bytes_str);
33              
34             # data line?
35 33 100       61 if ($type == 0) {
    100          
    100          
36 24         46 $self->write($addr_high_dec + hex $addr, \@bytes);
37             }
38             # extended linear address type?
39             elsif ($type == 4) {
40 3         6 $addr_high_dec = hex( join '', @bytes ) << 16;
41             }
42             # extended segment address type?
43             elsif ($type == 2) {
44 3         6 $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 17 my ($self, $hex_string) = @_;
53              
54 6         20 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         2 my @parts;
68 6         30 for my $line (split m{\n\r?}, $hex_string) {
69 22 100       62 next unless substr( $line, 0, 1 ) =~ m{s}i;
70              
71 21         19 my $type = substr $line, 1, 1;
72              
73 21   100     31 my $addr_length = $address_length_of_srec_type{$type} || next;
74              
75 20 100       156 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     68 if ($type == 1 || $type == 2 || $type == 3) {
      100        
86 18         94 $self->write(hex $addr, [ unpack '(A2)*', $bytes_str ]);
87             }
88             }
89              
90 6         18 return;
91             }
92              
93             sub write {
94 55     55 1 262 my ($self, $from, $bytes_hex_ref) = @_;
95              
96 55         65 $self->remove($from, scalar @$bytes_hex_ref);
97              
98 55         47 my $to = $from + @$bytes_hex_ref;
99              
100 55         43 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  107         423  
101 80         51 my $part = $self->{parts}->[$part_i];
102              
103 80         58 my $start_addr = $part->{start};
104 80         51 my $end_addr = $part->{start} + $#{ $part->{bytes} };
  80         62  
105              
106             # merge with this part
107 80 100       155 if ($to == $start_addr) {
    100          
    100          
108 4         4 $part->{start} = $from;
109 4         5 unshift @{ $part->{bytes} }, @$bytes_hex_ref;
  4         24  
110 4         9 return;
111             }
112             elsif ($from == $end_addr + 1) {
113 20         18 push @{ $part->{bytes} }, @$bytes_hex_ref;
  20         59  
114              
115 20 100       14 return if $part_i+1 == @{ $self->{parts} };
  20         59  
116              
117 3         4 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         2  
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         21 push @{ $self->{parts} }, {
  27         55  
135             start => $from,
136             bytes => $bytes_hex_ref
137             };
138              
139 27         45 return;
140             }
141              
142             sub get {
143 8     8 1 2430 my ($self, $from, $length) = @_;
144              
145 8         8 my @bytes;
146 8         7 my $end_last = $from;
147             my $get = sub {
148 8     8   32 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         3 push @bytes, @{ $part->{bytes} };
  5         17  
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         2 push @bytes, @{ $part->{bytes} }[ $from - $part->{start} .. $#{ $part->{bytes} } ];
  1         2  
  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         3  
165             }
166              
167 8         8 $end_last = $part->{start} + @{ $part->{bytes} };
  8         12  
168 8         36 };
169              
170 8         15 $self->_traverse($from, $length, $get);
171              
172 8         74 return [ @bytes, (undef) x ($length - @bytes) ];
173             }
174              
175             sub remove {
176 67     67 1 12484 my ($self, $from, $length) = @_;
177              
178 67         59 my $to = $from + $length;
179              
180             my $remove = sub {
181 33     33   31 my ($part, $part_i_ref, $overlap) = @_;
182 33 100       50 if ($overlap eq 'a') {
    100          
    100          
183 22         15 splice @{ $self->{parts} }, $$part_i_ref, 1;
  22         30  
184 22         71 --$$part_i_ref;
185             }
186             elsif ($overlap eq 'l') {
187 2         3 my $to_remove = $to - $part->{start};
188 2         2 splice @{ $part->{bytes} }, 0, $to_remove;
  2         3  
189 2         3 $part->{start} += $to_remove;
190             }
191             elsif ($overlap eq 'r') {
192 7         8 splice @{ $part->{bytes} }, $from - $part->{start};
  7         23  
193             }
194             else {
195 2         4 splice @{ $self->{parts} }, $$part_i_ref, 1,
196             {
197             start => $part->{start},
198 2         20 bytes => [ @{ $part->{bytes} }[ 0 .. $from - $part->{start} - 1 ] ],
199             },
200             {
201             start => $from + $length,
202 2         13 bytes => [ @{ $part->{bytes} }[
203 2         2 $from - $part->{start} + $length .. $#{ $part->{bytes} }
  2         4  
204             ] ],
205             };
206             };
207 67         202 };
208              
209 67         88 $self->_traverse($from, $length, $remove);
210              
211 67         287 return;
212             }
213              
214             sub _traverse {
215 75     75   67 my ($self, $from, $length, $process) = @_;
216              
217 75         53 my $to = $from + $length;
218              
219 75         69 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  181         286  
220 131         102 my $part = $self->{parts}->[$part_i];
221              
222 131         83 my $start_addr = $part->{start};
223 131         86 my $end_addr = $part->{start} + @{ $part->{bytes} };
  131         100  
224              
225 131 100 100     318 if ($from < $end_addr && $to > $start_addr) {
226 25 100       44 if ($from <= $start_addr) {
    100          
227 14 100       22 if ($to < $end_addr -1) {
228 2         5 $process->($part, \$part_i, 'l');
229 2         3 return;
230             }
231 12         20 $process->($part, \$part_i, 'a');
232             }
233             elsif ($to < $end_addr - 1) {
234 3         5 $process->($part, \$part_i, 'm');
235 3         10 return;
236             }
237             else {
238 8         32 $process->($part, $part_i, 'r');
239             }
240              
241             # found start -> search for end
242 20         45 while ( defined (my $part = $self->{parts}->[++$part_i]) ) {
243 25         37 my $start_addr = $part->{start};
244 25 100       43 return if $start_addr > $to;
245              
246 16         12 my $end_addr = $part->{start} + @{ $part->{bytes} };
  16         17  
247              
248 16 100       26 if ($to >= $end_addr) {
249 15         18 $process->($part, \$part_i, 'a');
250             }
251             else {
252 1         2 $process->($part, \$part_i, 'l');
253 1         2 return;
254             }
255             }
256 10         13 return;
257             }
258             }
259             }
260              
261             sub as_intel_hex {
262 2     2 1 146 my ($self, $bytes_hex_a_line) = @_;
263              
264 2         8 my $intel_hex_string = '';
265 2         3 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  9         16  
266 7         7 my $part = $self->{parts}->[$part_i];
267              
268 7         4 my $start_addr = $part->{start};
269 7         5 my $end_addr = $part->{start} + $#{ $part->{bytes} };
  7         8  
270              
271 7         4 my $cur_high_addr_hex = '0000';
272              
273 7         7 for (my $slice_i = 0; $slice_i * $bytes_hex_a_line < @{ $part->{bytes} }; $slice_i++) {
  19         33  
274 12         8 my $total_addr = $start_addr + $slice_i*$bytes_hex_a_line;
275              
276 12         30 my ($addr_high_hex, $addr_low_hex) = unpack '(A4)*', sprintf('%08X', $total_addr);
277              
278 12 100       19 if ($cur_high_addr_hex ne $addr_high_hex) {
279 3         3 $cur_high_addr_hex = $addr_high_hex;
280 3         6 $intel_hex_string .= _intel_hex_line_of( '0000', 4, [unpack '(A2)*', $cur_high_addr_hex]);
281             }
282              
283 12 100       11 if ( ($slice_i + 1) * $bytes_hex_a_line <= $#{ $part->{bytes} } ) {
  12         18  
284             $intel_hex_string .= _intel_hex_line_of(
285             $addr_low_hex, 0,
286             [
287 5         6 @{ $part->{bytes} }[
  5         10  
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         18 @{ $part->{bytes} }[
297 7         7 $slice_i * $bytes_hex_a_line .. $#{ $part->{bytes} }
  7         7  
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   15 my ($addr_low_hex, $type, $bytes_hex_ref) = @_;
310              
311 15         9 my $byte_count = @$bytes_hex_ref;
312              
313 15         10 my $sum = 0;
314 15         20 $sum += $_ for ( $byte_count, (map { hex $_ } unpack '(A2)*', $addr_low_hex),
  30         32  
315 121         109 $type, (map { hex $_ } @$bytes_hex_ref) );
316              
317             #convert to hex, take lsb
318 15         23 $sum = substr(sprintf( '%02X', $sum ), -2);
319              
320 15         15 my $checksum_hex = sprintf '%02X', (hex $sum ^ 255) + 1;
321 15 100       22 $checksum_hex = '00' if length $checksum_hex != 2;
322              
323 15         50 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 1     1 1 132 my ($self, $bytes_hex_a_line) = @_;
337              
338 1         2 my $srec_hex_string = '';
339 1         3 for (my $part_i = 0; $part_i < @{ $self->{parts} }; $part_i++) {
  8         17  
340 7         8 my $part = $self->{parts}->[$part_i];
341              
342 7         8 my $start_addr = $part->{start};
343 7         4 my $end_addr = $part->{start} + $#{ $part->{bytes} };
  7         9  
344              
345 7         7 for (my $slice_i = 0; $slice_i * $bytes_hex_a_line < @{ $part->{bytes} }; $slice_i++) {
  19         39  
346 12         13 my $total_addr = $start_addr + $slice_i*$bytes_hex_a_line;
347              
348 12 100       9 if ( ($slice_i + 1) * $bytes_hex_a_line <= $#{ $part->{bytes} } ) {
  12         19  
349             $srec_hex_string .= _srec_hex_line_of(
350             $total_addr,
351 5         7 [@{ $part->{bytes} }[$slice_i * $bytes_hex_a_line .. ($slice_i + 1) * $bytes_hex_a_line - 1]]
  5         11  
352             );
353             }
354             else {
355             $srec_hex_string .= _srec_hex_line_of(
356             $total_addr,
357 7         6 [@{ $part->{bytes} }[$slice_i * $bytes_hex_a_line .. $#{ $part->{bytes} }]]
  7         17  
  7         7  
358             );
359             }
360             }
361             }
362              
363 1         2 return $srec_hex_string;
364             }
365              
366             sub _srec_hex_line_of {
367 12     12   11 my ($total_addr, $bytes_hex_ref) = @_;
368              
369 12         19 my $total_addr_hex = sprintf '%04X', $total_addr;
370              
371 12         9 my $type;
372             # 16 bit addr
373 12 100       18 if (length $total_addr_hex == 4) {
    100          
374 5         3 $type = 1;
375             }
376             # 24 bit addr
377             elsif (length $total_addr_hex <= 6) {
378 4 100       9 $total_addr_hex = "0$total_addr_hex" if length $total_addr_hex == 5;
379 4         3 $type = 2;
380             }
381             # 32 bit addr
382             else {
383 3 100       9 $total_addr_hex = "0$total_addr_hex" if length $total_addr_hex == 7;
384 3         4 $type = 3;
385             }
386              
387             # count of data bytes + address bytes
388 12         19 my $byte_count = @$bytes_hex_ref + length($total_addr_hex) / 2;
389              
390 12         9 my $sum = 0;
391 12         27 $sum += $_ for ( $byte_count,
392 34         48 (map { hex $_ } unpack '(A2)*', $total_addr_hex),
393 93         94 (map { hex $_ } @$bytes_hex_ref) );
394              
395             #convert to hex, take lsb
396 12         26 $sum = substr(sprintf( '%02X', $sum ), -2);
397              
398 12         16 my $checksum_hex = sprintf '%02X', (hex $sum ^ 255);
399              
400 12         53 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             my $intel_hex_str = $hex_record->as_intel_hex(10);
490              
491             =head2 as_srec_hex($bytes_hex_a_line)
492              
493             Returns a string containing hex bytes formatted as srec hex.
494             Maximum of $bytes_hex_a_line in data field.
495             Tries to use the smallest address field. (16 bit, 24 bit, 32 bit)
496              
497             my $srec_hex_str = $hex_record->as_srec_hex(10);
498              
499             =head1 LICENSE
500              
501             This is released under the Artistic License.
502              
503             =head1 AUTHOR
504              
505             spebern
506              
507             =cut