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

# 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     [ 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 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.4 This module implements a I<very> low level BER/DER en-/decoder.
70 root 1.1
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 root 1.4 =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 root 1.6 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 root 1.4 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 root 1.6 =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 root 1.2 =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 root 1.4 for speeding up either of these, or write a similar module, or write your
263 root 1.2 own LDAP or SNMP module for example.
264    
265 root 1.1 =cut
266    
267     package Convert::BER::XS;
268    
269     use common::sense;
270    
271     use XSLoader ();
272     use Exporter qw(import);
273    
274 root 1.5 our $VERSION = 0.1;
275 root 1.1
276     XSLoader::load __PACKAGE__, $VERSION;
277    
278     our %EXPORT_TAGS = (
279 root 1.4 const => [qw(
280 root 1.1 BER_CLASS BER_TAG BER_CONSTRUCTED BER_DATA
281 root 1.4
282 root 1.1 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 root 1.4 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 root 1.1 )],
295     );
296    
297     our @EXPORT_OK = map @$_, values %EXPORT_TAGS;
298    
299 root 1.4 $EXPORT_TAGS{all} = \@EXPORT_OK;
300    
301 root 1.1 1;
302    
303 root 1.4 =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 root 1.1 =head1 AUTHOR
311    
312     Marc Lehmann <schmorp@schmorp.de>
313     http://software.schmorp.de/pkg/Convert-BER-XS
314    
315     =cut
316