ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/FCP.pm
Revision: 1.40
Committed: Sun Dec 23 15:42:48 2007 UTC (18 years, 8 months ago) by root
Branch: MAIN
CVS Tags: rel-1_1
Changes since 1.39: +1 -1 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.1';
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 Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
411
412 $self->{sbuf} =
413 "\x00\x00\x00\x02"
414 . (Net::FCP::touc $self->{type})
415 . "\012$attr$data";
416
417 #shutdown $fh, 1; # freenet buggy?, well, it's java...
418
419 $self->{fh} = $fh;
420
421 $self->{w} = AnyEvent->io (fh => $fh, poll => 'w', cb => sub { $self->fh_ready_w });
422
423 $self;
424 }
425
426 =item $txn = $txn->cb ($coderef)
427
428 Sets a callback to be called when the request is finished. The coderef
429 will be called with the txn as it's sole argument, so it has to call
430 C<result> itself.
431
432 Returns the txn object, useful for chaining.
433
434 Example:
435
436 $fcp->txn_client_get ("freenet:CHK....")
437 ->userdata ("ehrm")
438 ->cb(sub {
439 my $data = shift->result;
440 });
441
442 =cut
443
444 sub cb($$) {
445 my ($self, $cb) = @_;
446 $self->{cb} = $cb;
447 $self;
448 }
449
450 =item $txn = $txn->userdata ([$userdata])
451
452 Set user-specific data. This is useful in progress callbacks. The data can be accessed
453 using C<< $txn->{userdata} >>.
454
455 Returns the txn object, useful for chaining.
456
457 =cut
458
459 sub userdata($$) {
460 my ($self, $data) = @_;
461 $self->{userdata} = $data;
462 $self;
463 }
464
465 =item $txn->cancel (%attr)
466
467 Cancels the operation with a C<cancel> exception and the given attributes
468 (consider at least giving the attribute C<reason>).
469
470 UNTESTED.
471
472 =cut
473
474 sub cancel {
475 my ($self, %attr) = @_;
476 $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
477 $self->set_result;
478 $self->eof;
479 }
480
481 sub fh_ready_w {
482 my ($self) = @_;
483
484 my $len = syswrite $self->{fh}, $self->{sbuf};
485
486 if ($len > 0) {
487 substr $self->{sbuf}, 0, $len, "";
488 unless (length $self->{sbuf}) {
489 fcntl $self->{fh}, F_SETFL, 0;
490 $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub { $self->fh_ready_r });
491 }
492 } elsif (defined $len) {
493 $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
494 } else {
495 $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
496 }
497 }
498
499 sub fh_ready_r {
500 my ($self) = @_;
501
502 if (sysread $self->{fh}, $self->{buf}, 16384 + 1024, length $self->{buf}) {
503 for (;;) {
504 if ($self->{datalen}) {
505 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
506 if (length $self->{buf} >= $self->{datalen}) {
507 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
508 } else {
509 last;
510 }
511 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
512 $self->{datalen} = hex $1;
513 #warn "expecting new datachunk $self->{datalen}\n";#d#
514 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
515 $self->rcv ($1, {
516 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
517 split /\015?\012/, $2
518 });
519 } else {
520 last;
521 }
522 }
523 } else {
524 $self->eof;
525 }
526 }
527
528 sub rcv {
529 my ($self, $type, $attr) = @_;
530
531 $type = Net::FCP::tolc $type;
532
533 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
534
535 if (my $method = $self->can("rcv_$type")) {
536 $method->($self, $attr, $type);
537 } else {
538 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
539 }
540 }
541
542 # used as a default exception thrower
543 sub rcv_throw_exception {
544 my ($self, $attr, $type) = @_;
545 $self->throw (Net::FCP::Exception->new ($type, $attr));
546 }
547
548 *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
549 *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
550
551 sub throw {
552 my ($self, $exc) = @_;
553
554 $self->{exception} = $exc;
555 $self->set_result;
556 $self->eof; # must be last to avoid loops
557 }
558
559 sub set_result {
560 my ($self, $result) = @_;
561
562 unless (exists $self->{result}) {
563 $self->{result} = $result;
564 $self->{cb}->($self) if exists $self->{cb};
565 $self->{signal}->broadcast;
566 }
567 }
568
569 sub eof {
570 my ($self) = @_;
571
572 delete $self->{w};
573 delete $self->{fh};
574
575 delete $self->{fcp}{txn}{$self};
576
577 unless (exists $self->{result}) {
578 $self->throw (Net::FCP::Exception->new (short_data => {
579 reason => "unexpected eof or internal node error",
580 }));
581 }
582 }
583
584 sub progress {
585 my ($self, $type, $attr) = @_;
586
587 $self->{fcp}->progress ($self, $type, $attr);
588 }
589
590 =item $result = $txn->result
591
592 Waits until a result is available and then returns it.
593
594 This waiting is (depending on your event model) not very efficient, as it
595 is done outside the "mainloop". The biggest problem, however, is that it's
596 blocking one thread of execution. Try to use the callback mechanism, if
597 possible, and call result from within the callback (or after is has been
598 run), as then no waiting is necessary.
599
600 =cut
601
602 sub result {
603 my ($self) = @_;
604
605 $self->{signal}->wait while !exists $self->{result};
606
607 die $self->{exception} if $self->{exception};
608
609 return $self->{result};
610 }
611
612 package Net::FCP::Txn::ClientHello;
613
614 use base Net::FCP::Txn;
615
616 sub rcv_node_hello {
617 my ($self, $attr) = @_;
618
619 $self->set_result ($attr);
620 }
621
622 package Net::FCP::Txn::ClientInfo;
623
624 use base Net::FCP::Txn;
625
626 sub rcv_node_info {
627 my ($self, $attr) = @_;
628
629 $self->set_result ($attr);
630 }
631
632 package Net::FCP::Txn::GenerateCHK;
633
634 use base Net::FCP::Txn;
635
636 sub rcv_success {
637 my ($self, $attr) = @_;
638
639 $self->set_result ($attr->{uri});
640 }
641
642 package Net::FCP::Txn::GenerateSVKPair;
643
644 use base Net::FCP::Txn;
645
646 sub rcv_success {
647 my ($self, $attr) = @_;
648 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
649 }
650
651 package Net::FCP::Txn::InvertPrivateKey;
652
653 use base Net::FCP::Txn;
654
655 sub rcv_success {
656 my ($self, $attr) = @_;
657 $self->set_result ($attr->{public_key});
658 }
659
660 package Net::FCP::Txn::GetSize;
661
662 use base Net::FCP::Txn;
663
664 sub rcv_success {
665 my ($self, $attr) = @_;
666 $self->set_result (hex $attr->{length});
667 }
668
669 package Net::FCP::Txn::GetPut;
670
671 # base class for get and put
672
673 use base Net::FCP::Txn;
674
675 *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
676 *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
677
678 sub rcv_restarted {
679 my ($self, $attr, $type) = @_;
680
681 delete $self->{datalength};
682 delete $self->{metalength};
683 delete $self->{data};
684
685 $self->progress ($type, $attr);
686 }
687
688 package Net::FCP::Txn::ClientGet;
689
690 use base Net::FCP::Txn::GetPut;
691
692 *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
693
694 sub rcv_data {
695 my ($self, $chunk) = @_;
696
697 $self->{data} .= $chunk;
698
699 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
700
701 if ($self->{datalength} == length $self->{data}) {
702 my $data = delete $self->{data};
703 my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
704
705 $self->set_result ([$meta, $data]);
706 $self->eof;
707 }
708 }
709
710 sub rcv_data_found {
711 my ($self, $attr, $type) = @_;
712
713 $self->progress ($type, $attr);
714
715 $self->{datalength} = hex $attr->{data_length};
716 $self->{metalength} = hex $attr->{metadata_length};
717 }
718
719 package Net::FCP::Txn::ClientPut;
720
721 use base Net::FCP::Txn::GetPut;
722
723 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
724
725 sub rcv_pending {
726 my ($self, $attr, $type) = @_;
727 $self->progress ($type, $attr);
728 }
729
730 sub rcv_success {
731 my ($self, $attr, $type) = @_;
732 $self->set_result ($attr);
733 }
734
735 sub rcv_key_collision {
736 my ($self, $attr, $type) = @_;
737 $self->set_result ({ key_collision => 1, %$attr });
738 }
739
740 =back
741
742 =head2 The Net::FCP::Exception CLASS
743
744 Any unexpected (non-standard) responses that make it impossible to return
745 the advertised result will result in an exception being thrown when the
746 C<result> method is called.
747
748 These exceptions are represented by objects of this class.
749
750 =over 4
751
752 =cut
753
754 package Net::FCP::Exception;
755
756 use overload
757 '""' => sub {
758 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
759 };
760
761 =item $exc = new Net::FCP::Exception $type, \%attr
762
763 Create a new exception object of the given type (a string like
764 C<route_not_found>), and a hashref containing additional attributes
765 (usually the attributes of the message causing the exception).
766
767 =cut
768
769 sub new {
770 my ($class, $type, $attr) = @_;
771
772 bless [Net::FCP::tolc $type, { %$attr }], $class;
773 }
774
775 =item $exc->type([$type])
776
777 With no arguments, returns the exception type. Otherwise a boolean
778 indicating wether the exception is of the given type is returned.
779
780 =cut
781
782 sub type {
783 my ($self, $type) = @_;
784
785 @_ >= 2
786 ? $self->[0] eq $type
787 : $self->[0];
788 }
789
790 =item $exc->attr([$attr])
791
792 With no arguments, returns the attributes. Otherwise the named attribute
793 value is returned.
794
795 =cut
796
797 sub attr {
798 my ($self, $attr) = @_;
799
800 @_ >= 2
801 ? $self->[1]{$attr}
802 : $self->[1];
803 }
804
805 =back
806
807 =head1 SEE ALSO
808
809 L<http://freenet.sf.net>.
810
811 =head1 BUGS
812
813 =head1 AUTHOR
814
815 Marc Lehmann <schmorp@schmorp.de>
816 http://home.schmorp.de/
817
818 =cut
819
820 1
821