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

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3 root 1.4 Convert::BER::XS - I<very> low level BER en-/decoding
4 root 1.1
5     =head1 SYNOPSIS
6    
7     use Convert::BER::XS ':all';
8    
9     my $ber = ber_decode $buf
10 root 1.6 or die "unable to decode SNMP message";
11 root 1.1
12 root 1.6 # 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 root 1.3
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 root 1.6 [ ASN_CONTEXT, 4, 1, # CHOICE, constructed - trap PDU
23 root 1.3 [
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 root 1.8 [ ASN_UNIVERSAL, ASN_OBJECT_IDENTIFIER, 0, "1.3.6.1.4.1.9.9.215.1.1.8.1.2.1" ],
34 root 1.3 [ 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 root 1.1 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 root 1.3 # message is SNMP v1 or v2c?
49 root 1.1 if ($msg->[0][BER_DATA] == 0 || $msg->[0][BER_DATA] == 1) {
50    
51 root 1.3 # message is v1 trap?
52 root 1.1 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 root 1.4 # finally, let's encode it again and hope it results in the same bit pattern
64    
65     my $buf = ber_encode $ber;
66    
67 root 1.1 =head1 DESCRIPTION
68    
69 root 1.7 WARNING: Before release 1.0, the API is not considered stable in any way.
70    
71 root 1.4 This module implements a I<very> low level BER/DER en-/decoder.
72 root 1.1
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 root 1.4 =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 root 1.6 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 root 1.10 $ber->[BER_CLASS] = ASN_PRIVATE; # ERROR, CLASS/TAG/CONSTRUCTED are READ ONLY(!)
126 root 1.6
127     # but all of the following are fine:
128     $ber->[BER_DATA] = "string";
129     $ber->[BER_DATA] = [ASN_UNIVERSAL, ASN_INTEGER32, 0, 123];
130 root 1.11 @$ber = (ASN_APPLICATION, SNMP_TIMETICKS, 0, 1000);
131 root 1.6
132 root 1.4 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 root 1.7 =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 root 1.6 =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 root 1.2 =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 root 1.4 for speeding up either of these, or write a similar module, or write your
281 root 1.2 own LDAP or SNMP module for example.
282    
283 root 1.1 =cut
284    
285     package Convert::BER::XS;
286    
287     use common::sense;
288    
289     use XSLoader ();
290     use Exporter qw(import);
291    
292 root 1.7 our $VERSION = 0.2;
293 root 1.1
294     XSLoader::load __PACKAGE__, $VERSION;
295    
296     our %EXPORT_TAGS = (
297 root 1.4 const => [qw(
298 root 1.1 BER_CLASS BER_TAG BER_CONSTRUCTED BER_DATA
299 root 1.4
300 root 1.1 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 root 1.4 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 root 1.1 )],
313     );
314    
315     our @EXPORT_OK = map @$_, values %EXPORT_TAGS;
316    
317 root 1.4 $EXPORT_TAGS{all} = \@EXPORT_OK;
318    
319 root 1.1 1;
320    
321 root 1.4 =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 root 1.1 =head1 AUTHOR
329    
330     Marc Lehmann <schmorp@schmorp.de>
331     http://software.schmorp.de/pkg/Convert-BER-XS
332    
333     =cut
334