This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
The build in tracker has been removed for simplicity. Example usage: java -jar lib/i2psnark.jar myFile.torrent or, a more verbose setting: java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \ --i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \ --debug 6 myFile.torrent
This commit is contained in:
340
apps/i2psnark/COPYING
Normal file
340
apps/i2psnark/COPYING
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
24
apps/i2psnark/TODO
Normal file
24
apps/i2psnark/TODO
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
- I2PSnark:
|
||||||
|
- add multitorrent support by checking the metainfo hash in the
|
||||||
|
PeerAcceptor and feeding it off to the appropriate coordinator
|
||||||
|
- add a web interface
|
||||||
|
|
||||||
|
- BEncode
|
||||||
|
- Byte array length indicator can overflow.
|
||||||
|
- Support really big BigNums (only 256 chars allowed now)
|
||||||
|
- Better BEValue toString(). Uses stupid heuristic now for debugging.
|
||||||
|
- Implemented bencoding.
|
||||||
|
- Remove application level hack to calculate sha1 hash for metainfo
|
||||||
|
(But can it be done as efficiently?)
|
||||||
|
|
||||||
|
- Storage
|
||||||
|
- Check file name filter.
|
||||||
|
|
||||||
|
- TrackerClient
|
||||||
|
- Support undocumented &numwant= request.
|
||||||
|
|
||||||
|
- PeerCoordinator
|
||||||
|
- Disconnect from other seeds as soon as you are a seed yourself.
|
||||||
|
|
||||||
|
- Text UI
|
||||||
|
- Make it completely silent.
|
1
apps/i2psnark/authors.snark
Normal file
1
apps/i2psnark/authors.snark
Normal file
@ -0,0 +1 @@
|
|||||||
|
Mark Wielaard <mark@klomp.org>
|
487
apps/i2psnark/changelog.snark
Normal file
487
apps/i2psnark/changelog.snark
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
2003-06-27 14:24 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README: Update version number and explain new features.
|
||||||
|
|
||||||
|
2003-06-27 13:51 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/GnomeInfoWindow.java,
|
||||||
|
org/klomp/snark/GnomePeerList.java,
|
||||||
|
org/klomp/snark/PeerCoordinator.java,
|
||||||
|
org/klomp/snark/SnarkGnome.java: Add GnomeInfoWindow.
|
||||||
|
|
||||||
|
2003-06-27 00:37 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Implement 'info' and 'list' commands.
|
||||||
|
|
||||||
|
2003-06-27 00:05 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/GnomePeerList.java,
|
||||||
|
org/klomp/snark/SnarkGnome.java: Add GnomePeerList to show state of
|
||||||
|
connected peers.
|
||||||
|
|
||||||
|
2003-06-27 00:04 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Peer.java, PeerID.java: Make Comparable.
|
||||||
|
|
||||||
|
2003-06-23 23:32 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerMonitorTask.java: Correctly update
|
||||||
|
lastDownloaded and lastUploaded.
|
||||||
|
|
||||||
|
2003-06-23 23:20 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: When checking storage use the
|
||||||
|
MetaInfo from the storage.
|
||||||
|
|
||||||
|
2003-06-23 21:47 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Storage.java: Fill piece hashes, not info hashes.
|
||||||
|
|
||||||
|
2003-06-23 21:42 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/MetaInfo.java: New package private
|
||||||
|
getPieceHashes() method.
|
||||||
|
|
||||||
|
2003-06-22 19:49 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README, TODO, org/klomp/snark/Snark.java: Add new command line
|
||||||
|
switch --no-commands. Don't read interactive commands or show
|
||||||
|
usage info.
|
||||||
|
|
||||||
|
2003-06-22 19:26 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/PeerCheckerTask.java,
|
||||||
|
org/klomp/snark/PeerMonitorTask.java, org/klomp/snark/Snark.java:
|
||||||
|
Split peer statistic reporting from PeerCheckerTask into
|
||||||
|
PeerMonitorTask. Use new task in Snark text ui.
|
||||||
|
|
||||||
|
2003-06-22 18:32 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Only print peer id when debug level
|
||||||
|
is INFO or higher.
|
||||||
|
|
||||||
|
2003-06-22 18:00 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/ShutdownListener.java: Add new ShutdownListener
|
||||||
|
interface.
|
||||||
|
|
||||||
|
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* TODO: Text UI item to not read from stdin.
|
||||||
|
|
||||||
|
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* snark-gnome.sh: kaffe java-gnome support (but crashes hard at the
|
||||||
|
moment).
|
||||||
|
|
||||||
|
2003-06-22 14:04 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/CoordinatorListener.java,
|
||||||
|
org/klomp/snark/PeerCoordinator.java,
|
||||||
|
org/klomp/snark/ProgressListener.java, org/klomp/snark/Snark.java,
|
||||||
|
org/klomp/snark/SnarkGnome.java,
|
||||||
|
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||||
|
org/klomp/snark/StorageListener.java: Split ProgressListener into
|
||||||
|
Storage, Coordinator and Shutdown listener.
|
||||||
|
|
||||||
|
2003-06-20 19:06 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerCoordinator.java, Snark.java,
|
||||||
|
SnarkGnome.java, Storage.java: Progress listeners for both Storage
|
||||||
|
and PeerCoordinator.
|
||||||
|
|
||||||
|
2003-06-20 14:50 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/PeerCoordinator.java,
|
||||||
|
org/klomp/snark/ProgressListener.java,
|
||||||
|
org/klomp/snark/SnarkGnome.java: Add ProgressListener.
|
||||||
|
|
||||||
|
2003-06-20 13:22 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/SnarkGnome.java: Add Pieces collected field.
|
||||||
|
|
||||||
|
2003-06-20 12:26 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerCoordinator.java, PeerListener.java,
|
||||||
|
PeerState.java: Add PeerListener.downloaded() which gets called on
|
||||||
|
chunk updates. Keep PeerCoordinator.downloaded up to date using
|
||||||
|
this remove adjusting in gotPiece() except when we receive a bad
|
||||||
|
piece.
|
||||||
|
|
||||||
|
2003-06-16 00:27 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, snark-gnome.sh, org/klomp/snark/Snark.java,
|
||||||
|
org/klomp/snark/SnarkGnome.java: Start of a Gnome GUI.
|
||||||
|
|
||||||
|
2003-06-05 13:19 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerCoordinator.java: Don't remove a BAD piece
|
||||||
|
from the wantedPieces list. Revert to synchronizing on
|
||||||
|
wantedPieces for all relevant sections.
|
||||||
|
|
||||||
|
2003-06-03 21:09 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Only call readLine() when !quit.
|
||||||
|
Always print exception when fatal() is called.
|
||||||
|
|
||||||
|
2003-06-01 23:12 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README: Set release version to 0.4.
|
||||||
|
|
||||||
|
2003-06-01 22:59 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionIn.java: Handle negative length
|
||||||
|
prefixes (terminates connection).
|
||||||
|
|
||||||
|
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Snark.java, SnarkShutdown.java: Implement
|
||||||
|
correct shutdown and read commands from stdin.
|
||||||
|
|
||||||
|
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/TrackerInfo.java: Check that interval and peers
|
||||||
|
list actually exist.
|
||||||
|
|
||||||
|
2003-06-01 21:33 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Storage.java: Implement close().
|
||||||
|
|
||||||
|
2003-06-01 21:05 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Fix debug logging.
|
||||||
|
|
||||||
|
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerCoordinator.java: Implement halt().
|
||||||
|
|
||||||
|
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/ConnectionAcceptor.java: Rename stop() to halt().
|
||||||
|
|
||||||
|
2003-06-01 17:35 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Drop lock on this when calling
|
||||||
|
addRequest() from havePiece().
|
||||||
|
|
||||||
|
2003-06-01 14:46 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README, org/klomp/snark/ConnectionAcceptor.java,
|
||||||
|
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||||
|
org/klomp/snark/PeerCheckerTask.java,
|
||||||
|
org/klomp/snark/PeerConnectionIn.java,
|
||||||
|
org/klomp/snark/PeerConnectionOut.java,
|
||||||
|
org/klomp/snark/PeerCoordinator.java,
|
||||||
|
org/klomp/snark/PeerState.java, org/klomp/snark/Snark.java,
|
||||||
|
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||||
|
org/klomp/snark/Tracker.java, org/klomp/snark/TrackerClient.java:
|
||||||
|
Add debug/log level.
|
||||||
|
|
||||||
|
2003-05-31 23:04 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java: Use
|
||||||
|
just one lock (peers) for all synchronization (even for
|
||||||
|
wantedPieces). Let PeerChecker handle real disconnect and keep
|
||||||
|
count of uploaders.
|
||||||
|
|
||||||
|
2003-05-31 22:29 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Peer.java, PeerConnectionIn.java: Set state to
|
||||||
|
null on first disconnect() call. So always check whether it might
|
||||||
|
already be null. Helps disconnect check.
|
||||||
|
|
||||||
|
2003-05-31 22:27 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Don't explicitly close
|
||||||
|
the DataOutputStream (if another thread is using it libgcj seems to
|
||||||
|
not like it very much).
|
||||||
|
|
||||||
|
2003-05-30 21:33 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Cancel
|
||||||
|
(un)interested/(un)choke when (inverse) is still in send queue.
|
||||||
|
Remove pieces from send queue when choke message is actaully send.
|
||||||
|
|
||||||
|
2003-05-30 19:32 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Make sure listener.wantPiece(int)
|
||||||
|
is never called while lock on this is held.
|
||||||
|
|
||||||
|
2003-05-30 19:00 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Indentation cleanup.
|
||||||
|
|
||||||
|
2003-05-30 17:50 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Storage.java: Only synchronize on bitfield as
|
||||||
|
long as necessary.
|
||||||
|
|
||||||
|
2003-05-30 17:43 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Tracker.java: Identing cleanup.
|
||||||
|
|
||||||
|
2003-05-30 16:32 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Better error message.
|
||||||
|
|
||||||
|
2003-05-30 15:11 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Make sure not to hold the lock on
|
||||||
|
this when calling the listener to prevent deadlocks. Implement
|
||||||
|
handling and sending of cancel messages.
|
||||||
|
|
||||||
|
2003-05-30 14:50 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerCoordinator.java: First check if we still
|
||||||
|
want a piece before trying to add it to the Storage.
|
||||||
|
|
||||||
|
2003-05-30 14:49 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Implement
|
||||||
|
sendCancel(Request). Add cancelRequest(int, int, int).
|
||||||
|
|
||||||
|
2003-05-30 14:46 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Request.java: Add hashCode() and equals(Object)
|
||||||
|
methods.
|
||||||
|
|
||||||
|
2003-05-30 14:45 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Peer.java: Fix wheter -> whether javadoc
|
||||||
|
comments. Mark state null immediatly after calling
|
||||||
|
listener.disconnected(). Call PeerState.havePiece() not
|
||||||
|
PeerConnectionOut.sendHave() directly.
|
||||||
|
|
||||||
|
2003-05-25 19:23 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* TODO: Add PeerCoordinator TODO for connecting to seeds.
|
||||||
|
|
||||||
|
2003-05-23 12:12 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile: Create class files with jikes again.
|
||||||
|
|
||||||
|
2003-05-18 22:01 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||||
|
Prefer to (optimistically) unchoke first those peers that unchoked
|
||||||
|
us. And make sure to not unchoke a peer that we just choked.
|
||||||
|
|
||||||
|
2003-05-18 21:48 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Peer.java: Fix isChoked() to not always return
|
||||||
|
true.
|
||||||
|
|
||||||
|
2003-05-18 14:46 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||||
|
PeerCoordinator.java, PeerState.java: Remove separate Peer
|
||||||
|
downloading/uploading states. Keep choke and interest always up to
|
||||||
|
date. Uploading is now just when we are not choking the peer.
|
||||||
|
Downloading is now defined as being unchoked and interesting.
|
||||||
|
CHECK_PERIOD is now 20 seconds. MAX_CONNECTIONS is now 24.
|
||||||
|
MAX_DOWNLOADERS doesn't exists anymore. We download whenever we can
|
||||||
|
from peers.
|
||||||
|
|
||||||
|
2003-05-18 13:57 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Remove piece messages
|
||||||
|
from queue when we are choking. (They will have to be rerequested
|
||||||
|
when we unchoke the peer again.)
|
||||||
|
|
||||||
|
2003-05-15 00:08 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Ignore missed chunk requests,
|
||||||
|
don't requeue them.
|
||||||
|
|
||||||
|
2003-05-15 00:06 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Request.java: Add sanity check
|
||||||
|
|
||||||
|
2003-05-10 15:47 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Add extra '(' to usage message.
|
||||||
|
|
||||||
|
2003-05-10 15:22 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README: Set version to 0.3 (The Bakers Tale).
|
||||||
|
|
||||||
|
2003-05-10 15:17 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Mention received piece in warning
|
||||||
|
message.
|
||||||
|
|
||||||
|
2003-05-10 03:20 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerConnectionIn.java, PeerState.java,
|
||||||
|
Request.java: Remove currentRequest and handle all piece messages
|
||||||
|
from the lastRequested list.
|
||||||
|
|
||||||
|
2003-05-09 20:02 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Fix nothing requested warning
|
||||||
|
message.
|
||||||
|
|
||||||
|
2003-05-09 19:59 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionOut.java: Piece messages are big.
|
||||||
|
So if there are other (control) messages make sure they are send
|
||||||
|
first. Also remove request messages from the queue if we are
|
||||||
|
currently being choked to prevent them from being send even if we
|
||||||
|
get unchoked a little later. (Since we will resent them anyway in
|
||||||
|
that case.)
|
||||||
|
|
||||||
|
2003-05-09 18:33 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||||
|
PeerCoordinator.java, PeerID.java: New definition of PeerID.equals
|
||||||
|
(port + address + id) and new method PeerID.sameID (only id). These
|
||||||
|
are used to really see if we already have a connection to a certain
|
||||||
|
peer (active setup vs passive setup).
|
||||||
|
|
||||||
|
2003-05-08 03:05 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Use Snark.debug() not
|
||||||
|
System.out.println().
|
||||||
|
|
||||||
|
2003-05-06 20:29 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: s/noting/nothing/
|
||||||
|
|
||||||
|
2003-05-06 20:28 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile: s/lagacy/legacy/
|
||||||
|
|
||||||
|
2003-05-05 23:17 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* README: Set version to 0.2, explain new functionality and add
|
||||||
|
examples.
|
||||||
|
|
||||||
|
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* .cvsignore, Makefile, org/klomp/snark/StaticSnark.java: Enable
|
||||||
|
-static binary creation.
|
||||||
|
|
||||||
|
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Tracker.java: Disable --ip support.
|
||||||
|
|
||||||
|
2003-05-05 21:02 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: HttpAcceptor.java, PeerCheckerTask.java,
|
||||||
|
PeerCoordinator.java, TrackerClient.java: Use Snark.debug() not
|
||||||
|
System.out.println().
|
||||||
|
|
||||||
|
2003-05-05 21:01 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerConnectionIn.java: Be prepared to handle the
|
||||||
|
case where currentRequest is null.
|
||||||
|
|
||||||
|
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Improve argument parsing errors.
|
||||||
|
|
||||||
|
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile: Use gcj -C again for creating the class files.
|
||||||
|
|
||||||
|
2003-05-05 09:24 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Just clear outstandingRequests,
|
||||||
|
never make it null.
|
||||||
|
|
||||||
|
2003-05-05 02:55 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/TrackerClient.java: Always retry both first
|
||||||
|
started event and every other event as long the TrackerClient is
|
||||||
|
not stopped.
|
||||||
|
|
||||||
|
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Remove double assignment port.
|
||||||
|
|
||||||
|
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* TODO: Add Tracker TODO item.
|
||||||
|
|
||||||
|
2003-05-04 23:38 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: ConnectionAcceptor.java, MetaInfo.java,
|
||||||
|
Snark.java, Storage.java, Tracker.java: Add info hash calcultation
|
||||||
|
to MetaInfo. Add torrent creation to Storage. Add ip parameter
|
||||||
|
handling to Tracker. Make ConnectionAcceptor handle
|
||||||
|
null/non-existing HttpAcceptors. Add debug output, --ip handling
|
||||||
|
and all the above to Snark.
|
||||||
|
|
||||||
|
2003-05-04 23:36 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/TrackerClient.java: Handle all failing requests
|
||||||
|
the same (print a warning).
|
||||||
|
|
||||||
|
2003-05-03 15:46 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: Peer.java, PeerID.java, TrackerInfo.java: Split
|
||||||
|
Peer and PeerID a little more.
|
||||||
|
|
||||||
|
2003-05-03 15:44 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/MetaInfo.java: Add reannounce() and
|
||||||
|
getTorrentData().
|
||||||
|
|
||||||
|
2003-05-03 15:38 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||||
|
More concise verbose/debug output. Always use addUpDownloader() to
|
||||||
|
set peers upload or download state to true.
|
||||||
|
|
||||||
|
2003-05-03 13:38 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/TrackerClient.java: Compile fixes.
|
||||||
|
|
||||||
|
2003-05-03 13:32 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/TrackerClient.java: Only generate fatal() call on
|
||||||
|
first Tracker access. Otherwise just print a warning error message.
|
||||||
|
|
||||||
|
2003-05-03 03:10 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerState.java: Better handle resending
|
||||||
|
outstanding pieces and try to recover better from unrequested
|
||||||
|
pieces.
|
||||||
|
|
||||||
|
2003-05-02 21:33 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/HttpAcceptor.java,
|
||||||
|
org/klomp/snark/MetaInfo.java, org/klomp/snark/PeerID.java,
|
||||||
|
org/klomp/snark/Snark.java, org/klomp/snark/Tracker.java,
|
||||||
|
org/klomp/snark/TrackerClient.java,
|
||||||
|
org/klomp/snark/bencode/BEncoder.java: Add Tracker, PeerID and
|
||||||
|
BEncoder.
|
||||||
|
|
||||||
|
2003-05-01 20:17 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* Makefile, org/klomp/snark/ConnectionAcceptor.java,
|
||||||
|
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||||
|
org/klomp/snark/PeerAcceptor.java, org/klomp/snark/Snark.java: Add
|
||||||
|
ConnectionAcceptor that handles both PeerAcceptor and HttpAcceptor.
|
||||||
|
|
||||||
|
2003-05-01 18:39 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/PeerCoordinator.java: connected() synchronize on
|
||||||
|
peers.
|
||||||
|
|
||||||
|
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/SnarkShutdown.java: Wait some time before
|
||||||
|
returning...
|
||||||
|
|
||||||
|
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* TODO: More items.
|
||||||
|
|
||||||
|
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* org/klomp/snark/Snark.java: Calculate real random ID.
|
||||||
|
|
||||||
|
2003-04-27 Mark Wielaard <mark@klomp.org>
|
||||||
|
|
||||||
|
* snark: Initial (0.1) version.
|
35
apps/i2psnark/java/build.xml
Normal file
35
apps/i2psnark/java/build.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project basedir="." default="all" name="i2psnark">
|
||||||
|
<target name="all" depends="clean, build" />
|
||||||
|
<target name="build" depends="builddep, jar" />
|
||||||
|
<target name="builddep">
|
||||||
|
<ant dir="../../ministreaming/java/" target="build" />
|
||||||
|
<!-- ministreaming will build core -->
|
||||||
|
</target>
|
||||||
|
<target name="compile">
|
||||||
|
<mkdir dir="./build" />
|
||||||
|
<mkdir dir="./build/obj" />
|
||||||
|
<javac
|
||||||
|
srcdir="./src"
|
||||||
|
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||||
|
destdir="./build/obj"
|
||||||
|
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||||
|
</target>
|
||||||
|
<target name="jar" depends="builddep, compile">
|
||||||
|
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class">
|
||||||
|
<manifest>
|
||||||
|
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||||
|
<attribute name="Class-Path" value="i2p.jar jbigi.jar mstreaming.jar streaming.jar" />
|
||||||
|
</manifest>
|
||||||
|
</jar>
|
||||||
|
</target>
|
||||||
|
<target name="clean">
|
||||||
|
<delete dir="./build" />
|
||||||
|
</target>
|
||||||
|
<target name="cleandep" depends="clean">
|
||||||
|
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||||
|
</target>
|
||||||
|
<target name="distclean" depends="clean">
|
||||||
|
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||||
|
</target>
|
||||||
|
</project>
|
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/* BitField - Container of a byte array representing set and unset bits.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container of a byte array representing set and unset bits.
|
||||||
|
*/
|
||||||
|
public class BitField
|
||||||
|
{
|
||||||
|
|
||||||
|
private final byte[] bitfield;
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BitField that represents <code>size</code> unset bits.
|
||||||
|
*/
|
||||||
|
public BitField(int size)
|
||||||
|
{
|
||||||
|
this.size = size;
|
||||||
|
int arraysize = ((size-1)/8)+1;
|
||||||
|
bitfield = new byte[arraysize];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BitField that represents <code>size</code> bits
|
||||||
|
* as set by the given byte array. This will make a copy of the array.
|
||||||
|
* Extra bytes will be ignored.
|
||||||
|
*
|
||||||
|
* @exception ArrayOutOfBoundsException if give byte array is not large
|
||||||
|
* enough.
|
||||||
|
*/
|
||||||
|
public BitField(byte[] bitfield, int size)
|
||||||
|
{
|
||||||
|
this.size = size;
|
||||||
|
int arraysize = ((size-1)/8)+1;
|
||||||
|
this.bitfield = new byte[arraysize];
|
||||||
|
|
||||||
|
// XXX - More correct would be to check that unused bits are
|
||||||
|
// cleared or clear them explicitly ourselves.
|
||||||
|
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the actual byte array used. Changes to this array
|
||||||
|
* effect this BitField. Note that some bits at the end of the byte
|
||||||
|
* array are supposed to be always unset if they represent bits
|
||||||
|
* bigger then the size of the bitfield.
|
||||||
|
*/
|
||||||
|
public byte[] getFieldBytes()
|
||||||
|
{
|
||||||
|
return bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the size of the BitField. The returned value is one bigger
|
||||||
|
* then the last valid bit number (since bit numbers are counted
|
||||||
|
* from zero).
|
||||||
|
*/
|
||||||
|
public int size()
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the given bit to true.
|
||||||
|
*
|
||||||
|
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||||
|
* bigger then size (inclusive).
|
||||||
|
*/
|
||||||
|
public void set(int bit)
|
||||||
|
{
|
||||||
|
if (bit < 0 || bit >= size)
|
||||||
|
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||||
|
int index = bit/8;
|
||||||
|
int mask = 128 >> (bit % 8);
|
||||||
|
bitfield[index] |= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the bit is set or false if it is not.
|
||||||
|
*
|
||||||
|
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||||
|
* bigger then size (inclusive).
|
||||||
|
*/
|
||||||
|
public boolean get(int bit)
|
||||||
|
{
|
||||||
|
if (bit < 0 || bit >= size)
|
||||||
|
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||||
|
|
||||||
|
int index = bit/8;
|
||||||
|
int mask = 128 >> (bit % 8);
|
||||||
|
return (bitfield[index] & mask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
// Not very efficient
|
||||||
|
StringBuffer sb = new StringBuffer("BitField[");
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
if (get(i))
|
||||||
|
{
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(i);
|
||||||
|
}
|
||||||
|
sb.append(" ]");
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
143
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
143
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/* ConnectionAcceptor - Accepts connections and routes them to sub-acceptors.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
import net.i2p.I2PException;
|
||||||
|
import net.i2p.client.streaming.I2PServerSocket;
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts connections on a TCP port and routes them to sub-acceptors.
|
||||||
|
*/
|
||||||
|
public class ConnectionAcceptor implements Runnable
|
||||||
|
{
|
||||||
|
private final I2PServerSocket serverSocket;
|
||||||
|
private final PeerAcceptor peeracceptor;
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private boolean stop;
|
||||||
|
|
||||||
|
public ConnectionAcceptor(I2PServerSocket serverSocket,
|
||||||
|
PeerAcceptor peeracceptor)
|
||||||
|
{
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
this.peeracceptor = peeracceptor;
|
||||||
|
|
||||||
|
stop = false;
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void halt()
|
||||||
|
{
|
||||||
|
stop = true;
|
||||||
|
|
||||||
|
I2PServerSocket ss = serverSocket;
|
||||||
|
if (ss != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ss.close();
|
||||||
|
}
|
||||||
|
catch(I2PException ioe) { }
|
||||||
|
|
||||||
|
Thread t = thread;
|
||||||
|
if (t != null)
|
||||||
|
t.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort()
|
||||||
|
{
|
||||||
|
return 6881; // serverSocket.getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while(!stop)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
final I2PSocket socket = serverSocket.accept();
|
||||||
|
Thread t = new Thread("Connection-" + socket)
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream in = socket.getInputStream();
|
||||||
|
OutputStream out = socket.getOutputStream();
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(in);
|
||||||
|
BufferedOutputStream bos = new BufferedOutputStream(out);
|
||||||
|
|
||||||
|
// See what kind of connection it is.
|
||||||
|
/*
|
||||||
|
if (httpacceptor != null)
|
||||||
|
{
|
||||||
|
byte[] scratch = new byte[4];
|
||||||
|
bis.mark(4);
|
||||||
|
int len = bis.read(scratch);
|
||||||
|
if (len != 4)
|
||||||
|
throw new IOException("Need at least 4 bytes");
|
||||||
|
bis.reset();
|
||||||
|
if (scratch[0] == 19 && scratch[1] == 'B'
|
||||||
|
&& scratch[2] == 'i' && scratch[3] == 't')
|
||||||
|
peeracceptor.connection(socket, bis, bos);
|
||||||
|
else if (scratch[0] == 'G' && scratch[1] == 'E'
|
||||||
|
&& scratch[2] == 'T' && scratch[3] == ' ')
|
||||||
|
httpacceptor.connection(socket, bis, bos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
peeracceptor.connection(socket, bis, bos);
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ignored) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
catch (I2PException ioe)
|
||||||
|
{
|
||||||
|
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
catch (I2PException ignored) { }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/* CoordinatorListener.java - Callback when a peer changes state
|
||||||
|
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when some peer changes state.
|
||||||
|
*/
|
||||||
|
public interface CoordinatorListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the PeerCoordinator notices a change in the state of a peer.
|
||||||
|
*/
|
||||||
|
void peerChange(PeerCoordinator coordinator, Peer peer);
|
||||||
|
}
|
165
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
165
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.I2PException;
|
||||||
|
import net.i2p.util.EepGet;
|
||||||
|
import net.i2p.data.Base64;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.client.streaming.I2PServerSocket;
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManager;
|
||||||
|
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I2P specific helpers for I2PSnark
|
||||||
|
*/
|
||||||
|
public class I2PSnarkUtil {
|
||||||
|
private I2PAppContext _context;
|
||||||
|
private Log _log;
|
||||||
|
private static I2PSnarkUtil _instance = new I2PSnarkUtil();
|
||||||
|
public static I2PSnarkUtil instance() { return _instance; }
|
||||||
|
|
||||||
|
private boolean _shouldProxy;
|
||||||
|
private String _proxyHost;
|
||||||
|
private int _proxyPort;
|
||||||
|
private String _i2cpHost;
|
||||||
|
private int _i2cpPort;
|
||||||
|
private Properties _opts;
|
||||||
|
private I2PSocketManager _manager;
|
||||||
|
|
||||||
|
private I2PSnarkUtil() {
|
||||||
|
_context = I2PAppContext.getGlobalContext();
|
||||||
|
_log = _context.logManager().getLog(Snark.class);
|
||||||
|
setProxy("127.0.0.1", 4444);
|
||||||
|
setI2CPConfig("127.0.0.1", 7654, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify what HTTP proxy tracker requests should go through (specify a null
|
||||||
|
* host for no proxying)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void setProxy(String host, int port) {
|
||||||
|
if ( (host != null) && (port > 0) ) {
|
||||||
|
_shouldProxy = true;
|
||||||
|
_proxyHost = host;
|
||||||
|
_proxyPort = port;
|
||||||
|
} else {
|
||||||
|
_shouldProxy = false;
|
||||||
|
_proxyHost = null;
|
||||||
|
_proxyPort = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setI2CPConfig(String i2cpHost, int i2cpPort, Properties opts) {
|
||||||
|
_i2cpHost = i2cpHost;
|
||||||
|
_i2cpPort = i2cpPort;
|
||||||
|
if (opts != null)
|
||||||
|
_opts = opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the router, if we aren't already
|
||||||
|
*/
|
||||||
|
boolean connect() {
|
||||||
|
if (_manager == null) {
|
||||||
|
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, _opts);
|
||||||
|
}
|
||||||
|
return (_manager != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** connect to the given destination */
|
||||||
|
I2PSocket connect(PeerID peer) throws IOException {
|
||||||
|
try {
|
||||||
|
return _manager.connect(peer.getAddress());
|
||||||
|
} catch (I2PException ie) {
|
||||||
|
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch the given URL, returning the file it is stored in, or null on error
|
||||||
|
*/
|
||||||
|
File get(String url) {
|
||||||
|
File out = null;
|
||||||
|
try {
|
||||||
|
out = File.createTempFile("i2psnark", "url");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
ioe.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), url);
|
||||||
|
if (get.fetch()) {
|
||||||
|
return out;
|
||||||
|
} else {
|
||||||
|
out.delete();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2PServerSocket getServerSocket() {
|
||||||
|
return _manager.getServerSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOurIPString() {
|
||||||
|
return _manager.getSession().getMyDestination().toBase64();
|
||||||
|
}
|
||||||
|
Destination getDestination(String ip) {
|
||||||
|
if (ip == null) return null;
|
||||||
|
if (ip.endsWith(".i2p")) {
|
||||||
|
Destination dest = _context.namingService().lookup(ip);
|
||||||
|
if (dest != null) {
|
||||||
|
return dest;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return new Destination(ip);
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given http://blah.i2p/foo/announce turn it into http://i2p/blah/foo/announce
|
||||||
|
*/
|
||||||
|
String rewriteAnnounce(String origAnnounce) {
|
||||||
|
int destStart = "http://".length();
|
||||||
|
int destEnd = origAnnounce.indexOf(".i2p");
|
||||||
|
int pathStart = origAnnounce.indexOf('/', destEnd);
|
||||||
|
return "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** hook between snark's logger and an i2p log */
|
||||||
|
void debug(String msg, int snarkDebugLevel, Throwable t) {
|
||||||
|
switch (snarkDebugLevel) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
_log.error(msg, t);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
_log.warn(msg, t);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
_log.info(msg, t);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
default:
|
||||||
|
_log.debug(msg, t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
137
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/* Message - A protocol message which can be send through a DataOutputStream.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
// Used to queue outgoing connections
|
||||||
|
// sendMessage() should be used to translate them to wire format.
|
||||||
|
class Message
|
||||||
|
{
|
||||||
|
final static byte KEEP_ALIVE = -1;
|
||||||
|
final static byte CHOKE = 0;
|
||||||
|
final static byte UNCHOKE = 1;
|
||||||
|
final static byte INTERESTED = 2;
|
||||||
|
final static byte UNINTERESTED = 3;
|
||||||
|
final static byte HAVE = 4;
|
||||||
|
final static byte BITFIELD = 5;
|
||||||
|
final static byte REQUEST = 6;
|
||||||
|
final static byte PIECE = 7;
|
||||||
|
final static byte CANCEL = 8;
|
||||||
|
|
||||||
|
// Not all fields are used for every message.
|
||||||
|
// KEEP_ALIVE doesn't have a real wire representation
|
||||||
|
byte type;
|
||||||
|
|
||||||
|
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||||
|
int piece;
|
||||||
|
|
||||||
|
// Used for REQUEST, PIECE and CANCEL messages.
|
||||||
|
int begin;
|
||||||
|
int length;
|
||||||
|
|
||||||
|
// Used for PIECE and BITFIELD messages
|
||||||
|
byte[] data;
|
||||||
|
int off;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
/** Utility method for sending a message through a DataStream. */
|
||||||
|
void sendMessage(DataOutputStream dos) throws IOException
|
||||||
|
{
|
||||||
|
// KEEP_ALIVE is special.
|
||||||
|
if (type == KEEP_ALIVE)
|
||||||
|
{
|
||||||
|
dos.writeInt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the total length in bytes
|
||||||
|
|
||||||
|
// Type is one byte.
|
||||||
|
int datalen = 1;
|
||||||
|
|
||||||
|
// piece is 4 bytes.
|
||||||
|
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||||
|
datalen += 4;
|
||||||
|
|
||||||
|
// begin/offset is 4 bytes
|
||||||
|
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||||
|
datalen += 4;
|
||||||
|
|
||||||
|
// length is 4 bytes
|
||||||
|
if (type == REQUEST || type == CANCEL)
|
||||||
|
datalen += 4;
|
||||||
|
|
||||||
|
// add length of data for piece or bitfield array.
|
||||||
|
if (type == BITFIELD || type == PIECE)
|
||||||
|
datalen += len;
|
||||||
|
|
||||||
|
// Send length
|
||||||
|
dos.writeInt(datalen);
|
||||||
|
dos.writeByte(type & 0xFF);
|
||||||
|
|
||||||
|
// Send additional info (piece number)
|
||||||
|
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||||
|
dos.writeInt(piece);
|
||||||
|
|
||||||
|
// Send additional info (begin/offset)
|
||||||
|
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||||
|
dos.writeInt(begin);
|
||||||
|
|
||||||
|
// Send additional info (length); for PIECE this is implicit.
|
||||||
|
if (type == REQUEST || type == CANCEL)
|
||||||
|
dos.writeInt(length);
|
||||||
|
|
||||||
|
// Send actual data
|
||||||
|
if (type == BITFIELD || type == PIECE)
|
||||||
|
dos.write(data, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case KEEP_ALIVE:
|
||||||
|
return "KEEP_ALIVE";
|
||||||
|
case CHOKE:
|
||||||
|
return "CHOKE";
|
||||||
|
case UNCHOKE:
|
||||||
|
return "UNCHOKE";
|
||||||
|
case INTERESTED:
|
||||||
|
return "INTERESTED";
|
||||||
|
case UNINTERESTED:
|
||||||
|
return "UNINTERESTED";
|
||||||
|
case HAVE:
|
||||||
|
return "HAVE(" + piece + ")";
|
||||||
|
case BITFIELD:
|
||||||
|
return "BITFIELD";
|
||||||
|
case REQUEST:
|
||||||
|
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||||
|
case PIECE:
|
||||||
|
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||||
|
case CANCEL:
|
||||||
|
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||||
|
default:
|
||||||
|
return "<UNKNOWN>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
382
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
382
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
/* MetaInfo - Holds all information gotten from a torrent file.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
public class MetaInfo
|
||||||
|
{
|
||||||
|
private final String announce;
|
||||||
|
private final byte[] info_hash;
|
||||||
|
private final String name;
|
||||||
|
private final List files;
|
||||||
|
private final List lengths;
|
||||||
|
private final int piece_length;
|
||||||
|
private final byte[] piece_hashes;
|
||||||
|
private final long length;
|
||||||
|
|
||||||
|
private byte[] torrentdata;
|
||||||
|
|
||||||
|
MetaInfo(String announce, String name, List files, List lengths,
|
||||||
|
int piece_length, byte[] piece_hashes, long length)
|
||||||
|
{
|
||||||
|
this.announce = announce;
|
||||||
|
this.name = name;
|
||||||
|
this.files = files;
|
||||||
|
this.lengths = lengths;
|
||||||
|
this.piece_length = piece_length;
|
||||||
|
this.piece_hashes = piece_hashes;
|
||||||
|
this.length = length;
|
||||||
|
|
||||||
|
this.info_hash = calculateInfoHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MetaInfo from the given InputStream. The
|
||||||
|
* InputStream must start with a correctly bencoded dictonary
|
||||||
|
* describing the torrent.
|
||||||
|
*/
|
||||||
|
public MetaInfo(InputStream in) throws IOException
|
||||||
|
{
|
||||||
|
this(new BDecoder(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MetaInfo from the given BDecoder. The BDecoder
|
||||||
|
* must have a complete dictionary describing the torrent.
|
||||||
|
*/
|
||||||
|
public MetaInfo(BDecoder be) throws IOException
|
||||||
|
{
|
||||||
|
// Note that evaluation order matters here...
|
||||||
|
this(be.bdecodeMap().getMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
|
||||||
|
* the original bencoded info dictonary (this is a hack, we could
|
||||||
|
* reconstruct the bencoded stream and recalculate the hash). Will
|
||||||
|
* throw a InvalidBEncodingException if the given map does not
|
||||||
|
* contain a valid announce string or info dictonary.
|
||||||
|
*/
|
||||||
|
public MetaInfo(Map m) throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
BEValue val = (BEValue)m.get("announce");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing announce string");
|
||||||
|
this.announce = val.getString();
|
||||||
|
|
||||||
|
val = (BEValue)m.get("info");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing info map");
|
||||||
|
Map info = val.getMap();
|
||||||
|
|
||||||
|
val = (BEValue)info.get("name");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing name string");
|
||||||
|
name = val.getString();
|
||||||
|
|
||||||
|
val = (BEValue)info.get("piece length");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing piece length number");
|
||||||
|
piece_length = val.getInt();
|
||||||
|
|
||||||
|
val = (BEValue)info.get("pieces");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing piece bytes");
|
||||||
|
piece_hashes = val.getBytes();
|
||||||
|
|
||||||
|
val = (BEValue)info.get("length");
|
||||||
|
if (val != null)
|
||||||
|
{
|
||||||
|
// Single file case.
|
||||||
|
length = val.getLong();
|
||||||
|
files = null;
|
||||||
|
lengths = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Multi file case.
|
||||||
|
val = (BEValue)info.get("files");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException
|
||||||
|
("Missing length number and/or files list");
|
||||||
|
|
||||||
|
List list = val.getList();
|
||||||
|
int size = list.size();
|
||||||
|
if (size == 0)
|
||||||
|
throw new InvalidBEncodingException("zero size files list");
|
||||||
|
|
||||||
|
files = new ArrayList(size);
|
||||||
|
lengths = new ArrayList(size);
|
||||||
|
long l = 0;
|
||||||
|
for (int i = 0; i < list.size(); i++)
|
||||||
|
{
|
||||||
|
Map desc = ((BEValue)list.get(i)).getMap();
|
||||||
|
val = (BEValue)desc.get("length");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing length number");
|
||||||
|
long len = val.getLong();
|
||||||
|
lengths.add(new Long(len));
|
||||||
|
l += len;
|
||||||
|
|
||||||
|
val = (BEValue)desc.get("path");
|
||||||
|
if (val == null)
|
||||||
|
throw new InvalidBEncodingException("Missing path list");
|
||||||
|
List path_list = val.getList();
|
||||||
|
int path_length = path_list.size();
|
||||||
|
if (path_length == 0)
|
||||||
|
throw new InvalidBEncodingException("zero size file path list");
|
||||||
|
|
||||||
|
List file = new ArrayList(path_length);
|
||||||
|
Iterator it = path_list.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
file.add(((BEValue)it.next()).getString());
|
||||||
|
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
length = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_hash = calculateInfoHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string representing the URL of the tracker for this torrent.
|
||||||
|
*/
|
||||||
|
public String getAnnounce()
|
||||||
|
{
|
||||||
|
return announce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original 20 byte SHA1 hash over the bencoded info map.
|
||||||
|
*/
|
||||||
|
public byte[] getInfoHash()
|
||||||
|
{
|
||||||
|
// XXX - Should we return a clone, just to be sure?
|
||||||
|
return info_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the piece hashes. Only used by storage so package local.
|
||||||
|
*/
|
||||||
|
byte[] getPieceHashes()
|
||||||
|
{
|
||||||
|
return piece_hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the requested name for the file or toplevel directory.
|
||||||
|
* If it is a toplevel directory name getFiles() will return a
|
||||||
|
* non-null List of file name hierarchy name.
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of lists of file name hierarchies or null if it is
|
||||||
|
* a single name. It has the same size as the list returned by
|
||||||
|
* getLengths().
|
||||||
|
*/
|
||||||
|
public List getFiles()
|
||||||
|
{
|
||||||
|
// XXX - Immutable?
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of Longs indication the size of the individual
|
||||||
|
* files, or null if it is a single file. It has the same size as
|
||||||
|
* the list returned by getFiles().
|
||||||
|
*/
|
||||||
|
public List getLengths()
|
||||||
|
{
|
||||||
|
// XXX - Immutable?
|
||||||
|
return lengths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of pieces.
|
||||||
|
*/
|
||||||
|
public int getPieces()
|
||||||
|
{
|
||||||
|
return piece_hashes.length/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the length of a piece. All pieces are of equal length
|
||||||
|
* except for the last one (<code>getPieces()-1</code>).
|
||||||
|
*
|
||||||
|
* @exception IndexOutOfBoundsException when piece is equal to or
|
||||||
|
* greater then the number of pieces in the torrent.
|
||||||
|
*/
|
||||||
|
public int getPieceLength(int piece)
|
||||||
|
{
|
||||||
|
int pieces = getPieces();
|
||||||
|
if (piece >= 0 && piece < pieces -1)
|
||||||
|
return piece_length;
|
||||||
|
else if (piece == pieces -1)
|
||||||
|
return (int)(length - piece * piece_length);
|
||||||
|
else
|
||||||
|
throw new IndexOutOfBoundsException("no piece: " + piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the given piece has the same SHA1 hash as the given
|
||||||
|
* byte array. Returns random results or IndexOutOfBoundsExceptions
|
||||||
|
* when the piece number is unknown.
|
||||||
|
*/
|
||||||
|
public boolean checkPiece(int piece, byte[] bs, int off, int length)
|
||||||
|
{
|
||||||
|
// Check digest
|
||||||
|
MessageDigest sha1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sha1 = MessageDigest.getInstance("SHA");
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException nsae)
|
||||||
|
{
|
||||||
|
throw new InternalError("No SHA digest available: " + nsae);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha1.update(bs, off, length);
|
||||||
|
byte[] hash = sha1.digest();
|
||||||
|
for (int i = 0; i < 20; i++)
|
||||||
|
if (hash[i] != piece_hashes[20 * piece + i])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total length of the torrent in bytes.
|
||||||
|
*/
|
||||||
|
public long getTotalLength()
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "MetaInfo[info_hash='" + hexencode(info_hash)
|
||||||
|
+ "', announce='" + announce
|
||||||
|
+ "', name='" + name
|
||||||
|
+ "', files=" + files
|
||||||
|
+ ", #pieces='" + piece_hashes.length/20
|
||||||
|
+ "', piece_length='" + piece_length
|
||||||
|
+ "', length='" + length
|
||||||
|
+ "']";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a byte array as a hex encoded string.
|
||||||
|
*/
|
||||||
|
private static String hexencode(byte[] bs)
|
||||||
|
{
|
||||||
|
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||||
|
for (int i = 0; i < bs.length; i++)
|
||||||
|
{
|
||||||
|
int c = bs[i] & 0xFF;
|
||||||
|
if (c < 16)
|
||||||
|
sb.append('0');
|
||||||
|
sb.append(Integer.toHexString(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a copy of this MetaInfo that shares everything except the
|
||||||
|
* announce URL.
|
||||||
|
*/
|
||||||
|
public MetaInfo reannounce(String announce)
|
||||||
|
{
|
||||||
|
return new MetaInfo(announce, name, files,
|
||||||
|
lengths, piece_length,
|
||||||
|
piece_hashes, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getTorrentData()
|
||||||
|
{
|
||||||
|
if (torrentdata == null)
|
||||||
|
{
|
||||||
|
Map m = new HashMap();
|
||||||
|
m.put("announce", announce);
|
||||||
|
Map info = createInfoMap();
|
||||||
|
m.put("info", info);
|
||||||
|
torrentdata = BEncoder.bencode(m);
|
||||||
|
}
|
||||||
|
return torrentdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map createInfoMap()
|
||||||
|
{
|
||||||
|
Map info = new HashMap();
|
||||||
|
info.put("name", name);
|
||||||
|
info.put("piece length", new Integer(piece_length));
|
||||||
|
info.put("pieces", piece_hashes);
|
||||||
|
if (files == null)
|
||||||
|
info.put("length", new Long(length));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List l = new ArrayList();
|
||||||
|
for (int i = 0; i < files.size(); i++)
|
||||||
|
{
|
||||||
|
Map file = new HashMap();
|
||||||
|
file.put("path", files.get(i));
|
||||||
|
file.put("length", lengths.get(i));
|
||||||
|
l.add(file);
|
||||||
|
}
|
||||||
|
info.put("files", l);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] calculateInfoHash()
|
||||||
|
{
|
||||||
|
Map info = createInfoMap();
|
||||||
|
byte[] infoBytes = BEncoder.bencode(info);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA");
|
||||||
|
return digest.digest(infoBytes);
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException nsa)
|
||||||
|
{
|
||||||
|
throw new InternalError(nsa.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
388
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
388
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
/* Peer - All public information concerning a peer.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
|
||||||
|
public class Peer implements Comparable
|
||||||
|
{
|
||||||
|
// Identifying property, the peer id of the other side.
|
||||||
|
private final PeerID peerID;
|
||||||
|
|
||||||
|
private final byte[] my_id;
|
||||||
|
private final MetaInfo metainfo;
|
||||||
|
|
||||||
|
// The data in/output streams set during the handshake and used by
|
||||||
|
// the actual connections.
|
||||||
|
private DataInputStream din;
|
||||||
|
private DataOutputStream dout;
|
||||||
|
|
||||||
|
// Keeps state for in/out connections. Non-null when the handshake
|
||||||
|
// was successful, the connection setup and runs
|
||||||
|
PeerState state;
|
||||||
|
|
||||||
|
private boolean deregister = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a disconnected peer given a PeerID, your own id and the
|
||||||
|
* relevant MetaInfo.
|
||||||
|
*/
|
||||||
|
public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this.peerID = peerID;
|
||||||
|
this.my_id = my_id;
|
||||||
|
this.metainfo = metainfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a unconnected peer from the input and output stream got
|
||||||
|
* from the socket. Note that the complete handshake (which can take
|
||||||
|
* some time or block indefinitely) is done in the calling Thread to
|
||||||
|
* get the remote peer id. To completely start the connection call
|
||||||
|
* the connect() method.
|
||||||
|
*
|
||||||
|
* @exception IOException when an error occurred during the handshake.
|
||||||
|
*/
|
||||||
|
public Peer(final I2PSocket sock, BufferedInputStream bis,
|
||||||
|
BufferedOutputStream bos, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this.my_id = my_id;
|
||||||
|
this.metainfo = metainfo;
|
||||||
|
|
||||||
|
byte[] id = handshake(bis, bos);
|
||||||
|
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the id of the peer.
|
||||||
|
*/
|
||||||
|
public PeerID getPeerID()
|
||||||
|
{
|
||||||
|
return peerID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the String representation of the peerID.
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return peerID.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hash code of a Peer is the hash code of the peerID.
|
||||||
|
*/
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return peerID.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two Peers are equal when they have the same PeerID.
|
||||||
|
* All other properties are ignored.
|
||||||
|
*/
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (o instanceof Peer)
|
||||||
|
{
|
||||||
|
Peer p = (Peer)o;
|
||||||
|
return peerID.equals(p.peerID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the PeerIDs.
|
||||||
|
*/
|
||||||
|
public int compareTo(Object o)
|
||||||
|
{
|
||||||
|
Peer p = (Peer)o;
|
||||||
|
return peerID.compareTo(p.peerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the connection to the other peer. This method does not
|
||||||
|
* return until the connection is terminated.
|
||||||
|
*
|
||||||
|
* When the connection is correctly started the connected() method
|
||||||
|
* of the given PeerListener is called. If the connection ends or
|
||||||
|
* the connection could not be setup correctly the disconnected()
|
||||||
|
* method is called.
|
||||||
|
*
|
||||||
|
* If the given BitField is non-null it is send to the peer as first
|
||||||
|
* message.
|
||||||
|
*/
|
||||||
|
public void runConnection(PeerListener listener, BitField bitfield)
|
||||||
|
{
|
||||||
|
if (state != null)
|
||||||
|
throw new IllegalStateException("Peer already started");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Do we need to handshake?
|
||||||
|
if (din == null)
|
||||||
|
{
|
||||||
|
I2PSocket sock = I2PSnarkUtil.instance().connect(peerID);
|
||||||
|
BufferedInputStream bis
|
||||||
|
= new BufferedInputStream(sock.getInputStream());
|
||||||
|
BufferedOutputStream bos
|
||||||
|
= new BufferedOutputStream(sock.getOutputStream());
|
||||||
|
byte [] id = handshake(bis, bos);
|
||||||
|
byte [] expected_id = peerID.getID();
|
||||||
|
if (!Arrays.equals(expected_id, id))
|
||||||
|
throw new IOException("Unexpected peerID '"
|
||||||
|
+ PeerID.idencode(id)
|
||||||
|
+ "' expected '"
|
||||||
|
+ PeerID.idencode(expected_id) + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||||
|
PeerConnectionOut out = new PeerConnectionOut(this, dout);
|
||||||
|
PeerState s = new PeerState(this, listener, metainfo, in, out);
|
||||||
|
|
||||||
|
// Send our bitmap
|
||||||
|
if (bitfield != null)
|
||||||
|
s.out.sendBitfield(bitfield);
|
||||||
|
|
||||||
|
// We are up and running!
|
||||||
|
state = s;
|
||||||
|
listener.connected(this);
|
||||||
|
|
||||||
|
// Use this thread for running the incomming connection.
|
||||||
|
// The outgoing connection has created its own Thread.
|
||||||
|
s.in.run();
|
||||||
|
}
|
||||||
|
catch(IOException eofe)
|
||||||
|
{
|
||||||
|
// Ignore, probably just the other side closing the connection.
|
||||||
|
// Or refusing the connection, timing out, etc.
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
Snark.debug(this + ": " + t, Snark.ERROR);
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (deregister) listener.disconnected(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets DataIn/OutputStreams, does the handshake and returns the id
|
||||||
|
* reported by the other side.
|
||||||
|
*/
|
||||||
|
private byte[] handshake(BufferedInputStream bis, BufferedOutputStream bos)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
din = new DataInputStream(bis);
|
||||||
|
dout = new DataOutputStream(bos);
|
||||||
|
|
||||||
|
// Handshake write - header
|
||||||
|
dout.write(19);
|
||||||
|
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||||
|
// Handshake write - zeros
|
||||||
|
byte[] zeros = new byte[8];
|
||||||
|
dout.write(zeros);
|
||||||
|
// Handshake write - metainfo hash
|
||||||
|
byte[] shared_hash = metainfo.getInfoHash();
|
||||||
|
dout.write(shared_hash);
|
||||||
|
// Handshake write - peer id
|
||||||
|
dout.write(my_id);
|
||||||
|
dout.flush();
|
||||||
|
|
||||||
|
// Handshake read - header
|
||||||
|
byte b = din.readByte();
|
||||||
|
if (b != 19)
|
||||||
|
throw new IOException("Handshake failure, expected 19, got "
|
||||||
|
+ (b & 0xff));
|
||||||
|
|
||||||
|
byte[] bs = new byte[19];
|
||||||
|
din.readFully(bs);
|
||||||
|
String bittorrentProtocol = new String(bs, "UTF-8");
|
||||||
|
if (!"BitTorrent protocol".equals(bittorrentProtocol))
|
||||||
|
throw new IOException("Handshake failure, expected "
|
||||||
|
+ "'Bittorrent protocol', got '"
|
||||||
|
+ bittorrentProtocol + "'");
|
||||||
|
|
||||||
|
// Handshake read - zeros
|
||||||
|
din.readFully(zeros);
|
||||||
|
|
||||||
|
// Handshake read - metainfo hash
|
||||||
|
bs = new byte[20];
|
||||||
|
din.readFully(bs);
|
||||||
|
if (!Arrays.equals(shared_hash, bs))
|
||||||
|
throw new IOException("Unexpected MetaInfo hash");
|
||||||
|
|
||||||
|
// Handshake read - peer id
|
||||||
|
din.readFully(bs);
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return state != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects this peer if it was connected. If deregister is
|
||||||
|
* true, PeerListener.disconnected() will be called when the
|
||||||
|
* connection is completely terminated. Otherwise the connection is
|
||||||
|
* silently terminated.
|
||||||
|
*/
|
||||||
|
public void disconnect(boolean deregister)
|
||||||
|
{
|
||||||
|
// Both in and out connection will call this.
|
||||||
|
this.deregister = deregister;
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
if (s != null)
|
||||||
|
{
|
||||||
|
state = null;
|
||||||
|
|
||||||
|
PeerConnectionIn in = s.in;
|
||||||
|
if (in != null)
|
||||||
|
in.disconnect();
|
||||||
|
PeerConnectionOut out = s.out;
|
||||||
|
if (out != null)
|
||||||
|
out.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the peer we have another piece.
|
||||||
|
*/
|
||||||
|
public void have(int piece)
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
if (s != null)
|
||||||
|
s.havePiece(piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the peer is interested in pieces we have. Returns
|
||||||
|
* false if not connected.
|
||||||
|
*/
|
||||||
|
public boolean isInterested()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s != null) && s.interested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not we are interested in pieces from this peer.
|
||||||
|
* Defaults to false. When interest is true and this peer unchokes
|
||||||
|
* us then we start downloading from it. Has no effect when not connected.
|
||||||
|
*/
|
||||||
|
public void setInteresting(boolean interest)
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
if (s != null)
|
||||||
|
s.setInteresting(interest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the peer has pieces we want from it. Returns false
|
||||||
|
* if not connected.
|
||||||
|
*/
|
||||||
|
public boolean isInteresting()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s != null) && s.interesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not we are choking the peer. Defaults to
|
||||||
|
* true. When choke is false and the peer requests some pieces we
|
||||||
|
* upload them, otherwise requests of this peer are ignored.
|
||||||
|
*/
|
||||||
|
public void setChoking(boolean choke)
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
if (s != null)
|
||||||
|
s.setChoking(choke);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not we are choking the peer. Returns true when not connected.
|
||||||
|
*/
|
||||||
|
public boolean isChoking()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s == null) || s.choking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the peer choked us. Returns true when not connected.
|
||||||
|
*/
|
||||||
|
public boolean isChoked()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s == null) || s.choked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes that have been downloaded.
|
||||||
|
* Can be reset to zero with <code>resetCounters()</code>/
|
||||||
|
*/
|
||||||
|
public long getDownloaded()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s != null) ? s.downloaded : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes that have been uploaded.
|
||||||
|
* Can be reset to zero with <code>resetCounters()</code>/
|
||||||
|
*/
|
||||||
|
public long getUploaded()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
return (s != null) ? s.uploaded : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the downloaded and uploaded counters to zero.
|
||||||
|
*/
|
||||||
|
public void resetCounters()
|
||||||
|
{
|
||||||
|
PeerState s = state;
|
||||||
|
if (s != null)
|
||||||
|
{
|
||||||
|
s.downloaded = 0;
|
||||||
|
s.uploaded = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
62
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/* PeerAcceptor - Accepts incomming connections from peers.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts incomming connections from peers. The ConnectionAcceptor
|
||||||
|
* will call the connection() method when it detects an incomming BT
|
||||||
|
* protocol connection. The PeerAcceptor will then create a new peer
|
||||||
|
* if the PeerCoordinator wants more peers.
|
||||||
|
*/
|
||||||
|
public class PeerAcceptor
|
||||||
|
{
|
||||||
|
private final PeerCoordinator coordinator;
|
||||||
|
|
||||||
|
public PeerAcceptor(PeerCoordinator coordinator)
|
||||||
|
{
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connection(I2PSocket socket,
|
||||||
|
BufferedInputStream bis, BufferedOutputStream bos)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
if (coordinator.needPeers())
|
||||||
|
{
|
||||||
|
// XXX: inside this Peer constructor's handshake is where you'd deal with the other
|
||||||
|
// side saying they want to communicate with another torrent - aka multitorrent
|
||||||
|
// support. you'd then want to grab the meta info /they/ want, look that up in
|
||||||
|
// our own list of active torrents, and put it on the right coordinator for it.
|
||||||
|
// this currently, however, throws an IOException if the metainfo doesn't match
|
||||||
|
// coodinator.getMetaInfo (Peer.java:242)
|
||||||
|
Peer peer = new Peer(socket, bis, bos, coordinator.getID(),
|
||||||
|
coordinator.getMetaInfo());
|
||||||
|
coordinator.addPeer(peer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
197
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
197
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/* PeerCheckTasks - TimerTask that checks for good/bad up/downloaders.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimerTask that checks for good/bad up/downloader. Works together
|
||||||
|
* with the PeerCoordinator to select which Peers get (un)choked.
|
||||||
|
*/
|
||||||
|
class PeerCheckerTask extends TimerTask
|
||||||
|
{
|
||||||
|
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||||
|
|
||||||
|
private final PeerCoordinator coordinator;
|
||||||
|
|
||||||
|
PeerCheckerTask(PeerCoordinator coordinator)
|
||||||
|
{
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
synchronized(coordinator.peers)
|
||||||
|
{
|
||||||
|
// Calculate total uploading and worst downloader.
|
||||||
|
long worstdownload = Long.MAX_VALUE;
|
||||||
|
Peer worstDownloader = null;
|
||||||
|
|
||||||
|
int peers = 0;
|
||||||
|
int uploaders = 0;
|
||||||
|
int downloaders = 0;
|
||||||
|
int interested = 0;
|
||||||
|
int interesting = 0;
|
||||||
|
int choking = 0;
|
||||||
|
int choked = 0;
|
||||||
|
|
||||||
|
long uploaded = 0;
|
||||||
|
long downloaded = 0;
|
||||||
|
|
||||||
|
// Keep track of peers we remove now,
|
||||||
|
// we will add them back to the end of the list.
|
||||||
|
List removed = new ArrayList();
|
||||||
|
|
||||||
|
Iterator it = coordinator.peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)it.next();
|
||||||
|
|
||||||
|
// Remove dying peers
|
||||||
|
if (!peer.isConnected())
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
peers++;
|
||||||
|
|
||||||
|
if (!peer.isChoking())
|
||||||
|
uploaders++;
|
||||||
|
if (!peer.isChoked() && peer.isInteresting())
|
||||||
|
downloaders++;
|
||||||
|
if (peer.isInterested())
|
||||||
|
interested++;
|
||||||
|
if (peer.isInteresting())
|
||||||
|
interesting++;
|
||||||
|
if (peer.isChoking())
|
||||||
|
choking++;
|
||||||
|
if (peer.isChoked())
|
||||||
|
choked++;
|
||||||
|
|
||||||
|
// XXX - We should calculate the up/download rate a bit
|
||||||
|
// more intelligently
|
||||||
|
long upload = peer.getUploaded();
|
||||||
|
uploaded += upload;
|
||||||
|
long download = peer.getDownloaded();
|
||||||
|
downloaded += download;
|
||||||
|
peer.resetCounters();
|
||||||
|
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
{
|
||||||
|
Snark.debug(peer + ":", Snark.DEBUG);
|
||||||
|
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||||
|
+ " dl: " + download/KILOPERSECOND
|
||||||
|
+ " i: " + peer.isInterested()
|
||||||
|
+ " I: " + peer.isInteresting()
|
||||||
|
+ " c: " + peer.isChoking()
|
||||||
|
+ " C: " + peer.isChoked(),
|
||||||
|
Snark.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are at our max uploaders and we have lots of other
|
||||||
|
// interested peers try to make some room.
|
||||||
|
// (Note use of coordinator.uploaders)
|
||||||
|
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||||
|
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||||
|
&& !peer.isChoking())
|
||||||
|
{
|
||||||
|
// Check if it still wants pieces from us.
|
||||||
|
if (!peer.isInterested())
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Choke uninterested peer: " + peer,
|
||||||
|
Snark.INFO);
|
||||||
|
peer.setChoking(true);
|
||||||
|
uploaders--;
|
||||||
|
coordinator.uploaders--;
|
||||||
|
|
||||||
|
// Put it at the back of the list
|
||||||
|
it.remove();
|
||||||
|
removed.add(peer);
|
||||||
|
}
|
||||||
|
else if (peer.isChoked())
|
||||||
|
{
|
||||||
|
// If they are choking us make someone else a downloader
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||||
|
peer.setChoking(true);
|
||||||
|
uploaders--;
|
||||||
|
coordinator.uploaders--;
|
||||||
|
|
||||||
|
// Put it at the back of the list
|
||||||
|
it.remove();
|
||||||
|
removed.add(peer);
|
||||||
|
}
|
||||||
|
else if (peer.isInteresting()
|
||||||
|
&& !peer.isChoked()
|
||||||
|
&& download == 0)
|
||||||
|
{
|
||||||
|
// We are downloading but didn't receive anything...
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Choke downloader that doesn't deliver:"
|
||||||
|
+ peer, Snark.DEBUG);
|
||||||
|
peer.setChoking(true);
|
||||||
|
uploaders--;
|
||||||
|
coordinator.uploaders--;
|
||||||
|
|
||||||
|
// Put it at the back of the list
|
||||||
|
it.remove();
|
||||||
|
removed.add(peer);
|
||||||
|
}
|
||||||
|
else if (!peer.isChoking() && download < worstdownload)
|
||||||
|
{
|
||||||
|
// Make sure download is good if we are uploading
|
||||||
|
worstdownload = download;
|
||||||
|
worstDownloader = peer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resync actual uploaders value
|
||||||
|
// (can shift a bit by disconnecting peers)
|
||||||
|
coordinator.uploaders = uploaders;
|
||||||
|
|
||||||
|
// Remove the worst downloader if needed.
|
||||||
|
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||||
|
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||||
|
&& worstDownloader != null)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||||
|
Snark.DEBUG);
|
||||||
|
|
||||||
|
worstDownloader.setChoking(true);
|
||||||
|
coordinator.uploaders--;
|
||||||
|
|
||||||
|
// Put it at the back of the list
|
||||||
|
coordinator.peers.remove(worstDownloader);
|
||||||
|
removed.add(worstDownloader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimistically unchoke a peer
|
||||||
|
coordinator.unchokePeer();
|
||||||
|
|
||||||
|
// Put peers back at the end of the list that we removed earlier.
|
||||||
|
coordinator.peers.addAll(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
156
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/* PeerConnectionIn - Handles incomming messages and hands them to PeerState.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class PeerConnectionIn implements Runnable
|
||||||
|
{
|
||||||
|
private final Peer peer;
|
||||||
|
private final DataInputStream din;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private boolean quit;
|
||||||
|
|
||||||
|
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||||
|
{
|
||||||
|
this.peer = peer;
|
||||||
|
this.din = din;
|
||||||
|
quit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect()
|
||||||
|
{
|
||||||
|
if (quit == true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
quit = true;
|
||||||
|
Thread t = thread;
|
||||||
|
if (t != null)
|
||||||
|
t.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
thread = Thread.currentThread();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PeerState ps = peer.state;
|
||||||
|
while (!quit && ps != null)
|
||||||
|
{
|
||||||
|
// Common variables used for some messages.
|
||||||
|
int piece;
|
||||||
|
int begin;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
// Wait till we hear something...
|
||||||
|
// The length of a complete message in bytes.
|
||||||
|
int i = din.readInt();
|
||||||
|
if (i < 0)
|
||||||
|
throw new IOException("Unexpected length prefix: " + i);
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
ps.keepAliveMessage();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte b = din.readByte();
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = b;
|
||||||
|
switch (b)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
ps.chokeMessage(true);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
ps.chokeMessage(false);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ps.interestedMessage(true);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ps.interestedMessage(false);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
piece = din.readInt();
|
||||||
|
ps.haveMessage(piece);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
byte[] bitmap = new byte[i-1];
|
||||||
|
din.readFully(bitmap);
|
||||||
|
ps.bitfieldMessage(bitmap);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
piece = din.readInt();
|
||||||
|
begin = din.readInt();
|
||||||
|
len = din.readInt();
|
||||||
|
ps.requestMessage(piece, begin, len);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
piece = din.readInt();
|
||||||
|
begin = din.readInt();
|
||||||
|
len = i-9;
|
||||||
|
Request req = ps.getOutstandingRequest(piece, begin, len);
|
||||||
|
byte[] piece_bytes;
|
||||||
|
if (req != null)
|
||||||
|
{
|
||||||
|
piece_bytes = req.bs;
|
||||||
|
din.readFully(piece_bytes, begin, len);
|
||||||
|
ps.pieceMessage(req);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// XXX - Consume but throw away afterwards.
|
||||||
|
piece_bytes = new byte[len];
|
||||||
|
din.readFully(piece_bytes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
piece = din.readInt();
|
||||||
|
begin = din.readInt();
|
||||||
|
len = din.readInt();
|
||||||
|
ps.cancelMessage(piece, begin, len);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
byte[] bs = new byte[i-1];
|
||||||
|
din.readFully(bs);
|
||||||
|
ps.unknownMessage(b, bs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
// Ignore, probably the other side closed connection.
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
peer.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
342
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
342
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
/* PeerConnectionOut - Keeps a queue of outgoing messages and delivers them.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class PeerConnectionOut implements Runnable
|
||||||
|
{
|
||||||
|
private final Peer peer;
|
||||||
|
private final DataOutputStream dout;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private boolean quit;
|
||||||
|
|
||||||
|
// Contains Messages.
|
||||||
|
private List sendQueue = new ArrayList();
|
||||||
|
|
||||||
|
public PeerConnectionOut(Peer peer, DataOutputStream dout)
|
||||||
|
{
|
||||||
|
this.peer = peer;
|
||||||
|
this.dout = dout;
|
||||||
|
|
||||||
|
quit = false;
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuesly monitors for more outgoing messages that have to be send.
|
||||||
|
* Stops if quit is true of an IOException occurs.
|
||||||
|
*/
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!quit)
|
||||||
|
{
|
||||||
|
Message m = null;
|
||||||
|
PeerState state = null;
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
while (!quit && sendQueue.isEmpty())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Make sure everything will reach the other side.
|
||||||
|
dout.flush();
|
||||||
|
|
||||||
|
// Wait till more data arrives.
|
||||||
|
sendQueue.wait();
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie)
|
||||||
|
{
|
||||||
|
/* ignored */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = peer.state;
|
||||||
|
if (!quit && state != null)
|
||||||
|
{
|
||||||
|
// Piece messages are big. So if there are other
|
||||||
|
// (control) messages make sure they are send first.
|
||||||
|
// Also remove request messages from the queue if
|
||||||
|
// we are currently being choked to prevent them from
|
||||||
|
// being send even if we get unchoked a little later.
|
||||||
|
// (Since we will resent them anyway in that case.)
|
||||||
|
// And remove piece messages if we are choking.
|
||||||
|
Iterator it = sendQueue.iterator();
|
||||||
|
while (m == null && it.hasNext())
|
||||||
|
{
|
||||||
|
Message nm = (Message)it.next();
|
||||||
|
if (nm.type == Message.PIECE)
|
||||||
|
{
|
||||||
|
if (state.choking)
|
||||||
|
it.remove();
|
||||||
|
nm = null;
|
||||||
|
}
|
||||||
|
else if (nm.type == Message.REQUEST && state.choked)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
nm = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m == null && nm != null)
|
||||||
|
{
|
||||||
|
m = nm;
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m == null && sendQueue.size() > 0)
|
||||||
|
m = (Message)sendQueue.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m != null)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.ALL)
|
||||||
|
Snark.debug("Send " + peer + ": " + m, Snark.ALL);
|
||||||
|
m.sendMessage(dout);
|
||||||
|
|
||||||
|
// Remove all piece messages after sending a choke message.
|
||||||
|
if (m.type == Message.CHOKE)
|
||||||
|
removeMessage(Message.PIECE);
|
||||||
|
|
||||||
|
// XXX - Should also register overhead...
|
||||||
|
if (m.type == Message.PIECE)
|
||||||
|
state.uploaded(m.len);
|
||||||
|
|
||||||
|
m = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
// Ignore, probably other side closed connection.
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
quit = true;
|
||||||
|
peer.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
if (quit == true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
quit = true;
|
||||||
|
thread.interrupt();
|
||||||
|
|
||||||
|
sendQueue.clear();
|
||||||
|
sendQueue.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a message to the sendQueue and notifies the method waiting
|
||||||
|
* on the sendQueue to change.
|
||||||
|
*/
|
||||||
|
private void addMessage(Message m)
|
||||||
|
{
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
sendQueue.add(m);
|
||||||
|
sendQueue.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a particular message type from the queue.
|
||||||
|
*
|
||||||
|
* @param type the Message type to remove.
|
||||||
|
* @returns true when a message of the given type was removed, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
private boolean removeMessage(int type)
|
||||||
|
{
|
||||||
|
boolean removed = false;
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
Iterator it = sendQueue.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Message m = (Message)it.next();
|
||||||
|
if (m.type == type)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendAlive()
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.KEEP_ALIVE;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendChoke(boolean choke)
|
||||||
|
{
|
||||||
|
// We cancel the (un)choke but keep PIECE messages.
|
||||||
|
// PIECE messages are purged if a choke is actually send.
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
int inverseType = choke ? Message.UNCHOKE
|
||||||
|
: Message.CHOKE;
|
||||||
|
if (!removeMessage(inverseType))
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
if (choke)
|
||||||
|
m.type = Message.CHOKE;
|
||||||
|
else
|
||||||
|
m.type = Message.UNCHOKE;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendInterest(boolean interest)
|
||||||
|
{
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
int inverseType = interest ? Message.UNINTERESTED
|
||||||
|
: Message.INTERESTED;
|
||||||
|
if (!removeMessage(inverseType))
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
if (interest)
|
||||||
|
m.type = Message.INTERESTED;
|
||||||
|
else
|
||||||
|
m.type = Message.UNINTERESTED;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendHave(int piece)
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.HAVE;
|
||||||
|
m.piece = piece;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendBitfield(BitField bitfield)
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.BITFIELD;
|
||||||
|
m.data = bitfield.getFieldBytes();
|
||||||
|
m.off = 0;
|
||||||
|
m.len = m.data.length;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRequests(List requests)
|
||||||
|
{
|
||||||
|
Iterator it = requests.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Request req = (Request)it.next();
|
||||||
|
sendRequest(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRequest(Request req)
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.REQUEST;
|
||||||
|
m.piece = req.piece;
|
||||||
|
m.begin = req.off;
|
||||||
|
m.length = req.len;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||||
|
{
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.PIECE;
|
||||||
|
m.piece = piece;
|
||||||
|
m.begin = begin;
|
||||||
|
m.length = length;
|
||||||
|
m.data = bytes;
|
||||||
|
m.off = begin;
|
||||||
|
m.len = length;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendCancel(Request req)
|
||||||
|
{
|
||||||
|
// See if it is still in our send queue
|
||||||
|
synchronized(sendQueue)
|
||||||
|
{
|
||||||
|
Iterator it = sendQueue.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Message m = (Message)it.next();
|
||||||
|
if (m.type == Message.REQUEST
|
||||||
|
&& m.piece == req.piece
|
||||||
|
&& m.begin == req.off
|
||||||
|
&& m.length == req.len)
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always send, just to be sure it it is really canceled.
|
||||||
|
Message m = new Message();
|
||||||
|
m.type = Message.CANCEL;
|
||||||
|
m.piece = req.piece;
|
||||||
|
m.begin = req.off;
|
||||||
|
m.length = req.len;
|
||||||
|
addMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by the PeerState when the other side doesn't want this
|
||||||
|
// request to be handled anymore. Removes any pending Piece Message
|
||||||
|
// from out send queue.
|
||||||
|
void cancelRequest(int piece, int begin, int length)
|
||||||
|
{
|
||||||
|
synchronized (sendQueue)
|
||||||
|
{
|
||||||
|
Iterator it = sendQueue.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Message m = (Message)it.next();
|
||||||
|
if (m.type == Message.PIECE
|
||||||
|
&& m.piece == piece
|
||||||
|
&& m.begin == begin
|
||||||
|
&& m.length == length)
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
508
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
508
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
/* PeerCoordinator - Coordinates which peers do what (up and downloading).
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates what peer does what.
|
||||||
|
*/
|
||||||
|
public class PeerCoordinator implements PeerListener
|
||||||
|
{
|
||||||
|
final MetaInfo metainfo;
|
||||||
|
final Storage storage;
|
||||||
|
|
||||||
|
// package local for access by CheckDownLoadersTask
|
||||||
|
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||||
|
final static int MAX_CONNECTIONS = 24;
|
||||||
|
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||||
|
|
||||||
|
// Approximation of the number of current uploaders.
|
||||||
|
// Resynced by PeerChecker once in a while.
|
||||||
|
int uploaders = 0;
|
||||||
|
|
||||||
|
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||||
|
// int downloaders = 0;
|
||||||
|
|
||||||
|
private long uploaded;
|
||||||
|
private long downloaded;
|
||||||
|
|
||||||
|
// synchronize on this when changing peers or downloaders
|
||||||
|
final List peers = new ArrayList();
|
||||||
|
|
||||||
|
/** Timer to handle all periodical tasks. */
|
||||||
|
private final Timer timer = new Timer(true);
|
||||||
|
|
||||||
|
private final byte[] id;
|
||||||
|
|
||||||
|
// Some random wanted pieces
|
||||||
|
private final List wantedPieces;
|
||||||
|
|
||||||
|
private boolean halted = false;
|
||||||
|
|
||||||
|
private final CoordinatorListener listener;
|
||||||
|
|
||||||
|
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
|
||||||
|
CoordinatorListener listener)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.metainfo = metainfo;
|
||||||
|
this.storage = storage;
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
// Make a random list of piece numbers
|
||||||
|
wantedPieces = new ArrayList();
|
||||||
|
BitField bitfield = storage.getBitField();
|
||||||
|
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||||
|
if (!bitfield.get(i))
|
||||||
|
wantedPieces.add(new Integer(i));
|
||||||
|
Collections.shuffle(wantedPieces);
|
||||||
|
|
||||||
|
// Install a timer to check the uploaders.
|
||||||
|
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getID()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean completed()
|
||||||
|
{
|
||||||
|
return storage.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getPeers()
|
||||||
|
{
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
return peers.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how many bytes are still needed to get the complete file.
|
||||||
|
*/
|
||||||
|
public long getLeft()
|
||||||
|
{
|
||||||
|
// XXX - Only an approximation.
|
||||||
|
return storage.needed() * metainfo.getPieceLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of uploaded bytes of all peers.
|
||||||
|
*/
|
||||||
|
public long getUploaded()
|
||||||
|
{
|
||||||
|
return uploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of downloaded bytes of all peers.
|
||||||
|
*/
|
||||||
|
public long getDownloaded()
|
||||||
|
{
|
||||||
|
return downloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MetaInfo getMetaInfo()
|
||||||
|
{
|
||||||
|
return metainfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean needPeers()
|
||||||
|
{
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
return !halted && peers.size() < MAX_CONNECTIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void halt()
|
||||||
|
{
|
||||||
|
halted = true;
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
// Stop peer checker task.
|
||||||
|
timer.cancel();
|
||||||
|
|
||||||
|
// Stop peers.
|
||||||
|
Iterator it = peers.iterator();
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)it.next();
|
||||||
|
peer.disconnect();
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connected(Peer peer)
|
||||||
|
{
|
||||||
|
if (halted)
|
||||||
|
{
|
||||||
|
peer.disconnect(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
if (peerIDInList(peer.getPeerID(), peers))
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Already connected to: " + peer, Snark.INFO);
|
||||||
|
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("New connection to peer: " + peer, Snark.INFO);
|
||||||
|
|
||||||
|
// Add it to the beginning of the list.
|
||||||
|
// And try to optimistically make it a uploader.
|
||||||
|
peers.add(0, peer);
|
||||||
|
unchokePeer();
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean peerIDInList(PeerID pid, List peers)
|
||||||
|
{
|
||||||
|
Iterator it = peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
if (pid.sameID(((Peer)it.next()).getPeerID()))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPeer(final Peer peer)
|
||||||
|
{
|
||||||
|
if (halted)
|
||||||
|
{
|
||||||
|
peer.disconnect(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean need_more;
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_more)
|
||||||
|
{
|
||||||
|
// Run the peer with us as listener and the current bitfield.
|
||||||
|
final PeerListener listener = this;
|
||||||
|
final BitField bitfield = storage.getBitField();
|
||||||
|
Runnable r = new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
peer.runConnection(listener, bitfield);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
String threadName = peer.toString();
|
||||||
|
new Thread(r, threadName).start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
if (peer.isConnected())
|
||||||
|
Snark.debug("Add peer already connected: " + peer, Snark.INFO);
|
||||||
|
else
|
||||||
|
Snark.debug("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||||
|
+ " not accepting extra peer: " + peer, Snark.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// (Optimistically) unchoke. Should be called with peers synchronized
|
||||||
|
void unchokePeer()
|
||||||
|
{
|
||||||
|
// linked list will contain all interested peers that we choke.
|
||||||
|
// At the start are the peers that have us unchoked at the end the
|
||||||
|
// other peer that are interested, but are choking us.
|
||||||
|
List interested = new LinkedList();
|
||||||
|
Iterator it = peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)it.next();
|
||||||
|
boolean remove = false;
|
||||||
|
if (uploaders < MAX_UPLOADERS
|
||||||
|
&& peer.isChoking()
|
||||||
|
&& peer.isInterested())
|
||||||
|
{
|
||||||
|
if (!peer.isChoked())
|
||||||
|
interested.add(0, peer);
|
||||||
|
else
|
||||||
|
interested.add(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)interested.remove(0);
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||||
|
peer.setChoking(false);
|
||||||
|
uploaders++;
|
||||||
|
// Put peer back at the end of the list.
|
||||||
|
peers.remove(peer);
|
||||||
|
peers.add(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBitMap()
|
||||||
|
{
|
||||||
|
return storage.getBitField().getFieldBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if we don't have the given piece yet.
|
||||||
|
*/
|
||||||
|
public boolean gotHave(Peer peer, int piece)
|
||||||
|
{
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
|
||||||
|
synchronized(wantedPieces)
|
||||||
|
{
|
||||||
|
return wantedPieces.contains(new Integer(piece));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given bitfield contains at least one piece we
|
||||||
|
* are interested in.
|
||||||
|
*/
|
||||||
|
public boolean gotBitField(Peer peer, BitField bitfield)
|
||||||
|
{
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
|
||||||
|
synchronized(wantedPieces)
|
||||||
|
{
|
||||||
|
Iterator it = wantedPieces.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
int i = ((Integer)it.next()).intValue();
|
||||||
|
if (bitfield.get(i))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns one of pieces in the given BitField that is still wanted or
|
||||||
|
* -1 if none of the given pieces are wanted.
|
||||||
|
*/
|
||||||
|
public int wantPiece(Peer peer, BitField havePieces)
|
||||||
|
{
|
||||||
|
if (halted)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
synchronized(wantedPieces)
|
||||||
|
{
|
||||||
|
Integer piece = null;
|
||||||
|
Iterator it = wantedPieces.iterator();
|
||||||
|
while (piece == null && it.hasNext())
|
||||||
|
{
|
||||||
|
Integer i = (Integer)it.next();
|
||||||
|
if (havePieces.get(i.intValue()))
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
piece = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (piece == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// We add it back at the back of the list. It will be removed
|
||||||
|
// if gotPiece is called later. This means that the last
|
||||||
|
// couple of pieces might very well be asked from multiple
|
||||||
|
// peers but that is OK.
|
||||||
|
wantedPieces.add(piece);
|
||||||
|
|
||||||
|
return piece.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a byte array containing the requested piece or null of
|
||||||
|
* the piece is unknown.
|
||||||
|
*/
|
||||||
|
public byte[] gotRequest(Peer peer, int piece)
|
||||||
|
{
|
||||||
|
if (halted)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return storage.getPiece(piece);
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
Snark.fatal("Error reading storage", ioe);
|
||||||
|
return null; // Never reached.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a peer has uploaded some bytes of a piece.
|
||||||
|
*/
|
||||||
|
public void uploaded(Peer peer, int size)
|
||||||
|
{
|
||||||
|
uploaded += size;
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a peer has downloaded some bytes of a piece.
|
||||||
|
*/
|
||||||
|
public void downloaded(Peer peer, int size)
|
||||||
|
{
|
||||||
|
downloaded += size;
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if the piece is no good (according to the hash).
|
||||||
|
* In that case the peer that supplied the piece should probably be
|
||||||
|
* blacklisted.
|
||||||
|
*/
|
||||||
|
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||||
|
{
|
||||||
|
if (halted)
|
||||||
|
return true; // We don't actually care anymore.
|
||||||
|
|
||||||
|
synchronized(wantedPieces)
|
||||||
|
{
|
||||||
|
Integer p = new Integer(piece);
|
||||||
|
if (!wantedPieces.contains(p))
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug(peer + " piece " + piece + " no longer needed",
|
||||||
|
Snark.INFO);
|
||||||
|
|
||||||
|
// No need to announce have piece to peers.
|
||||||
|
// Assume we got a good piece, we don't really care anymore.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (storage.putPiece(piece, bs))
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Recv p" + piece + " " + peer, Snark.INFO);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Oops. We didn't actually download this then... :(
|
||||||
|
downloaded -= metainfo.getPieceLength(piece);
|
||||||
|
if (Snark.debug >= Snark.NOTICE)
|
||||||
|
Snark.debug("Got BAD piece " + piece + " from " + peer,
|
||||||
|
Snark.NOTICE);
|
||||||
|
return false; // No need to announce BAD piece to peers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
Snark.fatal("Error writing storage", ioe);
|
||||||
|
}
|
||||||
|
wantedPieces.remove(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announce to the world we have it!
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
Iterator it = peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Peer p = (Peer)it.next();
|
||||||
|
if (p.isConnected())
|
||||||
|
p.have(piece);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void gotChoke(Peer peer, boolean choke)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got choke(" + choke + "): " + peer, Snark.INFO);
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void gotInterest(Peer peer, boolean interest)
|
||||||
|
{
|
||||||
|
if (interest)
|
||||||
|
{
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
if (uploaders < MAX_UPLOADERS)
|
||||||
|
{
|
||||||
|
if(peer.isChoking())
|
||||||
|
{
|
||||||
|
uploaders++;
|
||||||
|
peer.setChoking(false);
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnected(Peer peer)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Disconnected " + peer, Snark.INFO);
|
||||||
|
|
||||||
|
synchronized(peers)
|
||||||
|
{
|
||||||
|
// Make sure it is no longer in our lists
|
||||||
|
if (peers.remove(peer))
|
||||||
|
{
|
||||||
|
// Unchoke some random other peer
|
||||||
|
unchokePeer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.peerChange(this, peer);
|
||||||
|
}
|
||||||
|
}
|
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
/* PeerID - All public information concerning a peer.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
import net.i2p.data.Base64;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
|
||||||
|
public class PeerID implements Comparable
|
||||||
|
{
|
||||||
|
private final byte[] id;
|
||||||
|
private final Destination address;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
private final int hash;
|
||||||
|
|
||||||
|
public PeerID(byte[] id, Destination address)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.address = address;
|
||||||
|
this.port = 6881;
|
||||||
|
|
||||||
|
hash = calculateHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a PeerID from a BDecoder.
|
||||||
|
*/
|
||||||
|
public PeerID(BDecoder be)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(be.bdecodeMap().getMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a PeerID from a Map containing BEncoded peer id, ip and
|
||||||
|
* port.
|
||||||
|
*/
|
||||||
|
public PeerID(Map m)
|
||||||
|
throws InvalidBEncodingException, UnknownHostException
|
||||||
|
{
|
||||||
|
BEValue bevalue = (BEValue)m.get("peer id");
|
||||||
|
if (bevalue == null)
|
||||||
|
throw new InvalidBEncodingException("peer id missing");
|
||||||
|
id = bevalue.getBytes();
|
||||||
|
|
||||||
|
bevalue = (BEValue)m.get("ip");
|
||||||
|
if (bevalue == null)
|
||||||
|
throw new InvalidBEncodingException("ip missing");
|
||||||
|
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
|
||||||
|
if (address == null)
|
||||||
|
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||||
|
|
||||||
|
port = 6881;
|
||||||
|
|
||||||
|
hash = calculateHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getID()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Destination getAddress()
|
||||||
|
{
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort()
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateHash()
|
||||||
|
{
|
||||||
|
int b = 0;
|
||||||
|
for (int i = 0; i < id.length; i++)
|
||||||
|
b ^= id[i];
|
||||||
|
return (b ^ address.hashCode()) ^ port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hash code of a PeerID is the exclusive or of all id bytes.
|
||||||
|
*/
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if and only if this peerID and the given peerID have
|
||||||
|
* the same 20 bytes as ID.
|
||||||
|
*/
|
||||||
|
public boolean sameID(PeerID pid)
|
||||||
|
{
|
||||||
|
boolean equal = true;
|
||||||
|
for (int i = 0; equal && i < id.length; i++)
|
||||||
|
equal = id[i] == pid.id[i];
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two PeerIDs are equal when they have the same id, address and port.
|
||||||
|
*/
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (o instanceof PeerID)
|
||||||
|
{
|
||||||
|
PeerID pid = (PeerID)o;
|
||||||
|
|
||||||
|
return port == pid.port
|
||||||
|
&& address.equals(pid.address)
|
||||||
|
&& sameID(pid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares port, address and id.
|
||||||
|
*/
|
||||||
|
public int compareTo(Object o)
|
||||||
|
{
|
||||||
|
PeerID pid = (PeerID)o;
|
||||||
|
|
||||||
|
int result = port - pid.port;
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = address.hashCode() - pid.address.hashCode();
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
for (int i = 0; i < id.length; i++)
|
||||||
|
{
|
||||||
|
result = id[i] - pid.id[i];
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the String "id@address" where id is the base64 encoded id.
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
int nonZero = 0;
|
||||||
|
for (int i = 0; i < id.length; i++) {
|
||||||
|
if (id[i] != 0) {
|
||||||
|
nonZero = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.calculateHash().toBase64().substring(0,6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode an id as a hex encoded string and remove leading zeros.
|
||||||
|
*/
|
||||||
|
public static String idencode(byte[] bs)
|
||||||
|
{
|
||||||
|
boolean leading_zeros = true;
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||||
|
for (int i = 0; i < bs.length; i++)
|
||||||
|
{
|
||||||
|
int c = bs[i] & 0xFF;
|
||||||
|
if (leading_zeros && c == 0)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
leading_zeros = false;
|
||||||
|
|
||||||
|
if (c < 16)
|
||||||
|
sb.append('0');
|
||||||
|
sb.append(Integer.toHexString(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/* PeerListener - Interface for listening to peer events.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for Peer events.
|
||||||
|
*/
|
||||||
|
public interface PeerListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the connection to the peer has started and the
|
||||||
|
* handshake was successfull.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that just got connected.
|
||||||
|
*/
|
||||||
|
void connected(Peer peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the connection to the peer was terminated or the
|
||||||
|
* connection handshake failed.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that just got disconnected.
|
||||||
|
*/
|
||||||
|
void disconnected(Peer peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a choke message is received.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that got the message.
|
||||||
|
* @param choke true when the peer got a choke message, false when
|
||||||
|
* the peer got an unchoke message.
|
||||||
|
*/
|
||||||
|
void gotChoke(Peer peer, boolean choke);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an interested message is received.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that got the message.
|
||||||
|
* @param interest true when the peer got a interested message, false when
|
||||||
|
* the peer got an uninterested message.
|
||||||
|
*/
|
||||||
|
void gotInterest(Peer peer, boolean interest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a have piece message is received. If the method
|
||||||
|
* returns true and the peer has not yet received a interested
|
||||||
|
* message or we indicated earlier to be not interested then an
|
||||||
|
* interested message will be send.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that got the message.
|
||||||
|
* @param piece the piece number that the per just got.
|
||||||
|
*
|
||||||
|
* @return true when it is a piece that we want, false if the piece is
|
||||||
|
* already known.
|
||||||
|
*/
|
||||||
|
boolean gotHave(Peer peer, int piece);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a bitmap message is received. If this method returns
|
||||||
|
* true a interested message will be send back to the peer.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that got the message.
|
||||||
|
* @param bitfield a BitField containing the pieces that the other
|
||||||
|
* side has.
|
||||||
|
*
|
||||||
|
* @return true when the BitField contains pieces we want, false if
|
||||||
|
* the piece is already known.
|
||||||
|
*/
|
||||||
|
boolean gotBitField(Peer peer, BitField bitfield);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a piece is received from the peer. The piece must be
|
||||||
|
* requested by Peer.request() first. If this method returns false
|
||||||
|
* that means the Peer provided a corrupted piece and the connection
|
||||||
|
* will be closed.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that got the piece.
|
||||||
|
* @param piece the piece number received.
|
||||||
|
* @param bs the byte array containing the piece.
|
||||||
|
*
|
||||||
|
* @return true when the bytes represent the piece, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean gotPiece(Peer peer, int piece, byte[] bs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the peer wants (part of) a piece from us. Only called
|
||||||
|
* when the peer is not choked by us (<code>peer.choke(false)</code>
|
||||||
|
* was called).
|
||||||
|
*
|
||||||
|
* @param peer the Peer that wants the piece.
|
||||||
|
* @param piece the piece number requested.
|
||||||
|
*
|
||||||
|
* @return a byte array containing the piece or null when the piece
|
||||||
|
* is not available (which is a protocol error).
|
||||||
|
*/
|
||||||
|
byte[] gotRequest(Peer peer, int piece);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a (partial) piece has been downloaded from the peer.
|
||||||
|
*
|
||||||
|
* @param peer the Peer from which size bytes where downloaded.
|
||||||
|
* @param size the number of bytes that where downloaded.
|
||||||
|
*/
|
||||||
|
void downloaded(Peer peer, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a (partial) piece has been uploaded to the peer.
|
||||||
|
*
|
||||||
|
* @param peer the Peer to which size bytes where uploaded.
|
||||||
|
* @param size the number of bytes that where uploaded.
|
||||||
|
*/
|
||||||
|
void uploaded(Peer peer, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when we are downloading from the peer and need to ask for
|
||||||
|
* a new piece. Might be called multiple times before
|
||||||
|
* <code>gotPiece()</code> is called.
|
||||||
|
*
|
||||||
|
* @param peer the Peer that will be asked to provide the piece.
|
||||||
|
* @param bitfield a BitField containing the pieces that the other
|
||||||
|
* side has.
|
||||||
|
*
|
||||||
|
* @return one of the pieces from the bitfield that we want or -1 if
|
||||||
|
* we are no longer interested in the peer.
|
||||||
|
*/
|
||||||
|
int wantPiece(Peer peer, BitField bitfield);
|
||||||
|
}
|
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* PeerMonitorTasks - TimerTask that monitors the peers and total up/down speed
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimerTask that monitors the peers and total up/download speeds.
|
||||||
|
* Works together with the main Snark class to report periodical statistics.
|
||||||
|
*/
|
||||||
|
class PeerMonitorTask extends TimerTask
|
||||||
|
{
|
||||||
|
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.
|
||||||
|
private final long KILOPERSECOND = 1024 * (MONITOR_PERIOD / 1000);
|
||||||
|
|
||||||
|
private final PeerCoordinator coordinator;
|
||||||
|
|
||||||
|
private long lastDownloaded = 0;
|
||||||
|
private long lastUploaded = 0;
|
||||||
|
|
||||||
|
PeerMonitorTask(PeerCoordinator coordinator)
|
||||||
|
{
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// Get some statistics
|
||||||
|
int peers = 0;
|
||||||
|
int uploaders = 0;
|
||||||
|
int downloaders = 0;
|
||||||
|
int interested = 0;
|
||||||
|
int interesting = 0;
|
||||||
|
int choking = 0;
|
||||||
|
int choked = 0;
|
||||||
|
|
||||||
|
synchronized(coordinator.peers)
|
||||||
|
{
|
||||||
|
Iterator it = coordinator.peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)it.next();
|
||||||
|
|
||||||
|
// Don't list dying peers
|
||||||
|
if (!peer.isConnected())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
peers++;
|
||||||
|
|
||||||
|
if (!peer.isChoking())
|
||||||
|
uploaders++;
|
||||||
|
if (!peer.isChoked() && peer.isInteresting())
|
||||||
|
downloaders++;
|
||||||
|
if (peer.isInterested())
|
||||||
|
interested++;
|
||||||
|
if (peer.isInteresting())
|
||||||
|
interesting++;
|
||||||
|
if (peer.isChoking())
|
||||||
|
choking++;
|
||||||
|
if (peer.isChoked())
|
||||||
|
choked++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print some statistics
|
||||||
|
long downloaded = coordinator.getDownloaded();
|
||||||
|
String totalDown;
|
||||||
|
if (downloaded >= 10 * 1024 * 1024)
|
||||||
|
totalDown = (downloaded / (1024 * 1024)) + "MB";
|
||||||
|
else
|
||||||
|
totalDown = (downloaded / 1024 )+ "KB";
|
||||||
|
long uploaded = coordinator.getUploaded();
|
||||||
|
String totalUp;
|
||||||
|
if (uploaded >= 10 * 1024 * 1024)
|
||||||
|
totalUp = (uploaded / (1024 * 1024)) + "MB";
|
||||||
|
else
|
||||||
|
totalUp = (uploaded / 1024) + "KB";
|
||||||
|
|
||||||
|
int needP = coordinator.storage.needed();
|
||||||
|
long needMB
|
||||||
|
= needP * coordinator.metainfo.getPieceLength(0) / (1024 * 1024);
|
||||||
|
int totalP = coordinator.metainfo.getPieces();
|
||||||
|
long totalMB = coordinator.metainfo.getTotalLength() / (1024 * 1024);
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Down: "
|
||||||
|
+ (downloaded - lastDownloaded) / KILOPERSECOND
|
||||||
|
+ "KB/s"
|
||||||
|
+ " (" + totalDown + ")"
|
||||||
|
+ " Up: "
|
||||||
|
+ (uploaded - lastUploaded) / KILOPERSECOND
|
||||||
|
+ "KB/s"
|
||||||
|
+ " (" + totalUp + ")"
|
||||||
|
+ " Need " + needP
|
||||||
|
+ " (" + needMB + "MB)"
|
||||||
|
+ " of " + totalP
|
||||||
|
+ " (" + totalMB + "MB)"
|
||||||
|
+ " pieces");
|
||||||
|
System.out.println(peers + ": Download #" + downloaders
|
||||||
|
+ " Upload #" + uploaders
|
||||||
|
+ " Interested #" + interested
|
||||||
|
+ " Interesting #" + interesting
|
||||||
|
+ " Choking #" + choking
|
||||||
|
+ " Choked #" + choked);
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
lastDownloaded = downloaded;
|
||||||
|
lastUploaded = uploaded;
|
||||||
|
}
|
||||||
|
}
|
539
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
539
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/* PeerState - Keeps track of the Peer state through connection callbacks.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
class PeerState
|
||||||
|
{
|
||||||
|
final Peer peer;
|
||||||
|
final PeerListener listener;
|
||||||
|
final MetaInfo metainfo;
|
||||||
|
|
||||||
|
// Interesting and choking describes whether we are interested in or
|
||||||
|
// are choking the other side.
|
||||||
|
boolean interesting = false;
|
||||||
|
boolean choking = true;
|
||||||
|
|
||||||
|
// Interested and choked describes whether the other side is
|
||||||
|
// interested in us or choked us.
|
||||||
|
boolean interested = false;
|
||||||
|
boolean choked = true;
|
||||||
|
|
||||||
|
// Package local for use by Peer.
|
||||||
|
long downloaded;
|
||||||
|
long uploaded;
|
||||||
|
|
||||||
|
BitField bitfield;
|
||||||
|
|
||||||
|
// Package local for use by Peer.
|
||||||
|
final PeerConnectionIn in;
|
||||||
|
final PeerConnectionOut out;
|
||||||
|
|
||||||
|
// Outstanding request
|
||||||
|
private final List outstandingRequests = new ArrayList();
|
||||||
|
private Request lastRequest = null;
|
||||||
|
|
||||||
|
// If we have te resend outstanding requests (true after we got choked).
|
||||||
|
private boolean resend = false;
|
||||||
|
|
||||||
|
private final static int MAX_PIPELINE = 5;
|
||||||
|
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||||
|
|
||||||
|
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||||
|
PeerConnectionIn in, PeerConnectionOut out)
|
||||||
|
{
|
||||||
|
this.peer = peer;
|
||||||
|
this.listener = listener;
|
||||||
|
this.metainfo = metainfo;
|
||||||
|
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE Methods that inspect or change the state synchronize (on this).
|
||||||
|
|
||||||
|
void keepAliveMessage()
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv alive", Snark.DEBUG);
|
||||||
|
/* XXX - ignored */
|
||||||
|
}
|
||||||
|
|
||||||
|
void chokeMessage(boolean choke)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv " + (choke ? "" : "un") + "choked",
|
||||||
|
Snark.DEBUG);
|
||||||
|
|
||||||
|
choked = choke;
|
||||||
|
if (choked)
|
||||||
|
resend = true;
|
||||||
|
|
||||||
|
listener.gotChoke(peer, choke);
|
||||||
|
|
||||||
|
if (!choked && interesting)
|
||||||
|
request();
|
||||||
|
}
|
||||||
|
|
||||||
|
void interestedMessage(boolean interest)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv " + (interest ? "" : "un")
|
||||||
|
+ "interested", Snark.DEBUG);
|
||||||
|
interested = interest;
|
||||||
|
listener.gotInterest(peer, interest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void haveMessage(int piece)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv have(" + piece + ")", Snark.DEBUG);
|
||||||
|
// Sanity check
|
||||||
|
if (piece < 0 || piece >= metainfo.getPieces())
|
||||||
|
{
|
||||||
|
// XXX disconnect?
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got strange 'have: " + piece + "' message from " + peer,
|
||||||
|
+ Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
// Can happen if the other side never send a bitfield message.
|
||||||
|
if (bitfield == null)
|
||||||
|
bitfield = new BitField(metainfo.getPieces());
|
||||||
|
|
||||||
|
bitfield.set(piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener.gotHave(peer, piece))
|
||||||
|
setInteresting(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bitfieldMessage(byte[] bitmap)
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv bitfield", Snark.DEBUG);
|
||||||
|
if (bitfield != null)
|
||||||
|
{
|
||||||
|
// XXX - Be liberal in what you except?
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got unexpected bitfield message from " + peer,
|
||||||
|
Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX - Check for weird bitfield and disconnect?
|
||||||
|
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||||
|
}
|
||||||
|
setInteresting(listener.gotBitField(peer, bitfield));
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestMessage(int piece, int begin, int length)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " rcv request("
|
||||||
|
+ piece + ", " + begin + ", " + length + ") ",
|
||||||
|
Snark.DEBUG);
|
||||||
|
if (choking)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Request received, but choking " + peer, Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (piece < 0
|
||||||
|
|| piece >= metainfo.getPieces()
|
||||||
|
|| begin < 0
|
||||||
|
|| begin > metainfo.getPieceLength(piece)
|
||||||
|
|| length <= 0
|
||||||
|
|| length > 4*PARTSIZE)
|
||||||
|
{
|
||||||
|
// XXX - Protocol error -> disconnect?
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got strange 'request: " + piece
|
||||||
|
+ ", " + begin
|
||||||
|
+ ", " + length
|
||||||
|
+ "' message from " + peer,
|
||||||
|
Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||||
|
if (pieceBytes == null)
|
||||||
|
{
|
||||||
|
// XXX - Protocol error-> diconnect?
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got request for unknown piece: " + piece, Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More sanity checks
|
||||||
|
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||||
|
{
|
||||||
|
// XXX - Protocol error-> disconnect?
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Got out of range 'request: " + piece
|
||||||
|
+ ", " + begin
|
||||||
|
+ ", " + length
|
||||||
|
+ "' message from " + peer,
|
||||||
|
Snark.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Sending (" + piece + ", " + begin + ", "
|
||||||
|
+ length + ")" + " to " + peer, Snark.DEBUG);
|
||||||
|
out.sendPiece(piece, begin, length, pieceBytes);
|
||||||
|
|
||||||
|
// Tell about last subpiece delivery.
|
||||||
|
if (begin + length == pieceBytes.length)
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Send p" + piece + " " + peer,
|
||||||
|
Snark.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when some bytes have left the outgoing connection.
|
||||||
|
* XXX - Should indicate whether it was a real piece or overhead.
|
||||||
|
*/
|
||||||
|
void uploaded(int size)
|
||||||
|
{
|
||||||
|
uploaded += size;
|
||||||
|
listener.uploaded(peer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a partial piece request has been handled by
|
||||||
|
* PeerConnectionIn.
|
||||||
|
*/
|
||||||
|
void pieceMessage(Request req)
|
||||||
|
{
|
||||||
|
int size = req.len;
|
||||||
|
downloaded += size;
|
||||||
|
listener.downloaded(peer, size);
|
||||||
|
|
||||||
|
// Last chunk needed for this piece?
|
||||||
|
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||||
|
{
|
||||||
|
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Got " + req.piece + ": " + peer, Snark.DEBUG);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Got BAD " + req.piece + " from " + peer,
|
||||||
|
Snark.DEBUG);
|
||||||
|
// XXX ARGH What now !?!
|
||||||
|
downloaded = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized private int getFirstOutstandingRequest(int piece)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||||
|
if (((Request)outstandingRequests.get(i)).piece == piece)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a piece message is being processed by the incoming
|
||||||
|
* connection. Returns null when there was no such request. It also
|
||||||
|
* requeues/sends requests when it thinks that they must have been
|
||||||
|
* lost.
|
||||||
|
*/
|
||||||
|
Request getOutstandingRequest(int piece, int begin, int length)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("getChunk("
|
||||||
|
+ piece + "," + begin + "," + length + ") "
|
||||||
|
+ peer, Snark.DEBUG);
|
||||||
|
|
||||||
|
int r = getFirstOutstandingRequest(piece);
|
||||||
|
|
||||||
|
// Unrequested piece number?
|
||||||
|
if (r == -1)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Unrequested 'piece: " + piece + ", "
|
||||||
|
+ begin + ", " + length + "' received from "
|
||||||
|
+ peer,
|
||||||
|
Snark.INFO);
|
||||||
|
downloaded = 0; // XXX - punishment?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the correct piece chunk request from the list.
|
||||||
|
Request req;
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
req = (Request)outstandingRequests.get(r);
|
||||||
|
while (req.piece == piece && req.off != begin
|
||||||
|
&& r < outstandingRequests.size() - 1)
|
||||||
|
{
|
||||||
|
r++;
|
||||||
|
req = (Request)outstandingRequests.get(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something wrong?
|
||||||
|
if (req.piece != piece || req.off != begin || req.len != length)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Unrequested or unneeded 'piece: "
|
||||||
|
+ piece + ", "
|
||||||
|
+ begin + ", "
|
||||||
|
+ length + "' received from "
|
||||||
|
+ peer,
|
||||||
|
Snark.INFO);
|
||||||
|
downloaded = 0; // XXX - punishment?
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report missing requests.
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
System.err.print("Some requests dropped, got " + req
|
||||||
|
+ ", wanted:");
|
||||||
|
for (int i = 0; i < r; i++)
|
||||||
|
{
|
||||||
|
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||||
|
outstandingRequests.add(dropReq);
|
||||||
|
// We used to rerequest the missing chunks but that mostly
|
||||||
|
// just confuses the other side. So now we just keep
|
||||||
|
// waiting for them. They will be rerequested when we get
|
||||||
|
// choked/unchoked again.
|
||||||
|
/*
|
||||||
|
if (!choked)
|
||||||
|
out.sendRequest(dropReq);
|
||||||
|
*/
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
System.err.print(" " + dropReq);
|
||||||
|
}
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
System.err.println(" " + peer);
|
||||||
|
}
|
||||||
|
outstandingRequests.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request more if necessary to keep the pipeline filled.
|
||||||
|
addRequest();
|
||||||
|
|
||||||
|
return req;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancelMessage(int piece, int begin, int length)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Got cancel message ("
|
||||||
|
+ piece + ", " + begin + ", " + length + ")",
|
||||||
|
Snark.DEBUG);
|
||||||
|
out.cancelRequest(piece, begin, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unknownMessage(int type, byte[] bs)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.WARNING)
|
||||||
|
Snark.debug("Warning: Ignoring unknown message type: " + type
|
||||||
|
+ " length: " + bs.length, Snark.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void havePiece(int piece)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug("Tell " + peer + " havePiece(" + piece + ")", Snark.DEBUG);
|
||||||
|
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
// Tell the other side that we are no longer interested in any of
|
||||||
|
// the outstanding requests for this piece.
|
||||||
|
if (lastRequest != null && lastRequest.piece == piece)
|
||||||
|
lastRequest = null;
|
||||||
|
|
||||||
|
Iterator it = outstandingRequests.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Request req = (Request)it.next();
|
||||||
|
if (req.piece == piece)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
// Send cancel even when we are choked to make sure that it is
|
||||||
|
// really never ever send.
|
||||||
|
out.sendCancel(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the other side that we really have this piece.
|
||||||
|
out.sendHave(piece);
|
||||||
|
|
||||||
|
// Request something else if necessary.
|
||||||
|
addRequest();
|
||||||
|
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
// Is the peer still interesting?
|
||||||
|
if (lastRequest == null)
|
||||||
|
setInteresting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts or resumes requesting pieces.
|
||||||
|
private void request()
|
||||||
|
{
|
||||||
|
// Are there outstanding requests that have to be resend?
|
||||||
|
if (resend)
|
||||||
|
{
|
||||||
|
out.sendRequests(outstandingRequests);
|
||||||
|
resend = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add/Send some more requests if necessary.
|
||||||
|
addRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new request to the outstanding requests list.
|
||||||
|
*/
|
||||||
|
private void addRequest()
|
||||||
|
{
|
||||||
|
boolean more_pieces = true;
|
||||||
|
while (more_pieces)
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want something and we don't have outstanding requests?
|
||||||
|
if (more_pieces && lastRequest == null)
|
||||||
|
more_pieces = requestNextPiece();
|
||||||
|
else if (more_pieces) // We want something
|
||||||
|
{
|
||||||
|
int pieceLength;
|
||||||
|
boolean isLastChunk;
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||||
|
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last part of a piece?
|
||||||
|
if (isLastChunk)
|
||||||
|
more_pieces = requestNextPiece();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
int nextPiece = lastRequest.piece;
|
||||||
|
int nextBegin = lastRequest.off + PARTSIZE;
|
||||||
|
byte[] bs = lastRequest.bs;
|
||||||
|
int maxLength = pieceLength - nextBegin;
|
||||||
|
int nextLength = maxLength > PARTSIZE ? PARTSIZE
|
||||||
|
: maxLength;
|
||||||
|
Request req
|
||||||
|
= new Request(nextPiece, bs, nextBegin, nextLength);
|
||||||
|
outstandingRequests.add(req);
|
||||||
|
if (!choked)
|
||||||
|
out.sendRequest(req);
|
||||||
|
lastRequest = req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " requests " + outstandingRequests, Snark.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts requesting first chunk of next piece. Returns true if
|
||||||
|
// something has been added to the requests, false otherwise.
|
||||||
|
private boolean requestNextPiece()
|
||||||
|
{
|
||||||
|
// Check that we already know what the other side has.
|
||||||
|
if (bitfield != null)
|
||||||
|
{
|
||||||
|
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " want piece " + nextPiece, Snark.DEBUG);
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
if (nextPiece != -1
|
||||||
|
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||||
|
{
|
||||||
|
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||||
|
byte[] bs = new byte[piece_length];
|
||||||
|
|
||||||
|
int length = Math.min(piece_length, PARTSIZE);
|
||||||
|
Request req = new Request(nextPiece, bs, 0, length);
|
||||||
|
outstandingRequests.add(req);
|
||||||
|
if (!choked)
|
||||||
|
out.sendRequest(req);
|
||||||
|
lastRequest = req;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void setInteresting(boolean interest)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " setInteresting(" + interest + ")", Snark.DEBUG);
|
||||||
|
|
||||||
|
if (interest != interesting)
|
||||||
|
{
|
||||||
|
interesting = interest;
|
||||||
|
out.sendInterest(interest);
|
||||||
|
|
||||||
|
if (interesting && !choked)
|
||||||
|
request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void setChoking(boolean choke)
|
||||||
|
{
|
||||||
|
if (Snark.debug >= Snark.DEBUG)
|
||||||
|
Snark.debug(peer + " setChoking(" + choke + ")", Snark.DEBUG);
|
||||||
|
|
||||||
|
if (choking != choke)
|
||||||
|
{
|
||||||
|
choking = choke;
|
||||||
|
out.sendChoke(choke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* Request - Holds all information needed for a (partial) piece request.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds all information needed for a partial piece request.
|
||||||
|
*/
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
final int piece;
|
||||||
|
final byte[] bs;
|
||||||
|
final int off;
|
||||||
|
final int len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Request.
|
||||||
|
*
|
||||||
|
* @param piece Piece number requested.
|
||||||
|
* @param bs byte array where response should be stored.
|
||||||
|
* @param off the offset in the array.
|
||||||
|
* @param len the number of bytes requested.
|
||||||
|
*/
|
||||||
|
Request(int piece, byte[] bs, int off, int len)
|
||||||
|
{
|
||||||
|
this.piece = piece;
|
||||||
|
this.bs = bs;
|
||||||
|
this.off = off;
|
||||||
|
this.len = len;
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
|
||||||
|
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return piece ^ off ^ len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (o instanceof Request)
|
||||||
|
{
|
||||||
|
Request req = (Request)o;
|
||||||
|
return req.piece == piece && req.off == off && req.len == len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "(" + piece + "," + off + "," + len + ")";
|
||||||
|
}
|
||||||
|
}
|
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* ShutdownListener - Callback for end of shutdown sequence
|
||||||
|
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for end of shutdown sequence.
|
||||||
|
*/
|
||||||
|
interface ShutdownListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the SnarkShutdown hook has finished shutting down all
|
||||||
|
* subcomponents.
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
}
|
598
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
598
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
@ -0,0 +1,598 @@
|
|||||||
|
/* Snark - Main snark program startup class.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
import net.i2p.client.streaming.I2PSocket;
|
||||||
|
import net.i2p.client.streaming.I2PServerSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Snark program startup class.
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org)
|
||||||
|
*/
|
||||||
|
public class Snark
|
||||||
|
implements StorageListener, CoordinatorListener, ShutdownListener
|
||||||
|
{
|
||||||
|
private final static int MIN_PORT = 6881;
|
||||||
|
private final static int MAX_PORT = 6889;
|
||||||
|
|
||||||
|
// Error messages (non-fatal)
|
||||||
|
public final static int ERROR = 1;
|
||||||
|
|
||||||
|
// Warning messages
|
||||||
|
public final static int WARNING = 2;
|
||||||
|
|
||||||
|
// Notices (peer level)
|
||||||
|
public final static int NOTICE = 3;
|
||||||
|
|
||||||
|
// Info messages (protocol policy level)
|
||||||
|
public final static int INFO = 4;
|
||||||
|
|
||||||
|
// Debug info (protocol level)
|
||||||
|
public final static int DEBUG = 5;
|
||||||
|
|
||||||
|
// Very low level stuff (network level)
|
||||||
|
public final static int ALL = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What level of debug info to show.
|
||||||
|
*/
|
||||||
|
public static int debug = NOTICE;
|
||||||
|
|
||||||
|
// Whether or not to ask the user for commands while sharing
|
||||||
|
private static boolean command_interpreter = true;
|
||||||
|
|
||||||
|
private static final String newline = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
private static final String copyright =
|
||||||
|
"The Hunting of the Snark Project - Copyright (C) 2003 Mark J. Wielaard"
|
||||||
|
+ newline + newline
|
||||||
|
+ "Snark comes with ABSOLUTELY NO WARRANTY. This is free software, and"
|
||||||
|
+ newline
|
||||||
|
+ "you are welcome to redistribute it under certain conditions; read the"
|
||||||
|
+ newline
|
||||||
|
+ "COPYING file for details." + newline + newline
|
||||||
|
+ "This is the I2P port, allowing anonymous bittorrent (http://www.i2p.net/)" + newline
|
||||||
|
+ "It will not work with normal torrents, so don't even try ;)";
|
||||||
|
|
||||||
|
private static final String usage =
|
||||||
|
"Press return for help. Type \"quit\" and return to stop.";
|
||||||
|
private static final String help =
|
||||||
|
"Commands: 'info', 'list', 'quit'.";
|
||||||
|
|
||||||
|
// String indicating main activity
|
||||||
|
static String activity = "Not started";
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
System.out.println(copyright);
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// Parse debug, share/ip and torrent file options.
|
||||||
|
Snark snark = parseArguments(args);
|
||||||
|
|
||||||
|
SnarkShutdown snarkhook
|
||||||
|
= new SnarkShutdown(snark.storage,
|
||||||
|
snark.coordinator,
|
||||||
|
snark.acceptor,
|
||||||
|
snark.trackerclient,
|
||||||
|
snark);
|
||||||
|
Runtime.getRuntime().addShutdownHook(snarkhook);
|
||||||
|
|
||||||
|
Timer timer = new Timer(true);
|
||||||
|
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
|
||||||
|
timer.schedule(monitor,
|
||||||
|
PeerMonitorTask.MONITOR_PERIOD,
|
||||||
|
PeerMonitorTask.MONITOR_PERIOD);
|
||||||
|
|
||||||
|
// Start command interpreter
|
||||||
|
if (Snark.command_interpreter)
|
||||||
|
{
|
||||||
|
boolean quit = false;
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
System.out.println(usage);
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BufferedReader br = new BufferedReader
|
||||||
|
(new InputStreamReader(System.in));
|
||||||
|
String line = br.readLine();
|
||||||
|
while(!quit && line != null)
|
||||||
|
{
|
||||||
|
line = line.toLowerCase();
|
||||||
|
if ("quit".equals(line))
|
||||||
|
quit = true;
|
||||||
|
else if ("list".equals(line))
|
||||||
|
{
|
||||||
|
synchronized(coordinator.peers)
|
||||||
|
{
|
||||||
|
System.out.println(coordinator.peers.size()
|
||||||
|
+ " peers -"
|
||||||
|
+ " (i)nterested,"
|
||||||
|
+ " (I)nteresting,"
|
||||||
|
+ " (c)hoking,"
|
||||||
|
+ " (C)hoked:");
|
||||||
|
Iterator it = coordinator.peers.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
Peer peer = (Peer)it.next();
|
||||||
|
System.out.println(peer);
|
||||||
|
System.out.println("\ti: " + peer.isInterested()
|
||||||
|
+ " I: " + peer.isInteresting()
|
||||||
|
+ " c: " + peer.isChoking()
|
||||||
|
+ " C: " + peer.isChoked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("info".equals(line))
|
||||||
|
{
|
||||||
|
System.out.println("Name: " + meta.getName());
|
||||||
|
System.out.println("Torrent: " + torrent);
|
||||||
|
System.out.println("Tracker: " + meta.getAnnounce());
|
||||||
|
List files = meta.getFiles();
|
||||||
|
System.out.println("Files: "
|
||||||
|
+ ((files == null) ? 1 : files.size()));
|
||||||
|
System.out.println("Pieces: " + meta.getPieces());
|
||||||
|
System.out.println("Piece size: "
|
||||||
|
+ meta.getPieceLength(0) / 1024
|
||||||
|
+ " KB");
|
||||||
|
System.out.println("Total size: "
|
||||||
|
+ meta.getTotalLength() / (1024 * 1024)
|
||||||
|
+ " MB");
|
||||||
|
}
|
||||||
|
else if ("".equals(line) || "help".equals(line))
|
||||||
|
{
|
||||||
|
System.out.println(usage);
|
||||||
|
System.out.println(help);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("Unknown command: " + line);
|
||||||
|
System.out.println(usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quit)
|
||||||
|
{
|
||||||
|
System.out.println();
|
||||||
|
line = br.readLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
debug("ERROR while reading stdin: " + ioe, ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit shutdown.
|
||||||
|
Runtime.getRuntime().removeShutdownHook(snarkhook);
|
||||||
|
snarkhook.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String torrent;
|
||||||
|
static MetaInfo meta;
|
||||||
|
static Storage storage;
|
||||||
|
static PeerCoordinator coordinator;
|
||||||
|
static ConnectionAcceptor acceptor;
|
||||||
|
static TrackerClient trackerclient;
|
||||||
|
|
||||||
|
private Snark(String torrent, String ip, int user_port,
|
||||||
|
StorageListener slistener, CoordinatorListener clistener)
|
||||||
|
{
|
||||||
|
if (slistener == null)
|
||||||
|
slistener = this;
|
||||||
|
|
||||||
|
if (clistener == null)
|
||||||
|
clistener = this;
|
||||||
|
|
||||||
|
this.torrent = torrent;
|
||||||
|
|
||||||
|
activity = "Network setup";
|
||||||
|
|
||||||
|
// "Taking Three as the subject to reason about--
|
||||||
|
// A convenient number to state--
|
||||||
|
// We add Seven, and Ten, and then multiply out
|
||||||
|
// By One Thousand diminished by Eight.
|
||||||
|
//
|
||||||
|
// "The result we proceed to divide, as you see,
|
||||||
|
// By Nine Hundred and Ninety Two:
|
||||||
|
// Then subtract Seventeen, and the answer must be
|
||||||
|
// Exactly and perfectly true.
|
||||||
|
|
||||||
|
// Create a new ID and fill it with something random. First nine
|
||||||
|
// zeros bytes, then three bytes filled with snark and then
|
||||||
|
// sixteen random bytes.
|
||||||
|
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||||
|
byte[] id = new byte[20];
|
||||||
|
Random random = new Random();
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 9; i++)
|
||||||
|
id[i] = 0;
|
||||||
|
id[i++] = snark;
|
||||||
|
id[i++] = snark;
|
||||||
|
id[i++] = snark;
|
||||||
|
while (i < 20)
|
||||||
|
id[i++] = (byte)random.nextInt(256);
|
||||||
|
|
||||||
|
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||||
|
|
||||||
|
int port;
|
||||||
|
IOException lastException = null;
|
||||||
|
boolean ok = I2PSnarkUtil.instance().connect();
|
||||||
|
if (!ok) fatal("Unable to connect to I2P");
|
||||||
|
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||||
|
if (serversocket == null)
|
||||||
|
fatal("Unable to listen for I2P connections");
|
||||||
|
else
|
||||||
|
debug("Listening on I2P destination " + serversocket.getManager().getSession().getMyDestination().toBase64(), NOTICE);
|
||||||
|
|
||||||
|
// Figure out what the torrent argument represents.
|
||||||
|
meta = null;
|
||||||
|
File f = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream in = null;
|
||||||
|
f = new File(torrent);
|
||||||
|
if (f.exists())
|
||||||
|
in = new FileInputStream(f);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activity = "Getting torrent";
|
||||||
|
File torrentFile = I2PSnarkUtil.instance().get(torrent);
|
||||||
|
if (torrentFile == null) {
|
||||||
|
fatal("Unable to fetch " + torrent);
|
||||||
|
if (false) return; // never reached - fatal(..) throws
|
||||||
|
} else {
|
||||||
|
torrentFile.deleteOnExit();
|
||||||
|
in = new FileInputStream(torrentFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
meta = new MetaInfo(new BDecoder(in));
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
// OK, so it wasn't a torrent metainfo file.
|
||||||
|
if (f != null && f.exists())
|
||||||
|
if (ip == null)
|
||||||
|
fatal("'" + torrent + "' exists,"
|
||||||
|
+ " but is not a valid torrent metainfo file."
|
||||||
|
+ System.getProperty("line.separator"), ioe);
|
||||||
|
else
|
||||||
|
fatal("I2PSnark does not support creating and tracking a torrent at the moment");
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
// Try to create a new metainfo file
|
||||||
|
Snark.debug
|
||||||
|
("Trying to create metainfo torrent for '" + torrent + "'",
|
||||||
|
NOTICE);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
activity = "Creating torrent";
|
||||||
|
storage = new Storage
|
||||||
|
(f, "http://" + ip + ":" + port + "/announce", slistener);
|
||||||
|
storage.create();
|
||||||
|
meta = storage.getMetaInfo();
|
||||||
|
}
|
||||||
|
catch (IOException ioe2)
|
||||||
|
{
|
||||||
|
fatal("Could not create torrent for '" + torrent + "'", ioe2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
else
|
||||||
|
fatal("Cannot open '" + torrent + "'", ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(meta.toString(), INFO);
|
||||||
|
|
||||||
|
// When the metainfo torrent was created from an existing file/dir
|
||||||
|
// it already exists.
|
||||||
|
if (storage == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
activity = "Checking storage";
|
||||||
|
storage = new Storage(meta, slistener);
|
||||||
|
storage.check();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
fatal("Could not create storage", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity = "Collecting pieces";
|
||||||
|
coordinator = new PeerCoordinator(id, meta, storage, clistener);
|
||||||
|
PeerAcceptor peeracceptor = new PeerAcceptor(coordinator);
|
||||||
|
ConnectionAcceptor acceptor = new ConnectionAcceptor(serversocket,
|
||||||
|
peeracceptor);
|
||||||
|
|
||||||
|
trackerclient = new TrackerClient(meta, coordinator);
|
||||||
|
trackerclient.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Snark parseArguments(String[] args)
|
||||||
|
{
|
||||||
|
return parseArguments(args, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets debug, ip and torrent variables then creates a Snark
|
||||||
|
* instance. Calls usage(), which terminates the program, if
|
||||||
|
* non-valid argument list. The given listeners will be
|
||||||
|
* passed to all components that take one.
|
||||||
|
*/
|
||||||
|
static Snark parseArguments(String[] args,
|
||||||
|
StorageListener slistener,
|
||||||
|
CoordinatorListener clistener)
|
||||||
|
{
|
||||||
|
int user_port = -1;
|
||||||
|
String ip = null;
|
||||||
|
String torrent = null;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < args.length)
|
||||||
|
{
|
||||||
|
if (args[i].equals("--debug"))
|
||||||
|
{
|
||||||
|
debug = INFO;
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// Try if there is an level argument.
|
||||||
|
if (i < args.length)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int level = Integer.parseInt(args[i]);
|
||||||
|
if (level >= 0)
|
||||||
|
{
|
||||||
|
debug = level;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException nfe) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args[i].equals("--port"))
|
||||||
|
{
|
||||||
|
if (args.length - 1 < i + 1)
|
||||||
|
usage("--port needs port number to listen on");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
user_port = Integer.parseInt(args[i + 1]);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException nfe)
|
||||||
|
{
|
||||||
|
usage("--port argument must be a number (" + nfe + ")");
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else if (args[i].equals("--no-commands"))
|
||||||
|
{
|
||||||
|
command_interpreter = false;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (args[i].equals("--eepproxy"))
|
||||||
|
{
|
||||||
|
String proxyHost = args[i+1];
|
||||||
|
String proxyPort = args[i+2];
|
||||||
|
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (args[i].equals("--i2cp"))
|
||||||
|
{
|
||||||
|
String i2cpHost = args[i+1];
|
||||||
|
String i2cpPort = args[i+2];
|
||||||
|
Properties opts = null;
|
||||||
|
if (i+3 < args.length) {
|
||||||
|
if (!args[i+3].startsWith("--")) {
|
||||||
|
opts = new Properties();
|
||||||
|
StringTokenizer tok = new StringTokenizer(args[i+3], " \t");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String str = tok.nextToken();
|
||||||
|
int split = str.indexOf('=');
|
||||||
|
if (split > 0) {
|
||||||
|
opts.setProperty(str.substring(0, split), str.substring(split+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||||
|
i += 3 + (opts != null ? 1 : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
torrent = args[i];
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent == null || i != args.length)
|
||||||
|
if (torrent != null && torrent.startsWith("-"))
|
||||||
|
usage("Unknow option '" + torrent + "'.");
|
||||||
|
else
|
||||||
|
usage("Need exactly one <url>, <file> or <dir>.");
|
||||||
|
|
||||||
|
return new Snark(torrent, ip, user_port, slistener, clistener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void usage(String s)
|
||||||
|
{
|
||||||
|
System.out.println("snark: " + s);
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void usage()
|
||||||
|
{
|
||||||
|
System.out.println
|
||||||
|
("Usage: snark [--debug [level]] [--no-commands] [--port <port>]");
|
||||||
|
System.out.println
|
||||||
|
(" [--eepproxy hostname portnum]");
|
||||||
|
System.out.println
|
||||||
|
(" [--i2cp routerHost routerPort ['name=val name=val name=val']]");
|
||||||
|
System.out.println
|
||||||
|
(" (<url>|<file>)");
|
||||||
|
System.out.println
|
||||||
|
(" --debug\tShows some extra info and stacktraces");
|
||||||
|
System.out.println
|
||||||
|
(" level\tHow much debug details to show");
|
||||||
|
System.out.println
|
||||||
|
(" \t(defaults to "
|
||||||
|
+ NOTICE + ", with --debug to "
|
||||||
|
+ INFO + ", highest level is "
|
||||||
|
+ ALL + ").");
|
||||||
|
System.out.println
|
||||||
|
(" --no-commands\tDon't read interactive commands or show usage info.");
|
||||||
|
System.out.println
|
||||||
|
(" --port\tThe port to listen on for incomming connections");
|
||||||
|
System.out.println
|
||||||
|
(" \t(if not given defaults to first free port between "
|
||||||
|
+ MIN_PORT + "-" + MAX_PORT + ").");
|
||||||
|
System.out.println
|
||||||
|
(" --share\tStart torrent tracker on <ip> address or <host> name.");
|
||||||
|
System.out.println
|
||||||
|
(" --eepproxy\thttp proxy to use (default of 127.0.0.1 port 4444)");
|
||||||
|
System.out.println
|
||||||
|
(" --i2cp\tlocation of your I2P router (default of 127.0.0.1 port 7654)");
|
||||||
|
System.out.println
|
||||||
|
(" \toptional settings may be included, such as");
|
||||||
|
System.out.println
|
||||||
|
(" \tinbound.length=2 outbound.length=2 inbound.lengthVariance=-1 ");
|
||||||
|
System.out.println
|
||||||
|
(" <url> \tURL pointing to .torrent metainfo file to download/share.");
|
||||||
|
System.out.println
|
||||||
|
(" <file> \tEither a local .torrent metainfo file to download");
|
||||||
|
System.out.println
|
||||||
|
(" \tor (with --share) a file to share.");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts program abnormally.
|
||||||
|
*/
|
||||||
|
public static void fatal(String s)
|
||||||
|
{
|
||||||
|
fatal(s, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts program abnormally.
|
||||||
|
*/
|
||||||
|
public static void fatal(String s, Throwable t)
|
||||||
|
{
|
||||||
|
I2PSnarkUtil.instance().debug(s, ERROR, t);
|
||||||
|
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
|
||||||
|
//if (debug >= INFO && t != null)
|
||||||
|
// t.printStackTrace();
|
||||||
|
throw new RuntimeException("die bart die");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show debug info if debug is true.
|
||||||
|
*/
|
||||||
|
public static void debug(String s, int level)
|
||||||
|
{
|
||||||
|
I2PSnarkUtil.instance().debug(s, level, null);
|
||||||
|
//if (debug >= level)
|
||||||
|
// System.out.println(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void peerChange(PeerCoordinator coordinator, Peer peer)
|
||||||
|
{
|
||||||
|
// System.out.println(peer.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allocating = false;
|
||||||
|
public void storageCreateFile(Storage storage, String name, long length)
|
||||||
|
{
|
||||||
|
if (allocating)
|
||||||
|
System.out.println(); // Done with last file.
|
||||||
|
|
||||||
|
System.out.print("Creating file '" + name
|
||||||
|
+ "' of length " + length + ": ");
|
||||||
|
allocating = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much storage space has been allocated
|
||||||
|
private long allocated = 0;
|
||||||
|
|
||||||
|
public void storageAllocated(Storage storage, long length)
|
||||||
|
{
|
||||||
|
allocating = true;
|
||||||
|
System.out.print(".");
|
||||||
|
allocated += length;
|
||||||
|
if (allocated == meta.getTotalLength())
|
||||||
|
System.out.println(); // We have all the disk space we need.
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allChecked = false;
|
||||||
|
boolean checking = false;
|
||||||
|
boolean prechecking = true;
|
||||||
|
public void storageChecked(Storage storage, int num, boolean checked)
|
||||||
|
{
|
||||||
|
allocating = false;
|
||||||
|
if (!allChecked && !checking)
|
||||||
|
{
|
||||||
|
// Use the MetaInfo from the storage since our own might not
|
||||||
|
// yet be setup correctly.
|
||||||
|
MetaInfo meta = storage.getMetaInfo();
|
||||||
|
if (meta != null)
|
||||||
|
System.out.print("Checking existing "
|
||||||
|
+ meta.getPieces()
|
||||||
|
+ " pieces: ");
|
||||||
|
checking = true;
|
||||||
|
}
|
||||||
|
if (checking)
|
||||||
|
if (checked)
|
||||||
|
System.out.print("+");
|
||||||
|
else
|
||||||
|
System.out.print("-");
|
||||||
|
else
|
||||||
|
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||||
|
Snark.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storageAllChecked(Storage storage)
|
||||||
|
{
|
||||||
|
if (checking)
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
allChecked = true;
|
||||||
|
checking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
// Should not be necessary since all non-deamon threads should
|
||||||
|
// have died. But in reality this does not always happen.
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
89
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
89
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* TrackerShutdown - Makes sure everything ends correctly when shutting down.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure everything ends correctly when shutting down.
|
||||||
|
*/
|
||||||
|
public class SnarkShutdown extends Thread
|
||||||
|
{
|
||||||
|
private final Storage storage;
|
||||||
|
private final PeerCoordinator coordinator;
|
||||||
|
private final ConnectionAcceptor acceptor;
|
||||||
|
private final TrackerClient trackerclient;
|
||||||
|
|
||||||
|
private final ShutdownListener listener;
|
||||||
|
|
||||||
|
public SnarkShutdown(Storage storage,
|
||||||
|
PeerCoordinator coordinator,
|
||||||
|
ConnectionAcceptor acceptor,
|
||||||
|
TrackerClient trackerclient,
|
||||||
|
ShutdownListener listener)
|
||||||
|
{
|
||||||
|
this.storage = storage;
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
this.acceptor = acceptor;
|
||||||
|
this.trackerclient = trackerclient;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
Snark.debug("Shutting down...", Snark.NOTICE);
|
||||||
|
|
||||||
|
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
|
||||||
|
if (acceptor != null)
|
||||||
|
acceptor.halt();
|
||||||
|
|
||||||
|
Snark.debug("Halting TrackerClient...", Snark.INFO);
|
||||||
|
if (trackerclient != null)
|
||||||
|
trackerclient.halt();
|
||||||
|
|
||||||
|
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
|
||||||
|
if (coordinator != null)
|
||||||
|
coordinator.halt();
|
||||||
|
|
||||||
|
Snark.debug("Closing Storage...", Snark.INFO);
|
||||||
|
if (storage != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
storage.close();
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
Snark.fatal("Couldn't properly close storage", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX - Should actually wait till done...
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Snark.debug("Waiting 5 seconds...", Snark.INFO);
|
||||||
|
Thread.sleep(5*1000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) { /* ignored */ }
|
||||||
|
|
||||||
|
listener.shutdown();
|
||||||
|
}
|
||||||
|
}
|
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/* StaticSnark - Main snark startup class for staticly linking with gcj.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main snark startup class for staticly linking with gcj.
|
||||||
|
* It references somee necessary classes that are normally loaded through
|
||||||
|
* reflection.
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org)
|
||||||
|
*/
|
||||||
|
public class StaticSnark
|
||||||
|
{
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
// The GNU security provider is needed for SHA-1 MessageDigest checking.
|
||||||
|
// So make sure it is available as a security provider.
|
||||||
|
//Provider gnu = new gnu.java.security.provider.Gnu();
|
||||||
|
//Security.addProvider(gnu);
|
||||||
|
|
||||||
|
// And finally call the normal starting point.
|
||||||
|
Snark.main(args);
|
||||||
|
}
|
||||||
|
}
|
525
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
525
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
/* Storage - Class used to store and retrieve pieces.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||||
|
*/
|
||||||
|
public class Storage
|
||||||
|
{
|
||||||
|
private MetaInfo metainfo;
|
||||||
|
private long[] lengths;
|
||||||
|
private RandomAccessFile[] rafs;
|
||||||
|
private String[] names;
|
||||||
|
|
||||||
|
private final StorageListener listener;
|
||||||
|
|
||||||
|
private final BitField bitfield;
|
||||||
|
private int needed;
|
||||||
|
|
||||||
|
// XXX - Not always set correctly
|
||||||
|
int piece_size;
|
||||||
|
int pieces;
|
||||||
|
|
||||||
|
/** The default piece size. */
|
||||||
|
private static int MIN_PIECE_SIZE = 256*1024;
|
||||||
|
/** The maximum number of pieces in a torrent. */
|
||||||
|
private static long MAX_PIECES = 100*1024/20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new storage based on the supplied MetaInfo. This will
|
||||||
|
* try to create and/or check all needed files in the MetaInfo.
|
||||||
|
*
|
||||||
|
* @exception IOException when creating and/or checking files fails.
|
||||||
|
*/
|
||||||
|
public Storage(MetaInfo metainfo, StorageListener listener)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this.metainfo = metainfo;
|
||||||
|
this.listener = listener;
|
||||||
|
needed = metainfo.getPieces();
|
||||||
|
bitfield = new BitField(needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a storage from the existing file or directory together
|
||||||
|
* with an appropriate MetaInfo file as can be announced on the
|
||||||
|
* given announce String location.
|
||||||
|
*/
|
||||||
|
public Storage(File baseFile, String announce, StorageListener listener)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
// Create names, rafs and lengths arrays.
|
||||||
|
getFiles(baseFile);
|
||||||
|
|
||||||
|
long total = 0;
|
||||||
|
ArrayList lengthsList = new ArrayList();
|
||||||
|
for (int i = 0; i < lengths.length; i++)
|
||||||
|
{
|
||||||
|
long length = lengths[i];
|
||||||
|
total += length;
|
||||||
|
lengthsList.add(new Long(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
piece_size = MIN_PIECE_SIZE;
|
||||||
|
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||||
|
while (pieces > MAX_PIECES)
|
||||||
|
{
|
||||||
|
piece_size = piece_size*2;
|
||||||
|
pieces = (int) ((total - 1)/piece_size) +1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that piece_hashes and the bitfield will be filled after
|
||||||
|
// the MetaInfo is created.
|
||||||
|
byte[] piece_hashes = new byte[20*pieces];
|
||||||
|
bitfield = new BitField(pieces);
|
||||||
|
needed = 0;
|
||||||
|
|
||||||
|
List files = new ArrayList();
|
||||||
|
for (int i = 0; i < names.length; i++)
|
||||||
|
{
|
||||||
|
List file = new ArrayList();
|
||||||
|
StringTokenizer st = new StringTokenizer(names[i], File.separator);
|
||||||
|
while (st.hasMoreTokens())
|
||||||
|
{
|
||||||
|
String part = st.nextToken();
|
||||||
|
file.add(part);
|
||||||
|
}
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = baseFile.getName();
|
||||||
|
if (files.size() == 1)
|
||||||
|
{
|
||||||
|
files = null;
|
||||||
|
lengthsList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the piece_hashes are not correctly setup yet.
|
||||||
|
metainfo = new MetaInfo(announce, baseFile.getName(), files,
|
||||||
|
lengthsList, piece_size, piece_hashes, total);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates piece hases for a new storage.
|
||||||
|
public void create() throws IOException
|
||||||
|
{
|
||||||
|
// Calculate piece_hashes
|
||||||
|
MessageDigest digest = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
digest = MessageDigest.getInstance("SHA");
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException nsa)
|
||||||
|
{
|
||||||
|
throw new InternalError(nsa.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] piece_hashes = metainfo.getPieceHashes();
|
||||||
|
|
||||||
|
byte[] piece = new byte[piece_size];
|
||||||
|
for (int i = 0; i < pieces; i++)
|
||||||
|
{
|
||||||
|
int length = getUncheckedPiece(i, piece, 0);
|
||||||
|
digest.update(piece, 0, length);
|
||||||
|
byte[] hash = digest.digest();
|
||||||
|
for (int j = 0; j < 20; j++)
|
||||||
|
piece_hashes[20 * i + j] = hash[j];
|
||||||
|
|
||||||
|
bitfield.set(i);
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageChecked(this, i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageAllChecked(this);
|
||||||
|
|
||||||
|
// Reannounce to force recalculating the info_hash.
|
||||||
|
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getFiles(File base) throws IOException
|
||||||
|
{
|
||||||
|
ArrayList files = new ArrayList();
|
||||||
|
addFiles(files, base);
|
||||||
|
|
||||||
|
int size = files.size();
|
||||||
|
names = new String[size];
|
||||||
|
lengths = new long[size];
|
||||||
|
rafs = new RandomAccessFile[size];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
Iterator it = files.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
File f = (File)it.next();
|
||||||
|
names[i] = f.getPath();
|
||||||
|
lengths[i] = f.length();
|
||||||
|
rafs[i] = new RandomAccessFile(f, "r");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addFiles(List l, File f)
|
||||||
|
{
|
||||||
|
if (!f.isDirectory())
|
||||||
|
l.add(f);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File[] files = f.listFiles();
|
||||||
|
if (files == null)
|
||||||
|
{
|
||||||
|
Snark.debug("WARNING: Skipping '" + f
|
||||||
|
+ "' not a normal file.", Snark.WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < files.length; i++)
|
||||||
|
addFiles(l, files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the MetaInfo associated with this Storage.
|
||||||
|
*/
|
||||||
|
public MetaInfo getMetaInfo()
|
||||||
|
{
|
||||||
|
return metainfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many pieces are still missing from this storage.
|
||||||
|
*/
|
||||||
|
public int needed()
|
||||||
|
{
|
||||||
|
return needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this storage contains all pieces if the MetaInfo.
|
||||||
|
*/
|
||||||
|
public boolean complete()
|
||||||
|
{
|
||||||
|
return needed == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BitField that tells which pieces this storage contains.
|
||||||
|
* Do not change this since this is the current state of the storage.
|
||||||
|
*/
|
||||||
|
public BitField getBitField()
|
||||||
|
{
|
||||||
|
return bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates (and/or checks) all files from the metainfo file list.
|
||||||
|
*/
|
||||||
|
public void check() throws IOException
|
||||||
|
{
|
||||||
|
File base = new File(filterName(metainfo.getName()));
|
||||||
|
|
||||||
|
List files = metainfo.getFiles();
|
||||||
|
if (files == null)
|
||||||
|
{
|
||||||
|
// Create base as file.
|
||||||
|
Snark.debug("Creating/Checking file: " + base, Snark.NOTICE);
|
||||||
|
if (!base.createNewFile() && !base.exists())
|
||||||
|
throw new IOException("Could not create file " + base);
|
||||||
|
|
||||||
|
lengths = new long[1];
|
||||||
|
rafs = new RandomAccessFile[1];
|
||||||
|
names = new String[1];
|
||||||
|
lengths[0] = metainfo.getTotalLength();
|
||||||
|
rafs[0] = new RandomAccessFile(base, "rw");
|
||||||
|
names[0] = base.getName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create base as dir.
|
||||||
|
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
|
||||||
|
if (!base.mkdir() && !base.isDirectory())
|
||||||
|
throw new IOException("Could not create directory " + base);
|
||||||
|
|
||||||
|
List ls = metainfo.getLengths();
|
||||||
|
int size = files.size();
|
||||||
|
long total = 0;
|
||||||
|
lengths = new long[size];
|
||||||
|
rafs = new RandomAccessFile[size];
|
||||||
|
names = new String[size];
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
File f = createFileFromNames(base, (List)files.get(i));
|
||||||
|
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||||
|
total += lengths[i];
|
||||||
|
rafs[i] = new RandomAccessFile(f, "rw");
|
||||||
|
names[i] = f.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check for metainfo file.
|
||||||
|
long metalength = metainfo.getTotalLength();
|
||||||
|
if (total != metalength)
|
||||||
|
throw new IOException("File lengths do not add up "
|
||||||
|
+ total + " != " + metalength);
|
||||||
|
}
|
||||||
|
checkCreateFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes 'suspicious' characters from the give file name.
|
||||||
|
*/
|
||||||
|
private String filterName(String name)
|
||||||
|
{
|
||||||
|
// XXX - Is this enough?
|
||||||
|
return name.replace(File.separatorChar, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createFileFromNames(File base, List names) throws IOException
|
||||||
|
{
|
||||||
|
File f = null;
|
||||||
|
Iterator it = names.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
String name = filterName((String)it.next());
|
||||||
|
if (it.hasNext())
|
||||||
|
{
|
||||||
|
// Another dir in the hierarchy.
|
||||||
|
f = new File(base, name);
|
||||||
|
if (!f.mkdir() && !f.isDirectory())
|
||||||
|
throw new IOException("Could not create directory " + f);
|
||||||
|
base = f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The final element (file) in the hierarchy.
|
||||||
|
f = new File(base, name);
|
||||||
|
if (!f.createNewFile() && !f.exists())
|
||||||
|
throw new IOException("Could not create file " + f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCreateFiles() throws IOException
|
||||||
|
{
|
||||||
|
// Whether we are resuming or not,
|
||||||
|
// if any of the files already exists we assume we are resuming.
|
||||||
|
boolean resume = false;
|
||||||
|
|
||||||
|
// Make sure all files are available and of correct length
|
||||||
|
for (int i = 0; i < rafs.length; i++)
|
||||||
|
{
|
||||||
|
long length = rafs[i].length();
|
||||||
|
if(length == lengths[i])
|
||||||
|
{
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageAllocated(this, length);
|
||||||
|
resume = true; // XXX Could dynamicly check
|
||||||
|
}
|
||||||
|
else if (length == 0)
|
||||||
|
allocateFile(i);
|
||||||
|
else
|
||||||
|
throw new IOException("File '" + names[i]
|
||||||
|
+ "' exists, but has wrong length");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check which pieces match and which don't
|
||||||
|
if (resume)
|
||||||
|
{
|
||||||
|
pieces = metainfo.getPieces();
|
||||||
|
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||||
|
for (int i = 0; i < pieces; i++)
|
||||||
|
{
|
||||||
|
int length = getUncheckedPiece(i, piece, 0);
|
||||||
|
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||||
|
if (correctHash)
|
||||||
|
{
|
||||||
|
bitfield.set(i);
|
||||||
|
needed--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageChecked(this, i, correctHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageAllChecked(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void allocateFile(int nr) throws IOException
|
||||||
|
{
|
||||||
|
// XXX - Is this the best way to make sure we have enough space for
|
||||||
|
// the whole file?
|
||||||
|
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||||
|
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
|
||||||
|
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
|
||||||
|
{
|
||||||
|
rafs[nr].write(zeros);
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageAllocated(this, ZEROBLOCKSIZE);
|
||||||
|
}
|
||||||
|
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
|
||||||
|
rafs[nr].write(zeros, 0, size);
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageAllocated(this, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the Storage and makes sure that all RandomAccessFiles are
|
||||||
|
* closed. The Storage is unusable after this.
|
||||||
|
*/
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
for (int i = 0; i < rafs.length; i++)
|
||||||
|
{
|
||||||
|
synchronized(rafs[i])
|
||||||
|
{
|
||||||
|
rafs[i].close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a byte array containing the requested piece or null if
|
||||||
|
* the storage doesn't contain the piece yet.
|
||||||
|
*/
|
||||||
|
public byte[] getPiece(int piece) throws IOException
|
||||||
|
{
|
||||||
|
if (!bitfield.get(piece))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||||
|
getUncheckedPiece(piece, bs, 0);
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the piece in the Storage if it is correct.
|
||||||
|
*
|
||||||
|
* @return true if the piece was correct (sha metainfo hash
|
||||||
|
* matches), otherwise false.
|
||||||
|
* @exception IOException when some storage related error occurs.
|
||||||
|
*/
|
||||||
|
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||||
|
{
|
||||||
|
// First check if the piece is correct.
|
||||||
|
// If we were paranoia we could copy the array first.
|
||||||
|
int length = bs.length;
|
||||||
|
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||||
|
if (listener != null)
|
||||||
|
listener.storageChecked(this, piece, correctHash);
|
||||||
|
if (!correctHash)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean complete;
|
||||||
|
synchronized(bitfield)
|
||||||
|
{
|
||||||
|
if (bitfield.get(piece))
|
||||||
|
return true; // No need to store twice.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bitfield.set(piece);
|
||||||
|
needed--;
|
||||||
|
complete = needed == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = piece * metainfo.getPieceLength(0);
|
||||||
|
int i = 0;
|
||||||
|
long raflen = lengths[i];
|
||||||
|
while (start > raflen)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
start -= raflen;
|
||||||
|
raflen = lengths[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int written = 0;
|
||||||
|
int off = 0;
|
||||||
|
while (written < length)
|
||||||
|
{
|
||||||
|
int need = length - written;
|
||||||
|
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||||
|
synchronized(rafs[i])
|
||||||
|
{
|
||||||
|
rafs[i].seek(start);
|
||||||
|
rafs[i].write(bs, off + written, len);
|
||||||
|
}
|
||||||
|
written += len;
|
||||||
|
if (need - len > 0)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
raflen = lengths[i];
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
// XXX - copy/paste code from putPiece().
|
||||||
|
long start = piece * metainfo.getPieceLength(0);
|
||||||
|
int length = metainfo.getPieceLength(piece);
|
||||||
|
int i = 0;
|
||||||
|
long raflen = lengths[i];
|
||||||
|
while (start > raflen)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
start -= raflen;
|
||||||
|
raflen = lengths[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int read = 0;
|
||||||
|
while (read < length)
|
||||||
|
{
|
||||||
|
int need = length - read;
|
||||||
|
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||||
|
synchronized(rafs[i])
|
||||||
|
{
|
||||||
|
rafs[i].seek(start);
|
||||||
|
rafs[i].readFully(bs, off + read, len);
|
||||||
|
}
|
||||||
|
read += len;
|
||||||
|
if (need - len > 0)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
raflen = lengths[i];
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
52
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
52
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* StorageListener.java - Interface used as callback when storage changes.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used when Storage changes.
|
||||||
|
*/
|
||||||
|
public interface StorageListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the storage creates a new file of a given length.
|
||||||
|
*/
|
||||||
|
void storageCreateFile(Storage storage, String name, long length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to indicate that length bytes have been allocated.
|
||||||
|
*/
|
||||||
|
void storageAllocated(Storage storage, long length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when storage is being checked and the num piece of that
|
||||||
|
* total pieces has been checked. When the piece hash matches the
|
||||||
|
* expected piece hash checked will be true, otherwise it will be
|
||||||
|
* false.
|
||||||
|
*/
|
||||||
|
void storageChecked(Storage storage, int num, boolean checked);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when all pieces in the storage have been checked. Does not
|
||||||
|
* mean that the storage is complete, just that the state of the
|
||||||
|
* storage is known.
|
||||||
|
*/
|
||||||
|
void storageAllChecked(Storage storage);
|
||||||
|
}
|
254
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
254
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/* TrackerClient - Class that informs a tracker and gets new peers.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs metainfo tracker of events and gets new peers for peer
|
||||||
|
* coordinator.
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org)
|
||||||
|
*/
|
||||||
|
public class TrackerClient extends Thread
|
||||||
|
{
|
||||||
|
private static final String NO_EVENT = "";
|
||||||
|
private static final String STARTED_EVENT = "started";
|
||||||
|
private static final String COMPLETED_EVENT = "completed";
|
||||||
|
private static final String STOPPED_EVENT = "stopped";
|
||||||
|
|
||||||
|
private final static int SLEEP = 5; // 5 minutes.
|
||||||
|
|
||||||
|
private final MetaInfo meta;
|
||||||
|
private final PeerCoordinator coordinator;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
private boolean stop;
|
||||||
|
|
||||||
|
private long interval;
|
||||||
|
private long lastRequestTime;
|
||||||
|
|
||||||
|
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
|
||||||
|
{
|
||||||
|
// Set unique name.
|
||||||
|
super("TrackerClient-" + urlencode(coordinator.getID()));
|
||||||
|
this.meta = meta;
|
||||||
|
this.coordinator = coordinator;
|
||||||
|
|
||||||
|
this.port = 6881; //(port == -1) ? 9 : port;
|
||||||
|
|
||||||
|
stop = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interrupts this Thread to stop it.
|
||||||
|
*/
|
||||||
|
public void halt()
|
||||||
|
{
|
||||||
|
stop = true;
|
||||||
|
this.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
// XXX - Support other IPs
|
||||||
|
String announce = I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||||
|
String infoHash = urlencode(meta.getInfoHash());
|
||||||
|
String peerID = urlencode(coordinator.getID());
|
||||||
|
|
||||||
|
long uploaded = coordinator.getUploaded();
|
||||||
|
long downloaded = coordinator.getDownloaded();
|
||||||
|
long left = coordinator.getLeft();
|
||||||
|
|
||||||
|
boolean completed = (left == 0);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
boolean started = false;
|
||||||
|
while (!started)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Send start.
|
||||||
|
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||||
|
uploaded, downloaded, left,
|
||||||
|
STARTED_EVENT);
|
||||||
|
Iterator it = info.getPeers().iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
coordinator.addPeer((Peer)it.next());
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
// Probably not fatal (if it doesn't last to long...)
|
||||||
|
Snark.debug
|
||||||
|
("WARNING: Could not contact tracker at '"
|
||||||
|
+ announce + "': " + ioe, Snark.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!started && !stop)
|
||||||
|
{
|
||||||
|
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sleep one minutes...
|
||||||
|
Thread.sleep(60*1000);
|
||||||
|
}
|
||||||
|
catch(InterruptedException interrupt)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(!stop)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sleep some minutes...
|
||||||
|
Thread.sleep(SLEEP*60*1000);
|
||||||
|
}
|
||||||
|
catch(InterruptedException interrupt)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop)
|
||||||
|
break;
|
||||||
|
|
||||||
|
uploaded = coordinator.getUploaded();
|
||||||
|
downloaded = coordinator.getDownloaded();
|
||||||
|
left = coordinator.getLeft();
|
||||||
|
|
||||||
|
// First time we got a complete download?
|
||||||
|
String event;
|
||||||
|
if (!completed && left == 0)
|
||||||
|
{
|
||||||
|
completed = true;
|
||||||
|
event = COMPLETED_EVENT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
event = NO_EVENT;
|
||||||
|
|
||||||
|
// Only do a request when necessary.
|
||||||
|
if (event == COMPLETED_EVENT
|
||||||
|
|| coordinator.needPeers()
|
||||||
|
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||||
|
uploaded, downloaded, left,
|
||||||
|
event);
|
||||||
|
|
||||||
|
Iterator it = info.getPeers().iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
coordinator.addPeer((Peer)it.next());
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
// Probably not fatal (if it doesn't last to long...)
|
||||||
|
Snark.debug
|
||||||
|
("WARNING: Could not contact tracker at '"
|
||||||
|
+ announce + "': " + ioe, Snark.WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
Snark.debug("TrackerClient: " + t, Snark.ERROR);
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
|
||||||
|
downloaded, left, STOPPED_EVENT);
|
||||||
|
}
|
||||||
|
catch(IOException ioe) { /* ignored */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrackerInfo doRequest(String announce, String infoHash,
|
||||||
|
String peerID, long uploaded,
|
||||||
|
long downloaded, long left, String event)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
String s = announce
|
||||||
|
+ "?info_hash=" + infoHash
|
||||||
|
+ "&peer_id=" + peerID
|
||||||
|
+ "&port=" + port
|
||||||
|
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString()
|
||||||
|
+ "&uploaded=" + uploaded
|
||||||
|
+ "&downloaded=" + downloaded
|
||||||
|
+ "&left=" + left
|
||||||
|
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||||
|
|
||||||
|
File fetched = I2PSnarkUtil.instance().get(s);
|
||||||
|
if (fetched == null) {
|
||||||
|
throw new IOException("Error fetching " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetched.deleteOnExit();
|
||||||
|
InputStream in = new FileInputStream(fetched);
|
||||||
|
|
||||||
|
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||||
|
coordinator.getMetaInfo());
|
||||||
|
if (Snark.debug >= Snark.INFO)
|
||||||
|
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||||
|
lastRequestTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
String failure = info.getFailureReason();
|
||||||
|
if (failure != null)
|
||||||
|
throw new IOException(failure);
|
||||||
|
|
||||||
|
interval = info.getInterval() * 1000;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very lazy byte[] to URL encoder. Just encodes everything, even
|
||||||
|
* "normal" chars.
|
||||||
|
*/
|
||||||
|
static String urlencode(byte[] bs)
|
||||||
|
{
|
||||||
|
StringBuffer sb = new StringBuffer(bs.length*3);
|
||||||
|
for (int i = 0; i < bs.length; i++)
|
||||||
|
{
|
||||||
|
int c = bs[i] & 0xFF;
|
||||||
|
sb.append('%');
|
||||||
|
if (c < 16)
|
||||||
|
sb.append('0');
|
||||||
|
sb.append(Integer.toHexString(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* TrackerInfo - Holds information returned by a tracker, mainly the peer list.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import org.klomp.snark.bencode.*;
|
||||||
|
|
||||||
|
public class TrackerInfo
|
||||||
|
{
|
||||||
|
private final String failure_reason;
|
||||||
|
private final int interval;
|
||||||
|
private final Set peers;
|
||||||
|
|
||||||
|
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(new BDecoder(in), my_id, metainfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
this(be.bdecodeMap().getMap(), my_id, metainfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
BEValue reason = (BEValue)m.get("failure reason");
|
||||||
|
if (reason != null)
|
||||||
|
{
|
||||||
|
failure_reason = reason.getString();
|
||||||
|
interval = -1;
|
||||||
|
peers = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failure_reason = null;
|
||||||
|
BEValue beInterval = (BEValue)m.get("interval");
|
||||||
|
if (beInterval == null)
|
||||||
|
throw new InvalidBEncodingException("No interval given");
|
||||||
|
else
|
||||||
|
interval = beInterval.getInt();
|
||||||
|
BEValue bePeers = (BEValue)m.get("peers");
|
||||||
|
if (bePeers == null)
|
||||||
|
throw new InvalidBEncodingException("No peer list");
|
||||||
|
else
|
||||||
|
peers = getPeers(bePeers.getList(), my_id, metainfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return getPeers(new BDecoder(in), my_id, metainfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return getPeers(be.bdecodeList().getList(), my_id, metainfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Set peers = new HashSet(l.size());
|
||||||
|
|
||||||
|
Iterator it = l.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||||
|
peers.add(new Peer(peerID, my_id, metainfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set getPeers()
|
||||||
|
{
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFailureReason()
|
||||||
|
{
|
||||||
|
return failure_reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInterval()
|
||||||
|
{
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if (failure_reason != null)
|
||||||
|
return "TrackerInfo[FAILED: " + failure_reason + "]";
|
||||||
|
else
|
||||||
|
return "TrackerInfo[interval=" + interval
|
||||||
|
+ ", peers=" + peers + "]";
|
||||||
|
}
|
||||||
|
}
|
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
/* BDecoder - Converts an InputStream to BEValues.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark.bencode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a bencoded stream to <code>BEValue</code>s.
|
||||||
|
*
|
||||||
|
* A bencoded byte stream can represent byte arrays, numbers, lists and
|
||||||
|
* maps (dictionaries).
|
||||||
|
*
|
||||||
|
* It currently contains a hack to indicate a name of a dictionary of
|
||||||
|
* which a SHA-1 digest hash should be calculated (the hash over the
|
||||||
|
* original bencoded bytes).
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org).
|
||||||
|
*/
|
||||||
|
public class BDecoder
|
||||||
|
{
|
||||||
|
// The InputStream to BDecode.
|
||||||
|
private final InputStream in;
|
||||||
|
|
||||||
|
// The last indicator read.
|
||||||
|
// Zero if unknown.
|
||||||
|
// '0'..'9' indicates a byte[].
|
||||||
|
// 'i' indicates an Number.
|
||||||
|
// 'l' indicates a List.
|
||||||
|
// 'd' indicates a Map.
|
||||||
|
// 'e' indicates end of Number, List or Map (only used internally).
|
||||||
|
// -1 indicates end of stream.
|
||||||
|
// Call getNextIndicator to get the current value (will never return zero).
|
||||||
|
private int indicator = 0;
|
||||||
|
|
||||||
|
// Used for ugly hack to get SHA hash over the metainfo info map
|
||||||
|
private String special_map = "info";
|
||||||
|
private boolean in_special_map = false;
|
||||||
|
private final MessageDigest sha_digest;
|
||||||
|
|
||||||
|
// Ugly hack. Return the SHA has over bytes that make up the special map.
|
||||||
|
public byte[] get_special_map_digest()
|
||||||
|
{
|
||||||
|
byte[] result = sha_digest.digest();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ugly hack. Name defaults to "info".
|
||||||
|
public void set_special_map_name(String name)
|
||||||
|
{
|
||||||
|
special_map = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initalizes a new BDecoder. Nothing is read from the given
|
||||||
|
* <code>InputStream</code> yet.
|
||||||
|
*/
|
||||||
|
public BDecoder(InputStream in)
|
||||||
|
{
|
||||||
|
this.in = in;
|
||||||
|
// XXX - Used for ugly hack.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sha_digest = MessageDigest.getInstance("SHA");
|
||||||
|
}
|
||||||
|
catch(NoSuchAlgorithmException nsa)
|
||||||
|
{
|
||||||
|
throw new InternalError(nsa.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BDecoder and immediatly decodes the first value it
|
||||||
|
* sees.
|
||||||
|
*
|
||||||
|
* @return The first BEValue on the stream or null when the stream
|
||||||
|
* has ended.
|
||||||
|
*
|
||||||
|
* @exception InvalidBEncoding when the stream doesn't start with a
|
||||||
|
* bencoded value or the stream isn't a bencoded stream at all.
|
||||||
|
* @exception IOException when somthing bad happens with the stream
|
||||||
|
* to read from.
|
||||||
|
*/
|
||||||
|
public static BEValue bdecode(InputStream in) throws IOException
|
||||||
|
{
|
||||||
|
return new BDecoder(in).bdecode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns what the next bencoded object will be on the stream or -1
|
||||||
|
* when the end of stream has been reached. Can return something
|
||||||
|
* unexpected (not '0' .. '9', 'i', 'l' or 'd') when the stream
|
||||||
|
* isn't bencoded.
|
||||||
|
*
|
||||||
|
* This might or might not read one extra byte from the stream.
|
||||||
|
*/
|
||||||
|
public int getNextIndicator() throws IOException
|
||||||
|
{
|
||||||
|
if (indicator == 0)
|
||||||
|
{
|
||||||
|
indicator = in.read();
|
||||||
|
// XXX - Used for ugly hack
|
||||||
|
if (in_special_map) sha_digest.update((byte)indicator);
|
||||||
|
}
|
||||||
|
return indicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the next indicator and returns either null when the stream
|
||||||
|
* has ended or bdecodes the rest of the stream and returns the
|
||||||
|
* appropriate BEValue encoded object.
|
||||||
|
*/
|
||||||
|
public BEValue bdecode() throws IOException
|
||||||
|
{
|
||||||
|
indicator = getNextIndicator();
|
||||||
|
if (indicator == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (indicator >= '0' && indicator <= '9')
|
||||||
|
return bdecodeBytes();
|
||||||
|
else if (indicator == 'i')
|
||||||
|
return bdecodeNumber();
|
||||||
|
else if (indicator == 'l')
|
||||||
|
return bdecodeList();
|
||||||
|
else if (indicator == 'd')
|
||||||
|
return bdecodeMap();
|
||||||
|
else
|
||||||
|
throw new InvalidBEncodingException
|
||||||
|
("Unknown indicator '" + indicator + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next bencoded value on the stream and makes sure it
|
||||||
|
* is a byte array. If it is not a bencoded byte array it will throw
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public BEValue bdecodeBytes() throws IOException
|
||||||
|
{
|
||||||
|
int c = getNextIndicator();
|
||||||
|
int num = c - '0';
|
||||||
|
if (num < 0 || num > 9)
|
||||||
|
throw new InvalidBEncodingException("Number expected, not '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
c = read();
|
||||||
|
int i = c - '0';
|
||||||
|
while (i >= 0 && i <= 9)
|
||||||
|
{
|
||||||
|
// XXX - This can overflow!
|
||||||
|
num = num*10 + i;
|
||||||
|
c = read();
|
||||||
|
i = c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != ':')
|
||||||
|
throw new InvalidBEncodingException("Colon expected, not '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
|
||||||
|
return new BEValue(read(num));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next bencoded value on the stream and makes sure it
|
||||||
|
* is a number. If it is not a number it will throw
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public BEValue bdecodeNumber() throws IOException
|
||||||
|
{
|
||||||
|
int c = getNextIndicator();
|
||||||
|
if (c != 'i')
|
||||||
|
throw new InvalidBEncodingException("Expected 'i', not '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
c = read();
|
||||||
|
if (c == '0')
|
||||||
|
{
|
||||||
|
c = read();
|
||||||
|
if (c == 'e')
|
||||||
|
return new BEValue(BigInteger.ZERO);
|
||||||
|
else
|
||||||
|
throw new InvalidBEncodingException("'e' expected after zero,"
|
||||||
|
+ " not '" + (char)c + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX - We don't support more the 255 char big integers
|
||||||
|
char[] chars = new char[256];
|
||||||
|
int off = 0;
|
||||||
|
|
||||||
|
if (c == '-')
|
||||||
|
{
|
||||||
|
c = read();
|
||||||
|
if (c == '0')
|
||||||
|
throw new InvalidBEncodingException("Negative zero not allowed");
|
||||||
|
chars[off] = (char)c;
|
||||||
|
off++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c < '1' || c > '9')
|
||||||
|
throw new InvalidBEncodingException("Invalid Integer start '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
chars[off] = (char)c;
|
||||||
|
off++;
|
||||||
|
|
||||||
|
c = read();
|
||||||
|
int i = c - '0';
|
||||||
|
while(i >= 0 && i <= 9)
|
||||||
|
{
|
||||||
|
chars[off] = (char)c;
|
||||||
|
off++;
|
||||||
|
c = read();
|
||||||
|
i = c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != 'e')
|
||||||
|
throw new InvalidBEncodingException("Integer should end with 'e'");
|
||||||
|
|
||||||
|
String s = new String(chars, 0, off);
|
||||||
|
return new BEValue(new BigInteger(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next bencoded value on the stream and makes sure it
|
||||||
|
* is a list. If it is not a list it will throw
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public BEValue bdecodeList() throws IOException
|
||||||
|
{
|
||||||
|
int c = getNextIndicator();
|
||||||
|
if (c != 'l')
|
||||||
|
throw new InvalidBEncodingException("Expected 'l', not '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
List result = new ArrayList();
|
||||||
|
c = getNextIndicator();
|
||||||
|
while (c != 'e')
|
||||||
|
{
|
||||||
|
result.add(bdecode());
|
||||||
|
c = getNextIndicator();
|
||||||
|
}
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
return new BEValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next bencoded value on the stream and makes sure it
|
||||||
|
* is a map (dictonary). If it is not a map it will throw
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public BEValue bdecodeMap() throws IOException
|
||||||
|
{
|
||||||
|
int c = getNextIndicator();
|
||||||
|
if (c != 'd')
|
||||||
|
throw new InvalidBEncodingException("Expected 'd', not '"
|
||||||
|
+ (char)c + "'");
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
Map result = new HashMap();
|
||||||
|
c = getNextIndicator();
|
||||||
|
while (c != 'e')
|
||||||
|
{
|
||||||
|
// Dictonary keys are always strings.
|
||||||
|
String key = bdecode().getString();
|
||||||
|
|
||||||
|
// XXX ugly hack
|
||||||
|
boolean special = special_map.equals(key);
|
||||||
|
if (special)
|
||||||
|
in_special_map = true;
|
||||||
|
|
||||||
|
BEValue value = bdecode();
|
||||||
|
result.put(key, value);
|
||||||
|
|
||||||
|
// XXX ugly hack continued
|
||||||
|
if (special)
|
||||||
|
in_special_map = false;
|
||||||
|
|
||||||
|
c = getNextIndicator();
|
||||||
|
}
|
||||||
|
indicator = 0;
|
||||||
|
|
||||||
|
return new BEValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next byte read from the InputStream (as int).
|
||||||
|
* Throws EOFException if InputStream.read() returned -1.
|
||||||
|
*/
|
||||||
|
private int read() throws IOException
|
||||||
|
{
|
||||||
|
int c = in.read();
|
||||||
|
if (c == -1)
|
||||||
|
throw new EOFException();
|
||||||
|
if (in_special_map) sha_digest.update((byte)c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a byte[] containing length valid bytes starting at offset
|
||||||
|
* zero. Throws EOFException if InputStream.read() returned -1
|
||||||
|
* before all requested bytes could be read. Note that the byte[]
|
||||||
|
* returned might be bigger then requested but will only contain
|
||||||
|
* length valid bytes. The returned byte[] will be reused when this
|
||||||
|
* method is called again.
|
||||||
|
*/
|
||||||
|
private byte[] read(int length) throws IOException
|
||||||
|
{
|
||||||
|
byte[] result = new byte[length];
|
||||||
|
|
||||||
|
int read = 0;
|
||||||
|
while (read < length)
|
||||||
|
{
|
||||||
|
int i = in.read(result, read, length - read);
|
||||||
|
if (i == -1)
|
||||||
|
throw new EOFException();
|
||||||
|
read += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_special_map) sha_digest.update(result, 0, length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
190
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
190
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/* BEValue - Holds different types that a bencoded byte array can represent.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark.bencode;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds different types that a bencoded byte array can represent.
|
||||||
|
* You need to call the correct get method to get the correct java
|
||||||
|
* type object. If the BEValue wasn't actually of the requested type
|
||||||
|
* you will get a InvalidBEncodingException.
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org)
|
||||||
|
*/
|
||||||
|
public class BEValue
|
||||||
|
{
|
||||||
|
// This is either a byte[], Number, List or Map.
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
public BEValue(byte[] value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BEValue(Number value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BEValue(List value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BEValue(Map value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as a String. This operation only succeeds
|
||||||
|
* when the BEValue is a byte[], otherwise it will throw a
|
||||||
|
* InvalidBEncodingException. The byte[] will be interpreted as
|
||||||
|
* UTF-8 encoded characters.
|
||||||
|
*/
|
||||||
|
public String getString() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new String(getBytes(), "UTF-8");
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
throw new InvalidBEncodingException(cce.toString());
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException uee)
|
||||||
|
{
|
||||||
|
throw new InternalError(uee.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as a byte[]. This operation only succeeds
|
||||||
|
* when the BEValue is actually a byte[], otherwise it will throw a
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public byte[] getBytes() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (byte[])value;
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
throw new InvalidBEncodingException(cce.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as a Number. This operation only succeeds
|
||||||
|
* when the BEValue is actually a Number, otherwise it will throw a
|
||||||
|
* InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public Number getNumber() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (Number)value;
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
throw new InvalidBEncodingException(cce.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as int. This operation only succeeds when
|
||||||
|
* the BEValue is actually a Number, otherwise it will throw a
|
||||||
|
* InvalidBEncodingException. The returned int is the result of
|
||||||
|
* <code>Number.intValue()</code>.
|
||||||
|
*/
|
||||||
|
public int getInt() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
return getNumber().intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as long. This operation only succeeds when
|
||||||
|
* the BEValue is actually a Number, otherwise it will throw a
|
||||||
|
* InvalidBEncodingException. The returned long is the result of
|
||||||
|
* <code>Number.longValue()</code>.
|
||||||
|
*/
|
||||||
|
public long getLong() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
return getNumber().longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as a List of BEValues. This operation only
|
||||||
|
* succeeds when the BEValue is actually a List, otherwise it will
|
||||||
|
* throw a InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public List getList() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (List)value;
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
throw new InvalidBEncodingException(cce.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this BEValue as a Map of BEValue keys and BEValue
|
||||||
|
* values. This operation only succeeds when the BEValue is actually
|
||||||
|
* a Map, otherwise it will throw a InvalidBEncodingException.
|
||||||
|
*/
|
||||||
|
public Map getMap() throws InvalidBEncodingException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (Map)value;
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
throw new InvalidBEncodingException(cce.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
String valueString;
|
||||||
|
if (value instanceof byte[])
|
||||||
|
{
|
||||||
|
byte[] bs = (byte[])value;
|
||||||
|
// XXX - Stupid heuristic...
|
||||||
|
if (bs.length <= 12)
|
||||||
|
valueString = new String(bs);
|
||||||
|
else
|
||||||
|
valueString = "bytes:" + bs.length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
valueString = value.toString();
|
||||||
|
|
||||||
|
return "BEValue[" + valueString + "]";
|
||||||
|
}
|
||||||
|
}
|
191
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
191
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/* BDecoder - Converts an InputStream to BEValues.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark.bencode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class BEncoder
|
||||||
|
{
|
||||||
|
|
||||||
|
public static byte[] bencode(Object o) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(o, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(Object o, OutputStream out)
|
||||||
|
throws IOException, IllegalArgumentException
|
||||||
|
{
|
||||||
|
if (o instanceof String)
|
||||||
|
bencode((String)o, out);
|
||||||
|
else if (o instanceof byte[])
|
||||||
|
bencode((byte[])o, out);
|
||||||
|
else if (o instanceof Number)
|
||||||
|
bencode((Number)o, out);
|
||||||
|
else if (o instanceof List)
|
||||||
|
bencode((List)o, out);
|
||||||
|
else if (o instanceof Map)
|
||||||
|
bencode((Map)o, out);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("Cannot bencode: " + o.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] bencode(String s)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(s, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(String s, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
byte[] bs = s.getBytes("UTF-8");
|
||||||
|
bencode(bs, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] bencode(Number n)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(n, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(Number n, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
out.write('i');
|
||||||
|
String s = n.toString();
|
||||||
|
out.write(s.getBytes("UTF-8"));
|
||||||
|
out.write('e');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] bencode(List l)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(l, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(List l, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
out.write('l');
|
||||||
|
Iterator it = l.iterator();
|
||||||
|
while (it.hasNext())
|
||||||
|
bencode(it.next(), out);
|
||||||
|
out.write('e');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] bencode(byte[] bs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(bs, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(byte[] bs, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
String l = Integer.toString(bs.length);
|
||||||
|
out.write(l.getBytes("UTF-8"));
|
||||||
|
out.write(':');
|
||||||
|
out.write(bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] bencode(Map m)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bencode(m, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
throw new InternalError(ioe.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bencode(Map m, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
out.write('d');
|
||||||
|
|
||||||
|
// Keys must be sorted. XXX - But is this the correct order?
|
||||||
|
Set s = m.keySet();
|
||||||
|
List l = new ArrayList(s);
|
||||||
|
Collections.sort(l);
|
||||||
|
|
||||||
|
Iterator it = l.iterator();
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
// Keys must be Strings.
|
||||||
|
String key = (String)it.next();
|
||||||
|
Object value = m.get(key);
|
||||||
|
bencode(key, out);
|
||||||
|
bencode(value, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write('e');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/* InvalidBEncodingException - Thrown when a bencoded stream is corrupted.
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
This file is part of Snark.
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software Foundation,
|
||||||
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.klomp.snark.bencode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a bencoded stream is corrupted.
|
||||||
|
*
|
||||||
|
* @author Mark Wielaard (mark@klomp.org)
|
||||||
|
*/
|
||||||
|
public class InvalidBEncodingException extends IOException
|
||||||
|
{
|
||||||
|
public InvalidBEncodingException(String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
11
apps/i2psnark/readme.txt
Normal file
11
apps/i2psnark/readme.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
|
||||||
|
|
||||||
|
The build in tracker has been removed for simplicity.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
java -jar lib/i2psnark.jar myFile.torrent
|
||||||
|
|
||||||
|
or, a more verbose setting:
|
||||||
|
java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
|
||||||
|
--i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
|
||||||
|
--debug 6 myFile.torrent
|
141
apps/i2psnark/readme.txt.snark
Normal file
141
apps/i2psnark/readme.txt.snark
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
The Hunting of the Snark Project - BitTorrent Application Suite
|
||||||
|
0.5 - The Beaver's Lesson (27 June 2003)
|
||||||
|
|
||||||
|
"It's a Snark!" was the sound that first came to their ears,
|
||||||
|
And seemed almost too good to be true.
|
||||||
|
Then followed a torrent of laughter and cheers:
|
||||||
|
Then the ominous words "It's a Boo-"
|
||||||
|
|
||||||
|
-- from The Hunting Of The Snark by Lewis Carroll
|
||||||
|
|
||||||
|
Snark is a client for downloading and sharing files distributed with
|
||||||
|
the BitTorrent protocol. It is mainly used for exploring the BitTorrent
|
||||||
|
protocol and experimenting with the the GNU Compiler for Java (gcj).
|
||||||
|
But it can also be used as a regular BitTorrent Client.
|
||||||
|
|
||||||
|
Snark can also act as a torrent creator, micro http server for delivering
|
||||||
|
metainfo.torrent files and has an integrated Tracker for making sharing of
|
||||||
|
files as easy as possible.
|
||||||
|
|
||||||
|
When you give the option --share Snark will automatically
|
||||||
|
create a .torrent file, start a very simple webserver to distribute
|
||||||
|
the metainfo.torrent file and a local tracker that other BitTorrent
|
||||||
|
clients can connect to.
|
||||||
|
|
||||||
|
Distribution
|
||||||
|
------------
|
||||||
|
|
||||||
|
Copyright (C) 2003 Mark J. Wielaard
|
||||||
|
|
||||||
|
Snark is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
Requirements/Installation
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
The GNU Compiler for java (gcj) version 3.3 or later.
|
||||||
|
(Earlier versions have a faulty SHA message digest implementation.)
|
||||||
|
On Debian GNU/Linux based distributions just install the gcj-3.3 package.
|
||||||
|
Edit the GCJ variable in the Makefile if your gcj binary is not gcj-3.3.
|
||||||
|
|
||||||
|
Typing 'make' will create the native snark binary and a snark.jar file
|
||||||
|
for use with traditional java byte code interpreters.
|
||||||
|
|
||||||
|
It is possible to compile the sources with other java compilers
|
||||||
|
like jikes or kjc to produce the snark.jar file. Edit the JAVAC and
|
||||||
|
JAVAC_FLAGS variables on top of the Makefile for this. And type
|
||||||
|
'make snark.jar' to create a jar file that can be used by traditional
|
||||||
|
java bytecode interpreters like kaffe: 'kaffe -jar snark.jar'.
|
||||||
|
You will need at least version 1.1 of kaffe for all functionality to work
|
||||||
|
correctly ('--share' does not work with older versions).
|
||||||
|
|
||||||
|
When trying out the experimental Gnome frontend you also need the java-gnome
|
||||||
|
bindings. On Debian GNU/Linux systems install the package libgnome0-java.
|
||||||
|
You can try it out by typing 'make snark-gnome' and then run 'snark-gnome.sh'
|
||||||
|
like you would with the normal command line client.
|
||||||
|
|
||||||
|
Running
|
||||||
|
-------
|
||||||
|
|
||||||
|
To use the program start it with:
|
||||||
|
|
||||||
|
snark [--debug [level]] [--no-commands] [--port <port>]
|
||||||
|
[--share (<ip>|<host>)] (<url>|<file>|<dir>)
|
||||||
|
--debug Shows some extra info and stacktraces.
|
||||||
|
level How much debug details to show
|
||||||
|
(defaults to 3, with --debug to 4, highest level is 6).
|
||||||
|
--no-commands Don't read interactive commands or show usage info.
|
||||||
|
--port The port to listen on for incomming connections
|
||||||
|
(if not given defaults to first free port between 6881-6889).
|
||||||
|
--share Start torrent tracker on <ip> address or <host> name.
|
||||||
|
<url> URL pointing to .torrent metainfo file to download/share.
|
||||||
|
<file> Either a local .torrent metainfo file to download
|
||||||
|
or (with --share) a file to share.
|
||||||
|
<dir> A directory with files to share (needs --share).
|
||||||
|
|
||||||
|
Since this is an early beta release there are probably still some bugs
|
||||||
|
in the program. To help find them run the program with the --debug
|
||||||
|
option which shows more information on what it going on. You can also give
|
||||||
|
the level of debug output you want. Zero will give (almost) no output at all.
|
||||||
|
Everything above debug level 4 is probably to much (only really useful to
|
||||||
|
see what goes on on the protocol/network level).
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
- To simple start downloading/sharing a file.
|
||||||
|
Either download the .torrent file to disk and start snark with:
|
||||||
|
./snark somefile.torrent
|
||||||
|
|
||||||
|
Or give it the complete URL:
|
||||||
|
./snark http://somehost.example.com/cd-images/bbc-lnx.iso.torrent
|
||||||
|
|
||||||
|
- To start seeding/sharing a local file:
|
||||||
|
./snark --share my-host.example.com some-file
|
||||||
|
|
||||||
|
Snark will respond with:
|
||||||
|
Listening on port: 6881
|
||||||
|
Trying to create metainfo torrent for 'some-file'
|
||||||
|
Creating torrent piece hashes: ++++++++++
|
||||||
|
Torrent available on http://my-host.example.com:6881/metainfo.torrent
|
||||||
|
|
||||||
|
You can now point other people to the above URL so they can share
|
||||||
|
the file with their own BitTorrent client.
|
||||||
|
|
||||||
|
Commands
|
||||||
|
|
||||||
|
While the program is running in text mode you can currently give the
|
||||||
|
following commands: 'info', 'list' and 'quit'.
|
||||||
|
|
||||||
|
Interactive commands are disabled when the '--no-commands' flag is given.
|
||||||
|
This is sometimes desireable for running snark in the background.
|
||||||
|
|
||||||
|
More information
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- The Evolution of Cooperation - Robert Axelrod
|
||||||
|
ISBN 0-465-02121-2
|
||||||
|
|
||||||
|
- The BitTorrent protocol description:
|
||||||
|
<http://bitconjurer.org/BitTorrent/protocol.html>
|
||||||
|
|
||||||
|
- The GNU Compiler for Java (gcj):
|
||||||
|
<http://gcc.gnu.org/java/>
|
||||||
|
|
||||||
|
- java-gnome bindings : <http://java-gnome.sourceforge.net/>
|
||||||
|
|
||||||
|
- The Hunting of the Snark - Lewis Carroll
|
||||||
|
|
||||||
|
Comments welcome
|
||||||
|
|
||||||
|
- Mark Wielaard <mark@klomp.org>
|
Reference in New Issue
Block a user