File Coverage

blib/lib/Net/OSCAR/XML.pm
Criterion Covered Total %
statement 24 177 13.5
branch 3 114 2.6
condition 0 49 0.0
subroutine 8 15 53.3
pod 0 6 0.0
total 35 361 9.7


line stmt bran cond sub pod time code
1             =pod
2              
3             =head1 NAME
4              
5             Net::OSCAR::XML -- XML functions for Net::OSCAR
6              
7             =head1 VERSION
8              
9             version 1.928
10              
11             =head1 DESCRIPTION
12              
13             We're doing the fancy-schmancy Protocol.xml stuff here, so I'll explain it here.
14              
15             Protocol.xml contains a number of "OSCAR protocol elements". One EdefineE block
16             is one OSCAR protocol elemennt.
17              
18             When the module is first loaded, Protocol.xml is parsed and two hashes are created,
19             one whose keys are the names the the elements and whose values are the contents
20             of the XML::Parser tree which represents the contents of those elements; the other
21             hash has a family/subtype tuple as a key and element names as a value.
22              
23             To do something with an element, given its name, Net::OSCAR calls C.
24             This returns a C object, which has C and C methods.
25             C takes a hash and returns a string of binary characters, and C goes the
26             other way around. The objects are cached, so C only has to do actual work once
27             for every protocol element.
28              
29             =cut
30              
31             package Net::OSCAR::XML;
32             BEGIN {
33 5     5   21548 $Net::OSCAR::XML::VERSION = '1.928';
34             }
35              
36             $REVISION = '$Revision$';
37              
38 5     5   28 use strict;
  5         8  
  5         159  
39 5     5   22 use vars qw(@ISA @EXPORT);
  5         9  
  5         270  
40 5     5   25 use Carp;
  5         8  
  5         319  
41 5     5   5437 use Data::Dumper;
  5         35763  
  5         421  
42              
43 5     5   505 use Net::OSCAR::TLV;
  5         10  
  5         227  
44 5     5   3273 use Net::OSCAR::XML::Template;
  5         15  
  5         10930  
