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__ |