File Coverage

blib/lib/Hex/Record.pm
Criterion Covered Total %
statement 210 210 100.0
branch 68 68 100.0
condition 19 19 100.0
subroutine 16 16 100.0
pod 7 8 87.5
total 320 321 99.6


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