| 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"DESCRIPTION"> 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 =~ /..;/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 |
||||||
| 704 | C |
||||||
| 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 "" for xml conversion | ||||||
| 789 | 0 | 0 | $hex =~ s/(..)/$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 "" for xml conversion | ||||||
| 809 | 0 | 0 | $hex =~ s/(..)/$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/(..)/$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 "" for xml conversion | ||||||
| 845 | 1 | 14 | $value =~ s/(..)/$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__ |