File Coverage

blib/lib/Convert/ASN1/asn1c.pm
Criterion Covered Total %
statement 37 332 11.1
branch 1 116 0.8
condition n/a
subroutine 9 24 37.5
pod 13 19 68.4
total 60 491 12.2


line stmt bran cond sub pod time code
1             package Convert::ASN1::asn1c;
2              
3 1     1   70037 use Carp;
  1         2  
  1         116  
4 1     1   7 use strict;
  1         3  
  1         59  
5 1     1   7 use warnings;
  1         6  
  1         36  
6 1     1   1095 use File::Slurp;
  1         63276  
  1         101  
7 1     1   7019 use IPC::Run qw(run pump start finish);
  1         122606  
  1         4885  
8              
9             require Exporter;
10              
11             =head1 NAME
12              
13             Convert::ASN1::asn1c - A perl module to convert ASN1 to XML and back, using the
14             asn1c tools enber and unber.
15              
16             =head1 SYNOPSIS
17              
18             To use this module you need a xml template for the ASN1 PDU's you want to
19             encode/decode. For now we assume we have a file named "test-pdu.xml" in the
20             current working directory with the following content (read L for
21             information on how to create such a template):
22              
23            
24            

$integer1

25            

$integer2

26            
27            

$enumerated1

28            
29            
30              
31             Now we can use this file together with Convert::ASN1::asn1c as shown:
32              
33             use Convert::ASN1::asn1c;
34            
35             my $pdu = "A1 0C 02 01 01 02 02 00 D3 30 03 0A 01 02";
36             $pdu =~ s/ //g;
37             $pdu = pack('H*', $pdu);
38              
39             # Now we have a binary ASN1 protocol data unit (PDU) in $pdu.
40             # Typically you would read such data i.e., from a socket of course.
41              
42             my $conv = Convert::ASN1::asn1c->new();
43            
44             # Now let's decode this pdu, assuming it is a pdu which corresponds
45             # to the test-pdu.xml file created earlier.
46              
47             my $values = $conv->decode("test-pdu.xml", $pdu);
48             print $values->{'integer2'} . "\n"; # prints '211' for this example
49              
50             # Now let's change some values, use the same number of bytes to store this value as before
51             $values->{'integer2'} = $conv->encode_integer(210, $values->{'integer2_length'});
52              
53             # and encode it into a binary ASN1 PDU again
54             my $pdu_new = $conv->encode("test-pdu.xml", $values);
55              
56             =head1 DESCRIPTION
57              
58             Abstract Syntax Notation One (ASN1) is a protocol for data exchange by
59             applications, defined by the ITU-T. It works as follows: All parties agree on a
60             ASN1 specification for the Protocol Data Units (PDUs). Such a specification
61             might look like:
62              
63             AARQ-apdu ::= [APPLICATION 0] IMPLICIT SEQUENCE {
64             application-context-name [1] Application-context-name,
65             sender-acse-requirements [10] IMPLICIT ACSE-requirements OPTIONAL,
66             calling-authentication-value [12] EXPLICIT Authentication-value OPTIONAL,
67             user-information [30] IMPLICIT Association-information OPTIONAL
68             }
69              
70             Application-context-name ::= SEQUENCE { foo OBJECT IDENTIFIER }
71             ACSE-requirements ::= BIT STRING
72             Authentication-value ::= CHOICE { external [2] IMPLICIT PrivatExtPassword }
73             PrivatExtPassword ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { encoding EncodingPassword }
74             ...
75              
76             Now every party (that is aware of this specification) can take some data and
77             encode it (using standardized encoding rules) - Every other party will be able
78             to decode the information afterwards.
79              
80             A module that does exactly this is Convert::ASN1. However, this approach has
81             a slight problem if you just want to receive a ASN1 encoded data unit, modify a
82             few values and send the modified PDU somewhere, for example during development,
83             testing or fuzzing of ASN1 processing entities: Sometimes you don't have the
84             ASN1 specification for that device.
85              
86             In that case you can try to reverse engineer it, which is error prone and
87             tiresome. One tool that can assist you with that is the open source ASN1
88             compiler asn1c. It comes with two tools, unber and enber. The unber program
89             takes a binary pdu and tries to decode it to xml (without a matching ASN1
90             specification) just using the encoding information present in the binary ASN1
91             data. Due to the nature of BER-encoded (the most widely used encoding standard)
92             data, this is almost always possible. The only information that might get lost
93             is the description what kind of data we are dealing with, i.e., if we should
94             interpret the data with a hex value of 0x31 as an 1-byte integer or a 1-char
95             character string.
96              
97             The enber tool can read the xml created by unber and convert it back into a
98             binary ASN1 pdu. Of course it is possible to edit the xml in between this
99             process to change some values. This is exactly what this module does.
100              
101             Suppose you sniffed a data packet from somewhere (for example from a Siemens
102             HiPath PBX, from which you know it uses the CSTA protocol, which itself uses
103             ASN1 PDUs). You dumped the data in a file called pdu-siemens.bin for analysis.
104              
105             $ hexdump pdu-siemens.bin
106             0000000 0ca1 0102 0201 0002 30d3 0a03 0201
107             000000e
108              
109             Now use the unber tool to decode this file:
110              
111             $ unber -p pdu-siemens.bin
112            
113            



