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