ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Convert-BER-XS/XS.pm
Revision: 1.6
Committed: Fri Apr 19 20:38:38 2019 UTC (5 years, 1 month ago) by root
Branch: MAIN
Changes since 1.5: +115 -5 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 Convert::BER::XS - I<very> low level BER en-/decoding
4
5 =head1 SYNOPSIS
6
7 use Convert::BER::XS ':all';
8
9 my $ber = ber_decode $buf
10 or die "unable to decode SNMP message";
11
12 # The above results in a data structure consisting of (class, tag,
13 # constructed, data) tuples. Below is such a message, SNMPv1 trap
14 # with a Cisco mac change notification.
15 # Did you know that Cisco is in the news almost every week because
16 # of some backdoor password or other extremely stupid security bug?
17
18 [ ASN_UNIVERSAL, ASN_SEQUENCE, 1,
19 [
20 [ ASN_UNIVERSAL, ASN_INTEGER32, 0, 0 ], # snmp version 1
21 [ ASN_UNIVERSAL, 4, 0, "public" ], # community
22 [ ASN_CONTEXT, 4, 1, # CHOICE, constructed - trap PDU
23 [
24 [ ASN_UNIVERSAL, ASN_OBJECT_IDENTIFIER, 0, "1.3.6.1.4.1.9.9.215.2" ], # enterprise oid
25 [ ASN_APPLICATION, 0, 0, "\x0a\x00\x00\x01" ], # SNMP IpAddress, 10.0.0.1
26 [ ASN_UNIVERSAL, ASN_INTEGER32, 0, 6 ], # generic trap
27 [ ASN_UNIVERSAL, ASN_INTEGER32, 0, 1 ], # specific trap
28 [ ASN_APPLICATION, ASN_TIMETICKS, 0, 1817903850 ], # SNMP TimeTicks
29 [ ASN_UNIVERSAL, ASN_SEQUENCE, 1, # the varbindlist
30 [
31 [ ASN_UNIVERSAL, ASN_SEQUENCE, 1, # a single varbind, "key value" pair
32 [
33 [ ASN_UNIVERSAL, ASN_OBJECT_IDENTIFIER, 0, "1.3.6.1.4.1.9.9.215.1.1.8.1.2.1" ], # the oid
34 [ ASN_UNIVERSAL, ASN_OCTET_STRING, 0, "...data..." # the value
35 ]
36 ]
37 ],
38 ...
39
40 # let's decode it a bit with some helper functions
41
42 my $msg = ber_is_seq $ber
43 or die "SNMP message does not start with a sequence";
44
45 ber_is $msg->[0], ASN_UNIVERSAL, ASN_INTEGER32, 0
46 or die "SNMP message does not start with snmp version\n";
47
48 # message is SNMP v1 or v2c?
49 if ($msg->[0][BER_DATA] == 0 || $msg->[0][BER_DATA] == 1) {
50
51 # message is v1 trap?
52 if (ber_is $msg->[2], ASN_CONTEXT, 4, 1) {
53 my $trap = $msg->[2][BER_DATA];
54
55 # check whether trap is a cisco mac notification mac changed message
56 if (
57 (ber_is_oid $trap->[0], "1.3.6.1.4.1.9.9.215.2") # cmnInterfaceObjects
58 and (ber_is_i32 $trap->[2], 6)
59 and (ber_is_i32 $trap->[3], 1) # mac changed msg
60 ) {
61 ... and so on
62
63 # finally, let's encode it again and hope it results in the same bit pattern
64
65 my $buf = ber_encode $ber;
66
67 =head1 DESCRIPTION
68
69 This module implements a I<very> low level BER/DER en-/decoder.
70
71 If is tuned for low memory and high speed, while still maintaining some
72 level of user-friendlyness.
73
74 Currently, not much is documented, as this is an initial release to
75 reserve CPAN namespace, stay tuned for a few days.
76
77 =head2 ASN.1/BER/DER/... BASICS
78
79 ASN.1 is a strange language that can be sed to describe protocols and
80 data structures. It supports various mappings to JSON, XML, but most
81 importantly, to a various binary encodings such as BER, that is the topic
82 of this module, and is used in SNMP or LDAP for example.
83
84 While ASN.1 defines a schema that is useful to interpret encoded data,
85 the BER encoding is actually somehat self-describing: you might not know
86 whether something is a string or a number or a sequence or something else,
87 but you can nevertheless decode the overall structure, even if you end up
88 with just a binary blob for the actual value.
89
90 This works because BER values are tagged with a type and a namespace,
91 and also have a flag that says whther a value consists of subvalues (is
92 "constructed") or not (is "primitive").
93
94 Tags are simple integers, and ASN.1 defines a somewhat weird assortment of
95 those - for example, you have 32 bit signed integers and 16(!) different
96 string types, but there is no unsigned32 type for example. Different
97 applications work around this in different ways, for example, SNMP defines
98 application-specific Gauge32, Counter32 and Unsigned32, which are mapped
99 to two different tags: you can distinguish between Counter32 and the
100 others, but not between Gause32 and Unsigned32, without the ASN.1 schema.
101
102 Ugh.
103
104 =head2 DECODED BER REPRESENTATION
105
106 This module represents every BER value as a 4-element tuple (actually an
107 array-reference):
108
109 [CLASS, TAG, CONSTRUCTED, DATA]
110
111 To avoid non-descriptive hardcoded array index numbers, this module
112 defines symbolic constants to access these members: C<BER_CLASS>,
113 C<BER_TAG>, C<BER_CONSTRUCTED> and C<BER_DATA>.
114
115 Also, the first three members are integers with a little caveat: for
116 performance reasons, these are readonly and shared, so you must not modify
117 them (increment, assign to them etc.) in any way. You may modify the
118 I<DATA> member, and you may re-assign the array itself, e.g.:
119
120 $ber = ber_decode $binbuf;
121
122 # the following is NOT legal:
123 $ber->[BER_CLASS] = ASN_PRIVATE; # ERROR, readonly(!)
124
125 # but all of the following are fine:
126 $ber->[BER_DATA] = "string";
127 $ber->[BER_DATA] = [ASN_UNIVERSAL, ASN_INTEGER32, 0, 123];
128 @$ber = (ASN_APPLICATION, SNMP_TIMETICKS, 1000);
129
130 I<CLASS> is something like a namespace for I<TAG>s - there is the
131 C<ASN_UNIVERSAL> namespace which defines tags common to all ASN.1
132 implementations, the C<ASN_APPLICATION> namespace which defines tags for
133 specific applications (for example, the SNMP C<Unsigned32> type is in this
134 namespace), a special-purpose context namespace (C<ASN_CONTEXT>, used e.g.
135 for C<CHOICE>) and a private namespace (C<ASN_PRIVATE>).
136
137 The meaning of the I<TAG> depends on the namespace, and defines a
138 (partial) interpretation of the data value. For example, right now, SNMP
139 application namespace knowledge ix hardcoded into this module, so it
140 knows that SNMP C<Unsigned32> values need to be decoded into actual perl
141 integers.
142
143 The most common tags in the C<ASN_UNIVERSAL> namespace are
144 C<ASN_INTEGER32>, C<ASN_BIT_STRING>, C<ASN_NULL>, C<ASN_OCTET_STRING>,
145 C<ASN_OBJECT_IDENTIFIER>, C<ASN_SEQUENCE>, C<ASN_SET> and
146 C<ASN_IA5_STRING>.
147
148 The most common tags in SNMP's C<ASN_APPLICATION> namespace
149 are C<SNMP_IPADDRESS>, C<SNMP_COUNTER32>, C<SNMP_UNSIGNED32>,
150 C<SNMP_TIMETICKS>, C<SNMP_OPAQUE> and C<SNMP_COUNTER64>.
151
152 The I<CONSTRUCTED> flag is really just a boolean - if it is false, the
153 the value is "primitive" and contains no subvalues, kind of like a
154 non-reference perl scalar. IF it is true, then the value is "constructed"
155 which just means it contains a list of subvalues which this module will
156 en-/decode as BER tuples themselves.
157
158 The I<DATA> value is either a reference to an array of further tuples (if
159 the value is I<CONSTRUCTED>), some decoded representation of the value,
160 if this module knows how to decode it (e.g. for the integer types above)
161 or a binary string with the raw octets if this module doesn't know how to
162 interpret the namespace/tag.
163
164 Thus, you can always decode a BER data structure and at worst you get a
165 string in place of some nice decoded value.
166
167 See the SYNOPSIS for an example of such an encoded tuple representation.
168
169 =head2 HELPER FUNCTIONS
170
171 Working with a 4-tuple for every value can be annoying. Or, rather, I<is>
172 annoying. To reduce this a bit, this module defines a number of helper
173 functions, both to match BER tuples and to conmstruct BER tuples:
174
175 =head3 MATCH HELPERS
176
177 Thse functions accept a BER tuple as first argument and either paertially
178 or fully match it. They often come in two forms, one which exactly matches
179 a value, and one which only matches the type and returns the value.
180
181 They do check whether valid tuples are passed in and croak otherwise. As
182 a ease-of-use exception, they usually also accept C<undef> instead of a
183 tuple reference. in which case they silently fail to match.
184
185 =over
186
187 =item $bool = ber_is $tuple, $class, $tag, $constructed, $data
188
189 This takes a BER C<$tuple> and matches its elements agains the privded
190 values, all of which are optional - values that are either missing or
191 C<undef> will be ignored, the others will be matched exactly (e.g. as if
192 you used C<==> or C<eq> (for C<$data>)).
193
194 Some examples:
195
196 ber_is $tuple, ASN_UNIVERSAL, ASN_SEQUENCE, 1
197 orf die "tuple is not an ASN SEQUENCE";
198
199 ber_is $tuple, ASN_UNIVERSAL, ASN_NULL
200 or die "tuple is not an ASN NULL value";
201
202 ber_is $tuple, ASN_UNIVERSAL, ASN_INTEGER32, 0, 50
203 or die "BER integer must be 50";
204
205 =item $seq = ber_is_seq $tuple
206
207 Returns the sequence members (the array of subvalues) if the C<$tuple> is
208 an ASN SEQUENCE, i.e. the C<BER_DATA> member. If the C<$tuple> is not a
209 sequence it returns C<undef>. For example, SNMP version 1/2c/3 packets all
210 consist of an outer SEQUENCE value:
211
212 my $ber = ber_decode $snmp_data;
213
214 my $snmp = ber_is_seq $ber
215 or die "SNMP packet invalid: does not start with SEQUENCE";
216
217 # now we know $snmp is a sequence, so decode the SNMP version
218
219 my $version = ber_is_i32 $snmp->[0]
220 or die "SNMP packet invalid: does not start with version number";
221
222 =item $bool = ber_is_i32 $tuple, $i32
223
224 Returns a true value if the C<$tuple> represents an ASN INTEGER32 with
225 the value C<$i32>.
226
227 =item $i32 = ber_is_i32 $tuple
228
229 Returns true (and extracts the integer value) if the C<$tuple> is an ASN
230 INTEGER32. For C<0>, this function returns a special value that is 0 but
231 true.
232
233 =item $bool = ber_is_oid $tuple, $oid_string
234
235 Returns true if the C<$tuple> represents an ASN_OBJECT_IDENTIFIER
236 that exactly matches C$oid_string>. Exmaple:
237
238 ber_is_oid $tuple, "1.3.6.1.4"
239 or die "oid must be 1.3.6.1.4";
240
241 =item $oid = ber_is_oid $tuple
242
243 Returns true (and extracts the OID string) if the C<$tuple> is an ASN
244 OBJECT IDENTIFIER. Otherwise, it returns C<undef>.
245
246 =back
247
248 =head3 CONSTRUCTION HELPERS
249
250 =over
251
252 =item $tuple = ber_i32 $value
253
254 Constructs a new C<ASN_INTEGER32> tuple.
255
256 =back
257
258 =head2 RELATIONSHIP TO L<Convert::BER> and L<Convert::ASN1>
259
260 This module is I<not> the XS version of L<Convert::BER>, but a different
261 take at doing the same thing. I imagine this module would be a good base
262 for speeding up either of these, or write a similar module, or write your
263 own LDAP or SNMP module for example.
264
265 =cut
266
267 package Convert::BER::XS;
268
269 use common::sense;
270
271 use XSLoader ();
272 use Exporter qw(import);
273
274 our $VERSION = 0.1;
275
276 XSLoader::load __PACKAGE__, $VERSION;
277
278 our %EXPORT_TAGS = (
279 const => [qw(
280 BER_CLASS BER_TAG BER_CONSTRUCTED BER_DATA
281
282 ASN_BOOLEAN ASN_INTEGER32 ASN_BIT_STRING ASN_OCTET_STRING ASN_NULL ASN_OBJECT_IDENTIFIER ASN_TAG_BER ASN_TAG_MASK
283 ASN_CONSTRUCTED ASN_UNIVERSAL ASN_APPLICATION ASN_CONTEXT ASN_PRIVATE ASN_CLASS_MASK ASN_CLASS_SHIFT
284 ASN_SEQUENCE
285
286 SNMP_IPADDRESS SNMP_COUNTER32 SNMP_UNSIGNED32 SNMP_TIMETICKS SNMP_OPAQUE SNMP_COUNTER64
287 )],
288 encode => [qw(
289 ber_decode
290 ber_is ber_is_seq ber_is_i32 ber_is_oid
291 )],
292 decode => [qw(
293 ber_encode
294 )],
295 );
296
297 our @EXPORT_OK = map @$_, values %EXPORT_TAGS;
298
299 $EXPORT_TAGS{all} = \@EXPORT_OK;
300
301 1;
302
303 =head2 BUGS / SHORTCOMINGs
304
305 This module does have a number of SNMPisms hardcoded, such as the SNMP
306 tags for Unsigned32 and so on. More configurability is needed, and, if
307 ever implemented, will come in a form similar to how L<JSON::XS> and
308 L<CBOR::XS> respresent things, namely with an object-oriented interface.
309
310 =head1 AUTHOR
311
312 Marc Lehmann <schmorp@schmorp.de>
313 http://software.schmorp.de/pkg/Convert-BER-XS
314
315 =cut
316