45             our(%xmlmap, %xml_revmap, $PROTOPARSE_DEBUG, $NO_XML_CACHE);
46              
47             require Exporter;
48             @ISA = qw(Exporter);
49             @EXPORT = qw(
50             protoparse protobit_to_snac snac_to_protobit
51             );
52              
53             $PROTOPARSE_DEBUG = 0;
54             $NO_XML_CACHE = 0;
55              
56             sub _protopack($$;@);
57             sub _xmlnode_to_template($$);
58              
59             sub load_xml(;$) {
60             # Look for parsed-xml file
61 5 50   5 0 41 if(!$NO_XML_CACHE) {
62 5         24 foreach (@INC) {
63 50 50       1713 next unless -f "$_/Net/OSCAR/XML/Protocol.parsed-xml";
64              
65 0 0       0 open(XMLCACHE, "$_/Net/OSCAR/XML/Protocol.parsed-xml") or next;
66 0         0 my $xmlcache = join("", );
67 0         0 close(XMLCACHE);
68              
69 0         0 my $xmlparse;
70 0 0       0 eval $xmlcache or die "Coldn't load xml cache: $@\n";
71 0 0       0 die $@ if $@;
72 0         0 return parse_xml($xmlparse);
73             }
74             }
75              
76 5 50       17 eval {
77 5         2727 require XML::Parser;
78             } or die "Couldn't load XML::Parser ($@)\n";
79 0 0         die $@ if $@;
80              
81 0           my $xmlparser = new XML::Parser(Style => "Tree");
82              
83 0           my $xmlfile = "";
84 0 0         if($_[0]) {
85 0           $xmlfile = shift;
86             } else {
87 0           foreach (@INC) {
88 0 0         next unless -f "$_/Net/OSCAR/XML/Protocol.xml";
89 0           $xmlfile = "$_/Net/OSCAR/XML/Protocol.xml";
90 0           last;
91             }
92 0 0         croak "Couldn't find Net/OSCAR/XML/Protocol.xml in search path: " . join(" ", @INC) unless $xmlfile;
93             }
94              
95 0 0         open(XMLFILE, $xmlfile) or croak "Couldn't open $xmlfile: $!";
96 0           my $xml = join("", );
97 0           close XMLFILE;
98 0 0         my $xmlparse = $xmlparser->parse($xml) or croak "Couldn't parse XML from $xmlfile: $@";
99              
100 0           parse_xml($xmlparse);
101             }
102              
103             sub add_xml_data($) {
104 0     0 0   my $xmlparse = shift;
105              
106 0           my @tags = @{$xmlparse->[1]}; # Get contents of
  0            
107 0           shift @tags;
108 0           while(@tags) {
109 0           my($name, $value);
110 0           (undef, undef, $name, $value) = splice(@tags, 0, 4);
111 0 0 0       next unless $name and $name eq "define";
112            
113 0           my %protobit = (xml => $value);
114 0           my %attrs = %{$value->[0]};
  0            
115 0           $protobit{$_} = $attrs{$_} foreach keys %attrs;
116 0 0 0       $xml_revmap{$attrs{family}}->{$attrs{subtype}} = $attrs{name} if exists($attrs{family}) and exists($attrs{subtype});
117 0           $xmlmap{$attrs{name}} = \%protobit;
118             }
119             }
120              
121             sub parse_xml($) {
122 0     0 0   my $xmlparse = shift;
123              
124 0           %xmlmap = ();
125 0           %xml_revmap = ();
126             # We set the autovivification so that keys of xml_revmap are Net::OSCAR::TLV hashrefs.
127 0 0         if(!tied(%xml_revmap)) {
128 0           tie %xml_revmap, "Net::OSCAR::TLV", 'tie %$value, ref($self)';
129             }
130              
131 0           add_xml_data($xmlparse);
132              
133 0           return 1;
134             }
135              
136             sub _num_to_packlen($$) {
137 0     0     my($type, $order) = @_;
138 0   0       $order ||= "network";
139              
140 0 0         if($type eq "byte") {
    0          
    0          
141 0           return ("C", 1);
142             } elsif($type eq "word") {
143 0 0         if($order eq "vax") {
144 0           return ("v", 2);
145             } else {
146 0           return ("n", 2);
147             }
148             } elsif($type eq "dword") {
149 0 0         if($order eq "vax") {
150 0           return ("V", 4);
151             } else {
152 0           return ("N", 4);
153             }
154             }
155              
156 0           confess "Invalid num type: $type";
157             }
158              
159             # Specification for OSCAR protocol template:
160             # -Listref whose elements are hashrefs.
161             # -Hashrefs have following keys:
162             # type: "ref", "num", "data", or "tlvchain"
163             # If type = "num":
164             # packlet: Pack template letter (C, n, N, v, V)
165             # len: Length of datum, in bytes
166             # enum_byname: If this is an enum, map of names to values.
167             # enum_byval: If this is an enum, map of values to names.
168             # If type = "data":
169             # Arbitrary data
170             # If prefix isn't present, all available data will be gobbled.
171             # len (optional): Size of datum, in bytes
172             # null_terminated (optional): Data is terminated by a null (0x00) byte
173             # If type = "ref":
174             # name: Name of protocol bit to punt to
175             # If type = "tlvchain":
176             # subtyped: If true, this is a 'subtyped' TLV, as per Protocol.dtd.
177             # prefix: If present, "count" or "length", and "packlet" and "len" will also be present.
178             # items: Listref containing TLVs, hashrefs in format identical to these, with extra key 'num' (and 'subtype', for subtyped TLVs.)
179             # value: If present, default value of this datum.
180             # name: If present, name in parameter list that this datum gets.
181             # count: If present, number of repetitions of this datum. count==-1 represents
182             # infinite. If a count is present when unpacking, the data will be encapsulated in a listref. If the user
183             # wants to pass in multiple data when packing, they should do so via a listref. Listref-encapsulated data with
184             # too many elements for the 'count' will trigger an exception when packing.
185             # prefix: If present, either "count" or "length", and indicates that datum has a prefix indicating its length.
186             # prefix_packet, prefix_len: As per "num".
187             #
188             sub _xmlnode_to_template($$) {
189 0     0     my($tag, $value) = @_;
190              
191 0 0         confess "Invalid value in xmlnode_to_template!" unless ref($value);
192 0           my $attrs = shift @$value;
193              
194 0           my $datum = {};
195 0 0         $datum->{name} = $attrs->{name} if $attrs->{name};
196 0 0 0       $datum->{value} = "" if $attrs->{default_generate} and $attrs->{default_generate} ne "no";
197 0 0 0       $datum->{value} = $value->[1] if @$value and $value->[1] =~ /\S/;
198              
199 0 0         $datum->{count} = $attrs->{count} if $attrs->{count};
200 0 0 0       if($attrs->{count_prefix} || $attrs->{length_prefix}) {
201 0   0       my($packlet, $len) = _num_to_packlen($attrs->{count_prefix} || $attrs->{length_prefix}, $attrs->{prefix_order});
202 0           $datum->{prefix_packlet} = $packlet;
203 0           $datum->{prefix_len} = $len;
204 0 0         $datum->{prefix} = $attrs->{count_prefix} ? "count" : "length";
205             }
206              
207              
208 0 0 0       if($tag eq "ref") {
    0 0        
    0 0        
    0          
209 0           $datum->{type} = "ref";
210             } elsif($tag eq "byte" or $tag eq "word" or $tag eq "dword" or $tag eq "enum") {
211 0           $datum->{type} = "num";
212              
213 0           my $enum = 0;
214 0 0         if($tag eq "enum") {
215 0           $tag = $attrs->{type};
216 0           $enum = 1;
217             }
218              
219 0           my($packlet, $len) = _num_to_packlen($tag, $attrs->{order});
220 0           $datum->{packlet} = $packlet;
221 0           $datum->{len} = $len;
222              
223 0 0         if($enum) {
224 0           $datum->{enum_byname} = {};
225 0           $datum->{enum_byval} = {};
226              
227 0           while(@$value) {
228 0           my($subtag, $subval) = splice(@$value, 0, 2);
229 0 0         next if $subtag eq "0";
230              
231 0           my $attrs = shift @$subval;
232 0           my($name, $value, $default) = ($attrs->{name}, $attrs->{value}, $attrs->{default});
233 0           $datum->{enum_byname}->{$name} = $value;
234 0           $datum->{enum_byval}->{$value} = $name;
235 0 0         $datum->{value} = $value if $default;
236             }
237             } else {
238 0 0         $datum->{value} = $value->[1] if @$value;
239             }
240             } elsif($tag eq "data") {
241 0           $datum->{type} = "data";
242 0 0         $datum->{len} = $attrs->{length} if $attrs->{length};
243 0 0         $datum->{pad} = $attrs->{pad} if exists($attrs->{pad});
244 0 0 0       $datum->{null_terminated} = 1 if $attrs->{null_terminated} and $attrs->{null_terminated} eq "yes";
245              
246 0           while(@$value) {
247 0           my($subtag, $subval) = splice(@$value, 0, 2);
248 0 0         if($subtag eq "0") {
249 0 0 0       $datum->{value} ||= $subval if $subval =~ /\S/;
250 0           next;
251             }
252              
253 0           my $item = _xmlnode_to_template($subtag, $subval);
254 0   0       $datum->{items} ||= [];
255 0           push @{$datum->{items}}, $item;
  0            
256             }
257             } elsif($tag eq "tlvchain") {
258 0           $datum->{type} = "tlvchain";
259 0 0         $datum->{len} = $attrs->{length} if $attrs->{length};
260 0 0 0       $datum->{subtyped} = 1 if $attrs->{subtyped} and $attrs->{subtyped} eq "yes";
261              
262 0           my($subtag, $subval);
263              
264 0           while(@$value) {
265 0           my($tlvtag, $tlvval) = splice(@$value, 0, 2);
266 0 0         next if $tlvtag ne "tlv";
267 0           my $tlvattrs = shift @$tlvval;
268              
269 0           my $item = {};
270 0           $item->{type} = "data";
271 0 0         $item->{name} = $tlvattrs->{name} if $tlvattrs->{name};
272 0           $item->{num} = $tlvattrs->{type};
273 0 0         $item->{subtype} = $tlvattrs->{subtype} if $tlvattrs->{subtype};
274 0 0         $item->{count} = $tlvattrs->{count} if $tlvattrs->{count};
275 0 0 0       $item->{value} = "" if $tlvattrs->{default_generate} and $tlvattrs->{default_generate} ne "no";
276 0           $item->{items} = [];
277              
278 0           while(@$tlvval) {
279 0           my($subtag, $subval) = splice(@$tlvval, 0, 2);
280 0 0         next if $subtag eq "0";
281 0           my $tlvitem = _xmlnode_to_template($subtag, $subval);
282              
283 0           push @{$item->{items}}, $tlvitem;
  0            
284             }
285              
286              
287 0           push @{$datum->{items}}, $item;
  0            
288             }
289             }
290              
291 0           return $datum;
292             }
293              
294              
295              
296             our(%PROTOCACHE);
297             sub protoparse($$) {
298 0     0 0   my ($oscar, $wanted) = @_;
299 0 0         return $PROTOCACHE{$wanted}->set_oscar($oscar) if exists($PROTOCACHE{$wanted});
300              
301 0 0         my $xml = $xmlmap{$wanted}->{xml} or croak "Couldn't find requested protocol element '$wanted'.";
302              
303 0 0         confess "No oscar!" unless $oscar;
304              
305 0           my $attrs = shift @$xml;
306              
307 0           my @template = ();
308              
309 0           while(@$xml) {
310 0           my $tag = shift @$xml;
311 0           my $value = shift @$xml;
312 0 0         next if $tag eq "0";
313 0           push @template, _xmlnode_to_template($tag, $value);
314             }
315              
316 0 0         return @template if $PROTOPARSE_DEBUG;
317 0           my $obj = Net::OSCAR::XML::Template->new(\@template);
318 0           $PROTOCACHE{$wanted} = $obj;
319 0           return $obj->set_oscar($oscar);
320             }
321              
322              
323              
324             # Map a "protobit" (XML ) to SNAC (family => foo, subtype => bar)
325             sub protobit_to_snac($) {
326 0     0 0   my $protobit = shift;
327 0 0         confess "Unknown protobit $protobit" unless $xmlmap{$protobit};
328              
329 0           my %ret = %{$xmlmap{$protobit}};
  0            
330 0           delete $ret{xml};
331 0           return %ret;
332             }
333              
334             # Map a SNAC (family => foo, subtype => bar) to "protobit" (XML )
335             sub snac_to_protobit(%) {
336 0     0 0   my(%snac) = @_;
337 0 0 0       if($xml_revmap{$snac{family}} and $xml_revmap{$snac{family}}->{$snac{subtype}}) {
    0 0        
338 0           return $xml_revmap{$snac{family}}->{$snac{subtype}};
339             } elsif($xml_revmap{'-1'} and $xml_revmap{'-1'}->{$snac{subtype}}) {
340 0           return $xml_revmap{'-1'}->{$snac{subtype}};
341             } else {
342 0           return undef;
343             }
344             }
345              
346             1;