1 |
root |
1.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 |
|
|
=head1 WARNING |
20 |
|
|
|
21 |
|
|
This module is alpha. While it probably won't destroy (much :) of your |
22 |
|
|
data, it currently works only with the Event module (alkthough the event |
23 |
|
|
mechanism is fully pluggable). |
24 |
|
|
|
25 |
|
|
=head2 THE Net::FCP CLASS |
26 |
|
|
|
27 |
|
|
=over 4 |
28 |
|
|
|
29 |
|
|
=cut |
30 |
|
|
|
31 |
|
|
package Net::FCP; |
32 |
|
|
|
33 |
|
|
use Carp; |
34 |
|
|
use IO::Socket::INET; |
35 |
|
|
|
36 |
|
|
$VERSION = 0.01; |
37 |
|
|
|
38 |
|
|
sub event_reg_cb { |
39 |
|
|
my ($obj) = @_; |
40 |
|
|
require Event; |
41 |
|
|
|
42 |
|
|
$obj->{eventdata} = Event->io ( |
43 |
|
|
fd => $obj->{fh}, |
44 |
|
|
poll => 'r', |
45 |
|
|
cb => sub { |
46 |
|
|
$obj->fh_ready; |
47 |
|
|
}, |
48 |
|
|
); |
49 |
|
|
} |
50 |
|
|
|
51 |
|
|
sub event_unreg_cb { |
52 |
|
|
$_[0]{eventdata} |
53 |
|
|
and (delete $_[0]{eventdata})->cancel; |
54 |
|
|
} |
55 |
|
|
|
56 |
|
|
sub event_wait_cb { |
57 |
|
|
Event::one_event(); |
58 |
|
|
} |
59 |
|
|
|
60 |
|
|
$regcb = \&event_reg_cb; |
61 |
|
|
$unregcb = \&event_unreg_cb; |
62 |
|
|
$waitcb = \&event_wait_cb; |
63 |
|
|
|
64 |
|
|
=item $fcp = new Net::FCP [host => $host][, port => $port] |
65 |
|
|
|
66 |
|
|
Create a new virtual FCP connection to the given host and port (default |
67 |
|
|
127.0.0.1:8481). |
68 |
|
|
|
69 |
|
|
Connections are virtual because no persistent physical connection is |
70 |
|
|
established. However, the existance of the node is checked by executing a |
71 |
|
|
C<ClientHello> transaction. |
72 |
|
|
|
73 |
|
|
=cut |
74 |
|
|
|
75 |
|
|
sub new { |
76 |
|
|
my $class = shift; |
77 |
|
|
my $self = bless { @_ }, $class; |
78 |
|
|
|
79 |
|
|
$self->{host} ||= "127.0.0.1"; |
80 |
|
|
$self->{port} ||= 8481; |
81 |
|
|
|
82 |
|
|
$self->{nodehello} = $self->txn("ClientHello")->result |
83 |
|
|
or croak "unable to get nodehello from node\n"; |
84 |
|
|
|
85 |
|
|
$self; |
86 |
|
|
} |
87 |
|
|
|
88 |
|
|
=item $txn = $fcp->txn(type => attr => val,...) |
89 |
|
|
|
90 |
|
|
The low-level interface to transactions. Don't use it. |
91 |
|
|
|
92 |
|
|
=cut |
93 |
|
|
|
94 |
|
|
sub txn { |
95 |
|
|
my ($self, $type, %attr) = @_; |
96 |
|
|
|
97 |
|
|
my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => $type, attr => \%attr); |
98 |
|
|
|
99 |
|
|
$txn; |
100 |
|
|
} |
101 |
|
|
|
102 |
|
|
sub _txn($&) { |
103 |
|
|
my ($name, $sub) = @_; |
104 |
|
|
*{"$name\_txn"} = $sub; |
105 |
|
|
*{$name} = sub { $sub->(@_)->result }; |
106 |
|
|
} |
107 |
|
|
|
108 |
|
|
=item $txn = $fcp->txn_client_hello |
109 |
|
|
|
110 |
|
|
=item $nodehello = $fcp->client_hello |
111 |
|
|
|
112 |
|
|
Executes a ClientHello request and returns it's results. |
113 |
|
|
|
114 |
|
|
{ |
115 |
|
|
MaxFileSize => "5f5e100", |
116 |
|
|
Protocol => "1.2", |
117 |
|
|
Node => "Fred,0.6,1.46,7050" |
118 |
|
|
} |
119 |
|
|
|
120 |
|
|
=cut |
121 |
|
|
|
122 |
|
|
_txn client_hello => sub { |
123 |
|
|
my ($self) = @_; |
124 |
|
|
|
125 |
|
|
$self->txn ("ClientHello"); |
126 |
|
|
}; |
127 |
|
|
|
128 |
|
|
=item $txn = $fcp->txn_client_info |
129 |
|
|
|
130 |
|
|
=item $nodeinfo = $fcp->client_info |
131 |
|
|
|
132 |
|
|
Executes a ClientInfo request and returns it's results. |
133 |
|
|
|
134 |
|
|
{ |
135 |
|
|
MaxFileSize => "5f5e100", |
136 |
|
|
DatastoreMax => "2540be400", |
137 |
|
|
NodePort => 369, |
138 |
|
|
JavaName => "Java HotSpot(TM) Server VM", |
139 |
|
|
OperatingSystemVersion => "2.4.20", |
140 |
|
|
EstimatedLoad => 52, |
141 |
|
|
FreeMemory => "5cc0148", |
142 |
|
|
DatastoreFree => "5ce03400", |
143 |
|
|
NodeAddress => "1.2.3.4", |
144 |
|
|
ActiveJobs => "1f", |
145 |
|
|
AllocatedMemory => "bde0000", |
146 |
|
|
Architecture => "i386", |
147 |
|
|
RoutingTime => "a5", |
148 |
|
|
LeastRecentTimestamp => "f41538b878", |
149 |
|
|
AvailableThreads => 17, |
150 |
|
|
DatastoreUsed => "1f72bb000", |
151 |
|
|
JavaVersion => "Blackdown-1.4.1-01", |
152 |
|
|
IsTransient => "false", |
153 |
|
|
OperatingSystem => "Linux", |
154 |
|
|
JavaVendor => "http://www.blackdown.org/", |
155 |
|
|
MostRecentTimestamp => "f77e2cc520" |
156 |
|
|
} |
157 |
|
|
|
158 |
|
|
=cut |
159 |
|
|
|
160 |
|
|
_txn client_info => sub { |
161 |
|
|
my ($self) = @_; |
162 |
|
|
|
163 |
|
|
$self->txn ("ClientInfo"); |
164 |
|
|
}; |
165 |
|
|
|
166 |
|
|
=item $txn = $fcp->txn_generate_chk ($metadata, $data) |
167 |
|
|
|
168 |
|
|
=item $uri = $fcp->generate_chk ($metadata, $data) |
169 |
|
|
|
170 |
|
|
Creates a new CHK, given the metadata and data. UNTESTED. |
171 |
|
|
|
172 |
|
|
=cut |
173 |
|
|
|
174 |
|
|
_txn generate_chk => sub { |
175 |
|
|
my ($self, $metadata, $data) = @_; |
176 |
|
|
|
177 |
|
|
$self->txn (GenerateCHK => data => "$data$metadata", MetaDataLength => length $metadata); |
178 |
|
|
}; |
179 |
|
|
|
180 |
|
|
=item $txn = $fcp->txn_generate_svk_pair |
181 |
|
|
|
182 |
|
|
=item ($public, $private) = @{ $fcp->generate_svk_pair } |
183 |
|
|
|
184 |
|
|
Creates a new SVK pair. Returns an arrayref. |
185 |
|
|
|
186 |
|
|
[ |
187 |
|
|
"hKs0-WDQA4pVZyMPKNFsK1zapWY", |
188 |
|
|
"ZnmvMITaTXBMFGl4~jrjuyWxOWg" |
189 |
|
|
] |
190 |
|
|
|
191 |
|
|
=cut |
192 |
|
|
|
193 |
|
|
_txn generate_svk_pair => sub { |
194 |
|
|
my ($self) = @_; |
195 |
|
|
|
196 |
|
|
$self->txn ("GenerateSVKPair"); |
197 |
|
|
}; |
198 |
|
|
|
199 |
|
|
=item $txn = $fcp->txn_insert_private_key ($private) |
200 |
|
|
|
201 |
|
|
=item $uri = $fcp->insert_private_key ($private) |
202 |
|
|
|
203 |
|
|
Inserts a private key. $private can be either an insert URI (must start |
204 |
|
|
with freenet:SSK@) or a raw private key (i.e. the private value you get back |
205 |
|
|
from C<generate_svk_pair>). |
206 |
|
|
|
207 |
|
|
Returns the public key. |
208 |
|
|
|
209 |
|
|
UNTESTED. |
210 |
|
|
|
211 |
|
|
=cut |
212 |
|
|
|
213 |
|
|
_txn insert_private_key => sub { |
214 |
|
|
my ($self, $privkey) = @_; |
215 |
|
|
|
216 |
|
|
$self->txn (InvertPrivateKey => Private => $privkey); |
217 |
|
|
}; |
218 |
|
|
|
219 |
|
|
=item $txn = $fcp->txn_get_size ($uri) |
220 |
|
|
|
221 |
|
|
=item $length = $fcp->get_size ($uri) |
222 |
|
|
|
223 |
|
|
Finds and returns the size (rounded up to the nearest power of two) of the |
224 |
|
|
given document. |
225 |
|
|
|
226 |
|
|
UNTESTED. |
227 |
|
|
|
228 |
|
|
=cut |
229 |
|
|
|
230 |
|
|
_txn get_size => sub { |
231 |
|
|
my ($self, $uri) = @_; |
232 |
|
|
|
233 |
|
|
$self->txn (GetSize => URI => $uri); |
234 |
|
|
}; |
235 |
|
|
|
236 |
|
|
=item MISSING: ClientGet, ClientPut |
237 |
|
|
|
238 |
|
|
=back |
239 |
|
|
|
240 |
|
|
=head2 THE Net::FCP::Txn CLASS |
241 |
|
|
|
242 |
|
|
All requests (or transactions) are executed in a asynchroneous way (LIE: |
243 |
|
|
uploads are blocking). For each request, a C<Net::FCP::Txn> object is |
244 |
|
|
created (worse: a tcp connection is created, too). |
245 |
|
|
|
246 |
|
|
For each request there is actually a different subclass (and it's possible |
247 |
|
|
to subclass these, although of course not documented). |
248 |
|
|
|
249 |
|
|
The most interesting method is C<result>. |
250 |
|
|
|
251 |
|
|
=over 4 |
252 |
|
|
|
253 |
|
|
=cut |
254 |
|
|
|
255 |
|
|
package Net::FCP::Txn; |
256 |
|
|
|
257 |
|
|
=item new arg => val,... |
258 |
|
|
|
259 |
|
|
Creates a new C<Net::FCP::Txn> object. Not normally used. |
260 |
|
|
|
261 |
|
|
=cut |
262 |
|
|
|
263 |
|
|
sub new { |
264 |
|
|
my $class = shift; |
265 |
|
|
my $self = bless { @_ }, $class; |
266 |
|
|
|
267 |
|
|
my $attr = ""; |
268 |
|
|
my $data = delete $self->{attr}{data}; |
269 |
|
|
|
270 |
|
|
while (my ($k, $v) = each %{$self->{attr}}) { |
271 |
|
|
$attr .= "$k=$v\012" |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
if (defined $data) { |
275 |
|
|
$attr .= "DataLength=" . (length $data) . "\012"; |
276 |
|
|
$data = "Data\012$data"; |
277 |
|
|
} else { |
278 |
|
|
$data = "EndMessage\012"; |
279 |
|
|
} |
280 |
|
|
|
281 |
|
|
my $fh = new IO::Socket::INET |
282 |
|
|
PeerHost => $self->{fcp}{host}, |
283 |
|
|
PeerPort => $self->{fcp}{port} |
284 |
|
|
or Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n"; |
285 |
|
|
|
286 |
|
|
binmode $fh, ":raw"; |
287 |
|
|
|
288 |
|
|
print |
289 |
|
|
$self->{type}, "\012", |
290 |
|
|
$attr, |
291 |
|
|
$data, "\012"; |
292 |
|
|
|
293 |
|
|
print $fh |
294 |
|
|
"\x00\x00", "\x00\x02", # SESSID, PRESID |
295 |
|
|
$self->{type}, "\012", |
296 |
|
|
$attr, |
297 |
|
|
$data; |
298 |
|
|
|
299 |
|
|
#$fh->shutdown (1); # freenet buggy?, well, it's java... |
300 |
|
|
|
301 |
|
|
$self->{fh} = $fh; |
302 |
|
|
|
303 |
|
|
$Net::FCP::regcb->($self); |
304 |
|
|
|
305 |
|
|
$self; |
306 |
|
|
} |
307 |
|
|
|
308 |
|
|
sub fh_ready { |
309 |
|
|
my ($self) = @_; |
310 |
|
|
|
311 |
|
|
if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) { |
312 |
|
|
for (;;) { |
313 |
|
|
if ($self->{datalen}) { |
314 |
|
|
if (length $self->{buf} >= $self->{datalen}) { |
315 |
|
|
$self->recv_data (substr $self->{buf}, 0, $self->{datalen}, ""); |
316 |
|
|
} else { |
317 |
|
|
last; |
318 |
|
|
} |
319 |
|
|
} elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=(\d+)\015?\012Data\015?\012//) { |
320 |
|
|
$self->{datalen} = $1; |
321 |
|
|
} elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(.*?)\015?\012EndMessage\015?\012//s) { |
322 |
|
|
$self->rcv ($1, {map { split /=/, $_, 2 } split /\015?\012/, $2}); |
323 |
|
|
} else { |
324 |
|
|
last; |
325 |
|
|
} |
326 |
|
|
} |
327 |
|
|
} else { |
328 |
|
|
$Net::FCP::unregcb->($self); |
329 |
|
|
delete $self->{fh}; |
330 |
|
|
$self->eof; |
331 |
|
|
} |
332 |
|
|
} |
333 |
|
|
|
334 |
|
|
sub rcv_data { |
335 |
|
|
my ($self, $chunk) = @_; |
336 |
|
|
} |
337 |
|
|
|
338 |
|
|
sub rcv { |
339 |
|
|
my ($self, $type, $attr) = @_; |
340 |
|
|
#use PApp::Util;warn "$type => ".PApp::Util::dumpval($attr); |
341 |
|
|
|
342 |
|
|
if (my $method = $self->can("rcv_\L$type")) { |
343 |
|
|
$method->($self, $attr, $type); |
344 |
|
|
} else { |
345 |
|
|
warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n"; |
346 |
|
|
$self->eof; |
347 |
|
|
} |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
sub eof { |
351 |
|
|
my ($self, $result) = @_; |
352 |
|
|
|
353 |
|
|
$self->{result} = $result unless exists $self->{result}; |
354 |
|
|
} |
355 |
|
|
|
356 |
|
|
=item $result = $txn->result |
357 |
|
|
|
358 |
|
|
Waits until a result is available and then returns it. |
359 |
|
|
|
360 |
|
|
This waiting is (depending on your event modul) not very efficient, as it |
361 |
|
|
is done outside the "mainloop". |
362 |
|
|
|
363 |
|
|
=cut |
364 |
|
|
|
365 |
|
|
sub result { |
366 |
|
|
my ($self) = @_; |
367 |
|
|
|
368 |
|
|
$Net::FCP::waitcb->() while !exists $self->{result}; |
369 |
|
|
|
370 |
|
|
return $self->{result}; |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
sub DESTROY { |
374 |
|
|
$Net::FCP::unregcb->($_[0]); |
375 |
|
|
} |
376 |
|
|
|
377 |
|
|
package Net::FCP::Txn::ClientHello; |
378 |
|
|
|
379 |
|
|
use base Net::FCP::Txn; |
380 |
|
|
|
381 |
|
|
sub rcv_nodehello { |
382 |
|
|
my ($self, $attr) = @_; |
383 |
|
|
|
384 |
|
|
$self->eof ($attr); |
385 |
|
|
} |
386 |
|
|
|
387 |
|
|
package Net::FCP::Txn::ClientInfo; |
388 |
|
|
|
389 |
|
|
use base Net::FCP::Txn; |
390 |
|
|
|
391 |
|
|
sub rcv_nodeinfo { |
392 |
|
|
my ($self, $attr) = @_; |
393 |
|
|
|
394 |
|
|
$self->eof ($attr); |
395 |
|
|
} |
396 |
|
|
|
397 |
|
|
package Net::FCP::Txn::GenerateCHK; |
398 |
|
|
|
399 |
|
|
use base Net::FCP::Txn; |
400 |
|
|
|
401 |
|
|
sub rcv_success { |
402 |
|
|
my ($self, $attr) = @_; |
403 |
|
|
|
404 |
|
|
$self->eof ($attr); |
405 |
|
|
} |
406 |
|
|
|
407 |
|
|
package Net::FCP::Txn::GenerateSVKPair; |
408 |
|
|
|
409 |
|
|
use base Net::FCP::Txn; |
410 |
|
|
|
411 |
|
|
sub rcv_success { |
412 |
|
|
my ($self, $attr) = @_; |
413 |
|
|
|
414 |
|
|
$self->eof ([$attr->{PublicKey}, $attr->{PrivateKey}]); |
415 |
|
|
} |
416 |
|
|
|
417 |
|
|
package Net::FCP::Txn::InvertPrivateKey; |
418 |
|
|
|
419 |
|
|
use base Net::FCP::Txn; |
420 |
|
|
|
421 |
|
|
sub rcv_success { |
422 |
|
|
my ($self, $attr) = @_; |
423 |
|
|
|
424 |
|
|
$self->eof ($attr->{PublicKey}); |
425 |
|
|
} |
426 |
|
|
|
427 |
|
|
package Net::FCP::Txn::GetSize; |
428 |
|
|
|
429 |
|
|
use base Net::FCP::Txn; |
430 |
|
|
|
431 |
|
|
sub rcv_success { |
432 |
|
|
my ($self, $attr) = @_; |
433 |
|
|
|
434 |
|
|
$self->eof ($attr->{Length}); |
435 |
|
|
} |
436 |
|
|
|
437 |
|
|
=back |
438 |
|
|
|
439 |
|
|
=head1 SEE ALSO |
440 |
|
|
|
441 |
|
|
L<http://freenet.sf.net>. |
442 |
|
|
|
443 |
|
|
=head1 BUGS |
444 |
|
|
|
445 |
|
|
=head1 AUTHOR |
446 |
|
|
|
447 |
|
|
Marc Lehmann <pcg@goof.com> |
448 |
|
|
http://www.goof.com/pcg/marc/ |
449 |
|
|
|
450 |
|
|
=cut |
451 |
|
|
|
452 |
|
|
1; |
453 |
|
|
|