ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-FCP/FCP.pm
Revision: 1.36
Committed: Thu Dec 1 22:07:40 2005 UTC (18 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-1_0
Changes since 1.35: +12 -69 lines
Log Message:
*** empty log message ***

File Contents

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