114            

�Ó

115            
116            



117            
118            
119              
120             The -p option instructs unber to generate xml that enber can understand. Now
121             let's assume we want to take control over the two integer values, maybe because
122             we want to change their values and see what happens or we want to examine their
123             values in similar PDUs. We create a template with the following content:
124              
125            
126            

$integer1

127            

$integer2

128            
129            



130            
131            
132              
133             And save it as "test-pdu.xml". Now we can use this module to read and create
134             simillar PDUs.
135              
136             use Convert::ASN1::asn1c;
137            
138             my $pdu = "A1 0C 02 01 01 02 02 00 D3 30 03 0A 01 02";
139             $pdu =~ s/ //g;
140             $pdu = pack('H*', $pdu);
141              
142             my $conv = Convert::ASN1::asn1c->new();
143             my $values = $conv->decode("test-pdu.xml", $pdu);
144             print $values->{'integer2'} . "\n"; # prints '211' for this example
145              
146             # Now let's change some values, use the same number of bytes to store this value as before
147             $values->{'integer2'} = $conv->encode_integer(210, $values->{'integer2_length'});
148              
149             # and encode it into a binary ASN1 PDU again
150             my $pdu_new = $conv->encode("test-pdu.xml", $values);
151              
152             Of course this is a quick hack and not a real protocol implementation. But
153             quick hacks can be extremely usefull during protocol implementations. :-D
154              
155             =head2 EXPORT
156              
157             None by default.
158              
159             =cut
160              
161              
162              
163             our @ISA = qw(Exporter);
164              
165             # Items to export into callers namespace by default. Note: do not export
166             # names by default without a very good reason. Use EXPORT_OK instead.
167             # Do not simply export all your public functions/methods/constants.
168              
169             # This allows declaration use Convert::ASN1::asn1c ':all';
170             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
171             # will save memory.
172             our %EXPORT_TAGS = ( 'all' => [ qw(
173            
174             ) ] );
175              
176             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
177              
178             our @EXPORT = qw(
179            
180             );
181              
182             our $VERSION = '0.07';
183              
184              
185             # Preloaded methods go here.
186              
187             =head1 METHODS
188              
189             =head2 new()
190              
191             Create a new ASN1 converter object
192              
193             =cut
194              
195             sub new {
196 1     1 1 41 my ($class_name) = @_;
197              
198 1         4 my $self = {};
199 1         6 bless ($self, $class_name);
200 1         17 $self->{'_templatedir'} = '.';
201 1         3 $self->{'_size_autocorrection'} = 1;
202 1         3 return $self;
203             }
204              
205             =head2 set_templatedir("./xmltemplates")
206              
207             Set a directory where the xml templates for later encoding/decoding can be found
208              
209             =cut
210              
211             sub set_templatedir {
212 1     1 1 805 my ($self, $dir) = @_;
213 1 50       52 if (-d $dir) {
214 1         4 $self->{'_templatedir'} = $dir;
215 1         7 return 1;
216             }
217             else {
218 0         0 carp "The directory $dir does not exists or is not a directory.\n";
219 0         0 return undef;
220             }
221             }
222              
223             =head2 enable_sizecorr()
224              
225             It is easily possible to produce invalid ASN1 packets with this module if you
226             specify incorrect sizes for the values in your template. If you turn on
227             automatic size correction with this function, such errors are automatically
228             corrected for you. Note that automatic size correction is turned on by default.
229              
230             =cut
231              
232             sub enable_sizecorr {
233 0     0 1 0 my ($self, $dir) = @_;
234 0         0 $self->{'_size_autocorrection'} = 1;
235             }
236              
237             =head2 disable_sizecorr()
238              
239             It is easily possible to produce invalid ASN1 packets with this module if you
240             specify incorrect sizes for the values in your template. If you turn off
241             automatic size correction with this function, such errors are NOT automatically
242             corrected for you. Note that automatic size correction is turned on by default.
243              
244             =cut
245              
246             sub disable_sizecorr {
247 0     0 1 0 my ($self, $dir) = @_;
248 0         0 $self->{'_size_autocorrection'} = 0;
249             }
250              
251              
252              
253             =head2 $pdu = encode('pduname', {
254             'value1'=>encode_integer(42, 1),
255             'value2'=>encode_bitstring("10010")
256             }
257             );
258              
259             The encode function takes the name of a template (the directory where to find
260             those templates can be modified with set_templatedir($dir)) and a reference to
261             a hash which's keys are names (the same that occur in the template) and values
262             with which these variables in the template should be substituted.
263              
264             Note that these values have to be in xml format. To encode perl scalars into
265             the correct format you can use the encoding functions provided by this module.
266              
267             The return value is the (binary) ASN1 PDU.
268              
269             =cut
270              
271             sub encode {
272              
273 0     0 1 0 my ($self, $pduname, $valueref) = @_;
274 0         0 my %values = %{$valueref};
  0         0  
275              
276             # try to find the packet description
277 0         0 my $text = read_file(File::Spec->catfile($self->{'_templatedir'}, $pduname));
278 0         0 foreach (keys %values) {
279 0         0 $text =~ s/\$$_(\W)/$values{$_}$1/g;
280             }
281 0 0       0 if ($text =~ m/(\$.+?)("|<| |>)/) {
282 0         0 carp "Undefined variable ($1) in $pduname, your template contained that variable, but you didn't specify a value for it!\n";
283             }
284              
285 0 0       0 if ($self->{'_size_autocorrection'}) {
286 0         0 $text = correct_sizes($self, $text);
287             }
288              
289 0         0 my $pdu;
290 0         0 my @enber = qw( enber - );
291 0         0 my $h = start \@enber, \$text, \$pdu;
292 0         0 pump $h while length $text;
293 0 0       0 finish $h or croak "enber returned $?";
294              
295 0         0 return $pdu;
296             }
297              
298              
299              
300             =head2 $pdu = sencode($xmltemplate, {
301             'value1'=>encode_integer(42, 1),
302             'value2'=>encode_bitstring("10010")
303             }
304             );
305              
306             The sencode function takes a template and a reference to a hash which's keys are
307             names (the same that occur in the template) and values with which these
308             variables in the template should be substituted.
309              
310             It works the same way as the encode() function but it directly takes the xml
311             template as the first argument instead of a filename.
312              
313             =cut
314              
315             sub sencode {
316              
317 0     0 1 0 my ($self, $text, $valueref) = @_;
318 0         0 my %values = %{$valueref};
  0         0  
319              
320 0         0 foreach (keys %values) {
321 0         0 $text =~ s/\$$_(\W)/$values{$_}$1/g;
322             }
323 0 0       0 if ($text =~ m/(\$.+?)("|<| |>)/) {
324 0         0 carp "Undefined variable ($1) in $text, your template contained that variable, but you didn't specify a value for it!\n";
325             }
326              
327 0 0       0 if ($self->{'_size_autocorrection'}) {
328 0         0 $text = correct_sizes($self, $text);
329             }
330              
331 0         0 my $pdu;
332 0         0 my @enber = qw( enber - );
333 0         0 my $h = start \@enber, \$text, \$pdu;
334 0         0 pump $h while length $text;
335 0 0       0 finish $h or croak "enber returned $?";
336              
337 0         0 return $pdu;
338             }
339              
340              
341             sub correct_sizes {
342 0     0 0 0 my ($self, $text) = @_;
343              
344 0         0 my @lines = split(/\n/, $text);
345            
346 0         0 my $current_offset = 0;
347 0         0 my @stack;
348 0         0 foreach (0 .. scalar(@lines)-1) {
349 0 0       0 if ($lines[$_] =~ m/

(.*?)<\/P>/) {

350 0         0 my $offset = $1;
351 0         0 my $tag = $2;
352 0         0 my $tag_length = $3;
353 0         0 my $value_length = $4;
354 0         0 my $rest = $5;
355 0         0 my $value = $6;
356              
357 0         0 $offset = $current_offset;
358             #count number of bytes in $value
359 0         0 $value_length = () = $value =~ /&#x..;/g;
360             #replace this line with the corrected values
361 0         0 $lines[$_] = "

$value

";
362 0         0 $current_offset += $tag_length;
363 0         0 $current_offset += $value_length;
364             }
365 0 0       0 if ($lines[$_] =~ m//) {
366 0         0 my $offset = $1;
367 0         0 my $tag = $2;
368 0         0 my $tag_length = $3;
369 0         0 my $value_length = $4;
370 0         0 my $rest = $5;
371 0         0 $offset = $current_offset;
372             #replace this line with the corrected values
373 0         0 $lines[$_] = "";
374 0         0 $current_offset += $tag_length;
375             # put this line number on the stack, so that we can jump back here and fill in the value length once we know it
376 0         0 push @stack, $_;
377             }
378 0 0       0 if ($lines[$_] =~ m/<\/C O=\"(\d+)\" T=\"(.+?)\"(.+?)L=\"(\d+)\">/) {
379 0         0 my $offset = $1;
380 0         0 my $tag = $2;
381 0         0 my $rest = $3;
382 0         0 my $length = $4;
383 0         0 $offset = $current_offset;
384              
385 0         0 my $opening_line = pop @stack;
386 0 0       0 if ($lines[$opening_line] =~ m//) {
387 0         0 my $op_offset = $1;
388 0         0 my $op_tag = $2;
389 0         0 my $op_tag_length = $3;
390 0         0 my $op_value_length = $4;
391 0         0 my $op_rest = $5;
392 0         0 $op_value_length = $current_offset - $op_offset - $op_tag_length;
393 0         0 $length = $current_offset - $op_offset;
394 0         0 $lines[$opening_line] = "";
395             }
396             else {
397 0         0 die "Internal error, file bug report!\n";
398             }
399              
400             #replace this line with the corrected values
401 0         0 $lines[$_] = "";
402             }
403             }
404              
405 0         0 $text = join("\n", @lines);
406              
407 0         0 return $text;
408             }
409              
410              
411              
412             =head2 $values = decode('pduname', $pdu);
413              
414             The decode function takes the name of a template (the directory where to find
415             those templates can be modified with set_templatedir($dir)) and a binary pdu.
416              
417             It will match the variables in the template against the decoded binary pdu and
418             return a reference to a hash which contains these values.
419              
420             For each variable $myvalue the hash will contain four keys:
421              
422             =head3 $values->{'myvalue'}
423              
424             The decoded value if we could "guess" myvalues type because it was
425             specified as i.e. INTEGER or BIT STRING in the asn1 pdu.
426              
427             =head3 $values->{'myvalue_orig'}
428              
429             The original value as it was found in the unber -p output. Note that these
430             values are still xml-encoded. To decode them you can use this modules
431             decode_-functions or write your own decoders if the provided ones are not
432             sufficient.
433              
434             =head3 $values->{'myvalue_length'}
435              
436             The length of $myvalue as it was encoded in the asn1 pdu. This value is
437             needed for some _decode routines and can also be usefull if you write your own
438             decoder functions.
439              
440             =head3 $values->{'myvalue_type'}
441              
442             If the type of $myvalue is specified in the pdu, for example as INTEGER, this
443             key contains the value.
444              
445             =cut
446              
447              
448              
449             sub decode {
450              
451 0     0 1 0 my ($self, $pduname, $pdu) = @_;
452              
453 0         0 my @stack;
454             my @varpos;
455              
456             # try to find the packet description
457 0         0 my @lines = read_file(File::Spec->catfile($self->{'_templatedir'}, $pduname));
458              
459             # we will parse the packet description
460             # to find out which "nodes" in the tag tree are interesting for us
461             # and we will construct a list of those interesting nodes (and how to "reach" them,
462             # i.e. which parent nodes they are located under. In the second step we will
463             # iterate over the decoded ASN data, if we are in an inetersting leaf we will decode it's value.
464            
465 0         0 foreach (@lines) {
466 0 0       0 if (m/
  0         0  
467 0 0       0 if (m/<\/C /) { pop @stack; }
  0         0  
468 0 0       0 if (m/

  0         0  
469 0         0 while (m/(\$.+?)("|<| |>)/gc) {
470 0         0 my $varname = $1;
471 0 0       0 if ($varname !~ m/_length$/) {
472 0         0 push(@varpos, $varname . ":" . join('|', @stack));
473             }
474             }
475 0 0       0 if (m/<\/P>/) { pop @stack; }
  0         0  
476             }
477              
478 0         0 my @unber = qw( unber -p - );
479 0         0 my $text;
480 0         0 my $h = start \@unber, \$pdu, \$text;
481 0         0 pump $h while length $pdu;
482 0 0       0 finish $h or croak "unber returned $?";
483              
484 0         0 @lines = qw();
485 0         0 @stack = qw();
486 0         0 @lines = split(/\n/, $text);
487 0         0 my %results;
488              
489 0         0 foreach (@lines) {
490 0         0 my $line = $_;
491 0 0       0 if ($line =~ m/
492 0         0 push @stack, $1;
493             }
494 0 0       0 if ($line =~ m/<\/C /) {
495 0         0 pop @stack;
496             }
497 0 0       0 if ($line =~ m/

498             #check if this node is "interesting" - is there a entry in @varpos which matches the current stack
499 0         0 push @stack, $1;
500 0         0 my $current = join('|', @stack);
501 0         0 foreach (0 .. scalar(@varpos)-1) {
502 0 0       0 croak "Internal Parser error!\n" unless ($varpos[$_] =~ m/^\$(.*?):(.*?)$/);
503 0         0 my $varname = $1;
504 0         0 my $varposition = $2;
505 0 0       0 if ($varposition eq $current) {
506             # we are in an interesting node!
507 0         0 my $value = undef;
508 0         0 my $value_len = undef;
509 0         0 my $value_type = undef;
510 0 0       0 if ($line =~ m/ V=\"(.*?)\".*?>(.*?)
511 0         0 $value_len = $1;
512 0         0 $value = $2;
513 0 0       0 if ($line =~ m/A=\"(.*?)\"/) { $value_type = $1; }
  0         0  
514 0         0 else { $value_type = 'UNDEFINED'; }
515 0         0 $results{$varname . '_length'} = $value_len;
516 0         0 $results{$varname . '_type'} = $value_type;
517 0         0 $results{$varname} = $value;
518 0         0 $results{$varname . '_orig'} = $value;
519             # remove the filled varpos entry
520 0         0 $varpos[$_] .= '--matched--';
521 0         0 last;
522             }
523             }
524             }
525 0         0 pop @stack;
526             }
527             }
528            
529             # now we have all interesting values in the results hash, together with
530             # their type (BE CAREFULL - "Siemens Bitstrings" have the type UNDEFINED)
531             # and length.
532              
533 0         0 foreach (keys %results) {
534 0         0 my $key = $_;
535 0 0       0 if ($key !~ m/(_length$|_type$|_orig$)/) {
536 0         0 my $value = $results{$key};
537 0         0 my $type = $results{$key . '_type'};
538 0         0 my $length = $results{$key . '_length'};
539 0 0       0 if ($type eq 'OCTET STRING') {
540 0         0 $results{$key} = decode_octet_string($self, $value, $length);
541             }
542 0 0       0 if ($type eq 'INTEGER') {
543 0         0 $results{$key} = decode_integer($self, $value, $length);
544             }
545 0 0       0 if ($type =~ m/(BIT STRING)/) {
546 0         0 $results{$key} = decode_bitstring($self, $value, $length);
547             }
548 0 0       0 if ($type eq "GeneralizedTime") {
549 0         0 $results{$key} = decode_timestamp($self, $value, $length);
550             }
551 0 0       0 if ($type eq "ENUMERATED") {
552             # of course not all enumerated types are int's but
553             # in our context it seems to be a good guess
554 0         0 $results{$key} = decode_integer($self, $value, $length);
555             }
556             }
557             }
558              
559 0         0 return \%results;
560             }
561              
562             =head2 $values = sdecode($xml_template, $pdu);
563              
564             The sdecode function takes a template and a binary pdu. It works the same way
565             as the decode function, but it directly takes the template as it's first
566             argument instead of a filename.
567              
568             =cut
569              
570              
571              
572             sub sdecode {
573              
574 0     0 1 0 my ($self, $xml_template, $pdu) = @_;
575              
576 0         0 my @stack;
577             my @varpos;
578              
579             # try to find the packet description
580 0         0 my @lines = split(/\n/, $xml_template);
581              
582             # we will parse the packet description
583             # to find out which "nodes" in the tag tree are interesting for us
584             # and we will construct a list of those interesting nodes (and how to "reach" them,
585             # i.e. which parent nodes they are located under. In the second step we will
586             # iterate over the decoded ASN data, if we are in an inetersting leaf we will decode it's value.
587            
588 0         0 foreach (@lines) {
589 0 0       0 if (m/
  0         0  
590 0 0       0 if (m/<\/C /) { pop @stack; }
  0         0  
591 0 0       0 if (m/

  0         0  
592 0         0 while (m/(\$.+?)("|<| |>)/gc) {
593 0         0 my $varname = $1;
594 0 0       0 if ($varname !~ m/_length$/) {
595 0         0 push(@varpos, $varname . ":" . join('|', @stack));
596             }
597             }
598 0 0       0 if (m/<\/P>/) { pop @stack; }
  0         0  
599             }
600              
601 0         0 my @unber = qw( unber -p - );
602 0         0 my $text;
603 0         0 my $h = start \@unber, \$pdu, \$text;
604 0         0 pump $h while length $pdu;
605 0 0       0 finish $h or croak "unber returned $?";
606              
607 0         0 @lines = qw();
608 0         0 @stack = qw();
609 0         0 @lines = split(/\n/, $text);
610 0         0 my %results;
611              
612 0         0 foreach (@lines) {
613 0         0 my $line = $_;
614 0 0       0 if ($line =~ m/
615 0         0 push @stack, $1;
616             }
617 0 0       0 if ($line =~ m/<\/C /) {
618 0         0 pop @stack;
619             }
620 0 0       0 if ($line =~ m/

621             #check if this node is "interesting" - is there a entry in @varpos which matches the current stack
622 0         0 push @stack, $1;
623 0         0 my $current = join('|', @stack);
624 0         0 foreach (0 .. scalar(@varpos)-1) {
625 0 0       0 croak "Internal Parser error!\n" unless ($varpos[$_] =~ m/^\$(.*?):(.*?)$/);
626 0         0 my $varname = $1;
627 0         0 my $varposition = $2;
628 0 0       0 if ($varposition eq $current) {
629             # we are in an interesting node!
630 0         0 my $value = undef;
631 0         0 my $value_len = undef;
632 0         0 my $value_type = undef;
633 0 0       0 if ($line =~ m/ V=\"(.*?)\".*?>(.*?)
634 0         0 $value_len = $1;
635 0         0 $value = $2;
636 0 0       0 if ($line =~ m/A=\"(.*?)\"/) { $value_type = $1; }
  0         0  
637 0         0 else { $value_type = 'UNDEFINED'; }
638 0         0 $results{$varname . '_length'} = $value_len;
639 0         0 $results{$varname . '_type'} = $value_type;
640 0         0 $results{$varname} = $value;
641 0         0 $results{$varname . '_orig'} = $value;
642             # remove the filled varpos entry
643 0         0 $varpos[$_] .= '--matched--';
644 0         0 last;
645             }
646             }
647             }
648 0         0 pop @stack;
649             }
650             }
651            
652             # now we have all interesting values in the results hash, together with
653             # their type (BE CAREFULL - "Siemens Bitstrings" have the type UNDEFINED)
654             # and length.
655              
656 0         0 foreach (keys %results) {
657 0         0 my $key = $_;
658 0 0       0 if ($key !~ m/(_length$|_type$|_orig$)/) {
659 0         0 my $value = $results{$key};
660 0         0 my $type = $results{$key . '_type'};
661 0         0 my $length = $results{$key . '_length'};
662 0 0       0 if ($type eq 'OCTET STRING') {
663 0         0 $results{$key} = decode_octet_string($self, $value, $length);
664             }
665 0 0       0 if ($type eq 'INTEGER') {
666 0         0 $results{$key} = decode_integer($self, $value, $length);
667             }
668 0 0       0 if ($type =~ m/(BIT STRING)/) {
669 0         0 $results{$key} = decode_bitstring($self, $value, $length);
670             }
671 0 0       0 if ($type eq "GeneralizedTime") {
672 0         0 $results{$key} = decode_timestamp($self, $value, $length);
673             }
674 0 0       0 if ($type eq "ENUMERATED") {
675             # of course not all enumerated types are int's but
676             # in our context it seems to be a good guess
677 0         0 $results{$key} = decode_integer($self, $value, $length);
678             }
679             }
680             }
681              
682 0         0 return \%results;
683             }
684              
685             =head2 $tagpths = get_tagpaths_with_prefix($pdu, $prefix);
686              
687             A ASN1 PDU is contains constructed and primitive datatypes. Constructed
688             datatypes can contain other constructed or primitive datatypes. Each datatype
689             (constructed or primitive) is identified by a tag.
690              
691             This function decodes the pdu and constructs "tag paths": If a constructed
692             datatype with tag "foo" contains a constructed datatype "bar" and a primitive
693             datatype "moo". The constructed datatype "bar" contains a primitive datatype
694             "frob", we have the following xml structure:
695              
696            
697            
698            

...

699            
700            

...

701            
702              
703             In that case we have the following "tag paths": C, C,
704             C, C. This function returns all tag paths that match the
705             given prefix. In the returned tag paths (as well as in the prefix) single tags
706             have to be concatenated by the pipe character '|'.
707              
708             Note that this function doesn't require a name or a xml template for a PDU.
709             It's primary usage is to decide which template should be used to extract values
710             from a PDU.
711              
712             The result is returned as a reference to an array which contains the matching
713             tag paths.
714              
715             =cut
716              
717             sub get_tagpaths_with_prefix {
718              
719 0     0 1 0 my ($self, $pdu, $prefix) = @_;
720              
721 0         0 my @unber = qw( unber -p - );
722 0         0 my $text;
723 0         0 my $h = start \@unber, \$pdu, \$text;
724 0         0 pump $h while length $pdu;
725 0 0       0 finish $h or croak "unber returned $?";
726              
727 0         0 my @stack = qw();
728 0         0 my @results = qw();
729 0         0 my @lines = split(/\n/, $text);
730 0         0 $prefix = quotemeta($prefix);
731              
732 0         0 foreach (@lines) {
733 0         0 my $line = $_;
734 0 0       0 if ($line =~ m/
735 0         0 push @stack, $1;
736 0         0 my $current = join('|', @stack);
737 0 0       0 if ($current =~ m/^ $prefix/x) {
738 0         0 push @results, $current;
739             }
740             }
741 0 0       0 if ($line =~ m/<\/C /) {
742 0         0 pop @stack;
743             }
744 0 0       0 if ($line =~ m/

745 0         0 push @stack, $1;
746 0         0 my $current = join('|', @stack);
747 0 0       0 if ($current =~ m/^$prefix/) {
748 0         0 push @results, $current;
749             }
750 0         0 pop @stack;
751             }
752             }
753              
754 0         0 return \@results;
755              
756             }
757              
758              
759             =head2 Encoding Functions
760              
761             =head3 $xml = encode_bitstring("1010100")
762              
763             Takes a string which contains 0's and 1's and encodes this binary string into
764             xml understandable by enber(1).
765              
766             =cut
767              
768             sub encode_bitstring {
769              
770             # we get a string like "101" and convert it to
771             # number of unused bits + hex value of binary string
772              
773 0     0 1 0 my ($self, $bits) = @_;
774 0         0 $bits =~ s/ //g;
775            
776             # calculate how many unused bits will be in the bitstring
777 0         0 my $len = length($bits);
778 0         0 $len = $len % 8;
779 0         0 $len = 8 - $len;
780 0 0       0 if ($len == 8) {
781 0         0 $len = 0;
782             }
783              
784             # append zeroes until we have a number of bits devideable by eight
785 0         0 $bits .= '0' x $len;
786             #convert bits to hex
787 0         0 my $hex = unpack('H*', pack('B*', $bits));
788             #prepend every byte with "&#x" for xml conversion
789 0         0 $hex =~ s/(..)/&#x$1;/g;
790              
791 0         0 my $text = '�'.$len.';'.$hex;
792 0         0 return $text;
793             }
794              
795              
796             =head3 $xml = encode_octet_string("foo")
797              
798             Takes a perl string and encodes it as an ASN1 "OCTET STRING" in the xml format
799             understandable by enber(1).
800              
801             =cut
802              
803             sub encode_octet_string {
804             # we get a string like "foo" and convert it in it's hex notation
805 0     0 1 0 my ($self, $string) = @_;
806              
807 0         0 my $hex = unpack('H*', $string);
808             #prepend every byte with "&#x" for xml conversion
809 0         0 $hex =~ s/(..)/&#x$1;/g;
810 0         0 return $hex;
811             }
812              
813             =head3 $xml = encode_hextxt2xml("DEADBEEF")
814              
815             Takes a perl string which containts the characters [0-9] and [A-F] or [a-f],
816             interprets this string as a hexadecimal value and encodes it in the xml format
817             understandable by enber(1).
818              
819             =cut
820              
821             sub encode_hextxt2xml {
822              
823 0     0 1 0 my ($self, $value) = @_;
824              
825 0         0 $value =~ s/(..)/&#x$1;/g;
826 0         0 return $value;
827             }
828              
829             =head3 $xml = encode_integer(42, 4)
830              
831             Takes a integer and a size and encodes the integer in the xml format
832             understandable by enber(1). The size specifies how many bytes should be used to
833             encode the integer in ASN1.
834              
835             =cut
836              
837             sub encode_integer {
838            
839 1     1 1 1400 my ($self, $value, $length) = @_;
840              
841 1         5 $value = pack('N', $value);
842 1         8 $value = unpack('H*', $value);
843 1         4 $value = substr($value, (4-$length)*2, length($value));
844             #prepend every byte with "&#x" for xml conversion
845 1         14 $value =~ s/(..)/&#x$1;/g;
846 1         7 return $value;
847             }
848              
849              
850             =head2 Decoding Functions
851              
852             =head3 $bitstr = decode_bitstring($vals->{'myvalue_orig'})
853              
854             Takes a ASN1 BIT STRING value in the format returned by unber(1) or this
855             modules decode function and converts it into a perl string such as "101001".
856              
857             =cut
858              
859             sub decode_bitstring {
860              
861 0     0 0 0 my ($self, $value) = @_;
862              
863 0         0 my $orig = $value;
864             # first byte: number of unused bits (must be smaller than 8)
865 0         0 $value =~ s/(&|#|x|;)//g;
866 0         0 $value =~ s/^.(.)//;
867 0         0 my $unused_bits = $1;
868 0         0 $value = pack('H*', $value);
869 0         0 $value = unpack('B*', $value);
870             # remove unused bits
871 0 0       0 if ($unused_bits > 0) {
872 0         0 $value = substr($value, 0, -$unused_bits);
873             }
874 0         0 return $value;
875             }
876              
877             =head3 $time = decode_timestamp($vals->{'myvalue_orig'})
878              
879             Takes a ASN1 value of the type GeneralizedTimestamp in the format returned by
880             unber(1) or this modules decode function and converts it into a perl string
881             such as "2010-09-25 11:35:10" (year-month-day hour:minute:seconds).
882              
883             =cut
884              
885             sub decode_timestamp {
886 0     0 0 0 my ($self, $value) = @_;
887 0         0 $value =~ s/(&|#|x|;)//g;
888 0         0 $value = pack('H*', $value);
889 0 0       0 if ($value =~ m/(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/) {
890 0         0 return "$1-$2-$3 $4:$5:$6"
891             }
892             }
893              
894             =head3 $val = decode_octet_string($vals->{'myvalue_orig'})
895              
896             Takes a ASN1 value of the type OCTET STRING in the format returned by unber(1)
897             or this modules decode function and converts it into a perl scalar.
898              
899             =cut
900              
901              
902             sub decode_octet_string {
903 0     0 0 0 my ($self, $value) = @_;
904 0         0 $value =~ s/(&|#|x|;)//g;
905 0         0 $value = pack('H*', $value);
906 0         0 return $value;
907             }
908              
909             =head3 $int = decode_integer($vals->{'myvalue_orig'}, $vals->{'myvalue_length'})
910              
911             Takes a ASN1 value of the type INTEGER in the format returned by unber(1)
912             or this modules decode function and converts it into a perl scalar.
913              
914             =cut
915              
916             sub decode_integer {
917              
918 1     1 0 3 my ($self, $value, $length) = @_;
919            
920 1         23 $value =~ s/(&|#|x|;)//g;
921 1         6 $value = '00'x(4-$length) . $value;
922 1         4 $value = pack('H*', $value);
923 1         4 $value = unpack("N", $value);
924 1         6 return $value;
925             }
926              
927             =head3 $hex = decode_xml2hextxt($vals->{'myvalue_orig'});
928              
929             Takes any value in the format returned by unber(1) or this modules decode
930             function and converts it into a string which consists of this values hex
931             representation. This is usefull for opaque objects like identifiers, where you
932             don't really know what they mean but still want to display and compare them.
933              
934             =cut
935              
936             sub decode_xml2hextxt {
937              
938 0     0 0   my ($self, $value) = @_;
939              
940 0           $value =~ s/(&|#|x|;)//g;
941 0           return $value;
942             }
943              
944              
945             1;
946              
947             __END__