ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-FCP/FCP.pm
Revision: 1.1
Committed: Sat Jul 18 05:57:59 2009 UTC (14 years, 10 months ago) by root
Branch: MAIN
Log Message:
riddify us of meta.yml garbage in manifest

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::FCP - freenet client protocol 2.0
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent::FCP;
8    
9     my $fcp = new AnyEvent::FCP;
10    
11     my $ni = $fcp->txn_node_info->result;
12     my $ni = $fcp->node_info;
13    
14     =head1 DESCRIPTION
15    
16     This module implements the freenet client protocol version 2.0, as used by
17     freenet 0.7. See L<Net::FCP> for the earlier freenet 0.5 version.
18    
19     See L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0> for a description
20     of what the messages do.
21    
22     The module uses L<AnyEvent> to find a suitable event module.
23    
24     =head2 IMPORT TAGS
25    
26     Nothing much can be "imported" from this module right now.
27    
28     =head2 FREENET BASICS
29    
30     Ok, this section will not explain any freenet basics to you, just some
31     problems I found that you might want to avoid:
32    
33     =over 4
34    
35     =item freenet URIs are _NOT_ URIs
36    
37     Whenever a "uri" is required by the protocol, freenet expects a kind of
38     URI prefixed with the "freenet:" scheme, e.g. "freenet:CHK...". However,
39     these are not URIs, as freeent fails to parse them correctly, that is, you
40     must unescape an escaped characters ("%2c" => ",") yourself. Maybe in the
41     future this library will do it for you, so watch out for this incompatible
42     change.
43    
44     =back
45    
46     =head2 THE AnyEvent::FCP CLASS
47    
48     =over 4
49    
50     =cut
51    
52     package AnyEvent::FCP;
53    
54     use Carp;
55    
56     $VERSION = '0.1';
57    
58     no warnings;
59    
60     use AnyEvent;
61     use AnyEvent::Socket;
62    
63     sub touc($) {
64     local $_ = shift;
65     1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/;
66     s/(?:^|_)(.)/\U$1/g;
67     $_
68     }
69    
70     sub tolc($) {
71     local $_ = shift;
72     1 while s/(SVK|CHK|URI)([^_])/$1\_$2/i;
73     1 while s/([^_])(SVK|CHK|URI)/$1\_$2/i;
74     s/(?<=[a-z])(?=[A-Z])/_/g;
75     lc
76     }
77    
78     =item $fcp = new AnyEvent::FCP [host => $host][, port => $port][, progress => \&cb][, name => $name]
79    
80     Create a new FCP connection to the given host and port (default
81     127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
82    
83     If no C<name> was specified, then AnyEvent::FCP will generate a (hopefully)
84     unique client name for you.
85    
86     #TODO
87     #You can install a progress callback that is being called with the Net::FCP
88     #object, a txn object, the type of the transaction and the attributes. Use
89     #it like this:
90     #
91     # sub progress_cb {
92     # my ($self, $txn, $type, $attr) = @_;
93     #
94     # warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
95     # }
96    
97     =cut
98    
99     sub new {
100     my $class = shift;
101     my $self = bless { @_ }, $class;
102    
103     $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
104     $self->{port} ||= $ENV{FREDPORT} || 9481;
105     $self->{name} ||= time.rand.rand.rand; # lame
106    
107     $self->{conn} = new AnyEvent::Socket
108     PeerAddr => "$self->{host}:$self->{port}",
109     on_eof => $self->{on_eof} || sub { },
110    
111     $self
112     }
113    
114     sub progress {
115     my ($self, $txn, $type, $attr) = @_;
116    
117     $self->{progress}->($self, $txn, $type, $attr)
118     if $self->{progress};
119     }
120    
121     =item $txn = $fcp->txn (type => attr => val,...)
122    
123     The low-level interface to transactions. Don't use it unless you have
124     "special needs". Instead, use predefiend transactions like this:
125    
126     The blocking case, no (visible) transactions involved:
127    
128     my $nodehello = $fcp->client_hello;
129    
130     A transaction used in a blocking fashion:
131    
132     my $txn = $fcp->txn_client_hello;
133     ...
134     my $nodehello = $txn->result;
135    
136     Or shorter:
137    
138     my $nodehello = $fcp->txn_client_hello->result;
139    
140     Setting callbacks:
141    
142     $fcp->txn_client_hello->cb(
143     sub { my $nodehello => $_[0]->result }
144     );
145    
146     =cut
147    
148     sub txn {
149     my ($self, $type, %attr) = @_;
150    
151     $type = touc $type;
152    
153     my $txn = "Net::FCP::Txn::$type"->new (fcp => $self, type => tolc $type, attr => \%attr);
154    
155     $txn;
156     }
157    
158     { # transactions
159    
160     my $txn = sub {
161     my ($name, $sub) = @_;
162     *{"txn_$name"} = $sub;
163     *{$name} = sub { $sub->(@_)->result };
164     };
165    
166     =item $txn = $fcp->txn_client_hello
167    
168     =item $nodehello = $fcp->client_hello
169    
170     Executes a ClientHello request and returns it's results.
171    
172     {
173     max_file_size => "5f5e100",
174     node => "Fred,0.6,1.46,7050"
175     protocol => "1.2",
176     }
177    
178     =cut
179    
180     $txn->(client_hello => sub {
181     my ($self) = @_;
182    
183     $self->txn ("client_hello");
184     });
185    
186     =item $txn = $fcp->txn_client_info
187    
188     =item $nodeinfo = $fcp->client_info
189    
190     Executes a ClientInfo request and returns it's results.
191    
192     {
193     active_jobs => "1f",
194     allocated_memory => "bde0000",
195     architecture => "i386",
196     available_threads => 17,
197     datastore_free => "5ce03400",
198     datastore_max => "2540be400",
199     datastore_used => "1f72bb000",
200     estimated_load => 52,
201     free_memory => "5cc0148",
202     is_transient => "false",
203     java_name => "Java HotSpot(_T_M) Server VM",
204     java_vendor => "http://www.blackdown.org/",
205     java_version => "Blackdown-1.4.1-01",
206     least_recent_timestamp => "f41538b878",
207     max_file_size => "5f5e100",
208     most_recent_timestamp => "f77e2cc520"
209     node_address => "1.2.3.4",
210     node_port => 369,
211     operating_system => "Linux",
212     operating_system_version => "2.4.20",
213     routing_time => "a5",
214     }
215    
216     =cut
217    
218     $txn->(client_info => sub {
219     my ($self) = @_;
220    
221     $self->txn ("client_info");
222     });
223    
224     =item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
225    
226     =item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
227    
228     Calculates a CHK, given the metadata and data. C<$cipher> is either
229     C<Rijndael> or C<Twofish>, with the latter being the default.
230    
231     =cut
232    
233     $txn->(generate_chk => sub {
234     my ($self, $metadata, $data, $cipher) = @_;
235    
236     $metadata = Net::FCP::Metadata::build_metadata $metadata;
237    
238     $self->txn (generate_chk =>
239     data => "$metadata$data",
240     metadata_length => xeh length $metadata,
241     cipher => $cipher || "Twofish");
242     });
243    
244     =item $txn = $fcp->txn_generate_svk_pair
245    
246     =item ($public, $private, $crypto) = @{ $fcp->generate_svk_pair }
247    
248     Creates a new SVK pair. Returns an arrayref with the public key, the
249     private key and a crypto key, which is just additional entropy.
250    
251     [
252     "acLx4dux9fvvABH15Gk6~d3I-yw",
253     "cPoDkDMXDGSMM32plaPZDhJDxSs",
254     "BH7LXCov0w51-y9i~BoB3g",
255     ]
256    
257     A private key (for inserting) can be constructed like this:
258    
259     SSK@<private_key>,<crypto_key>/<name>
260    
261     It can be used to insert data. The corresponding public key looks like this:
262    
263     SSK@<public_key>PAgM,<crypto_key>/<name>
264    
265     Watch out for the C<PAgM>-part!
266    
267     =cut
268    
269     $txn->(generate_svk_pair => sub {
270     my ($self) = @_;
271    
272     $self->txn ("generate_svk_pair");
273     });
274    
275     =item $txn = $fcp->txn_invert_private_key ($private)
276    
277     =item $public = $fcp->invert_private_key ($private)
278    
279     Inverts a private key (returns the public key). C<$private> can be either
280     an insert URI (must start with C<freenet:SSK@>) or a raw private key (i.e.
281     the private value you get back from C<generate_svk_pair>).
282    
283     Returns the public key.
284    
285     =cut
286    
287     $txn->(invert_private_key => sub {
288     my ($self, $privkey) = @_;
289    
290     $self->txn (invert_private_key => private => $privkey);
291     });
292    
293     =item $txn = $fcp->txn_get_size ($uri)
294    
295     =item $length = $fcp->get_size ($uri)
296    
297     Finds and returns the size (rounded up to the nearest power of two) of the
298     given document.
299    
300     =cut
301    
302     $txn->(get_size => sub {
303     my ($self, $uri) = @_;
304    
305     $self->txn (get_size => URI => $uri);
306     });
307    
308     =item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
309    
310     =item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
311    
312     Fetches a (small, as it should fit into memory) key content block from
313     freenet. C<$meta> is a C<Net::FCP::Metadata> object or C<undef>).
314    
315     The C<$uri> should begin with C<freenet:>, but the scheme is currently
316     added, if missing.
317    
318     my ($meta, $data) = @{
319     $fcp->client_get (
320     "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
321     )
322     };
323    
324     =cut
325    
326     $txn->(client_get => sub {
327     my ($self, $uri, $htl, $removelocal) = @_;
328    
329     $uri =~ s/^freenet://; $uri = "freenet:$uri";
330    
331     $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
332     remove_local_key => $removelocal ? "true" : "false");
333     });
334    
335     =item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
336    
337     =item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
338    
339     Insert a new key. If the client is inserting a CHK, the URI may be
340     abbreviated as just CHK@. In this case, the node will calculate the
341     CHK. If the key is a private SSK key, the node will calculcate the public
342     key and the resulting public URI.
343    
344     C<$meta> can be a hash reference (same format as returned by
345     C<Net::FCP::parse_metadata>) or a string.
346    
347     The result is an arrayref with the keys C<uri>, C<public_key> and C<private_key>.
348    
349     =cut
350    
351     $txn->(client_put => sub {
352     my ($self, $uri, $metadata, $data, $htl, $removelocal) = @_;
353    
354     $metadata = Net::FCP::Metadata::build_metadata $metadata;
355     $uri =~ s/^freenet://; $uri = "freenet:$uri";
356    
357     $self->txn (client_put => URI => $uri,
358     hops_to_live => xeh (defined $htl ? $htl : 15),
359     remove_local_key => $removelocal ? "true" : "false",
360     data => "$metadata$data", metadata_length => xeh length $metadata);
361     });
362    
363     } # transactions
364    
365     =back
366    
367     =head2 THE Net::FCP::Txn CLASS
368    
369     All requests (or transactions) are executed in a asynchronous way. For
370     each request, a C<Net::FCP::Txn> object is created (worse: a tcp
371     connection is created, too).
372    
373     For each request there is actually a different subclass (and it's possible
374     to subclass these, although of course not documented).
375    
376     The most interesting method is C<result>.
377    
378     =over 4
379    
380     =cut
381    
382     package Net::FCP::Txn;
383    
384     use Fcntl;
385     use Socket;
386    
387     =item new arg => val,...
388    
389     Creates a new C<Net::FCP::Txn> object. Not normally used.
390    
391     =cut
392    
393     sub new {
394     my $class = shift;
395     my $self = bless { @_ }, $class;
396    
397     $self->{signal} = AnyEvent->condvar;
398    
399     $self->{fcp}{txn}{$self} = $self;
400    
401     my $attr = "";
402     my $data = delete $self->{attr}{data};
403    
404     while (my ($k, $v) = each %{$self->{attr}}) {
405     $attr .= (Net::FCP::touc $k) . "=$v\012"
406     }
407    
408     if (defined $data) {
409     $attr .= sprintf "DataLength=%x\012", length $data;
410     $data = "Data\012$data";
411     } else {
412     $data = "EndMessage\012";
413     }
414    
415     socket my $fh, PF_INET, SOCK_STREAM, 0
416     or Carp::croak "unable to create new tcp socket: $!";
417     binmode $fh, ":raw";
418     fcntl $fh, F_SETFL, O_NONBLOCK;
419     connect $fh, (sockaddr_in $self->{fcp}{port}, inet_aton $self->{fcp}{host});
420     # and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
421    
422     $self->{sbuf} =
423     "\x00\x00\x00\x02"
424     . (Net::FCP::touc $self->{type})
425     . "\012$attr$data";
426    
427     #shutdown $fh, 1; # freenet buggy?, well, it's java...
428    
429     $self->{fh} = $fh;
430    
431     $self->{w} = AnyEvent->io (fh => $fh, poll => 'w', cb => sub { $self->fh_ready_w });
432    
433     $self;
434     }
435    
436     =item $txn = $txn->cb ($coderef)
437    
438     Sets a callback to be called when the request is finished. The coderef
439     will be called with the txn as it's sole argument, so it has to call
440     C<result> itself.
441    
442     Returns the txn object, useful for chaining.
443    
444     Example:
445    
446     $fcp->txn_client_get ("freenet:CHK....")
447     ->userdata ("ehrm")
448     ->cb(sub {
449     my $data = shift->result;
450     });
451    
452     =cut
453    
454     sub cb($$) {
455     my ($self, $cb) = @_;
456     $self->{cb} = $cb;
457     $self;
458     }
459    
460     =item $txn = $txn->userdata ([$userdata])
461    
462     Set user-specific data. This is useful in progress callbacks. The data can be accessed
463     using C<< $txn->{userdata} >>.
464    
465     Returns the txn object, useful for chaining.
466    
467     =cut
468    
469     sub userdata($$) {
470     my ($self, $data) = @_;
471     $self->{userdata} = $data;
472     $self;
473     }
474    
475     =item $txn->cancel (%attr)
476    
477     Cancels the operation with a C<cancel> exception and the given attributes
478     (consider at least giving the attribute C<reason>).
479    
480     UNTESTED.
481    
482     =cut
483    
484     sub cancel {
485     my ($self, %attr) = @_;
486     $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
487     $self->set_result;
488     $self->eof;
489     }
490    
491     sub fh_ready_w {
492     my ($self) = @_;
493    
494     my $len = syswrite $self->{fh}, $self->{sbuf};
495    
496     if ($len > 0) {
497     substr $self->{sbuf}, 0, $len, "";
498     unless (length $self->{sbuf}) {
499     fcntl $self->{fh}, F_SETFL, 0;
500     $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub { $self->fh_ready_r });
501     }
502     } elsif (defined $len) {
503     $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
504     } else {
505     $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
506     }
507     }
508    
509     sub fh_ready_r {
510     my ($self) = @_;
511    
512     if (sysread $self->{fh}, $self->{buf}, 16384 + 1024, length $self->{buf}) {
513     for (;;) {
514     if ($self->{datalen}) {
515     #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
516     if (length $self->{buf} >= $self->{datalen}) {
517     $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
518     } else {
519     last;
520     }
521     } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
522     $self->{datalen} = hex $1;
523     #warn "expecting new datachunk $self->{datalen}\n";#d#
524     } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
525     $self->rcv ($1, {
526     map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
527     split /\015?\012/, $2
528     });
529     } else {
530     last;
531     }
532     }
533     } else {
534     $self->eof;
535     }
536     }
537    
538     sub rcv {
539     my ($self, $type, $attr) = @_;
540    
541     $type = Net::FCP::tolc $type;
542    
543     #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
544    
545     if (my $method = $self->can("rcv_$type")) {
546     $method->($self, $attr, $type);
547     } else {
548     warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
549     }
550     }
551    
552     # used as a default exception thrower
553     sub rcv_throw_exception {
554     my ($self, $attr, $type) = @_;
555     $self->throw (Net::FCP::Exception->new ($type, $attr));
556     }
557    
558     *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
559     *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
560    
561     sub throw {
562     my ($self, $exc) = @_;
563    
564     $self->{exception} = $exc;
565     $self->set_result;
566     $self->eof; # must be last to avoid loops
567     }
568    
569     sub set_result {
570     my ($self, $result) = @_;
571    
572     unless (exists $self->{result}) {
573     $self->{result} = $result;
574     $self->{cb}->($self) if exists $self->{cb};
575     $self->{signal}->broadcast;
576     }
577     }
578    
579     sub eof {
580     my ($self) = @_;
581    
582     delete $self->{w};
583     delete $self->{fh};
584    
585     delete $self->{fcp}{txn}{$self};
586    
587     unless (exists $self->{result}) {
588     $self->throw (Net::FCP::Exception->new (short_data => {
589     reason => "unexpected eof or internal node error",
590     }));
591     }
592     }
593    
594     sub progress {
595     my ($self, $type, $attr) = @_;
596    
597     $self->{fcp}->progress ($self, $type, $attr);
598     }
599    
600     =item $result = $txn->result
601    
602     Waits until a result is available and then returns it.
603    
604     This waiting is (depending on your event model) not very efficient, as it
605     is done outside the "mainloop". The biggest problem, however, is that it's
606     blocking one thread of execution. Try to use the callback mechanism, if
607     possible, and call result from within the callback (or after is has been
608     run), as then no waiting is necessary.
609    
610     =cut
611    
612     sub result {
613     my ($self) = @_;
614    
615     $self->{signal}->wait while !exists $self->{result};
616    
617     die $self->{exception} if $self->{exception};
618    
619     return $self->{result};
620     }
621    
622     package Net::FCP::Txn::ClientHello;
623    
624     use base Net::FCP::Txn;
625    
626     sub rcv_node_hello {
627     my ($self, $attr) = @_;
628    
629     $self->set_result ($attr);
630     }
631    
632     package Net::FCP::Txn::ClientInfo;
633    
634     use base Net::FCP::Txn;
635    
636     sub rcv_node_info {
637     my ($self, $attr) = @_;
638    
639     $self->set_result ($attr);
640     }
641    
642     package Net::FCP::Txn::GenerateCHK;
643    
644     use base Net::FCP::Txn;
645    
646     sub rcv_success {
647     my ($self, $attr) = @_;
648    
649     $self->set_result ($attr->{uri});
650     }
651    
652     package Net::FCP::Txn::GenerateSVKPair;
653    
654     use base Net::FCP::Txn;
655    
656     sub rcv_success {
657     my ($self, $attr) = @_;
658     $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
659     }
660    
661     package Net::FCP::Txn::InvertPrivateKey;
662    
663     use base Net::FCP::Txn;
664    
665     sub rcv_success {
666     my ($self, $attr) = @_;
667     $self->set_result ($attr->{public_key});
668     }
669    
670     package Net::FCP::Txn::GetSize;
671    
672     use base Net::FCP::Txn;
673    
674     sub rcv_success {
675     my ($self, $attr) = @_;
676     $self->set_result (hex $attr->{length});
677     }
678    
679     package Net::FCP::Txn::GetPut;
680    
681     # base class for get and put
682    
683     use base Net::FCP::Txn;
684    
685     *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
686     *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
687    
688     sub rcv_restarted {
689     my ($self, $attr, $type) = @_;
690    
691     delete $self->{datalength};
692     delete $self->{metalength};
693     delete $self->{data};
694    
695     $self->progress ($type, $attr);
696     }
697    
698     package Net::FCP::Txn::ClientGet;
699    
700     use base Net::FCP::Txn::GetPut;
701    
702     *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
703    
704     sub rcv_data {
705     my ($self, $chunk) = @_;
706    
707     $self->{data} .= $chunk;
708    
709     $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
710    
711     if ($self->{datalength} == length $self->{data}) {
712     my $data = delete $self->{data};
713     my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
714    
715     $self->set_result ([$meta, $data]);
716     $self->eof;
717     }
718     }
719    
720     sub rcv_data_found {
721     my ($self, $attr, $type) = @_;
722    
723     $self->progress ($type, $attr);
724    
725     $self->{datalength} = hex $attr->{data_length};
726     $self->{metalength} = hex $attr->{metadata_length};
727     }
728    
729     package Net::FCP::Txn::ClientPut;
730    
731     use base Net::FCP::Txn::GetPut;
732    
733     *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
734    
735     sub rcv_pending {
736     my ($self, $attr, $type) = @_;
737     $self->progress ($type, $attr);
738     }
739    
740     sub rcv_success {
741     my ($self, $attr, $type) = @_;
742     $self->set_result ($attr);
743     }
744    
745     sub rcv_key_collision {
746     my ($self, $attr, $type) = @_;
747     $self->set_result ({ key_collision => 1, %$attr });
748     }
749    
750     =back
751    
752     =head2 The Net::FCP::Exception CLASS
753    
754     Any unexpected (non-standard) responses that make it impossible to return
755     the advertised result will result in an exception being thrown when the
756     C<result> method is called.
757    
758     These exceptions are represented by objects of this class.
759    
760     =over 4
761    
762     =cut
763    
764     package Net::FCP::Exception;
765    
766     use overload
767     '""' => sub {
768     "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
769     };
770    
771     =item $exc = new Net::FCP::Exception $type, \%attr
772    
773     Create a new exception object of the given type (a string like
774     C<route_not_found>), and a hashref containing additional attributes
775     (usually the attributes of the message causing the exception).
776    
777     =cut
778    
779     sub new {
780     my ($class, $type, $attr) = @_;
781    
782     bless [Net::FCP::tolc $type, { %$attr }], $class;
783     }
784    
785     =item $exc->type([$type])
786    
787     With no arguments, returns the exception type. Otherwise a boolean
788     indicating wether the exception is of the given type is returned.
789    
790     =cut
791    
792     sub type {
793     my ($self, $type) = @_;
794    
795     @_ >= 2
796     ? $self->[0] eq $type
797     : $self->[0];
798     }
799    
800     =item $exc->attr([$attr])
801    
802     With no arguments, returns the attributes. Otherwise the named attribute
803     value is returned.
804    
805     =cut
806    
807     sub attr {
808     my ($self, $attr) = @_;
809    
810     @_ >= 2
811     ? $self->[1]{$attr}
812     : $self->[1];
813     }
814    
815     =back
816    
817     =head1 SEE ALSO
818    
819     L<http://freenet.sf.net>.
820    
821     =head1 BUGS
822    
823     =head1 AUTHOR
824    
825     Marc Lehmann <schmorp@schmorp.de>
826     http://home.schmorp.de/
827    
828     =cut
829    
830     1
831