forked from I2P_Developers/i2p.i2p
Compare commits
36 Commits
i2p_0_6_1_
...
i2p_0_6_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7c2ae9a3b | ||
|
|
ac3b88b9e9 | ||
|
|
60124cdcdc | ||
|
|
e7d2281772 | ||
|
|
52ace2d695 | ||
|
|
b5a25801b4 | ||
|
|
3fc0558810 | ||
|
|
bd9c6ff463 | ||
|
|
a0c822af96 | ||
|
|
4de302101d | ||
|
|
84383c3dab | ||
|
|
a94abb13a4 | ||
|
|
ad59ab691b | ||
|
|
ee9ac31c8b | ||
|
|
788998307a | ||
|
|
2f8a2879bb | ||
|
|
c7b9525d2c | ||
|
|
3fbc6f41af | ||
|
|
6534c84578 | ||
|
|
3816c79193 | ||
|
|
8458e4e0af | ||
|
|
0b9e4967a0 | ||
|
|
05e2da7c22 | ||
|
|
13bda1f6d7 | ||
|
|
ea22c73a73 | ||
|
|
aa5f1cb18d | ||
|
|
ab9c6d59cf | ||
|
|
76655d01d0 | ||
|
|
138f7d3b8d | ||
|
|
df4b998a6a | ||
|
|
2d70103f88 | ||
|
|
731e26e7d6 | ||
|
|
f9d3b157f0 | ||
|
|
12775c416d | ||
|
|
2ca4e63216 | ||
|
|
918d8f851f |
69
Makefile.gcj
Normal file
69
Makefile.gcj
Normal file
@@ -0,0 +1,69 @@
|
||||
# Makefile for building native I2P binaries and libraries with GCJ
|
||||
#
|
||||
# WARNING: Do not use this yet, as it may explode (etc).
|
||||
#
|
||||
GCJ=gcj #/usr/local/gcc-4.0.2/bin/gcj
|
||||
EXTRA_LD_PATH= #/usr/local/gcc-4.0.2/lib
|
||||
ANT=ant #/opt/apache-ant-1.6.5/bin/ant
|
||||
ANT_TARGET=buildclean
|
||||
NATIVE_DIR=native
|
||||
|
||||
##
|
||||
# Define what jar files get into libi2p.so. The current setup is
|
||||
# *incredibly* lazy, throwing everything in the .so, rather than
|
||||
# give each .jar file its own .so.
|
||||
# i2p.jar: base SDK
|
||||
# mstreaming.jar: streaming API
|
||||
# streaming.jar: full streaming lib implementation
|
||||
# i2ptunnel.jar: I2PTunnel proxy
|
||||
# sam.jar: SAM bridge and API
|
||||
# i2psnark.jar: bittorrent client
|
||||
# router.jar: full I2P router
|
||||
# jbigi.jar: collection of native optimized GMP routines for crypto
|
||||
JAR_BASE=i2p.jar mstreaming.jar streaming.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
|
||||
JAR_ROUTER=router.jar
|
||||
JAR_JBIGI=jbigi.jar
|
||||
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
|
||||
|
||||
SYSTEM_PROPS=
|
||||
#SYSTEM_PROPS=-Di2p.weakPRNG=true
|
||||
|
||||
LD_LIBRARY_PATH=${EXTRA_LD_PATH}:.
|
||||
|
||||
all: jars native
|
||||
@echo "* Build complete"
|
||||
|
||||
jars:
|
||||
@${ANT} ${ANT_TARGET}
|
||||
|
||||
clean: native_clean
|
||||
|
||||
native: native_clean native_shared
|
||||
@echo "* Native code build in ${NATIVE}"
|
||||
|
||||
native_clean:
|
||||
@rm -rf ${NATIVE_DIR}
|
||||
@mkdir ${NATIVE_DIR}
|
||||
|
||||
native_shared: libi2p.so
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
|
||||
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
|
||||
@echo " and launching the appropriate proxies"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@echo "* i2psnark is an anonymous bittorrent client"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@echo "* i2prouter is the main I2P router"
|
||||
@echo " it can be used, and while the router console won't load,"
|
||||
@echo " i2ptunnel will, so it will start all the proxies defined in i2ptunnel.config"
|
||||
|
||||
libi2p.so:
|
||||
@echo "* Building libi2p.so"
|
||||
@(cd build ; ${GCJ} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@ls -l ${NATIVE_DIR}/libi2p.so
|
||||
@echo "* libi2p.so built"
|
||||
@@ -143,7 +143,7 @@ public class Daemon {
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("update_delay", "1");
|
||||
defaultSettings.put("update_delay", "12");
|
||||
|
||||
File homeFile = new File(home);
|
||||
if (!homeFile.exists()) {
|
||||
@@ -188,4 +188,4 @@ public class Daemon {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
198
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
198
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/* 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();
|
||||
coordinator.removePeerFromPieces(peer);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
534
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
534
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
@@ -0,0 +1,534 @@
|
||||
/* 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 list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(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();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 Piece(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())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
p.addPeer(peer);
|
||||
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)
|
||||
{
|
||||
Piece piece = null;
|
||||
Collections.sort(wantedPieces); // Sort in order of rarest first.
|
||||
List requested = new ArrayList();
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (piece == null && it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
if (havePieces.get(p.getId()) && !p.isRequested())
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
else if (p.isRequested())
|
||||
{
|
||||
requested.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it2.next();
|
||||
if (havePieces.get(p.getId()))
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
if (piece == null) return -1; //If we still can't find a piece we want, so be it.
|
||||
}
|
||||
piece.setRequested(true);
|
||||
return piece.getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
Piece p = new Piece(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();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/** Called when a peer is removed, to prevent it from being used in
|
||||
* rarest-first calculations.
|
||||
*/
|
||||
public void removePeerFromPieces(Peer peer) {
|
||||
synchronized(wantedPieces) {
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
piece.removePeer(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
|
||||
private int id;
|
||||
private Set peers;
|
||||
private boolean requested;
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = Collections.synchronizedSet(new HashSet());
|
||||
this.requested = false;
|
||||
}
|
||||
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
return this.peers.size() - ((Piece)o).peers.size();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
try {
|
||||
return this.id == ((Piece)o).id;
|
||||
} catch (ClassCastException cce) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getId() { return this.id; }
|
||||
public Set getPeers() { return this.peers; }
|
||||
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
}
|
||||
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>
|
||||
@@ -240,6 +240,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
runClient(args, l);
|
||||
} else if ("httpclient".equals(cmdname)) {
|
||||
runHttpClient(args, l);
|
||||
} else if ("ircclient".equals(cmdname)) {
|
||||
runIrcClient(args, l);
|
||||
} else if ("sockstunnel".equals(cmdname)) {
|
||||
runSOCKSTunnel(args, l);
|
||||
} else if ("config".equals(cmdname)) {
|
||||
@@ -620,11 +622,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
|
||||
boolean isShared = true;
|
||||
if (args.length > 1) {
|
||||
if (args.length > 2) {
|
||||
if ("true".equalsIgnoreCase(args[2].trim())) {
|
||||
isShared = true;
|
||||
} else if ("false".equalsIgnoreCase(args[1].trim())) {
|
||||
_log.warn("args[1] == [" + args[1] + "] and rejected explicitly");
|
||||
} else if ("false".equalsIgnoreCase(args[2].trim())) {
|
||||
_log.warn("args[2] == [" + args[2] + "] and rejected explicitly");
|
||||
isShared = false;
|
||||
} else {
|
||||
// isShared not specified, default to true
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -14,6 +12,7 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
|
||||
@@ -81,9 +80,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
try {
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread in = new Thread(new IrcInboundFilter(s,i2ps));
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
|
||||
in.start();
|
||||
Thread out = new Thread(new IrcOutboundFilter(s,i2ps));
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -138,41 +137,43 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcInboundFilter: Running.");
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("inbound FILTERED: "+outmsg);
|
||||
_log.warn(" - inbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("inbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("inbound BLOCKED: "+inmsg);
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcInboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
local.close();
|
||||
} catch (IOException e) {
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("inbound FILTERED: "+outmsg);
|
||||
_log.warn(" - inbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("inbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("inbound BLOCKED: "+inmsg);
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcInboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
_log.error("Error filtering inbound data", re);
|
||||
} finally {
|
||||
if (local != null) try { local.close(); } catch (IOException e) {}
|
||||
}
|
||||
if(_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcInboundFilter: Done.");
|
||||
@@ -206,41 +207,43 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcOutboundFilter: Running.");
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("outbound FILTERED: "+outmsg);
|
||||
_log.warn(" - outbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("outbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IrcOutboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException e) {
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("outbound FILTERED: "+outmsg);
|
||||
_log.warn(" - outbound was: "+inmsg);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("outbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IrcOutboundFilter: disconnected",e1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
_log.error("Error filtering outbound data", re);
|
||||
} finally {
|
||||
if (remote != null) try { remote.close(); } catch (IOException e) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("IrcOutboundFilter: Done.");
|
||||
@@ -268,7 +271,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"QUIT",
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR"
|
||||
"ERROR",
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
@@ -332,13 +336,19 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"NICK",
|
||||
// "QUIT", // replace with a filtered QUIT to hide client quit messages
|
||||
"SILENCE",
|
||||
"MAP", // seems safe enough, the ircd should protect themselves though
|
||||
"PART",
|
||||
"OPER",
|
||||
"PING",
|
||||
"KICK",
|
||||
"HELPME",
|
||||
"RULES"
|
||||
"RULES",
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
if(field[0].length()==0)
|
||||
return null; // W T F?
|
||||
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
return null; // wtf
|
||||
|
||||
@@ -29,6 +29,7 @@ public class PeerHelper {
|
||||
public String getPeerSummary() {
|
||||
try {
|
||||
_context.commSystem().renderStatusHTML(_out);
|
||||
_context.bandwidthLimiter().renderStatusHTML(_out);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ public class UpdateHandler {
|
||||
|
||||
private static final String SIGNED_UPDATE_FILE = "i2pupdate.sud";
|
||||
|
||||
public UpdateHandler() {}
|
||||
public UpdateHandler() {
|
||||
this(ContextHelper.getContext(null));
|
||||
}
|
||||
public UpdateHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(UpdateHandler.class);
|
||||
|
||||
@@ -72,8 +72,8 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 8*1000;
|
||||
public static final long MIN_RESEND_DELAY = 3*1000;
|
||||
public static final long MAX_RESEND_DELAY = 15*1000;
|
||||
public static final long MIN_RESEND_DELAY = 2*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
public static int DISCONNECT_TIMEOUT = 5*60*1000;
|
||||
@@ -193,7 +193,28 @@ public class Connection {
|
||||
}
|
||||
|
||||
void ackImmediately() {
|
||||
PacketLocal packet = _receiver.send(null, 0, 0);
|
||||
PacketLocal packet = null;
|
||||
synchronized (_outboundPackets) {
|
||||
if (_outboundPackets.size() > 0) {
|
||||
// ordered, so pick the lowest to retransmit
|
||||
Iterator iter = _outboundPackets.values().iterator();
|
||||
packet = (PacketLocal)iter.next();
|
||||
//iter.remove();
|
||||
}
|
||||
}
|
||||
if (packet != null) {
|
||||
ResendPacketEvent evt = (ResendPacketEvent)packet.getResendEvent();
|
||||
if (evt != null) {
|
||||
boolean sent = evt.retransmit(false);
|
||||
if (sent) {
|
||||
return;
|
||||
} else {
|
||||
//SimpleTimer.getInstance().addEvent(evt, evt.getNextSendTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we don't have anything to retransmit, send a small ack
|
||||
packet = _receiver.send(null, 0, 0);
|
||||
//packet.releasePayload();
|
||||
}
|
||||
|
||||
@@ -277,7 +298,7 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
||||
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet, timeout + _context.clock().now()), timeout);
|
||||
}
|
||||
|
||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||
@@ -899,18 +920,29 @@ public class Connection {
|
||||
*/
|
||||
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
|
||||
private PacketLocal _packet;
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
private long _nextSendTime;
|
||||
public ResendPacketEvent(PacketLocal packet, long sendTime) {
|
||||
_packet = packet;
|
||||
_nextSendTime = sendTime;
|
||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
public long getNextSendTime() { return _nextSendTime; }
|
||||
public void timeReached() { retransmit(true); }
|
||||
/**
|
||||
* Retransmit the packet if we need to.
|
||||
*
|
||||
* @param penalize true if this retransmission is caused by a timeout, false if we
|
||||
* are just sending this packet instead of an ACK
|
||||
* @return true if the packet was sent, false if it was not
|
||||
*/
|
||||
public boolean retransmit(boolean penalize) {
|
||||
if (_packet.getAckTime() > 0)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (_resetSent || _resetReceived) {
|
||||
_packet.cancelled();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -932,7 +964,8 @@ public class Connection {
|
||||
_log.warn("Delaying resend of " + _packet + " as there are "
|
||||
+ _activeResends + " active resends already in play");
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
||||
return;
|
||||
_nextSendTime = 1000 + _context.clock().now();
|
||||
return false;
|
||||
}
|
||||
// revamp various fields, in case we need to ack more, etc
|
||||
_inputStream.updateAcks(_packet);
|
||||
@@ -949,7 +982,7 @@ public class Connection {
|
||||
|
||||
int newWindowSize = getOptions().getWindowSize();
|
||||
|
||||
if (_ackSinceCongestion) {
|
||||
if (penalize && _ackSinceCongestion) {
|
||||
// only shrink the window once per window
|
||||
if (_packet.getSequenceNum() > _lastCongestionHighestUnacked) {
|
||||
congestionOccurred();
|
||||
@@ -1004,7 +1037,7 @@ public class Connection {
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numSends - 1 > _options.getMaxResends()) {
|
||||
@@ -1023,11 +1056,14 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
_nextSendTime = timeout + _context.clock().now();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Packet acked before resend (resend="+ resend + "): "
|
||||
// + _packet + " on " + Connection.this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +54,11 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
public static final String PROP_SLOW_START_GROWTH_RATE_FACTOR = "i2p.streaming.slowStartGrowthRateFactor";
|
||||
|
||||
private static final int TREND_COUNT = 3;
|
||||
static final int INITIAL_WINDOW_SIZE = 4;
|
||||
static final int INITIAL_WINDOW_SIZE = 6;
|
||||
static final int DEFAULT_MAX_SENDS = 8;
|
||||
|
||||
static final int MIN_WINDOW_SIZE = 6;
|
||||
|
||||
public ConnectionOptions() {
|
||||
super();
|
||||
}
|
||||
@@ -179,10 +181,15 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
*/
|
||||
public int getWindowSize() { return _windowSize; }
|
||||
public void setWindowSize(int numMsgs) {
|
||||
if (numMsgs <= 0)
|
||||
numMsgs = 1;
|
||||
if (numMsgs < MIN_WINDOW_SIZE)
|
||||
numMsgs = MIN_WINDOW_SIZE;
|
||||
// the stream's max window size may be less than the min window size, for
|
||||
// instance, with interactive streams of cwin=1. This is why we test it here
|
||||
// after checking MIN_WINDOW_SIZE
|
||||
if (numMsgs > _maxWindowSize)
|
||||
numMsgs = _maxWindowSize;
|
||||
else if (numMsgs <= 0)
|
||||
numMsgs = 1;
|
||||
_windowSize = numMsgs;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,16 @@ public class ConnectionPacketHandler {
|
||||
packet.releasePayload();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (con.getCloseSentOn() > 0) && (con.getUnackedPacketsSent() <= 0) &&
|
||||
(packet.getSequenceNum() > 0) && (packet.getPayloadSize() > 0)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received new data when we've sent them data and all of our data is acked: "
|
||||
+ packet + " on " + con + "");
|
||||
con.sendReset();
|
||||
packet.releasePayload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.isFlagSet(Packet.FLAG_MAX_PACKET_SIZE_INCLUDED)) {
|
||||
if (packet.getOptionalMaxSize() < con.getOptions().getMaxMessageSize()) {
|
||||
@@ -285,8 +295,10 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().addRateData("stream.trend", trend, newWindowSize);
|
||||
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if ( (newWindowSize > con.getLastCongestionSeenAt() / 2) ||
|
||||
(trend > 0) ) { // tcp vegas: avoidance if rtt is increasing, even if we arent at ssthresh/2 yet
|
||||
if (trend < 0) {
|
||||
// rtt is shrinking, so lets increment the cwin
|
||||
newWindowSize++;
|
||||
} else if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
|
||||
@@ -77,6 +77,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
|
||||
public void write(byte b[], int off, int len) throws IOException {
|
||||
if (_closed) throw new IOException("Already closed");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("write(b[], " + off + ", " + len + ") ");
|
||||
int cur = off;
|
||||
|
||||
@@ -236,8 +236,8 @@ public class PacketHandler {
|
||||
}
|
||||
packet.releasePayload();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received on an unknown stream (and not an ECHO): " + packet);
|
||||
if (_log.shouldLog(Log.DEBUG) && !packet.isFlagSet(Packet.FLAG_SYNCHRONIZE))
|
||||
_log.debug("Packet received on an unknown stream (and not an ECHO or SYN): " + packet);
|
||||
if (sendId <= 0) {
|
||||
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
|
||||
if (con != null) {
|
||||
|
||||
@@ -101,6 +101,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
||||
}
|
||||
public SimpleTimer.TimedEvent getResendEvent() { return _resendEvent; }
|
||||
|
||||
/** how long after packet creation was it acked? */
|
||||
public int getAckTime() {
|
||||
|
||||
@@ -340,9 +340,13 @@ public class BlogManager {
|
||||
|
||||
public String getDefaultProxyHost() { return _context.getProperty("syndie.defaultProxyHost", ""); }
|
||||
public String getDefaultProxyPort() { return _context.getProperty("syndie.defaultProxyPort", ""); }
|
||||
public int getUpdateDelay() { return Integer.parseInt(_context.getProperty("syndie.updateDelay", "12")); }
|
||||
public String[] getUpdateArchives() { return _context.getProperty("syndie.updateArchives", "").split(","); }
|
||||
public boolean getImportAddresses() { return _context.getProperty("syndie.importAddresses", "false").equals("true"); }
|
||||
public int getUpdateDelay() {
|
||||
int delay = Integer.parseInt(_context.getProperty("syndie.updateDelay", "12"));
|
||||
if (delay < 1) delay = 1;
|
||||
return delay;
|
||||
}
|
||||
|
||||
public boolean authorizeAdmin(String pass) {
|
||||
if (isSingleUser()) return true;
|
||||
|
||||
@@ -7,23 +7,16 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import sun.security.provider.SHA;
|
||||
//import sun.security.provider.SHA;
|
||||
|
||||
import com.sun.syndication.feed.atom.Entry;
|
||||
import com.sun.syndication.feed.synd.SyndCategory;
|
||||
import com.sun.syndication.feed.synd.SyndContent;
|
||||
import com.sun.syndication.feed.synd.SyndEntry;
|
||||
@@ -33,6 +26,7 @@ import com.sun.syndication.io.SyndFeedInput;
|
||||
import com.sun.syndication.io.XmlReader;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
public class Sucker {
|
||||
@@ -50,6 +44,8 @@ public class Sucker {
|
||||
private boolean importEnclosures=true;
|
||||
private boolean importRefs=true;
|
||||
private boolean pendingEndLink;
|
||||
private boolean shouldProxy;
|
||||
private int proxyPortNum;
|
||||
|
||||
public Sucker() {}
|
||||
|
||||
@@ -96,22 +92,14 @@ public class Sucker {
|
||||
if(idx==0)
|
||||
idx=x;
|
||||
baseUrl=urlToLoad.substring(0,idx);
|
||||
System.out.println("BaseUrl: "+baseUrl);
|
||||
|
||||
System.out.println("Processing: "+urlToLoad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void suck() {
|
||||
URL feedUrl;
|
||||
SyndFeed feed;
|
||||
int i;
|
||||
|
||||
//if (proxyHost != null && proxyPort != null) {
|
||||
// // Set proxy
|
||||
// System.setProperty("http.proxyHost", proxyHost);
|
||||
// System.setProperty("http.proxyPort", proxyPort);
|
||||
//}
|
||||
|
||||
|
||||
//
|
||||
try {
|
||||
@@ -141,8 +129,8 @@ public class Sucker {
|
||||
|
||||
SyndFeedInput input = new SyndFeedInput();
|
||||
|
||||
boolean shouldProxy = false;
|
||||
int proxyPortNum = -1;
|
||||
shouldProxy = false;
|
||||
proxyPortNum = -1;
|
||||
if ( (proxyHost != null) && (proxyPort != null) ) {
|
||||
try {
|
||||
proxyPortNum = Integer.parseInt(proxyPort);
|
||||
@@ -206,14 +194,15 @@ public class Sucker {
|
||||
System.out.println("new: " + messageId);
|
||||
|
||||
if (convertToSml(e, ""+messageNumber)) {
|
||||
hos.write(messageId.getBytes());
|
||||
hos.write("\n".getBytes());
|
||||
|
||||
if (pushScript != null) {
|
||||
if (!execPushScript(""+messageNumber, time))
|
||||
System.out.println("push failed");
|
||||
else
|
||||
if (!execPushScript(""+messageNumber, time)) {
|
||||
System.out.println("################## push failed");
|
||||
}else {
|
||||
System.out.println("push success");
|
||||
hos.write(messageId.getBytes());
|
||||
hos.write("\n".getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
messageNumber++;
|
||||
@@ -285,7 +274,6 @@ public class Sucker {
|
||||
private boolean convertToSml(SyndEntry e, String messageName) {
|
||||
|
||||
// Create message
|
||||
File messageFile = new File(messageName);
|
||||
FileOutputStream fos;
|
||||
messagePath=outputDir+"/"+messageName;
|
||||
try {
|
||||
@@ -353,6 +341,11 @@ public class Sucker {
|
||||
//System.out.println("html: "+html.substring(i));
|
||||
|
||||
int tagLen = findTagLen(html.substring(i));
|
||||
if(tagLen<=0)
|
||||
{
|
||||
System.out.println("Bad html? ("+html+")");
|
||||
break;
|
||||
}
|
||||
//
|
||||
String htmlTag = html.substring(i,i+tagLen);
|
||||
|
||||
@@ -376,14 +369,15 @@ public class Sucker {
|
||||
|
||||
private String htmlTagToSmlTag(String htmlTag) {
|
||||
String ret="";
|
||||
String htmlTagLowerCase=htmlTag.toLowerCase();
|
||||
|
||||
if(importEnclosures && htmlTag.startsWith("<img"))
|
||||
if(importEnclosures && htmlTagLowerCase.startsWith("<img"))
|
||||
{
|
||||
System.out.println("Found image tag: "+htmlTag);
|
||||
int a,b;
|
||||
a=htmlTag.indexOf("src=\"")+5;
|
||||
a=htmlTagLowerCase.indexOf("src=\"")+5;
|
||||
b=a+1;
|
||||
while(htmlTag.charAt(b)!='\"')
|
||||
while(htmlTagLowerCase.charAt(b)!='\"')
|
||||
b++;
|
||||
String imageLink=htmlTag.substring(a,b);
|
||||
|
||||
@@ -394,11 +388,11 @@ public class Sucker {
|
||||
|
||||
ret += "[img attachment=\""+""+ attachmentCounter +"\"]";
|
||||
|
||||
a=htmlTag.indexOf("alt=\"")+5;
|
||||
a=htmlTagLowerCase.indexOf("alt=\"")+5;
|
||||
if(a>=5)
|
||||
{
|
||||
b=a+1;
|
||||
while(htmlTag.charAt(b)!='\"')
|
||||
while(htmlTagLowerCase.charAt(b)!='\"')
|
||||
b++;
|
||||
String altText=htmlTag.substring(a,b);
|
||||
ret+=altText;
|
||||
@@ -416,14 +410,14 @@ public class Sucker {
|
||||
return ret;
|
||||
|
||||
}
|
||||
if(importRefs && htmlTag.startsWith("<a "))
|
||||
if(importRefs && htmlTagLowerCase.startsWith("<a "))
|
||||
{
|
||||
System.out.println("Found link tag: "+htmlTag);
|
||||
int a,b;
|
||||
|
||||
a=htmlTag.indexOf("href=\"")+6;
|
||||
a=htmlTagLowerCase.indexOf("href=\"")+6;
|
||||
b=a+1;
|
||||
while(htmlTag.charAt(b)!='\"')
|
||||
while(htmlTagLowerCase.charAt(b)!='\"')
|
||||
b++;
|
||||
String link=htmlTag.substring(a,b);
|
||||
if(link.indexOf("http")<0)
|
||||
@@ -439,49 +433,44 @@ public class Sucker {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ("</a>".equals(htmlTag)) {
|
||||
if ("</a>".equals(htmlTagLowerCase)) {
|
||||
if (pendingEndLink)
|
||||
return "[/link]";
|
||||
}
|
||||
|
||||
if("<b>".equals(htmlTag))
|
||||
if("<b>".equals(htmlTagLowerCase))
|
||||
return "[b]";
|
||||
if("</b>".equals(htmlTag))
|
||||
if("</b>".equals(htmlTagLowerCase))
|
||||
return "[/b]";
|
||||
if("<i>".equals(htmlTag))
|
||||
if("<i>".equals(htmlTagLowerCase))
|
||||
return "[i]";
|
||||
if("</i>".equals(htmlTag))
|
||||
if("</i>".equals(htmlTagLowerCase))
|
||||
return "[/i]";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fetchAttachment(String link) {
|
||||
System.out.println("Fetch attachment from: "+link);
|
||||
String attachmentPath = messagePath+"."+attachmentCounter;
|
||||
try {
|
||||
link=link.replaceAll("&","&");
|
||||
URL attachmentUrl = new URL(link);
|
||||
InputStream is = attachmentUrl.openStream();
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(attachmentPath);
|
||||
|
||||
while(true)
|
||||
{
|
||||
int i =is.read();
|
||||
if(i<0)
|
||||
break;
|
||||
fos.write(i);
|
||||
}
|
||||
|
||||
} catch (MalformedURLException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("Fetch attachment from: "+link);
|
||||
|
||||
String attachmentPath = messagePath+"."+attachmentCounter;
|
||||
link=link.replaceAll("&","&");
|
||||
|
||||
int numRetries = 2;
|
||||
File fetched = new File(attachmentPath);
|
||||
// we use eepGet, since it retries and doesn't leak DNS requests like URL does
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), shouldProxy, proxyHost, proxyPortNum,
|
||||
numRetries, fetched.getAbsolutePath(), link);
|
||||
SuckerFetchListener lsnr = new SuckerFetchListener();
|
||||
get.addStatusListener(lsnr);
|
||||
get.fetch();
|
||||
boolean ok = lsnr.waitForSuccess();
|
||||
if (!ok) {
|
||||
System.err.println("Unable to retrieve the url after " + numRetries + " tries.");
|
||||
fetched.delete();
|
||||
return;
|
||||
}
|
||||
attachmentCounter++;
|
||||
}
|
||||
|
||||
@@ -494,12 +483,12 @@ public class Sucker {
|
||||
if(s.charAt(i)=='"')
|
||||
{
|
||||
i++;
|
||||
while(s.charAt(i)!='"')
|
||||
while(i<s.length() && s.charAt(i)!='"')
|
||||
i++;
|
||||
}
|
||||
}
|
||||
System.out.println("WTF");
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private boolean existsInHistory(String messageId) {
|
||||
@@ -548,7 +537,7 @@ public class Sucker {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA");
|
||||
md.update(s.getBytes());
|
||||
byte[] buf = md.digest();
|
||||
String ret = new sun.misc.BASE64Encoder().encode(buf);
|
||||
String ret = Base64.encode(buf);
|
||||
return ret;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
ex.printStackTrace();
|
||||
@@ -632,4 +621,4 @@ class SuckerFetchListener implements EepGet.StatusListener {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,24 +19,25 @@ public class Updater {
|
||||
}
|
||||
_lastUpdate = System.currentTimeMillis();
|
||||
_log.debug("Update started.");
|
||||
User user = new User();
|
||||
RemoteArchiveBean rab = new RemoteArchiveBean();
|
||||
String[] archives = bm.getUpdateArchives();
|
||||
for (int i = 0; i < archives.length; i++) {
|
||||
_log.debug("Fetching " + archives[i]);
|
||||
rab.fetchIndex(user, "web", archives[i], bm.getDefaultProxyHost(), bm.getDefaultProxyPort());
|
||||
if (rab.getRemoteIndex() != null) {
|
||||
_log.debug("Index fetched, getting new entries.");
|
||||
HashMap parameters = new HashMap();
|
||||
parameters.put("action", "Fetch all new entries");
|
||||
//rab.fetchSelectedBulk(user, parameters);
|
||||
rab.fetchAllEntries(user, parameters);
|
||||
_log.debug("Update finished.");
|
||||
} else {
|
||||
_log.debug("Index fetch failed.");
|
||||
}
|
||||
fetchArchive(archives[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchArchive(String archive) {
|
||||
BlogManager bm = BlogManager.instance();
|
||||
User user = new User();
|
||||
RemoteArchiveBean rab = new RemoteArchiveBean();
|
||||
|
||||
rab.fetchIndex(user, "web", archive, bm.getDefaultProxyHost(), bm.getDefaultProxyPort());
|
||||
if (rab.getRemoteIndex() != null) {
|
||||
HashMap parameters = new HashMap();
|
||||
parameters.put("action", new String[] {"Fetch all new entries"});
|
||||
rab.fetchSelectedBulk(user, parameters);
|
||||
}
|
||||
_log.debug(rab.getStatus());
|
||||
}
|
||||
|
||||
public static void main() {
|
||||
_instance.run();
|
||||
@@ -51,7 +52,6 @@ public class Updater {
|
||||
|
||||
while (true) {
|
||||
int delay = BlogManager.instance().getUpdateDelay();
|
||||
if (delay < 1) delay = 1;
|
||||
update();
|
||||
try {
|
||||
synchronized (this) {
|
||||
|
||||
@@ -145,4 +145,6 @@ public class HTMLPreviewRenderer extends HTMLRenderer {
|
||||
_postBodyBuffer.append("</td>\n</form>\n</tr>\n");
|
||||
_postBodyBuffer.append("</table>\n");
|
||||
}
|
||||
|
||||
protected void renderMetaCell() { _preBodyBuffer.append("<td></td></tr>"); }
|
||||
}
|
||||
|
||||
@@ -754,6 +754,7 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
public static final String HEADER_IN_REPLY_TO = "InReplyTo";
|
||||
public static final String HEADER_STYLE = "Style";
|
||||
public static final String HEADER_PETNAME = "PetName";
|
||||
public static final String HEADER_TAGS = "Tags";
|
||||
|
||||
private void renderSubjectCell() {
|
||||
_preBodyBuffer.append("<form action=\"index.jsp\">");
|
||||
@@ -774,20 +775,7 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
_preBodyBuffer.append((bgcolor != null ? " bgcolor=\"" + sanitizeTagParam(bgcolor) + "\"" : "") + ">");
|
||||
}
|
||||
|
||||
private void renderMetaCell() {
|
||||
String tags[] = (_entry != null ? _entry.getTags() : null);
|
||||
_preBodyBuffer.append("<td nowrap=\"nowrap\" align=\"right\" valign=\"top\" ");
|
||||
_preBodyBuffer.append(getClass("meta")).append(">\n");
|
||||
|
||||
PetName pn = null;
|
||||
if ( (_entry != null) && (_user != null) )
|
||||
pn = _user.getPetNameDB().getByLocation(_entry.getURI().getKeyHash().toBase64());
|
||||
//if (knownName != null)
|
||||
// _preBodyBuffer.append("Pet name: ").append(sanitizeString(knownName)).append(" ");
|
||||
|
||||
BlogInfo info = null;
|
||||
if (_entry != null)
|
||||
info = _archive.getBlogInfo(_entry.getURI());
|
||||
private void renderMetaPetname(PetName pn, BlogInfo info) {
|
||||
if (info != null) {
|
||||
_preBodyBuffer.append("<a ").append(getClass("metaLink")).append(" href=\"").append(getMetadataURL()).append("\">");
|
||||
if (pn != null) {
|
||||
@@ -803,7 +791,23 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
} else {
|
||||
_preBodyBuffer.append(getSpan("metaUnknown")).append("[unknown blog]</span>");
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderMetaCell() {
|
||||
String tags[] = (_entry != null ? _entry.getTags() : null);
|
||||
_preBodyBuffer.append("<td nowrap=\"nowrap\" align=\"right\" valign=\"top\" ");
|
||||
_preBodyBuffer.append(getClass("meta")).append(">\n");
|
||||
|
||||
PetName pn = null;
|
||||
if ( (_entry != null) && (_user != null) )
|
||||
pn = _user.getPetNameDB().getByLocation(_entry.getURI().getKeyHash().toBase64());
|
||||
//if (knownName != null)
|
||||
// _preBodyBuffer.append("Pet name: ").append(sanitizeString(knownName)).append(" ");
|
||||
|
||||
BlogInfo info = null;
|
||||
if (_entry != null)
|
||||
info = _archive.getBlogInfo(_entry.getURI());
|
||||
renderMetaPetname(pn, info);
|
||||
|
||||
if ( (_user != null) && (_user.getAuthenticated()) && (_entry != null) ) {
|
||||
if ( (pn == null) || (!pn.isMember("Favorites")) )
|
||||
@@ -857,7 +861,19 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
|
||||
if ( (_user != null) && (_user.getAuthenticated()) ) {
|
||||
_preBodyBuffer.append(" <a ").append(getClass("replyLink"));
|
||||
_preBodyBuffer.append(" href=\"").append(getPostURL(_user.getBlog(), true)).append("\">Reply</a>\n");
|
||||
String subject = (String)_headers.get(HEADER_SUBJECT);
|
||||
if (subject != null) {
|
||||
if (!subject.startsWith("re:"))
|
||||
subject = "re: " + subject;
|
||||
} else {
|
||||
subject = "re: ";
|
||||
}
|
||||
StringBuffer tagStr = new StringBuffer(32);
|
||||
if ( (tags != null) && (tags.length > 0) )
|
||||
for (int i = 0; i < tags.length; i++)
|
||||
tagStr.append(tags[i]).append('\t');
|
||||
String replyURL = getPostURL(_user.getBlog(), true, subject, tagStr.toString());
|
||||
_preBodyBuffer.append(" href=\"").append(replyURL).append("\">Reply</a>\n");
|
||||
}
|
||||
_preBodyBuffer.append("\n</td>");
|
||||
_preBodyBuffer.append("</tr>\n");
|
||||
@@ -984,11 +1000,18 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
public static String getPostURL(Hash blog) {
|
||||
return "post.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" + Base64.encode(blog.getData());
|
||||
}
|
||||
public String getPostURL(Hash blog, boolean asReply) {
|
||||
public String getPostURL(Hash blog, boolean asReply, String subject, String tags) {
|
||||
if (asReply && _entry != null) {
|
||||
return "post.jsp?" + ArchiveViewerBean.PARAM_BLOG + "=" + Base64.encode(blog.getData())
|
||||
+ "&" + ArchiveViewerBean.PARAM_IN_REPLY_TO + '='
|
||||
+ Base64.encode("entry://" + _entry.getURI().getKeyHash().toBase64() + "/" + _entry.getURI().getEntryId());
|
||||
StringBuffer rv = new StringBuffer(128);
|
||||
rv.append("post.jsp?").append(ArchiveViewerBean.PARAM_BLOG).append("=").append(Base64.encode(blog.getData()));
|
||||
rv.append('&').append(ArchiveViewerBean.PARAM_IN_REPLY_TO).append('=');
|
||||
rv.append(Base64.encode("entry://" + _entry.getURI().getKeyHash().toBase64() + "/" + _entry.getURI().getEntryId()));
|
||||
if (subject != null)
|
||||
rv.append('&').append(ArchiveViewerBean.PARAM_SUBJECT).append('=').append(Base64.encode(subject));
|
||||
if (tags != null)
|
||||
rv.append('&').append(ArchiveViewerBean.PARAM_TAGS).append('=').append(Base64.encode(tags));
|
||||
rv.append('&').append(ArchiveViewerBean.PARAM_PARENT).append('=').append(Base64.encode(_entry.getURI().toString()));
|
||||
return rv.toString();
|
||||
} else {
|
||||
return getPostURL(blog);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,13 @@ public class ArchiveViewerBean {
|
||||
/** we are replying to a particular blog/tag/entry/whatever (value == base64 encoded selector) */
|
||||
public static final String PARAM_IN_REPLY_TO = "inReplyTo";
|
||||
|
||||
/** prepopulate the subject field with the given value */
|
||||
public static final String PARAM_SUBJECT = "replySubject";
|
||||
/** prepopulate the tags with the given value */
|
||||
public static final String PARAM_TAGS = "replyTags";
|
||||
/** prepopulate the body with the given value */
|
||||
public static final String PARAM_PARENT = "parentURI";
|
||||
|
||||
/**
|
||||
* Drop down multichooser:
|
||||
* blog://base64(key)
|
||||
@@ -552,6 +559,8 @@ public class ArchiveViewerBean {
|
||||
return (String)c.iterator().next();
|
||||
else
|
||||
return null;
|
||||
} else if (vals instanceof String) {
|
||||
return (String)vals;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.PetName;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.BlogURI;
|
||||
import net.i2p.syndie.data.EntryContainer;
|
||||
import net.i2p.syndie.sml.HTMLPreviewRenderer;
|
||||
import net.i2p.syndie.sml.HTMLRenderer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -137,6 +139,16 @@ public class PostBean {
|
||||
_previewed = true;
|
||||
}
|
||||
|
||||
public void renderReplyPreview(Writer out, String parentURI) throws IOException {
|
||||
HTMLRenderer r = new HTMLRenderer(_context);
|
||||
Archive a = BlogManager.instance().getArchive();
|
||||
BlogURI uri = new BlogURI(parentURI);
|
||||
if (uri.getEntryId() > 0) {
|
||||
EntryContainer entry = a.getEntry(uri);
|
||||
r.render(_user, a, entry, out, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private String renderSMLContent() {
|
||||
StringBuffer raw = new StringBuffer();
|
||||
raw.append("Subject: ").append(_subject).append('\n');
|
||||
|
||||
@@ -134,6 +134,10 @@ public class RemoteArchiveBean {
|
||||
}
|
||||
|
||||
public void fetchSelectedBulk(User user, Map parameters) {
|
||||
fetchSelectedBulk(user, parameters, false);
|
||||
}
|
||||
|
||||
public void fetchSelectedBulk(User user, Map parameters, boolean shouldBlock) {
|
||||
String entries[] = ArchiveViewerBean.getStrings(parameters, "entry");
|
||||
String action = ArchiveViewerBean.getString(parameters, "action");
|
||||
if ("Fetch all new entries".equals(action)) {
|
||||
@@ -206,7 +210,7 @@ public class RemoteArchiveBean {
|
||||
File t = File.createTempFile("fetchBulk", ".dat", BlogManager.instance().getTempDir());
|
||||
tmpFiles.add(t);
|
||||
}
|
||||
fetch(urls, tmpFiles, user, new BlogStatusListener());
|
||||
fetch(urls, tmpFiles, user, new BlogStatusListener(), shouldBlock);
|
||||
} catch (IOException ioe) {
|
||||
_statusMessages.add("Internal error creating temporary file to fetch posts: " + HTMLRenderer.sanitizeString(urls.toString()));
|
||||
}
|
||||
@@ -258,8 +262,12 @@ public class RemoteArchiveBean {
|
||||
}
|
||||
|
||||
private void fetch(List urls, List tmpFiles, User user, EepGet.StatusListener lsnr) {
|
||||
fetch(urls, tmpFiles, user, lsnr, false);
|
||||
}
|
||||
|
||||
private void fetch(List urls, List tmpFiles, User user, EepGet.StatusListener lsnr, boolean shouldBlock) {
|
||||
EepGetScheduler scheduler = new EepGetScheduler(I2PAppContext.getGlobalContext(), urls, tmpFiles, _proxyHost, _proxyPort, lsnr);
|
||||
scheduler.fetch();
|
||||
scheduler.fetch(shouldBlock);
|
||||
}
|
||||
|
||||
public void fetchIndex(User user, String schema, String location, String proxyHost, String proxyPort) {
|
||||
|
||||
@@ -121,6 +121,21 @@ for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
} else {
|
||||
// logged in and not confirmed because they didn't send us anything!
|
||||
// give 'em a new form
|
||||
String entrySubject = request.getParameter("replySubject");
|
||||
String entryTags = request.getParameter("replyTags");
|
||||
String parentURI = request.getParameter("parentURI");
|
||||
if (entrySubject != null)
|
||||
post.setSubject(new String(Base64.decode(entrySubject), "UTF-8"));
|
||||
if (entryTags != null)
|
||||
post.setTags(new String(Base64.decode(entryTags), "UTF-8"));
|
||||
|
||||
if (parentURI != null) {
|
||||
parentURI = new String(Base64.decode(parentURI), "UTF-8");
|
||||
%><span class="b_postField">Replying to
|
||||
<a href="<%=HTMLRenderer.getPageURL(user, parentURI)%>">parent</a>
|
||||
(text <a href="#parentText">below</a>).</span><br />
|
||||
<%
|
||||
}
|
||||
%><form action="post.jsp" method="POST" enctype="multipart/form-data">
|
||||
<span class="b_postField">Post subject:</span> <input class="b_postSubject" type="text" size="80" name="entrysubject" value="<%=post.getSubject()%>" /><br />
|
||||
<span class="b_postField">Post tags:</span> <input class="b_postTags" type="text" size="20" name="entrytags" value="<%=post.getTags()%>" /><br />
|
||||
@@ -164,6 +179,12 @@ if ( (s != null) && (s.trim().length() > 0) ) {%>
|
||||
<hr />
|
||||
<input class="b_postPreview" type="submit" name="Post" value="Preview..." /> <input class="b_postReset" type="reset" value="Cancel" />
|
||||
<%
|
||||
if (parentURI != null) {
|
||||
%><hr /><span id="parentText" class="b_postParent"><%
|
||||
post.renderReplyPreview(out, parentURI);
|
||||
%></span><hr /><%
|
||||
}
|
||||
|
||||
} // end of the 'logged in, not confirmed, nothing posted' section
|
||||
} // end of the 'logged in, not confirmed' section
|
||||
} // end of the 'logged in' section
|
||||
|
||||
40
build.xml
40
build.xml
@@ -30,6 +30,15 @@
|
||||
<ant dir="apps/susimail/" target="war" />
|
||||
<ant dir="apps/susidns/src" target="all" />
|
||||
<ant dir="apps/syndie/java/" target="jar" />
|
||||
<ant dir="apps/i2psnark/java/" target="jar" />
|
||||
</target>
|
||||
<target name="buildrouter">
|
||||
<ant dir="core/java/" target="distclean" />
|
||||
<ant dir="router/java/" target="distclean" />
|
||||
<ant dir="core/java/" target="jar" />
|
||||
<ant dir="router/java/" target="jar" />
|
||||
<copy file="core/java/build/i2p.jar" todir="build/" />
|
||||
<copy file="router/java/build/router.jar" todir="build/" />
|
||||
</target>
|
||||
<target name="buildWEB">
|
||||
<ant dir="apps/jetty" target="fetchJettylib" />
|
||||
@@ -46,15 +55,23 @@
|
||||
<copy file="apps/jetty/jettylib/javax.servlet.jar" todir="build/" />
|
||||
</target>
|
||||
<target name="buildexe">
|
||||
<condition property="noExe">
|
||||
<os family="mac" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<os arch="x86_64" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<os arch="ppc" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<os arch="amd64" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<not>
|
||||
<or>
|
||||
<os name="Linux" />
|
||||
<os family="windows" />
|
||||
</or>
|
||||
</not>
|
||||
</condition>
|
||||
<ant target="doBuildEXE" />
|
||||
</target>
|
||||
<target name="doBuildEXE" unless="noExe">
|
||||
@@ -91,6 +108,7 @@
|
||||
<copy file="apps/syndie/java/build/syndie.jar" todir="build/" />
|
||||
<copy file="apps/syndie/java/build/sucker.jar" todir="build/" />
|
||||
<copy file="apps/syndie/syndie.war" todir="build/" />
|
||||
<copy file="apps/i2psnark/java/build/i2psnark.jar" todir="build/" />
|
||||
<copy file="apps/jdom/jdom.jar" todir="build/" />
|
||||
<copy file="apps/rome/rome-0.7.jar" todir="build/" />
|
||||
</target>
|
||||
@@ -147,6 +165,7 @@
|
||||
<ant dir="apps/susimail/" target="distclean" />
|
||||
<ant dir="apps/susidns/src/" target="distclean" />
|
||||
<ant dir="apps/systray/java/" target="distclean" />
|
||||
<ant dir="apps/i2psnark/java/" target="distclean" />
|
||||
<ant dir="installer/java/" target="distclean" />
|
||||
<delete>
|
||||
<fileset dir="." includes="**/*.class" />
|
||||
@@ -217,6 +236,7 @@
|
||||
<copy file="build/jdom.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/rome-0.7.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/sucker.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/i2psnark.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="i2p.exe" todir="pkg-temp/" failonerror="false" />
|
||||
<copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
|
||||
<copy file="apps/systray/java/lib/systray4j.jar" todir="pkg-temp/lib" />
|
||||
@@ -321,6 +341,7 @@
|
||||
<copy file="build/jdom.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/rome-0.7.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/sucker.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/i2psnark.jar" todir="pkg-temp/lib" />
|
||||
|
||||
<copy file="i2p.exe" todir="pkg-temp/" failonerror="false" />
|
||||
<copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
|
||||
@@ -364,8 +385,8 @@
|
||||
<copy file="installer/resources/blogMeta.snm" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
|
||||
<copy file="installer/resources/blogPost.snd" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1126915200003.snd" />
|
||||
</target>
|
||||
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />
|
||||
<target name="installer" depends="preppkg">
|
||||
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />
|
||||
<jar destfile="./pkg-temp/lib/copy.jar" basedir="./core/java/build/obj" includes="net/i2p/util/*.class">
|
||||
<manifest><attribute name="Main-Class" value="net.i2p.util.Copy" /></manifest>
|
||||
</jar>
|
||||
@@ -380,15 +401,20 @@
|
||||
<ant target="installerexe" />
|
||||
</target>
|
||||
<target name="installerexe">
|
||||
<condition property="noExe">
|
||||
<os family="mac" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<os arch="x86_64" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<os arch="amd64" />
|
||||
</condition>
|
||||
<condition property="noExe">
|
||||
<not>
|
||||
<or>
|
||||
<os name="Linux" />
|
||||
<os family="windows" />
|
||||
</or>
|
||||
</not>
|
||||
</condition>
|
||||
<ant target="doInstallerEXE" />
|
||||
</target>
|
||||
<target name="doInstallerEXE" unless="noExe">
|
||||
|
||||
183
core/java/src/gnu/crypto/prng/BasePRNG.java
Normal file
183
core/java/src/gnu/crypto/prng/BasePRNG.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
// This file is part of GNU Crypto.
|
||||
//
|
||||
// GNU Crypto 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.
|
||||
//
|
||||
// GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
//
|
||||
// Free Software Foundation Inc.,
|
||||
// 51 Franklin Street, Fifth Floor,
|
||||
// Boston, MA 02110-1301
|
||||
// USA
|
||||
//
|
||||
// Linking this library statically or dynamically with other modules is
|
||||
// making a combined work based on this library. Thus, the terms and
|
||||
// conditions of the GNU General Public License cover the whole
|
||||
// combination.
|
||||
//
|
||||
// As a special exception, the copyright holders of this library give
|
||||
// you permission to link this library with independent modules to
|
||||
// produce an executable, regardless of the license terms of these
|
||||
// independent modules, and to copy and distribute the resulting
|
||||
// executable under terms of your choice, provided that you also meet,
|
||||
// for each linked independent module, the terms and conditions of the
|
||||
// license of that module. An independent module is a module which is
|
||||
// not derived from or based on this library. If you modify this
|
||||
// library, you may extend this exception to your version of the
|
||||
// library, but you are not obligated to do so. If you do not wish to
|
||||
// do so, delete this exception statement from your version.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>An abstract class to facilitate implementing PRNG algorithms.</p>
|
||||
*
|
||||
* Modified slightly by jrandom for I2P (removing unneeded exceptions)
|
||||
* @version $Revision: 1.12 $
|
||||
*/
|
||||
public abstract class BasePRNG implements IRandom {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** The canonical name prefix of the PRNG algorithm. */
|
||||
protected String name;
|
||||
|
||||
/** Indicate if this instance has already been initialised or not. */
|
||||
protected boolean initialised;
|
||||
|
||||
/** A temporary buffer to serve random bytes. */
|
||||
protected byte[] buffer;
|
||||
|
||||
/** The index into buffer of where the next byte will come from. */
|
||||
protected int ndx;
|
||||
|
||||
// Constructor(s)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Trivial constructor for use by concrete subclasses.</p>
|
||||
*
|
||||
* @param name the canonical name of this instance.
|
||||
*/
|
||||
protected BasePRNG(String name) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
initialised = false;
|
||||
buffer = new byte[0];
|
||||
}
|
||||
|
||||
// Class methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// IRandom interface implementation ----------------------------------------
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void init(Map attributes) {
|
||||
this.setup(attributes);
|
||||
|
||||
ndx = 0;
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
public byte nextByte() throws IllegalStateException {//, LimitReachedException {
|
||||
if (!initialised) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return nextByteInternal();
|
||||
}
|
||||
|
||||
public void nextBytes(byte[] out) throws IllegalStateException {//, LimitReachedException {
|
||||
nextBytes(out, 0, out.length);
|
||||
}
|
||||
|
||||
public void nextBytes(byte[] out, int offset, int length)
|
||||
throws IllegalStateException //, LimitReachedException
|
||||
{
|
||||
if (!initialised)
|
||||
throw new IllegalStateException("not initialized");
|
||||
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
if (offset < 0 || length < 0 || offset + length > out.length)
|
||||
throw new ArrayIndexOutOfBoundsException("offset=" + offset + " length="
|
||||
+ length + " limit=" + out.length);
|
||||
|
||||
if (ndx >= buffer.length) {
|
||||
fillBlock();
|
||||
ndx = 0;
|
||||
}
|
||||
int count = 0;
|
||||
while (count < length) {
|
||||
int amount = Math.min(buffer.length - ndx, length - count);
|
||||
System.arraycopy(buffer, ndx, out, offset+count, amount);
|
||||
count += amount;
|
||||
ndx += amount;
|
||||
if (ndx >= buffer.length) {
|
||||
fillBlock();
|
||||
ndx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addRandomByte(byte b) {
|
||||
throw new UnsupportedOperationException("random state is non-modifiable");
|
||||
}
|
||||
|
||||
public void addRandomBytes(byte[] buffer) {
|
||||
addRandomBytes(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public void addRandomBytes(byte[] buffer, int offset, int length) {
|
||||
throw new UnsupportedOperationException("random state is non-modifiable");
|
||||
}
|
||||
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public boolean isInitialised() {
|
||||
return initialised;
|
||||
}
|
||||
|
||||
private byte nextByteInternal() {//throws LimitReachedException {
|
||||
if (ndx >= buffer.length) {
|
||||
this.fillBlock();
|
||||
ndx = 0;
|
||||
}
|
||||
|
||||
return buffer[ndx++];
|
||||
}
|
||||
|
||||
// abstract methods to implement by subclasses -----------------------------
|
||||
|
||||
public Object clone() throws CloneNotSupportedException
|
||||
{
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
public abstract void setup(Map attributes);
|
||||
|
||||
public abstract void fillBlock(); //throws LimitReachedException;
|
||||
}
|
||||
358
core/java/src/gnu/crypto/prng/Fortuna.java
Normal file
358
core/java/src/gnu/crypto/prng/Fortuna.java
Normal file
@@ -0,0 +1,358 @@
|
||||
/* Fortuna.java -- The Fortuna PRNG.
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
|
||||
GNU Crypto 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.
|
||||
|
||||
GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
|
||||
Free Software Foundation Inc.,
|
||||
51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Linking this library statically or dynamically with other modules is
|
||||
making a combined work based on this library. Thus, the terms and
|
||||
conditions of the GNU General Public License cover the whole
|
||||
combination.
|
||||
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent
|
||||
modules, and to copy and distribute the resulting executable under
|
||||
terms of your choice, provided that you also meet, for each linked
|
||||
independent module, the terms and conditions of the license of that
|
||||
module. An independent module is a module which is not derived from
|
||||
or based on this library. If you modify this library, you may extend
|
||||
this exception to your version of the library, but you are not
|
||||
obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. */
|
||||
|
||||
|
||||
package gnu.crypto.prng;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import net.i2p.crypto.CryptixRijndael_Algorithm;
|
||||
import net.i2p.crypto.CryptixAESKeyCache;
|
||||
|
||||
/**
|
||||
* The Fortuna continuously-seeded pseudo-random number generator. This
|
||||
* generator is composed of two major pieces: the entropy accumulator
|
||||
* and the generator function. The former takes in random bits and
|
||||
* incorporates them into the generator's state. The latter takes this
|
||||
* base entropy and generates pseudo-random bits from it.
|
||||
*
|
||||
* <p>There are some things users of this class <em>must</em> be aware of:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>Adding Random Data</dt>
|
||||
* <dd>This class does not do any polling of random sources, but rather
|
||||
* provides an interface for adding random events. Applications that use
|
||||
* this code <em>must</em> provide this mechanism. We use this design
|
||||
* because an application writer who knows the system he is targeting
|
||||
* is in a better position to judge what random data is available.</dd>
|
||||
*
|
||||
* <dt>Storing the Seed</dt>
|
||||
* <dd>This class implements {@link Serializable} in such a way that it
|
||||
* writes a 64 byte seed to the stream, and reads it back again when being
|
||||
* deserialized. This is the extent of seed file management, however, and
|
||||
* those using this class are encouraged to think deeply about when, how
|
||||
* often, and where to store the seed.</dd>
|
||||
* </dl>
|
||||
*
|
||||
* <p><b>References:</b></p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Niels Ferguson and Bruce Schneier, <i>Practical Cryptography</i>,
|
||||
* pp. 155--184. Wiley Publishing, Indianapolis. (2003 Niels Ferguson and
|
||||
* Bruce Schneier). ISBN 0-471-22357-3.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Modified by jrandom for I2P to use Bouncycastle's SHA256, Cryptix's AES,
|
||||
* to strip out some unnecessary dependencies and increase the buffer size.
|
||||
*
|
||||
*/
|
||||
public class Fortuna extends BasePRNG
|
||||
implements Serializable, RandomEventListener
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 0xFACADE;
|
||||
|
||||
private static final int SEED_FILE_SIZE = 64;
|
||||
private static final int NUM_POOLS = 32;
|
||||
private static final int MIN_POOL_SIZE = 64;
|
||||
private final Generator generator;
|
||||
private final SHA256Digest[] pools;
|
||||
private long lastReseed;
|
||||
private int pool;
|
||||
private int pool0Count;
|
||||
private int reseedCount;
|
||||
|
||||
public static final String SEED = "gnu.crypto.prng.fortuna.seed";
|
||||
|
||||
public Fortuna()
|
||||
{
|
||||
super("Fortuna i2p");
|
||||
generator = new Generator();
|
||||
pools = new SHA256Digest[NUM_POOLS];
|
||||
for (int i = 0; i < NUM_POOLS; i++)
|
||||
pools[i] = new SHA256Digest();
|
||||
lastReseed = 0;
|
||||
pool = 0;
|
||||
pool0Count = 0;
|
||||
buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
|
||||
}
|
||||
|
||||
public void seed(byte val[]) {
|
||||
Map props = new HashMap(1);
|
||||
props.put(SEED, (Object)val);
|
||||
init(props);
|
||||
fillBlock();
|
||||
}
|
||||
|
||||
public void setup(Map attributes)
|
||||
{
|
||||
lastReseed = 0;
|
||||
reseedCount = 0;
|
||||
pool = 0;
|
||||
pool0Count = 0;
|
||||
generator.init(attributes);
|
||||
}
|
||||
|
||||
/** fillBlock is not thread safe, so will be locked anyway */
|
||||
private byte fillBlockBuf[] = new byte[32];
|
||||
public void fillBlock()
|
||||
{
|
||||
if (pool0Count >= MIN_POOL_SIZE
|
||||
&& System.currentTimeMillis() - lastReseed > 100)
|
||||
{
|
||||
reseedCount++;
|
||||
//byte[] seed = new byte[0];
|
||||
for (int i = 0; i < NUM_POOLS; i++)
|
||||
{
|
||||
if (reseedCount % (1 << i) == 0) {
|
||||
byte buf[] = fillBlockBuf;//new byte[32];
|
||||
pools[i].doFinal(buf, 0);
|
||||
generator.addRandomBytes(buf);//pools[i].digest());
|
||||
}
|
||||
}
|
||||
lastReseed = System.currentTimeMillis();
|
||||
}
|
||||
generator.nextBytes(buffer);
|
||||
}
|
||||
|
||||
public void addRandomByte(byte b)
|
||||
{
|
||||
pools[pool].update(b);
|
||||
if (pool == 0)
|
||||
pool0Count++;
|
||||
pool = (pool + 1) % NUM_POOLS;
|
||||
}
|
||||
|
||||
public void addRandomBytes(byte[] buf, int offset, int length)
|
||||
{
|
||||
pools[pool].update(buf, offset, length);
|
||||
if (pool == 0)
|
||||
pool0Count += length;
|
||||
pool = (pool + 1) % NUM_POOLS;
|
||||
}
|
||||
|
||||
public void addRandomEvent(RandomEvent event)
|
||||
{
|
||||
if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
|
||||
throw new IllegalArgumentException("pool number out of range: "
|
||||
+ event.getPoolNumber());
|
||||
pools[event.getPoolNumber()].update(event.getSourceNumber());
|
||||
pools[event.getPoolNumber()].update((byte) event.getData().length);
|
||||
byte data[] = event.getData();
|
||||
pools[event.getPoolNumber()].update(data, 0, data.length); //event.getData());
|
||||
if (event.getPoolNumber() == 0)
|
||||
pool0Count += event.getData().length;
|
||||
}
|
||||
|
||||
// Reading and writing this object is equivalent to storing and retrieving
|
||||
// the seed.
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException
|
||||
{
|
||||
byte[] seed = new byte[SEED_FILE_SIZE];
|
||||
generator.nextBytes(seed);
|
||||
out.write(seed);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException
|
||||
{
|
||||
byte[] seed = new byte[SEED_FILE_SIZE];
|
||||
in.readFully(seed);
|
||||
generator.addRandomBytes(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Fortuna generator function. The generator is a PRNG in its own
|
||||
* right; Fortuna itself is basically a wrapper around this generator
|
||||
* that manages reseeding in a secure way.
|
||||
*/
|
||||
public static class Generator extends BasePRNG implements Cloneable
|
||||
{
|
||||
|
||||
private static final int LIMIT = 1 << 20;
|
||||
|
||||
private final SHA256Digest hash;
|
||||
private final byte[] counter;
|
||||
private final byte[] key;
|
||||
/** current encryption key built from the keying material */
|
||||
private Object cryptixKey;
|
||||
private CryptixAESKeyCache.KeyCacheEntry cryptixKeyBuf;
|
||||
private boolean seeded;
|
||||
|
||||
public Generator ()
|
||||
{
|
||||
super("Fortuna.generator.i2p");
|
||||
this.hash = new SHA256Digest();
|
||||
counter = new byte[16]; //cipher.defaultBlockSize()];
|
||||
buffer = new byte[16]; //cipher.defaultBlockSize()];
|
||||
int keysize = 32;
|
||||
key = new byte[keysize];
|
||||
cryptixKeyBuf = CryptixAESKeyCache.createNew();
|
||||
}
|
||||
|
||||
public final byte nextByte()
|
||||
{
|
||||
byte[] b = new byte[1];
|
||||
nextBytes(b, 0, 1);
|
||||
return b[0];
|
||||
}
|
||||
|
||||
public final void nextBytes(byte[] out, int offset, int length)
|
||||
{
|
||||
if (!seeded)
|
||||
throw new IllegalStateException("generator not seeded");
|
||||
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
int amount = Math.min(LIMIT, length - count);
|
||||
super.nextBytes(out, offset+count, amount);
|
||||
count += amount;
|
||||
|
||||
for (int i = 0; i < key.length; i += counter.length)
|
||||
{
|
||||
//fillBlock(); // inlined
|
||||
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
|
||||
incrementCounter();
|
||||
int l = Math.min(key.length - i, 16);//cipher.currentBlockSize());
|
||||
System.arraycopy(buffer, 0, key, i, l);
|
||||
}
|
||||
resetKey();
|
||||
}
|
||||
while (count < length);
|
||||
//fillBlock(); // inlined
|
||||
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
|
||||
incrementCounter();
|
||||
ndx = 0;
|
||||
}
|
||||
|
||||
public final void addRandomByte(byte b)
|
||||
{
|
||||
addRandomBytes(new byte[] { b });
|
||||
}
|
||||
|
||||
public final void addRandomBytes(byte[] seed, int offset, int length)
|
||||
{
|
||||
hash.update(key, 0, key.length);
|
||||
hash.update(seed, offset, length);
|
||||
//byte[] newkey = hash.digest();
|
||||
//System.arraycopy(newkey, 0, key, 0, Math.min(key.length, newkey.length));
|
||||
hash.doFinal(key, 0);
|
||||
resetKey();
|
||||
incrementCounter();
|
||||
seeded = true;
|
||||
}
|
||||
|
||||
public final void fillBlock()
|
||||
{
|
||||
////i2p: this is not being checked as a microoptimization
|
||||
//if (!seeded)
|
||||
// throw new IllegalStateException("generator not seeded");
|
||||
CryptixRijndael_Algorithm.blockEncrypt(counter, buffer, 0, 0, cryptixKey);
|
||||
incrementCounter();
|
||||
}
|
||||
|
||||
public void setup(Map attributes)
|
||||
{
|
||||
seeded = false;
|
||||
Arrays.fill(key, (byte) 0);
|
||||
Arrays.fill(counter, (byte) 0);
|
||||
byte[] seed = (byte[]) attributes.get(SEED);
|
||||
if (seed != null)
|
||||
addRandomBytes(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the cipher's key. This is done after every reseed, which
|
||||
* combines the old key and the seed, and processes that throigh the
|
||||
* hash function.
|
||||
*/
|
||||
private final void resetKey()
|
||||
{
|
||||
try {
|
||||
cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, cryptixKeyBuf);
|
||||
} catch (InvalidKeyException ike) {
|
||||
throw new Error("hrmf", ike);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment `counter' as a sixteen-byte little-endian unsigned integer
|
||||
* by one.
|
||||
*/
|
||||
private final void incrementCounter()
|
||||
{
|
||||
for (int i = 0; i < counter.length; i++)
|
||||
{
|
||||
counter[i]++;
|
||||
if (counter[i] != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
Fortuna f = new Fortuna();
|
||||
java.util.HashMap props = new java.util.HashMap();
|
||||
byte initSeed[] = new byte[1234];
|
||||
new java.util.Random().nextBytes(initSeed);
|
||||
long before = System.currentTimeMillis();
|
||||
props.put(SEED, (byte[])initSeed);
|
||||
f.init(props);
|
||||
byte buf[] = new byte[8*1024];
|
||||
for (int i = 0; i < 64*1024; i++) {
|
||||
f.nextBytes(buf);
|
||||
}
|
||||
long time = System.currentTimeMillis() - before;
|
||||
System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
|
||||
}
|
||||
}
|
||||
186
core/java/src/gnu/crypto/prng/IRandom.java
Normal file
186
core/java/src/gnu/crypto/prng/IRandom.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: IRandom.java,v 1.12 2005/10/06 04:24:17 rsdio Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
|
||||
//
|
||||
// This file is part of GNU Crypto.
|
||||
//
|
||||
// GNU Crypto 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.
|
||||
//
|
||||
// GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
//
|
||||
// Free Software Foundation Inc.,
|
||||
// 51 Franklin Street, Fifth Floor,
|
||||
// Boston, MA 02110-1301
|
||||
// USA
|
||||
//
|
||||
// Linking this library statically or dynamically with other modules is
|
||||
// making a combined work based on this library. Thus, the terms and
|
||||
// conditions of the GNU General Public License cover the whole
|
||||
// combination.
|
||||
//
|
||||
// As a special exception, the copyright holders of this library give
|
||||
// you permission to link this library with independent modules to
|
||||
// produce an executable, regardless of the license terms of these
|
||||
// independent modules, and to copy and distribute the resulting
|
||||
// executable under terms of your choice, provided that you also meet,
|
||||
// for each linked independent module, the terms and conditions of the
|
||||
// license of that module. An independent module is a module which is
|
||||
// not derived from or based on this library. If you modify this
|
||||
// library, you may extend this exception to your version of the
|
||||
// library, but you are not obligated to do so. If you do not wish to
|
||||
// do so, delete this exception statement from your version.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>The basic visible methods of any pseudo-random number generator.</p>
|
||||
*
|
||||
* <p>The [HAC] defines a PRNG (as implemented in this library) as follows:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>"5.6 Definition: A pseudorandom bit generator (PRBG) is said to pass
|
||||
* the <em>next-bit test</em> if there is no polynomial-time algorithm which,
|
||||
* on input of the first <code>L</code> bits of an output sequence <code>S</code>,
|
||||
* can predict the <code>(L+1)</code>st bit of <code>S</code> with a
|
||||
* probability significantly grater than <code>1/2</code>."</li>
|
||||
*
|
||||
* <li>"5.8 Definition: A PRBG that passes the <em>next-bit test</em>
|
||||
* (possibly under some plausible but unproved mathematical assumption such
|
||||
* as the intractability of factoring integers) is called a
|
||||
* <em>cryptographically secure pseudorandom bit generator</em> (CSPRBG)."</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>IMPLEMENTATION NOTE</b>: Although all the concrete classes in this
|
||||
* package implement the {@link Cloneable} interface, it is important to note
|
||||
* here that such an operation, for those algorithms that use an underlting
|
||||
* symmetric key block cipher, <b>DOES NOT</b> clone any session key material
|
||||
* that may have been used in initialising the source PRNG (the instance to be
|
||||
* cloned). Instead a clone of an already initialised PRNG, that uses and
|
||||
* underlying symmetric key block cipher, is another instance with a clone of
|
||||
* the same cipher that operates with the <b>same block size</b> but without any
|
||||
* knowledge of neither key material nor key size.</p>
|
||||
*
|
||||
* <p>References:</p>
|
||||
*
|
||||
* <ol>
|
||||
* <li><a href="http://www.cacr.math.uwaterloo.ca/hac">[HAC]</a>: Handbook of
|
||||
* Applied Cryptography.<br>
|
||||
* CRC Press, Inc. ISBN 0-8493-8523-7, 1997<br>
|
||||
* Menezes, A., van Oorschot, P. and S. Vanstone.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @version $Revision: 1.12 $
|
||||
*/
|
||||
public interface IRandom extends Cloneable {
|
||||
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* <p>Returns the canonical name of this instance.</p>
|
||||
*
|
||||
* @return the canonical name of this instance. */
|
||||
String name();
|
||||
|
||||
/**
|
||||
* <p>Initialises the pseudo-random number generator scheme with the
|
||||
* appropriate attributes.</p>
|
||||
*
|
||||
* @param attributes a set of name-value pairs that describe the desired
|
||||
* future instance behaviour.
|
||||
* @exception IllegalArgumentException if at least one of the defined name/
|
||||
* value pairs contains invalid data.
|
||||
*/
|
||||
void init(Map attributes);
|
||||
|
||||
/**
|
||||
* <p>Returns the next 8 bits of random data generated from this instance.</p>
|
||||
*
|
||||
* @return the next 8 bits of random data generated from this instance.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
byte nextByte() throws IllegalStateException, LimitReachedException;
|
||||
|
||||
/**
|
||||
* <p>Fills the designated byte array, starting from byte at index
|
||||
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
|
||||
* output of this generator instance.
|
||||
*
|
||||
* @param out the placeholder to contain the generated random bytes.
|
||||
* @param offset the starting index in <i>out</i> to consider. This method
|
||||
* does nothing if this parameter is not within <code>0</code> and
|
||||
* <code>out.length</code>.
|
||||
* @param length the maximum number of required random bytes. This method
|
||||
* does nothing if this parameter is less than <code>1</code>.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
void nextBytes(byte[] out, int offset, int length)
|
||||
throws IllegalStateException, LimitReachedException;
|
||||
|
||||
/**
|
||||
* <p>Supplement, or possibly replace, the random state of this PRNG with
|
||||
* a random byte.</p>
|
||||
*
|
||||
* <p>Implementations are not required to implement this method in any
|
||||
* meaningful way; this may be a no-operation, and implementations may
|
||||
* throw an {@link UnsupportedOperationException}.</p>
|
||||
*
|
||||
* @param b The byte to add.
|
||||
*/
|
||||
void addRandomByte(byte b);
|
||||
|
||||
/**
|
||||
* <p>Supplement, or possibly replace, the random state of this PRNG with
|
||||
* a sequence of new random bytes.</p>
|
||||
*
|
||||
* <p>Implementations are not required to implement this method in any
|
||||
* meaningful way; this may be a no-operation, and implementations may
|
||||
* throw an {@link UnsupportedOperationException}.</p>
|
||||
*
|
||||
* @param in The buffer of new random bytes to add.
|
||||
*/
|
||||
void addRandomBytes(byte[] in);
|
||||
|
||||
/**
|
||||
* <p>Supplement, or possibly replace, the random state of this PRNG with
|
||||
* a sequence of new random bytes.</p>
|
||||
*
|
||||
* <p>Implementations are not required to implement this method in any
|
||||
* meaningful way; this may be a no-operation, and implementations may
|
||||
* throw an {@link UnsupportedOperationException}.</p>
|
||||
*
|
||||
* @param in The buffer of new random bytes to add.
|
||||
* @param offset The offset from whence to begin reading random bytes.
|
||||
* @param length The number of random bytes to add.
|
||||
* @exception IndexOutOfBoundsException If <i>offset</i>, <i>length</i>,
|
||||
* or <i>offset</i>+<i>length</i> is out of bounds.
|
||||
*/
|
||||
void addRandomBytes(byte[] in, int offset, int length);
|
||||
|
||||
/**
|
||||
* <p>Returns a clone copy of this instance.</p>
|
||||
*
|
||||
* @return a clone copy of this instance.
|
||||
*/
|
||||
Object clone() throws CloneNotSupportedException;
|
||||
}
|
||||
73
core/java/src/gnu/crypto/prng/LimitReachedException.java
Normal file
73
core/java/src/gnu/crypto/prng/LimitReachedException.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: LimitReachedException.java,v 1.5 2005/10/06 04:24:17 rsdio Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
// This file is part of GNU Crypto.
|
||||
//
|
||||
// GNU Crypto 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.
|
||||
//
|
||||
// GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
//
|
||||
// Free Software Foundation Inc.,
|
||||
// 51 Franklin Street, Fifth Floor,
|
||||
// Boston, MA 02110-1301
|
||||
// USA
|
||||
//
|
||||
// Linking this library statically or dynamically with other modules is
|
||||
// making a combined work based on this library. Thus, the terms and
|
||||
// conditions of the GNU General Public License cover the whole
|
||||
// combination.
|
||||
//
|
||||
// As a special exception, the copyright holders of this library give
|
||||
// you permission to link this library with independent modules to
|
||||
// produce an executable, regardless of the license terms of these
|
||||
// independent modules, and to copy and distribute the resulting
|
||||
// executable under terms of your choice, provided that you also meet,
|
||||
// for each linked independent module, the terms and conditions of the
|
||||
// license of that module. An independent module is a module which is
|
||||
// not derived from or based on this library. If you modify this
|
||||
// library, you may extend this exception to your version of the
|
||||
// library, but you are not obligated to do so. If you do not wish to
|
||||
// do so, delete this exception statement from your version.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A checked exception that indicates that a pseudo random number generated has
|
||||
* reached its theoretical limit in generating random bytes.
|
||||
*
|
||||
* @version $Revision: 1.5 $
|
||||
*/
|
||||
public class LimitReachedException extends Exception {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Constructor(s)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public LimitReachedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LimitReachedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
// Class methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Instant methods
|
||||
// -------------------------------------------------------------------------
|
||||
}
|
||||
82
core/java/src/gnu/crypto/prng/RandomEvent.java
Normal file
82
core/java/src/gnu/crypto/prng/RandomEvent.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/* RandomEvent.java -- a random event.
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
|
||||
GNU Crypto 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.
|
||||
|
||||
GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
|
||||
Free Software Foundation Inc.,
|
||||
51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Linking this library statically or dynamically with other modules is
|
||||
making a combined work based on this library. Thus, the terms and
|
||||
conditions of the GNU General Public License cover the whole
|
||||
combination.
|
||||
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent
|
||||
modules, and to copy and distribute the resulting executable under
|
||||
terms of your choice, provided that you also meet, for each linked
|
||||
independent module, the terms and conditions of the license of that
|
||||
module. An independent module is a module which is not derived from
|
||||
or based on this library. If you modify this library, you may extend
|
||||
this exception to your version of the library, but you are not
|
||||
obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. */
|
||||
|
||||
|
||||
package gnu.crypto.prng;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
/**
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public class RandomEvent extends EventObject
|
||||
{
|
||||
|
||||
private final byte sourceNumber;
|
||||
private final byte poolNumber;
|
||||
private final byte[] data;
|
||||
|
||||
public RandomEvent(Object source, byte sourceNumber, byte poolNumber,
|
||||
byte[] data)
|
||||
{
|
||||
super(source);
|
||||
this.sourceNumber = sourceNumber;
|
||||
this.poolNumber = poolNumber;
|
||||
if (data.length == 0 || data.length > 32)
|
||||
throw new IllegalArgumentException("random events take between 1 and 32 bytes of data");
|
||||
this.data = (byte[]) data.clone();
|
||||
}
|
||||
|
||||
public byte getSourceNumber()
|
||||
{
|
||||
return sourceNumber;
|
||||
}
|
||||
|
||||
public byte getPoolNumber()
|
||||
{
|
||||
return poolNumber;
|
||||
}
|
||||
|
||||
public byte[] getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
53
core/java/src/gnu/crypto/prng/RandomEventListener.java
Normal file
53
core/java/src/gnu/crypto/prng/RandomEventListener.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/* RandomEventListener.java -- event listener
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
|
||||
GNU Crypto 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.
|
||||
|
||||
GNU Crypto 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; see the file COPYING. If not, write to the
|
||||
|
||||
Free Software Foundation Inc.,
|
||||
51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Linking this library statically or dynamically with other modules is
|
||||
making a combined work based on this library. Thus, the terms and
|
||||
conditions of the GNU General Public License cover the whole
|
||||
combination.
|
||||
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent
|
||||
modules, and to copy and distribute the resulting executable under
|
||||
terms of your choice, provided that you also meet, for each linked
|
||||
independent module, the terms and conditions of the license of that
|
||||
module. An independent module is a module which is not derived from
|
||||
or based on this library. If you modify this library, you may extend
|
||||
this exception to your version of the library, but you are not
|
||||
obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. */
|
||||
|
||||
|
||||
package gnu.crypto.prng;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public interface RandomEventListener extends EventListener
|
||||
{
|
||||
void addRandomEvent(RandomEvent event);
|
||||
}
|
||||
@@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.45 $ $Date: 2005/10/07 15:19:09 $";
|
||||
public final static String VERSION = "0.6.1.3";
|
||||
public final static String ID = "$Revision: 1.46 $ $Date: 2005/10/14 08:48:04 $";
|
||||
public final static String VERSION = "0.6.1.4";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.PooledRandomSource;
|
||||
import net.i2p.util.FortunaRandomSource;
|
||||
|
||||
/**
|
||||
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
|
||||
@@ -456,7 +457,9 @@ public class I2PAppContext {
|
||||
private void initializeRandom() {
|
||||
synchronized (this) {
|
||||
if (_random == null) {
|
||||
if ("true".equals(getProperty("i2p.weakPRNG", "false")))
|
||||
if (true)
|
||||
_random = new FortunaRandomSource(this);
|
||||
else if ("true".equals(getProperty("i2p.weakPRNG", "false")))
|
||||
_random = new DummyPooledRandomSource(this);
|
||||
else
|
||||
_random = new PooledRandomSource(this);
|
||||
@@ -464,4 +467,4 @@ public class I2PAppContext {
|
||||
_randomInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage2", "third part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
|
||||
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage3", "fourth part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
|
||||
//ctx.statManager().createRateStat("i2cp.sendBestEffortStage4", "fifth part of sendBestEffort?", "i2cp", new long[] { 10*60*1000 } );
|
||||
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.0", "How long it took to get status=0 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.1", "How long it took to get status=1 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.2", "How long it took to get status=2 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.3", "How long it took to get status=3 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.4", "How long it took to get status=4 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime.5", "How long it took to get status=5 back", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("i2cp.receiveStatusTime", "How long it took to get any status", "i2cp", new long[] { 60*1000, 10*60*1000 });
|
||||
}
|
||||
|
||||
protected long getTimeout() {
|
||||
@@ -296,6 +302,26 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
state.setMessageId(id);
|
||||
}
|
||||
state.receive(status);
|
||||
|
||||
long lifetime = state.getElapsed();
|
||||
switch (status) {
|
||||
case 1:
|
||||
_context.statManager().addRateData("i2cp.receiveStatusTime.1", lifetime, 0);
|
||||
break;
|
||||
case 2:
|
||||
_context.statManager().addRateData("i2cp.receiveStatusTime.2", lifetime, 0);
|
||||
break;
|
||||
case 3:
|
||||
_context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0);
|
||||
break;
|
||||
case 4:
|
||||
_context.statManager().addRateData("i2cp.receiveStatusTime.4", lifetime, 0);
|
||||
break;
|
||||
case 5:
|
||||
_context.statManager().addRateData("i2cp.receiveStatusTime.5", lifetime, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix() + "No matching state for messageId " + msgId + " / " + nonce
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
* of code)
|
||||
*
|
||||
*/
|
||||
final class CryptixAESKeyCache {
|
||||
public final class CryptixAESKeyCache {
|
||||
private List _availableKeys;
|
||||
|
||||
private static final int KEYSIZE = 32; // 256bit AES
|
||||
@@ -48,7 +48,7 @@ final class CryptixAESKeyCache {
|
||||
}
|
||||
}
|
||||
|
||||
private static final KeyCacheEntry createNew() {
|
||||
public static final KeyCacheEntry createNew() {
|
||||
KeyCacheEntry e = new KeyCacheEntry();
|
||||
e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys
|
||||
e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys
|
||||
|
||||
@@ -130,9 +130,13 @@ public class DSAEngine {
|
||||
Signature sig = new Signature();
|
||||
BigInteger k;
|
||||
|
||||
boolean ok = false;
|
||||
do {
|
||||
k = new BigInteger(160, _context.random());
|
||||
} while (k.compareTo(CryptoConstants.dsaq) != 1);
|
||||
ok = k.compareTo(CryptoConstants.dsaq) != 1;
|
||||
ok = ok && !k.equals(BigInteger.ZERO);
|
||||
//System.out.println("K picked (ok? " + ok + "): " + k.bitLength() + ": " + k.toString());
|
||||
} while (!ok);
|
||||
|
||||
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
|
||||
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
|
||||
@@ -145,6 +149,9 @@ public class DSAEngine {
|
||||
byte[] sbytes = s.toByteArray();
|
||||
byte[] out = new byte[40];
|
||||
|
||||
// (q^random)%p is computationally random
|
||||
_context.random().harvester().feedEntropy("DSA.sign", rbytes, 0, rbytes.length);
|
||||
|
||||
if (rbytes.length == 20) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
out[i] = rbytes[i];
|
||||
@@ -203,4 +210,19 @@ public class DSAEngine {
|
||||
byte digested[] = h.digest();
|
||||
return new Hash(digested);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
byte data[] = new byte[4096];
|
||||
ctx.random().nextBytes(data);
|
||||
Object keys[] = ctx.keyGenerator().generateSigningKeypair();
|
||||
try {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Signature sig = ctx.dsa().sign(data, (SigningPrivateKey)keys[1]);
|
||||
boolean ok = ctx.dsa().verifySignature(sig, data, (SigningPublicKey)keys[0]);
|
||||
System.out.println("OK: " + ok);
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
ctx.random().saveSeed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class HMACSHA256Generator {
|
||||
/** set of available byte[] buffers for verify */
|
||||
private List _availableTmp;
|
||||
private boolean _useMD5;
|
||||
private int _macSize;
|
||||
|
||||
public static final boolean DEFAULT_USE_MD5 = true;
|
||||
|
||||
@@ -39,6 +40,10 @@ public class HMACSHA256Generator {
|
||||
_useMD5 = true;
|
||||
else
|
||||
_useMD5 = false;
|
||||
if ("true".equals(context.getProperty("i2p.HMACBrokenSize", "true")))
|
||||
_macSize = 32;
|
||||
else
|
||||
_macSize = (_useMD5 ? 16 : 32);
|
||||
}
|
||||
|
||||
public static HMACSHA256Generator getInstance() {
|
||||
@@ -123,10 +128,13 @@ public class HMACSHA256Generator {
|
||||
if (_available.size() > 0)
|
||||
return (HMac)_available.remove(0);
|
||||
}
|
||||
// the HMAC is hardcoded to use SHA256 digest size
|
||||
// for backwards compatability. next time we have a backwards
|
||||
// incompatible change, we should update this by removing ", 32"
|
||||
if (_useMD5)
|
||||
return new HMac(new MD5Digest());
|
||||
return new HMac(new MD5Digest(), 32);
|
||||
else
|
||||
return new HMac(new SHA256Digest());
|
||||
return new HMac(new SHA256Digest(), 32);
|
||||
}
|
||||
private void release(HMac mac) {
|
||||
synchronized (_available) {
|
||||
|
||||
@@ -71,14 +71,18 @@ public class RoutingKeyGenerator {
|
||||
*/
|
||||
public void generateDateBasedModData() {
|
||||
Date today = null;
|
||||
long now = _context.clock().now();
|
||||
synchronized (_cal) {
|
||||
_cal.setTime(new Date(_context.clock().now()));
|
||||
_cal.setTime(new Date(now));
|
||||
_cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround
|
||||
_cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
|
||||
_cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
_cal.set(Calendar.MINUTE, 0);
|
||||
_cal.set(Calendar.SECOND, 0);
|
||||
_cal.set(Calendar.MILLISECOND, 0);
|
||||
today = _cal.getTime();
|
||||
}
|
||||
|
||||
byte mod[] = null;
|
||||
String modVal = null;
|
||||
synchronized (_fmt) {
|
||||
@@ -91,7 +95,7 @@ public class RoutingKeyGenerator {
|
||||
_log.info("Routing modifier generated: " + modVal);
|
||||
setModData(mod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a modified (yet consistent) hash from the origKey by generating the
|
||||
* SHA256 of the targetKey with the current modData appended to it, *then*
|
||||
|
||||
@@ -115,27 +115,29 @@ public class BufferedStatLog implements StatLog {
|
||||
int writeStart = -1;
|
||||
int writeEnd = -1;
|
||||
while (true) {
|
||||
synchronized (_events) {
|
||||
if (_eventNext > _lastWrite) {
|
||||
if (_eventNext - _lastWrite < _flushFrequency)
|
||||
try { _events.wait(30*1000); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
if (_events.length - 1 - _lastWrite + _eventNext < _flushFrequency)
|
||||
try { _events.wait(30*1000); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
synchronized (_events) {
|
||||
if (_eventNext > _lastWrite) {
|
||||
if (_eventNext - _lastWrite < _flushFrequency)
|
||||
_events.wait(30*1000);
|
||||
} else {
|
||||
if (_events.length - 1 - _lastWrite + _eventNext < _flushFrequency)
|
||||
_events.wait(30*1000);
|
||||
}
|
||||
writeStart = (_lastWrite + 1) % _events.length;
|
||||
writeEnd = _eventNext;
|
||||
_lastWrite = (writeEnd == 0 ? _events.length-1 : writeEnd - 1);
|
||||
}
|
||||
writeStart = (_lastWrite + 1) % _events.length;
|
||||
writeEnd = _eventNext;
|
||||
_lastWrite = (writeEnd == 0 ? _events.length-1 : writeEnd - 1);
|
||||
}
|
||||
if (writeStart != writeEnd) {
|
||||
try {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("writing " + writeStart +"->"+ writeEnd);
|
||||
writeEvents(writeStart, writeEnd);
|
||||
} catch (Exception e) {
|
||||
_log.error("error writing " + writeStart +"->"+ writeEnd, e);
|
||||
if (writeStart != writeEnd) {
|
||||
try {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("writing " + writeStart +"->"+ writeEnd);
|
||||
writeEvents(writeStart, writeEnd);
|
||||
} catch (Exception e) {
|
||||
_log.error("error writing " + writeStart +"->"+ writeEnd, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -292,7 +292,11 @@ public class EepGet {
|
||||
}
|
||||
|
||||
public void stopFetching() { _keepFetching = false; }
|
||||
public void fetch() {
|
||||
/**
|
||||
* Blocking fetch, returning true if the URL was retrieved, false if all retries failed
|
||||
*
|
||||
*/
|
||||
public boolean fetch() {
|
||||
_keepFetching = true;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -301,7 +305,7 @@ public class EepGet {
|
||||
try {
|
||||
sendRequest();
|
||||
doFetch();
|
||||
return;
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
|
||||
@@ -328,6 +332,7 @@ public class EepGet {
|
||||
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** return true if the URL was completely retrieved */
|
||||
@@ -501,7 +506,8 @@ public class EepGet {
|
||||
try {
|
||||
return Integer.parseInt(rc);
|
||||
} catch (NumberFormatException nfe) {
|
||||
nfe.printStackTrace();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("ERR: status is invalid: " + line, nfe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,15 @@ public class EepGetScheduler implements EepGet.StatusListener {
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void fetch(boolean shouldBlock) {
|
||||
//Checking for a valid index is done in fetchNext, so we don't have to worry about it.
|
||||
if (shouldBlock) {
|
||||
fetchNext();
|
||||
} else {
|
||||
fetch();
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNext() {
|
||||
_curURL++;
|
||||
if (_curURL >= _urls.size()) return;
|
||||
|
||||
178
core/java/src/net/i2p/util/FortunaRandomSource.java
Normal file
178
core/java/src/net/i2p/util/FortunaRandomSource.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package net.i2p.util;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EntropyHarvester;
|
||||
|
||||
import gnu.crypto.prng.Fortuna;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Wrapper around GNU-Crypto's Fortuna PRNG. This seeds from /dev/urandom and
|
||||
* ./prngseed.rnd on startup (if they exist), writing a new seed to ./prngseed.rnd
|
||||
* on an explicit call to saveSeed().
|
||||
*
|
||||
*/
|
||||
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
|
||||
private Fortuna _fortuna;
|
||||
private double _nextGaussian;
|
||||
private boolean _haveNextGaussian;
|
||||
|
||||
public FortunaRandomSource(I2PAppContext context) {
|
||||
super(context);
|
||||
_fortuna = new Fortuna();
|
||||
byte seed[] = new byte[1024];
|
||||
if (initSeed(seed)) {
|
||||
_fortuna.seed(seed);
|
||||
} else {
|
||||
SecureRandom sr = new SecureRandom();
|
||||
sr.nextBytes(seed);
|
||||
_fortuna.seed(seed);
|
||||
}
|
||||
// kickstart it
|
||||
_fortuna.nextBytes(seed);
|
||||
_haveNextGaussian = false;
|
||||
}
|
||||
|
||||
public synchronized void setSeed(byte buf[]) {
|
||||
_fortuna.addRandomBytes(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* According to the java docs (http://java.sun.com/j2se/1.4.1/docs/api/java/util/Random.html#nextInt(int))
|
||||
* nextInt(n) should return a number between 0 and n (including 0 and excluding n). However, their pseudocode,
|
||||
* as well as sun's, kaffe's, and classpath's implementation INCLUDES NEGATIVE VALUES.
|
||||
* WTF. Ok, so we're going to have it return between 0 and n (including 0, excluding n), since
|
||||
* thats what it has been used for.
|
||||
*
|
||||
*/
|
||||
public int nextInt(int n) {
|
||||
if (n == 0) return 0;
|
||||
int rv = signedNextInt(n);
|
||||
if (rv < 0)
|
||||
rv = 0 - rv;
|
||||
rv %= n;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public int nextInt() { return signedNextInt(Integer.MAX_VALUE); }
|
||||
|
||||
/**
|
||||
* Implementation from Sun's java.util.Random javadocs
|
||||
*/
|
||||
private int signedNextInt(int n) {
|
||||
if (n<=0)
|
||||
throw new IllegalArgumentException("n must be positive");
|
||||
|
||||
if ((n & -n) == n) // i.e., n is a power of 2
|
||||
return (int)((n * (long)nextBits(31)) >> 31);
|
||||
|
||||
int bits, val;
|
||||
do {
|
||||
bits = nextBits(31);
|
||||
val = bits % n;
|
||||
} while(bits - val + (n-1) < 0);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
|
||||
* including 0, excluding n.
|
||||
*/
|
||||
public long nextLong(long n) {
|
||||
if (n == 0) return 0;
|
||||
long rv = signedNextLong(n);
|
||||
if (rv < 0)
|
||||
rv = 0 - rv;
|
||||
rv %= n;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public long nextLong() { return signedNextLong(Long.MAX_VALUE); }
|
||||
|
||||
/**
|
||||
* Implementation from Sun's java.util.Random javadocs
|
||||
*/
|
||||
private long signedNextLong(long n) {
|
||||
return ((long)nextBits(32) << 32) + nextBits(32);
|
||||
}
|
||||
|
||||
public synchronized boolean nextBoolean() {
|
||||
// wasteful, might be worth caching the boolean byte later
|
||||
byte val = _fortuna.nextByte();
|
||||
return ((val & 0x01) == 1);
|
||||
}
|
||||
|
||||
public synchronized void nextBytes(byte buf[]) {
|
||||
_fortuna.nextBytes(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation from sun's java.util.Random javadocs
|
||||
*/
|
||||
public double nextDouble() {
|
||||
return (((long)nextBits(26) << 27) + nextBits(27)) / (double)(1L << 53);
|
||||
}
|
||||
/**
|
||||
* Implementation from sun's java.util.Random javadocs
|
||||
*/
|
||||
public float nextFloat() {
|
||||
return nextBits(24) / ((float)(1 << 24));
|
||||
}
|
||||
/**
|
||||
* Implementation from sun's java.util.Random javadocs
|
||||
*/
|
||||
public synchronized double nextGaussian() {
|
||||
if (_haveNextGaussian) {
|
||||
_haveNextGaussian = false;
|
||||
return _nextGaussian;
|
||||
} else {
|
||||
double v1, v2, s;
|
||||
do {
|
||||
v1 = 2 * nextDouble() - 1; // between -1.0 and 1.0
|
||||
v2 = 2 * nextDouble() - 1; // between -1.0 and 1.0
|
||||
s = v1 * v1 + v2 * v2;
|
||||
} while (s >= 1 || s == 0);
|
||||
double multiplier = Math.sqrt(-2 * Math.log(s)/s);
|
||||
_nextGaussian = v2 * multiplier;
|
||||
_haveNextGaussian = true;
|
||||
return v1 * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the next numBits of random data off the fortuna instance (returning -2^numBits-1
|
||||
* through 2^numBits-1
|
||||
*/
|
||||
protected synchronized int nextBits(int numBits) {
|
||||
int rv = 0;
|
||||
int bytes = (numBits + 7) / 8;
|
||||
for (int i = 0; i < bytes; i++)
|
||||
rv += ((_fortuna.nextByte() & 0xFF) << i*8);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public EntropyHarvester harvester() { return this; }
|
||||
|
||||
/** reseed the fortuna */
|
||||
public synchronized void feedEntropy(String source, long data, int bitoffset, int bits) {
|
||||
_fortuna.addRandomByte((byte)(data & 0xFF));
|
||||
}
|
||||
|
||||
/** reseed the fortuna */
|
||||
public synchronized void feedEntropy(String source, byte[] data, int offset, int len) {
|
||||
_fortuna.addRandomBytes(data, offset, len);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ package net.i2p.util;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EntropyHarvester;
|
||||
import net.i2p.data.Base64;
|
||||
|
||||
/**
|
||||
* Maintain a set of PRNGs to feed the apps
|
||||
@@ -48,6 +49,9 @@ public class PooledRandomSource extends RandomSource {
|
||||
totalSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
byte buf[] = new byte[1024];
|
||||
initSeed(buf);
|
||||
for (int i = 0; i < POOL_SIZE; i++) {
|
||||
if (totalSize < 0)
|
||||
_pool[i] = new BufferedRandomSource(context);
|
||||
@@ -55,8 +59,14 @@ public class PooledRandomSource extends RandomSource {
|
||||
_pool[i] = new BufferedRandomSource(context, (totalSize*1024) / POOL_SIZE);
|
||||
else
|
||||
_pool[i] = new RandomSource(context);
|
||||
_pool[i].nextBoolean();
|
||||
_pool[i].setSeed(buf);
|
||||
if (i > 0) {
|
||||
_pool[i-1].nextBytes(buf);
|
||||
_pool[i].setSeed(buf);
|
||||
}
|
||||
}
|
||||
_pool[0].nextBytes(buf);
|
||||
System.out.println("seeded and initialized: " + Base64.encode(buf));
|
||||
_nextPool = 0;
|
||||
}
|
||||
|
||||
@@ -174,7 +184,11 @@ public class PooledRandomSource extends RandomSource {
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
PooledRandomSource prng = new PooledRandomSource(I2PAppContext.getGlobalContext());
|
||||
//PooledRandomSource prng = new PooledRandomSource(I2PAppContext.getGlobalContext());
|
||||
long start = System.currentTimeMillis();
|
||||
RandomSource prng = I2PAppContext.getGlobalContext().random();
|
||||
long created = System.currentTimeMillis();
|
||||
System.out.println("prng type: " + prng.getClass().getName());
|
||||
int size = 8*1024*1024;
|
||||
try {
|
||||
java.io.FileOutputStream out = new java.io.FileOutputStream("random.file");
|
||||
@@ -183,6 +197,8 @@ public class PooledRandomSource extends RandomSource {
|
||||
}
|
||||
out.close();
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
System.out.println("Written to random.file");
|
||||
long done = System.currentTimeMillis();
|
||||
System.out.println("Written to random.file: create took " + (created-start) + ", generate took " + (done-created));
|
||||
prng.saveSeed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,19 @@ import java.security.SecureRandom;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EntropyHarvester;
|
||||
import net.i2p.data.Base64;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Singleton for whatever PRNG i2p uses.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RandomSource extends SecureRandom {
|
||||
public class RandomSource extends SecureRandom implements EntropyHarvester {
|
||||
private Log _log;
|
||||
private EntropyHarvester _entropyHarvester;
|
||||
protected I2PAppContext _context;
|
||||
@@ -30,7 +36,7 @@ public class RandomSource extends SecureRandom {
|
||||
_log = context.logManager().getLog(RandomSource.class);
|
||||
// when we replace to have hooks for fortuna (etc), replace with
|
||||
// a factory (or just a factory method)
|
||||
_entropyHarvester = new DummyEntropyHarvester();
|
||||
_entropyHarvester = this;
|
||||
}
|
||||
public static RandomSource getInstance() {
|
||||
return I2PAppContext.getGlobalContext().random();
|
||||
@@ -101,9 +107,105 @@ public class RandomSource extends SecureRandom {
|
||||
|
||||
public EntropyHarvester harvester() { return _entropyHarvester; }
|
||||
|
||||
public void feedEntropy(String source, long data, int bitoffset, int bits) {
|
||||
if (bitoffset == 0)
|
||||
setSeed(data);
|
||||
}
|
||||
|
||||
public void feedEntropy(String source, byte[] data, int offset, int len) {
|
||||
if ( (offset == 0) && (len == data.length) ) {
|
||||
setSeed(data);
|
||||
} else {
|
||||
setSeed(_context.sha().calculateHash(data, offset, len).getData());
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSeed() {
|
||||
byte buf[] = new byte[1024];
|
||||
if (initSeed(buf))
|
||||
setSeed(buf);
|
||||
}
|
||||
|
||||
public void saveSeed() {
|
||||
byte buf[] = new byte[1024];
|
||||
nextBytes(buf);
|
||||
writeSeed(buf);
|
||||
}
|
||||
|
||||
private static final String SEEDFILE = "prngseed.rnd";
|
||||
|
||||
public static final void writeSeed(byte buf[]) {
|
||||
File f = new File(SEEDFILE);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(f);
|
||||
fos.write(buf);
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean initSeed(byte buf[]) {
|
||||
// why urandom? because /dev/random blocks, and there are arguments
|
||||
// suggesting such blockages are largely meaningless
|
||||
boolean ok = seedFromFile("/dev/urandom", buf);
|
||||
// we merge (XOR) in the data from /dev/urandom with our own seedfile
|
||||
ok = seedFromFile("prngseed.rnd", buf) || ok;
|
||||
return ok;
|
||||
}
|
||||
|
||||
private static final boolean seedFromFile(String filename, byte buf[]) {
|
||||
File f = new File(filename);
|
||||
if (f.exists()) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(f);
|
||||
int read = 0;
|
||||
byte tbuf[] = new byte[buf.length];
|
||||
while (read < buf.length) {
|
||||
int curRead = fis.read(tbuf, read, tbuf.length - read);
|
||||
if (curRead < 0)
|
||||
break;
|
||||
read += curRead;
|
||||
}
|
||||
for (int i = 0; i < read; i++)
|
||||
buf[i] ^= tbuf[i];
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
RandomSource rs = new RandomSource(I2PAppContext.getGlobalContext());
|
||||
byte buf[] = new byte[1024];
|
||||
boolean seeded = rs.initSeed(buf);
|
||||
System.out.println("PRNG class hierarchy: ");
|
||||
Class c = rs.getClass();
|
||||
while (c != null) {
|
||||
System.out.println("\t" + c.getName());
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
System.out.println("Provider: \n" + rs.getProvider());
|
||||
if (seeded) {
|
||||
System.out.println("Initialized seed: " + Base64.encode(buf));
|
||||
rs.setSeed(buf);
|
||||
}
|
||||
for (int i = 0; i < 64; i++) rs.nextBytes(buf);
|
||||
rs.saveSeed();
|
||||
}
|
||||
}
|
||||
|
||||
// noop
|
||||
private static class DummyEntropyHarvester implements EntropyHarvester {
|
||||
public void feedEntropy(String source, long data, int bitoffset, int bits) {}
|
||||
public void feedEntropy(String source, byte[] data, int offset, int len) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,14 @@ implements Mac
|
||||
|
||||
public HMac(
|
||||
Digest digest)
|
||||
{
|
||||
this(digest, digest.getDigestSize());
|
||||
}
|
||||
public HMac(
|
||||
Digest digest, int sz)
|
||||
{
|
||||
this.digest = digest;
|
||||
digestSize = digest.getDigestSize();
|
||||
this.digestSize = sz;
|
||||
}
|
||||
|
||||
public String getAlgorithmName()
|
||||
@@ -141,7 +146,7 @@ implements Mac
|
||||
byte[] out,
|
||||
int outOff)
|
||||
{
|
||||
byte[] tmp = acquireTmp();
|
||||
byte[] tmp = acquireTmp(digestSize);
|
||||
//byte[] tmp = new byte[digestSize];
|
||||
digest.doFinal(tmp, 0);
|
||||
|
||||
@@ -156,23 +161,27 @@ implements Mac
|
||||
return len;
|
||||
}
|
||||
|
||||
private static ArrayList _tmpBuf = new ArrayList();
|
||||
private static byte[] acquireTmp() {
|
||||
/**
|
||||
* list of buffers - index 0 is the cache for 32 byte arrays, while index 1 is the cache for 16 byte arrays
|
||||
*/
|
||||
private static ArrayList _tmpBuf[] = new ArrayList[] { new ArrayList(), new ArrayList() };
|
||||
private static byte[] acquireTmp(int sz) {
|
||||
byte rv[] = null;
|
||||
synchronized (_tmpBuf) {
|
||||
if (_tmpBuf.size() > 0)
|
||||
rv = (byte[])_tmpBuf.remove(0);
|
||||
synchronized (_tmpBuf[sz == 32 ? 0 : 1]) {
|
||||
if (_tmpBuf[sz == 32 ? 0 : 1].size() > 0)
|
||||
rv = (byte[])_tmpBuf[sz == 32 ? 0 : 1].remove(0);
|
||||
}
|
||||
if (rv != null)
|
||||
Arrays.fill(rv, (byte)0x0);
|
||||
else
|
||||
rv = new byte[32]; // hard coded against SHA256 (should be digestSize)
|
||||
rv = new byte[sz];
|
||||
return rv;
|
||||
}
|
||||
private static void releaseTmp(byte buf[]) {
|
||||
synchronized (_tmpBuf) {
|
||||
if (_tmpBuf.size() < 100)
|
||||
_tmpBuf.add((Object)buf);
|
||||
if (buf == null) return;
|
||||
synchronized (_tmpBuf[buf.length == 32 ? 0 : 1]) {
|
||||
if (_tmpBuf[buf.length == 32 ? 0 : 1].size() < 100)
|
||||
_tmpBuf[buf.length == 32 ? 0 : 1].add((Object)buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
100
history.txt
100
history.txt
@@ -1,4 +1,102 @@
|
||||
$Id: history.txt,v 1.294 2005/10/13 21:15:40 jrandom Exp $
|
||||
$Id: history.txt,v 1.308 2005/10/29 16:35:27 jrandom Exp $
|
||||
|
||||
* 2005-10-29 0.6.1.4 released
|
||||
|
||||
2005-10-29 jrandom
|
||||
* Improved the bandwidth throtting on tunnel participation, especially for
|
||||
low bandwidth peers.
|
||||
* Improved failure handling in SSU with proactive reestablishment of
|
||||
failing idle peers, and rather than shitlisting a peer who failed too
|
||||
much, drop the SSU session and allow a new attempt (which, if it fails,
|
||||
will cause a shitlisting)
|
||||
* Clarify the cause of the shitlist on the profiles page, and include
|
||||
bandwidth limiter info at the bottom of the peers page.
|
||||
|
||||
2005-10-26 jrandom
|
||||
* In Syndie, propogate the subject and tags in a reply, and show the parent
|
||||
post on the edit page for easy quoting. (thanks identiguy and CofE!)
|
||||
* Streamline some netDb query handling to run outside the jobqueue -
|
||||
which means they'll run on the particular SSU thread that handles the
|
||||
message. This should help out heavily loaded netDb peers.
|
||||
|
||||
2005-10-25 jrandom
|
||||
* Defer netDb searches for newly referenced peers until we actually want
|
||||
them
|
||||
* Ignore netDb references to peers on our shitlist
|
||||
* Set the timeout for end to end client messages to the max delay after
|
||||
finding the leaseSet, so we don't have as many expired messages floating
|
||||
around.
|
||||
* Add a floor to the streaming lib window size
|
||||
* When we need to send a streaming lib ACK, try to retransmit one of the
|
||||
unacked packets instead (with updated ACK/NACK fields, of course). The
|
||||
bandwidth cost of an unnecessary retransmission should be minor as
|
||||
compared to both an ACK packet (rounded up to 1KB in the tunnels) and
|
||||
the probability of a necessary retransmission.
|
||||
* Adjust the streaming lib cwin algorithm to allow growth after a full
|
||||
cwin messages if the rtt is trending downwards. If it is not, use the
|
||||
existing algorithm.
|
||||
* Increased the maximum rto size in the streaming lib.
|
||||
* Load balancing bugfix on end to end messages to distribute across
|
||||
tunnels more evenly.
|
||||
|
||||
2005-10-22 jrandom
|
||||
* Integrated GNU-Crypto's Fortuna PRNG, seeding it off /dev/urandom and
|
||||
./prngseed.rnd (if they exist), and reseeding it with data out of
|
||||
various crypto operations (unused bits in a DH exchange, intermediary
|
||||
bits in a DSA signature generation, extra bits in an ElGamal decrypt).
|
||||
The Fortuna implementation under gnu.crypto.prng has been modified to
|
||||
use BouncyCastle's SHA256 and Cryptix's AES (since those are the ones
|
||||
I2P uses), and the resulting gnu.crypto.prng.* are therefor available
|
||||
under GPL+Classpath's linking exception (~= LGPL). I2P's SecureRandom
|
||||
wrapper around it is, of course, public domain.
|
||||
|
||||
2005-10-20 dust
|
||||
* Fix bug in ircclient that prevented it to use its own dest (i.e. was
|
||||
always shared. (thx for info Ragnarok)
|
||||
* Fix crash in Sucker with some bad html.
|
||||
|
||||
2005-10-20 jrandom
|
||||
* Workaround a bug in GCJ's Calendar implementation
|
||||
* Propery throw an exception in the streaming lib if we try to write to a
|
||||
closed stream. This will hopefully help clear some I2Phex bugs (thanks
|
||||
GregorK!)
|
||||
|
||||
2005-10-19 jrandom
|
||||
* Ported the snark bittorrent client to I2P such that it is compatible
|
||||
with i2p-bt and azneti2p. For usage information, grab an update and run
|
||||
"java -jar lib/i2psnark.jar". It isn't currently multitorrent capable,
|
||||
but adding in support would be fairly easy (see PeerAcceptor.java:49)
|
||||
* Don't allow leaseSets expiring too far in the future (thanks postman)
|
||||
|
||||
2005-10-19 jrandom
|
||||
* Bugfix for the auto-update code to handle different usage patterns
|
||||
* Decreased the addressbook recheck frequency to once every 12 hours
|
||||
instead of hourly.
|
||||
* Handle dynamically changing the HMAC size (again, unless your nym is
|
||||
toad or jrandom, ignore this ;)
|
||||
* Cleaned up some synchronization/locking code
|
||||
|
||||
2005-10-17 dust
|
||||
* Exchange the remaining URL with EepGet in Sucker.
|
||||
* Allow /TOPIC irc command.
|
||||
|
||||
2005-10-17 jrandom
|
||||
* Allow an env prop to configure whether we want to use the backwards
|
||||
compatible (but not standards compliant) HMAC-MD5, or whether we want
|
||||
to use the not-backwards compatible (but standards compliant) one. No
|
||||
one should touch this setting, unless your name is toad or jrandom ;)
|
||||
* Added some new dummy facades
|
||||
* Be more aggressive on loading up the router.config before building the
|
||||
router context
|
||||
* Added new hooks for apps to deal with previously undefined I2NP message
|
||||
types without having to modify any code.
|
||||
* Demo code for using a castrated router for SSU comm (SSUDemo.java)
|
||||
|
||||
2005-10-14 jrandom
|
||||
* More explicit filter for linux/PPC building (thanks anon!)
|
||||
* Fixed Syndie's Sucker to not explicitly reference something only found
|
||||
in sun's JVM (thanks cervantes!)
|
||||
* Don't filter IRC "MAP" messages (not critical, but it doesn't hurt)
|
||||
|
||||
* 2005-10-14 0.6.1.3 released
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.160 2005/09/27 17:21:05 jrandom Exp $
|
||||
; $Id: hosts.txt,v 1.161 2005/09/28 22:54:30 jrandom Exp $
|
||||
; changelog:
|
||||
; (1.183) added ttc.i2p
|
||||
; (1.182) added tracker-fr.i2p
|
||||
; (1.181) added syncline.i2p, cerebrum.i2p, news.underscore.i2p,
|
||||
; onionforum.i2p, frostmirror.i2p, ptm.i2p, gloinsblog.i2p
|
||||
@@ -443,4 +444,5 @@ wiht.i2p=p1tg9Rd36Ttcxt0SM3fG-4blad4aoRYcbaxU9g0EdBDOXt9Y0t0dC1iFY60N-b2dGz5eWXr
|
||||
jazzy.i2p=CWHSDlZ4DKnngu84vCPRJnB3cgODJ5YWZEFX2GugisWCKjSYudJkvzfZ0eDzNNXfhY8l289E7P5wprLsj7zEqX79vCoNfaI5xdpWPt6e6HNoC6WNMdmhiYH7Wzpx77SRv-~yGJ8kVHRCkEJ41BdHCVxGJLS-agunQYEXs9GZxFisLaDUff23kC98OiOs3599cacRs5aURTdgpk39cJ4K-fzlkAptXOugAAql~dREI2keNLuBlGEm1GPP6plHxxpXYeWlxvnWL~4y8~VHqf7jhk0XbzkCWRHe5zMbDm1Ex9p3QHTeI4JbKcxqd8MpQ9ESH6nCj4TTP47gNhGQdCbk~ZBurGomjWbETJ~Uf1M-YoDT3hPkrV337S2lpr62-AAVoKGlwJgKPxhWYag0zi1ysnnlGs0BsVh9kfBXxIsaMCL3gfAgN7TrdgkwW5XepTOCwRk2p62Qasr0Veg4FtDC4gwOzTYQpgd3lshKn1VfOgIPUxszZ8pWGE60VtcJIl7KAAAA
|
||||
trwcln.i2p=-4anDxCTGt6UIOoG~G6dKY0gyaq9qg-wH-TimOpbwf6zbuktiwYXuLcg~fScqyfZjdAEQoS3d8o67wPq0oBeZzXZGppmhP7wYmeLYmc9cYTCwGavZ7WGMvBnNR2TCCfymeVKIQFWrY1NP8z5YXKpUMywW0M5BSupx6ik99kG2W7RtEU~Yr11EF0MsS-~Fr1cQp2YvxHaUTBVmkG1RzkM7S5udlWcuCoRQsgDJlfNIOSEekTEGVls9~gwP4L8-gD6clXP3pRWHXnnbjtS0mY~A7Ci7WErS5w6Q7X6hiEB51DWSUMWZ~2LkUGpKj7n-I~6nx8jbSqKV1r3dQit1hJ-F5h5GOyXH1jgUtF1SpagQ~LzCVj0Rp5396DVLcckzlwhtcFYH2mPoHVgxliO1wU-zU99iNnGACrOyCv7gpQZjyFh8WKa4r~vGvyYWEt9VSp6W~Z7GeCgh7Bjvf9fx9NIBa2vqQkaj0spGg~yxSGXKboNx3dpwknhzRPioZBRUZDVAAAA
|
||||
tracker-fr.i2p=iZ67Ba62MzXPtQGoMDU7lrDaiHFDrCebwH2cLD0ll~n6i~4sw-yoeHx9mw3x1NyxiTifE7k5Dhr0IF33ry56BwbK-PZOJ3j~nYKBcfZrj4JsTzWH2c3NjlXs1nOUoWrpSrzNO-dzZLO50U7Kh3cQJHukovTfnT47SMHpkwDZ4frNIz0bAmO81s09yzei1bdufuHC63-xjvPVdsUxnV4jCht3skV0P0G~4VCT5vVuYjnD~BXAI84xjDDFOwxq3V9ABKhtO9qyi9n06hVLVK1aaPKSo7ieWGrGXQyIrauwhBEchy08MrrtXa1fsf9XpMl5RkDgUKSejrVzpKhfHexubwozyuq0o8sX6HJZ8qvl-lZhY9v6L16f0ibLLMTZPZmrOKwZaOYItR86~-7pw072rjVMnFuVc4ZjLkTRu2ljlkTY35Oj9Ck9dp-sn63WEO~Ec-vI8KKc9Q3E2Yn1Ckys-Qn-Q1uMCrwrfoZL-OMn7lXTumnH9yoE~7p3Wk6JE4f7AAAA
|
||||
ttc.i2p=rVaxsiggLlFY9mtmSlNZT5vuWMydT9V03gRwE0IxFtofadYj~5U1V1M7DBwP627UjFLurUOvN8EnqlqusvsbmFvQkY5Y-yoBD4YViRnzPav90GmoaPCOUPW~UQdHXRswFCB19c8p7o79UCjm77doTelkGDzZvUBvAVYyr2BKG1r2rUPzFOjYHYxVfNBaSxXc07e9syRg0aCcbTamknN4Vb1G-mQW1F5c~3Xo~5rHpTQeUbtW-v~PJer5fSKMHNJmq-goEd7OzM~IAfMXvKwz0uEi~YLl8B49gJpbrSM3Rz1C3PbR3i-Fsg4Bo7YFxvzFKyhgRhVjXqbeevXY61xxVR-PWTj742h1Lt~TCTe4GDcM9o1oJKIIIly2R46eIU8xSH-52hiWVh7Hb9IT7SEgd60rVE11DVW7htfDBNNL7Zg~yHiSZMrKXTid7c2t9cehCu2jwkvwu0y~CJsVfhulKCyP6m6nhy3LtIWHPLVGhTYetsHPq4NEj92dt6gKXM8cAAAA
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<i2p.news date="$Date: 2005/10/07 15:19:04 $">
|
||||
<i2p.release version="0.6.1.3" date="2005/10/14" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2005/10/14 08:48:05 $">
|
||||
<i2p.release version="0.6.1.4" date="2005/10/29" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<info>
|
||||
<appname>i2p</appname>
|
||||
<appversion>0.6.1.3</appversion>
|
||||
<appversion>0.6.1.4</appversion>
|
||||
<authors>
|
||||
<author name="I2P" email="support@i2p.net"/>
|
||||
</authors>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
#!/bin/sh
|
||||
java -cp lib/i2p.jar net.i2p.util.EepGet $*
|
||||
#export I2P=~i2p/i2p
|
||||
export I2P=.
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.util.EepGet $*
|
||||
|
||||
@@ -26,7 +26,7 @@ tunnel.1.i2cpPort=7654
|
||||
tunnel.1.option.inbound.nickname=shared clients
|
||||
tunnel.1.option.outbound.nickname=shared clients
|
||||
tunnel.1.option.i2p.streaming.connectDelay=1000
|
||||
tunnel.1.option.i2p.maxWindowSize=1
|
||||
tunnel.1.option.i2p.streaming.maxWindowSize=1
|
||||
tunnel.1.startOnLoad=true
|
||||
|
||||
# I2P's cvs server
|
||||
|
||||
4
news.xml
4
news.xml
@@ -1,5 +1,5 @@
|
||||
<i2p.news date="$Date: 2005/10/07 18:45:48 $">
|
||||
<i2p.release version="0.6.1.3" date="2005/10/14" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2005/10/14 08:48:05 $">
|
||||
<i2p.release version="0.6.1.4" date="2005/10/29" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"
|
||||
|
||||
@@ -12,6 +12,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
@@ -36,6 +39,15 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
|
||||
|
||||
private static final boolean RAW_FULL_SIZE = false;
|
||||
|
||||
/** unsynchronized as its pretty much read only (except at startup) */
|
||||
private static final Map _builders = new HashMap(8);
|
||||
public static final void registerBuilder(Builder builder, int type) { _builders.put(new Integer(type), builder); }
|
||||
/** interface for extending the types of messages handled */
|
||||
public interface Builder {
|
||||
/** instantiate a new I2NPMessage to be populated shortly */
|
||||
public I2NPMessage build(I2PAppContext ctx);
|
||||
}
|
||||
|
||||
public I2NPMessageImpl(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(I2NPMessageImpl.class);
|
||||
@@ -334,7 +346,11 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
|
||||
case TunnelCreateStatusMessage.MESSAGE_TYPE:
|
||||
return new TunnelCreateStatusMessage(context);
|
||||
default:
|
||||
return null;
|
||||
Builder builder = (Builder)_builders.get(new Integer(type));
|
||||
if (builder == null)
|
||||
return null;
|
||||
else
|
||||
return builder.build(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ public class InNetMessagePool implements Service {
|
||||
if (job != null) {
|
||||
_context.jobQueue().addJob(job);
|
||||
jobFound = true;
|
||||
} else {
|
||||
// ok, we may not have *found* a job, per se, but we could have, the
|
||||
// job may have just executed inline
|
||||
jobFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ public class JobQueue {
|
||||
|
||||
long numReady = 0;
|
||||
boolean alreadyExists = false;
|
||||
boolean dropped = false;
|
||||
synchronized (_jobLock) {
|
||||
if (_readyJobs.contains(job))
|
||||
alreadyExists = true;
|
||||
@@ -144,34 +145,33 @@ public class JobQueue {
|
||||
alreadyExists = true;
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("jobQueue.readyJobs", numReady, 0);
|
||||
if (shouldDrop(job, numReady)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping job due to overload! # ready jobs: "
|
||||
+ numReady + ": job = " + job);
|
||||
job.dropped();
|
||||
_context.statManager().addRateData("jobQueue.droppedJobs", 1, 1);
|
||||
_jobLock.notifyAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alreadyExists) {
|
||||
if (job.getTiming().getStartAfter() <= _context.clock().now()) {
|
||||
// don't skew us - its 'start after' its been queued, or later
|
||||
job.getTiming().setStartAfter(_context.clock().now());
|
||||
if (job instanceof JobImpl)
|
||||
((JobImpl)job).madeReady();
|
||||
_readyJobs.add(job);
|
||||
_jobLock.notifyAll();
|
||||
} else {
|
||||
_timedJobs.add(job);
|
||||
_jobLock.notifyAll();
|
||||
}
|
||||
dropped = true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not adding already enqueued job " + job.getName());
|
||||
if (!alreadyExists) {
|
||||
if (job.getTiming().getStartAfter() <= _context.clock().now()) {
|
||||
// don't skew us - its 'start after' its been queued, or later
|
||||
job.getTiming().setStartAfter(_context.clock().now());
|
||||
if (job instanceof JobImpl)
|
||||
((JobImpl)job).madeReady();
|
||||
_readyJobs.add(job);
|
||||
} else {
|
||||
_timedJobs.add(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
_jobLock.notifyAll();
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("jobQueue.readyJobs", numReady, 0);
|
||||
if (dropped) {
|
||||
_context.statManager().addRateData("jobQueue.droppedJobs", 1, 1);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping job due to overload! # ready jobs: "
|
||||
+ numReady + ": job = " + job);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -329,13 +329,15 @@ public class JobQueue {
|
||||
*/
|
||||
Job getNext() {
|
||||
while (_alive) {
|
||||
synchronized (_jobLock) {
|
||||
if (_readyJobs.size() > 0) {
|
||||
return (Job)_readyJobs.remove(0);
|
||||
} else {
|
||||
try { _jobLock.wait(); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
synchronized (_jobLock) {
|
||||
if (_readyJobs.size() > 0) {
|
||||
return (Job)_readyJobs.remove(0);
|
||||
} else {
|
||||
_jobLock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No longer alive, returning null");
|
||||
@@ -403,50 +405,50 @@ public class JobQueue {
|
||||
long now = _context.clock().now();
|
||||
long timeToWait = -1;
|
||||
ArrayList toAdd = null;
|
||||
synchronized (_jobLock) {
|
||||
for (int i = 0; i < _timedJobs.size(); i++) {
|
||||
Job j = (Job)_timedJobs.get(i);
|
||||
// find jobs due to start before now
|
||||
long timeLeft = j.getTiming().getStartAfter() - now;
|
||||
if (timeLeft <= 0) {
|
||||
if (j instanceof JobImpl)
|
||||
((JobImpl)j).madeReady();
|
||||
try {
|
||||
synchronized (_jobLock) {
|
||||
for (int i = 0; i < _timedJobs.size(); i++) {
|
||||
Job j = (Job)_timedJobs.get(i);
|
||||
// find jobs due to start before now
|
||||
long timeLeft = j.getTiming().getStartAfter() - now;
|
||||
if (timeLeft <= 0) {
|
||||
if (j instanceof JobImpl)
|
||||
((JobImpl)j).madeReady();
|
||||
|
||||
if (toAdd == null) toAdd = new ArrayList(4);
|
||||
toAdd.add(j);
|
||||
_timedJobs.remove(i);
|
||||
i--; // so the index stays consistent
|
||||
} else {
|
||||
if ( (timeToWait <= 0) || (timeLeft < timeToWait) )
|
||||
timeToWait = timeLeft;
|
||||
if (toAdd == null) toAdd = new ArrayList(4);
|
||||
toAdd.add(j);
|
||||
_timedJobs.remove(i);
|
||||
i--; // so the index stays consistent
|
||||
} else {
|
||||
if ( (timeToWait <= 0) || (timeLeft < timeToWait) )
|
||||
timeToWait = timeLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toAdd != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not waiting - we have " + toAdd.size() + " newly ready jobs");
|
||||
// rather than addAll, which allocs a byte array rv before adding,
|
||||
// we iterate, since toAdd is usually going to only be 1 or 2 entries
|
||||
// and since readyJobs will often have the space, we can avoid the
|
||||
// extra alloc. (no, i'm not just being insane - i'm updating this based
|
||||
// on some profiling data ;)
|
||||
for (int i = 0; i < toAdd.size(); i++)
|
||||
_readyJobs.add(toAdd.get(i));
|
||||
_jobLock.notifyAll();
|
||||
} else {
|
||||
if (timeToWait < 0)
|
||||
timeToWait = 30*1000;
|
||||
else if (timeToWait < 10)
|
||||
timeToWait = 10;
|
||||
else if (timeToWait > 10*1000)
|
||||
timeToWait = 10*1000;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Waiting " + timeToWait + " before rechecking the timed queue");
|
||||
try {
|
||||
if (toAdd != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not waiting - we have " + toAdd.size() + " newly ready jobs");
|
||||
// rather than addAll, which allocs a byte array rv before adding,
|
||||
// we iterate, since toAdd is usually going to only be 1 or 2 entries
|
||||
// and since readyJobs will often have the space, we can avoid the
|
||||
// extra alloc. (no, i'm not just being insane - i'm updating this based
|
||||
// on some profiling data ;)
|
||||
for (int i = 0; i < toAdd.size(); i++)
|
||||
_readyJobs.add(toAdd.get(i));
|
||||
_jobLock.notifyAll();
|
||||
} else {
|
||||
if (timeToWait < 0)
|
||||
timeToWait = 30*1000;
|
||||
else if (timeToWait < 10)
|
||||
timeToWait = 10;
|
||||
else if (timeToWait > 10*1000)
|
||||
timeToWait = 10*1000;
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Waiting " + timeToWait + " before rechecking the timed queue");
|
||||
_jobLock.wait(timeToWait);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
} // synchronize (_jobLock)
|
||||
}
|
||||
} // synchronize (_jobLock)
|
||||
} catch (InterruptedException ie) {}
|
||||
} // while (_alive)
|
||||
} catch (Throwable t) {
|
||||
_context.clock().removeUpdateListener(this);
|
||||
|
||||
@@ -10,10 +10,7 @@ package net.i2p.router;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
@@ -68,7 +65,7 @@ class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
private RouterContext _context;
|
||||
|
||||
public DummyNetworkDatabaseFacade(RouterContext ctx) {
|
||||
_routers = new HashMap();
|
||||
_routers = Collections.synchronizedMap(new HashMap());
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
@@ -93,11 +90,13 @@ class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
public void publish(RouterInfo localRouterInfo) {}
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; }
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) {
|
||||
_routers.put(key, routerInfo);
|
||||
return routerInfo;
|
||||
RouterInfo rv = (RouterInfo)_routers.put(key, routerInfo);
|
||||
return rv;
|
||||
}
|
||||
public void unpublish(LeaseSet localLeaseSet) {}
|
||||
public void fail(Hash dbEntry) {}
|
||||
public void fail(Hash dbEntry) {
|
||||
_routers.remove(dbEntry);
|
||||
}
|
||||
|
||||
public Set findNearestRouters(Hash key, int maxNumRouters, Set peersToIgnore) { return new HashSet(_routers.values()); }
|
||||
|
||||
|
||||
@@ -106,11 +106,30 @@ public class Router {
|
||||
}
|
||||
|
||||
_config = new Properties();
|
||||
_context = new RouterContext(this, envProps);
|
||||
if (configFilename == null)
|
||||
_configFilename = _context.getProperty(PROP_CONFIG_FILE, "router.config");
|
||||
else
|
||||
|
||||
if (configFilename == null) {
|
||||
if (envProps != null) {
|
||||
_configFilename = envProps.getProperty(PROP_CONFIG_FILE);
|
||||
}
|
||||
if (_configFilename == null)
|
||||
_configFilename = System.getProperty(PROP_CONFIG_FILE, "router.config");
|
||||
} else {
|
||||
_configFilename = configFilename;
|
||||
}
|
||||
|
||||
readConfig();
|
||||
if (envProps == null) {
|
||||
envProps = _config;
|
||||
} else {
|
||||
for (Iterator iter = _config.keySet().iterator(); iter.hasNext(); ) {
|
||||
String k = (String)iter.next();
|
||||
String v = _config.getProperty(k);
|
||||
envProps.setProperty(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_context = new RouterContext(this, envProps);
|
||||
_routerInfo = null;
|
||||
_higherVersionSeen = false;
|
||||
_log = _context.logManager().getLog(Router.class);
|
||||
@@ -248,9 +267,12 @@ public class Router {
|
||||
}
|
||||
|
||||
private static Properties getConfig(RouterContext ctx, String filename) {
|
||||
Log log = ctx.logManager().getLog(Router.class);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Config file: " + filename);
|
||||
Log log = null;
|
||||
if (ctx != null) {
|
||||
log = ctx.logManager().getLog(Router.class);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Config file: " + filename, new Exception("location"));
|
||||
}
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@@ -260,10 +282,12 @@ public class Router {
|
||||
// dont be a wanker
|
||||
props.remove(PROP_SHUTDOWN_IN_PROGRESS);
|
||||
} else {
|
||||
log.warn("Configuration file " + filename + " does not exist");
|
||||
if (log != null)
|
||||
log.warn("Configuration file " + filename + " does not exist");
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
log.error("Error loading the router configuration from " + filename, ioe);
|
||||
if (log != null)
|
||||
log.error("Error loading the router configuration from " + filename, ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
@@ -436,6 +460,8 @@ public class Router {
|
||||
private long getTimeTillMidnight() {
|
||||
long now = Router.this._context.clock().now();
|
||||
_cal.setTime(new Date(now));
|
||||
_cal.set(Calendar.YEAR, _cal.get(Calendar.YEAR)); // gcj <= 4.0 workaround
|
||||
_cal.set(Calendar.DAY_OF_YEAR, _cal.get(Calendar.DAY_OF_YEAR)); // gcj <= 4.0 workaround
|
||||
_cal.add(Calendar.DATE, 1);
|
||||
_cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
_cal.set(Calendar.MINUTE, 0);
|
||||
@@ -444,7 +470,7 @@ public class Router {
|
||||
long then = _cal.getTime().getTime();
|
||||
long howLong = then - now;
|
||||
if (howLong < 0) // hi kaffe
|
||||
howLong = 24*60*60*1000 + howLong;
|
||||
howLong = 24*60*60*1000l + howLong;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Time till midnight: " + howLong + "ms");
|
||||
return howLong;
|
||||
@@ -744,6 +770,7 @@ public class Router {
|
||||
|
||||
public void shutdown(int exitCode) {
|
||||
_isAlive = false;
|
||||
_context.random().saveSeed();
|
||||
I2PThread.removeOOMEventListener(_oomListener);
|
||||
try { _context.jobQueue().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the job queue", t); }
|
||||
//try { _context.adminManager().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the admin manager", t); }
|
||||
|
||||
@@ -90,7 +90,10 @@ public class RouterContext extends I2PAppContext {
|
||||
}
|
||||
private void initAll() {
|
||||
_adminManager = new AdminManager(this);
|
||||
_clientManagerFacade = new ClientManagerFacadeImpl(this);
|
||||
if ("false".equals(getProperty("i2p.dummyClientFacade", "false")))
|
||||
_clientManagerFacade = new ClientManagerFacadeImpl(this);
|
||||
else
|
||||
_clientManagerFacade = new DummyClientManagerFacade(this);
|
||||
_clientMessagePool = new ClientMessagePool(this);
|
||||
_jobQueue = new JobQueue(this);
|
||||
_inNetMessagePool = new InNetMessagePool(this);
|
||||
@@ -98,17 +101,26 @@ public class RouterContext extends I2PAppContext {
|
||||
_messageHistory = new MessageHistory(this);
|
||||
_messageRegistry = new OutboundMessageRegistry(this);
|
||||
_messageStateMonitor = new MessageStateMonitor(this);
|
||||
_netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
|
||||
if ("false".equals(getProperty("i2p.dummyNetDb", "false")))
|
||||
_netDb = new FloodfillNetworkDatabaseFacade(this); // new KademliaNetworkDatabaseFacade(this);
|
||||
else
|
||||
_netDb = new DummyNetworkDatabaseFacade(this);
|
||||
_keyManager = new KeyManager(this);
|
||||
if ("false".equals(getProperty("i2p.vmCommSystem", "false")))
|
||||
_commSystem = new CommSystemFacadeImpl(this);
|
||||
else
|
||||
_commSystem = new VMCommSystem(this);
|
||||
_profileOrganizer = new ProfileOrganizer(this);
|
||||
_peerManagerFacade = new PeerManagerFacadeImpl(this);
|
||||
if ("false".equals(getProperty("i2p.dummyPeerManager", "false")))
|
||||
_peerManagerFacade = new PeerManagerFacadeImpl(this);
|
||||
else
|
||||
_peerManagerFacade = new DummyPeerManagerFacade();
|
||||
_profileManager = new ProfileManagerImpl(this);
|
||||
_bandwidthLimiter = new FIFOBandwidthLimiter(this);
|
||||
_tunnelManager = new TunnelPoolManager(this);
|
||||
if ("false".equals(getProperty("i2p.dummyTunnelManager", "false")))
|
||||
_tunnelManager = new TunnelPoolManager(this);
|
||||
else
|
||||
_tunnelManager = new DummyTunnelManagerFacade();
|
||||
_tunnelDispatcher = new TunnelDispatcher(this);
|
||||
_statPublisher = new StatisticsManager(this);
|
||||
_shitlist = new Shitlist(this);
|
||||
|
||||
@@ -50,6 +50,8 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
_context.statManager().createRateStat("router.throttleTunnelProbTooFast", "How many tunnels beyond the previous 1h average are we participating in when we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelBandwidthExceeded", "How much bandwidth is allocated when we refuse due to bandwidth allocation?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelBytesAllowed", "How many bytes are allowed to be sent when we get a tunnel request (period is how many are currently allocated)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("router.throttleTunnelBytesUsed", "Used Bps at request (period = max KBps)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public boolean acceptNetworkMessage() {
|
||||
@@ -212,7 +214,13 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
r = null;
|
||||
if (rs != null)
|
||||
r = rs.getRate(10*60*1000);
|
||||
double bytesAllocated = (r != null ? r.getCurrentTotalValue() * 1024 : 0);
|
||||
double messagesPerTunnel = (r != null ? r.getAverageValue() : 0d);
|
||||
if (messagesPerTunnel < DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE)
|
||||
messagesPerTunnel = DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE;
|
||||
int participatingTunnels = (r != null ? (int) (r.getLastEventCount() + r.getCurrentEventCount()) : 0);
|
||||
if (participatingTunnels <= 0)
|
||||
participatingTunnels = _context.tunnelManager().getParticipatingCount();
|
||||
double bytesAllocated = messagesPerTunnel * participatingTunnels * 1024;
|
||||
|
||||
if (!allowTunnel(bytesAllocated, numTunnels)) {
|
||||
_context.statManager().addRateData("router.throttleTunnelBandwidthExceeded", (long)bytesAllocated, 0);
|
||||
@@ -227,6 +235,8 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
return TUNNEL_ACCEPT;
|
||||
}
|
||||
|
||||
private static final int DEFAULT_MESSAGES_PER_TUNNEL_ESTIMATE = 600; // 1KBps
|
||||
|
||||
/**
|
||||
* with bytesAllocated already accounted for across the numTunnels existing
|
||||
* tunnels we have agreed to, can we handle another tunnel with our existing
|
||||
@@ -234,26 +244,33 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
*
|
||||
*/
|
||||
private boolean allowTunnel(double bytesAllocated, int numTunnels) {
|
||||
long bytesAllowed = getBytesAllowed();
|
||||
|
||||
bytesAllowed *= getSharePercentage();
|
||||
|
||||
double bytesPerTunnel = (numTunnels > 0 ? bytesAllocated / numTunnels : 0);
|
||||
double toAllocate = (numTunnels > 0 ? bytesPerTunnel * (numTunnels + 1) : 0);
|
||||
|
||||
double pctFull = toAllocate / bytesAllowed;
|
||||
int maxKBps = Math.min(_context.bandwidthLimiter().getOutboundKBytesPerSecond(), _context.bandwidthLimiter().getInboundKBytesPerSecond());
|
||||
int used = (int)Math.max(_context.bandwidthLimiter().getSendBps(), _context.bandwidthLimiter().getReceiveBps());
|
||||
int availBps = (int)(((maxKBps*1024) - used) * getSharePercentage());
|
||||
|
||||
_context.statManager().addRateData("router.throttleTunnelBytesUsed", used, maxKBps);
|
||||
_context.statManager().addRateData("router.throttleTunnelBytesAllowed", availBps, (long)bytesAllocated);
|
||||
|
||||
if (maxKBps <= 8) {
|
||||
// lets be more conservative for dialup users and assume 1KBps per tunnel
|
||||
return ( (numTunnels + 1)*1024 < availBps);
|
||||
}
|
||||
|
||||
double growthFactor = ((double)(numTunnels+1))/(double)numTunnels;
|
||||
double toAllocate = (numTunnels > 0 ? bytesAllocated * growthFactor : 0);
|
||||
|
||||
double allocatedKBps = toAllocate / (10 * 60 * 1024);
|
||||
double pctFull = allocatedKBps / availBps;
|
||||
|
||||
if (pctFull < 1.0) { // (_context.random().nextInt(100) > 100 * pctFull) {
|
||||
if ( (pctFull < 1.0) && (pctFull >= 0.0) ) { // (_context.random().nextInt(100) > 100 * pctFull) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Probabalistically allowing the tunnel w/ " + pctFull + " of our " + bytesAllowed
|
||||
+ "bytes/" + allocatedKBps + "KBps allocated through " + numTunnels + " tunnels");
|
||||
_log.debug("Probabalistically allowing the tunnel w/ " + pctFull + " of our " + availBps
|
||||
+ "Bps/" + allocatedKBps + "KBps allocated through " + numTunnels + " tunnels");
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Rejecting the tunnel w/ " + pctFull + " of our " + bytesAllowed
|
||||
+ "bytes allowed (" + toAllocate + "bytes / " + allocatedKBps
|
||||
_log.debug("Rejecting the tunnel w/ " + pctFull + " of our " + availBps
|
||||
+ "Bps allowed (" + toAllocate + "bytes / " + allocatedKBps
|
||||
+ "KBps) through " + numTunnels + " tunnels");
|
||||
return false;
|
||||
}
|
||||
@@ -268,7 +285,11 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
String pct = _context.getProperty(PROP_BANDWIDTH_SHARE_PERCENTAGE, "0.8");
|
||||
if (pct != null) {
|
||||
try {
|
||||
return Double.parseDouble(pct);
|
||||
double d = Double.parseDouble(pct);
|
||||
if (d > 1)
|
||||
return d/100d; // *cough* sometimes its 80 instead of .8 (!stab jrandom)
|
||||
else
|
||||
return d;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the share percentage");
|
||||
@@ -276,50 +297,7 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
}
|
||||
return 0.8;
|
||||
}
|
||||
|
||||
/**
|
||||
* BytesPerSecond that we can pass along data
|
||||
*/
|
||||
private long getBytesAllowed() {
|
||||
String kbpsOutStr = _context.getProperty("i2np.bandwidth.outboundKBytesPerSecond");
|
||||
long kbpsOut = -1;
|
||||
if (kbpsOutStr != null) {
|
||||
try {
|
||||
kbpsOut = Integer.parseInt(kbpsOutStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the bytes allowed (outbound)");
|
||||
}
|
||||
}
|
||||
|
||||
String kbpsInStr = _context.getProperty("i2np.bandwidth.inboundKBytesPerSecond");
|
||||
long kbpsIn = -1;
|
||||
if (kbpsInStr != null) {
|
||||
try {
|
||||
kbpsIn = Integer.parseInt(kbpsInStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to get the bytes allowed (inbound)");
|
||||
}
|
||||
}
|
||||
|
||||
// whats our choke?
|
||||
long kbps = (kbpsOut > kbpsIn ? kbpsIn : kbpsOut);
|
||||
|
||||
if (kbps <= 0) {
|
||||
try {
|
||||
kbps = Integer.parseInt(_context.getProperty(PROP_DEFAULT_KBPS_THROTTLE, "64")); // absurd
|
||||
} catch (NumberFormatException nfe) {
|
||||
kbps = 64;
|
||||
}
|
||||
}
|
||||
|
||||
return kbps
|
||||
* 60 // per minute
|
||||
* 10 // per 10 minute period
|
||||
* 1024; // bytes;
|
||||
}
|
||||
|
||||
|
||||
/** dont ever probabalistically throttle tunnels if we have less than this many */
|
||||
private int getMinThrottleTunnels() {
|
||||
try {
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.269 $ $Date: 2005/10/13 21:15:40 $";
|
||||
public final static String VERSION = "0.6.1.3";
|
||||
public final static String ID = "$Revision: 1.279 $ $Date: 2005/10/29 16:35:24 $";
|
||||
public final static String VERSION = "0.6.1.4";
|
||||
public final static long BUILD = 0;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
||||
|
||||
271
router/java/src/net/i2p/router/SSUDemo.java
Normal file
271
router/java/src/net/i2p/router/SSUDemo.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.data.i2np.*;
|
||||
|
||||
/**
|
||||
* Demo of a stripped down router - no tunnels, no netDb, no i2cp, no peer profiling,
|
||||
* just the SSU comm layer, crypto, and associated infrastructure, extended to handle
|
||||
* a new type of message ("FooMessage").
|
||||
*
|
||||
*/
|
||||
public class SSUDemo {
|
||||
RouterContext _us;
|
||||
|
||||
public static void main(String args[]) {
|
||||
SSUDemo demo = new SSUDemo();
|
||||
demo.run();
|
||||
}
|
||||
|
||||
public SSUDemo() {}
|
||||
public void run() {
|
||||
String cfgFile = "router.config";
|
||||
Properties envProps = getEnv();
|
||||
Router r = new Router(cfgFile, envProps);
|
||||
r.runRouter();
|
||||
_us = r.getContext();
|
||||
setupHandlers();
|
||||
// wait for it to warm up a bit
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
// now write out our ident and info
|
||||
RouterInfo myInfo = _us.router().getRouterInfo();
|
||||
storeMyInfo(myInfo);
|
||||
// look for any other peers written to the same directory, and send each
|
||||
// a single Foo message (0x0123), unless they've already contacted us first.
|
||||
// this call never returns
|
||||
loadPeers();
|
||||
}
|
||||
|
||||
private Properties getEnv() {
|
||||
Properties envProps = System.getProperties();
|
||||
// disable the TCP transport, as its deprecated
|
||||
envProps.setProperty("i2np.tcp.disable", "true");
|
||||
// we want SNTP synchronization for replay prevention
|
||||
envProps.setProperty("time.disabled", "false");
|
||||
// allow 127.0.0.1/10.0.0.1/etc (useful for testing). If this is false,
|
||||
// peers who say they're on an invalid IP are shitlisted
|
||||
envProps.setProperty("i2np.udp.allowLocal", "true");
|
||||
// explicit IP+port. at least one router on the net has to have their IP+port
|
||||
// set, since there has to be someone to detect one's IP off. most don't need
|
||||
// to set these though
|
||||
envProps.setProperty("i2np.udp.host", "127.0.0.1");
|
||||
envProps.setProperty("i2np.udp.internalPort", "12000");
|
||||
envProps.setProperty("i2np.udp.port", "12000");
|
||||
// disable I2CP, the netDb, peer testing/profile persistence, and tunnel
|
||||
// creation/management
|
||||
envProps.setProperty("i2p.dummyClientFacade", "true");
|
||||
envProps.setProperty("i2p.dummyNetDb", "true");
|
||||
envProps.setProperty("i2p.dummyPeerManager", "true");
|
||||
envProps.setProperty("i2p.dummyTunnelManager", "true");
|
||||
// set to false if you want to use HMAC-SHA256-128 instead of HMAC-MD5-128 as
|
||||
// the SSU MAC
|
||||
envProps.setProperty("i2p.HMACMD5", "true");
|
||||
// if you're using the HMAC MD5, by default it will use a 32 byte MAC field,
|
||||
// which is a bug, as it doesn't generate the same values as a 16 byte MAC field.
|
||||
// set this to false if you don't want the bug
|
||||
envProps.setProperty("i2p.HMACBrokenSize", "false");
|
||||
// no need to include any stats in the routerInfo we send to people on SSU
|
||||
// session establishment
|
||||
envProps.setProperty("router.publishPeerRankings", "false");
|
||||
// write the logs to ./logs/log-router-*.txt (logger configured with the file
|
||||
// ./logger.config, or another config file specified as
|
||||
// -Dlogger.configLocation=blah)
|
||||
envProps.setProperty("loggerFilenameOverride", "logs/log-router-@.txt");
|
||||
return envProps;
|
||||
}
|
||||
|
||||
private void setupHandlers() {
|
||||
// netDb store is sent on connection establishment, which includes contact info
|
||||
// for the peer. the DBStoreJobBuilder builds a new asynchronous Job to process
|
||||
// each one received (storing it in our in-memory, passive netDb)
|
||||
_us.inNetMessagePool().registerHandlerJobBuilder(DatabaseStoreMessage.MESSAGE_TYPE, new DBStoreJobBuilder());
|
||||
// handle any Foo messages by displaying them on stdout
|
||||
_us.inNetMessagePool().registerHandlerJobBuilder(FooMessage.MESSAGE_TYPE, new FooJobBuilder());
|
||||
}
|
||||
|
||||
/** random place for storing router info files - written as $dir/base64(SHA256(info.getIdentity)) */
|
||||
private File getInfoDir() { return new File("/tmp/ssuDemoInfo/"); }
|
||||
|
||||
private void storeMyInfo(RouterInfo info) {
|
||||
File infoDir = getInfoDir();
|
||||
if (!infoDir.exists())
|
||||
infoDir.mkdirs();
|
||||
FileOutputStream fos = null;
|
||||
File infoFile = new File(infoDir, info.getIdentity().calculateHash().toBase64());
|
||||
try {
|
||||
fos = new FileOutputStream(infoFile);
|
||||
info.writeBytes(fos);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
} catch (DataFormatException dfe) {
|
||||
dfe.printStackTrace();
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
System.out.println("Our info stored at: " + infoFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private void loadPeers() {
|
||||
File infoDir = getInfoDir();
|
||||
if (!infoDir.exists())
|
||||
infoDir.mkdirs();
|
||||
while (true) {
|
||||
File peerFiles[] = infoDir.listFiles();
|
||||
if ( (peerFiles != null) && (peerFiles.length > 0) ) {
|
||||
for (int i = 0; i < peerFiles.length; i++) {
|
||||
if (peerFiles[i].isFile() && !peerFiles[i].isHidden()) {
|
||||
if (!_us.routerHash().toBase64().equals(peerFiles[i].getName())) {
|
||||
System.out.println("Reading info: " + peerFiles[i].getAbsolutePath());
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(peerFiles[i]);
|
||||
RouterInfo ri = new RouterInfo();
|
||||
ri.readBytes(in);
|
||||
peerRead(ri);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error reading " + peerFiles[i].getAbsolutePath());
|
||||
ioe.printStackTrace();
|
||||
} catch (DataFormatException dfe) {
|
||||
System.err.println("Corrupt " + peerFiles[i].getAbsolutePath());
|
||||
dfe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void peerRead(RouterInfo ri) {
|
||||
RouterInfo old = _us.netDb().store(ri.getIdentity().calculateHash(), ri);
|
||||
if (old == null)
|
||||
newPeerRead(ri);
|
||||
}
|
||||
|
||||
private void newPeerRead(RouterInfo ri) {
|
||||
OutNetMessage out = new OutNetMessage(_us);
|
||||
// _us.clock() is an ntp synchronized clock. give up on sending this message
|
||||
// if it doesn't get ACKed within the next 10 seconds
|
||||
out.setExpiration(_us.clock().now() + 10*1000);
|
||||
out.setPriority(100);
|
||||
out.setTarget(ri);
|
||||
FooMessage data = new FooMessage(_us, new byte[] { 0x0, 0x1, 0x2, 0x3 });
|
||||
System.out.println("SEND: " + Base64.encode(data.getData()));
|
||||
out.setMessage(data);
|
||||
// job fired if we can't contact them, or if it takes too long to get an ACK
|
||||
out.setOnFailedSendJob(null);
|
||||
// job fired once the transport gets a full ACK of the message
|
||||
out.setOnSendJob(new AfterACK());
|
||||
// queue up the message, establishing a new SSU session if necessary, using
|
||||
// their direct SSU address if they have one, or their indirect SSU addresses
|
||||
// if they don't. If we cannot contact them, we will 'shitlist' their address,
|
||||
// during which time we will not even attempt to send messages to them. We also
|
||||
// drop their netDb info when we shitlist them, in case their info is no longer
|
||||
// correct. Since the netDb is disabled for all meaningful purposes, the SSUDemo
|
||||
// will be responsible for fetching such information.
|
||||
_us.outNetMessagePool().add(out);
|
||||
}
|
||||
|
||||
/** fired if and only if the FooMessage is ACKed before we time out */
|
||||
private class AfterACK extends JobImpl {
|
||||
public AfterACK() { super(_us); }
|
||||
public void runJob() { System.out.println("Foo message sent completely"); }
|
||||
public String getName() { return "After Foo message send"; }
|
||||
}
|
||||
|
||||
////
|
||||
// Foo and netDb store handling below
|
||||
|
||||
/**
|
||||
* Deal with an Foo message received
|
||||
*/
|
||||
private class FooJobBuilder implements HandlerJobBuilder {
|
||||
public FooJobBuilder() {
|
||||
I2NPMessageImpl.registerBuilder(new FooBuilder(), FooMessage.MESSAGE_TYPE);
|
||||
}
|
||||
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
|
||||
return new FooHandleJob(_us, receivedMessage, from, fromHash);
|
||||
}
|
||||
}
|
||||
private class FooHandleJob extends JobImpl {
|
||||
private I2NPMessage _msg;
|
||||
public FooHandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
|
||||
super(ctx);
|
||||
_msg = receivedMessage;
|
||||
}
|
||||
public void runJob() {
|
||||
// we know its a FooMessage, since thats the type of message that the handler
|
||||
// is registered as
|
||||
FooMessage m = (FooMessage)_msg;
|
||||
System.out.println("RECV: " + Base64.encode(m.getData()));
|
||||
}
|
||||
public String getName() { return "Handle Foo message"; }
|
||||
}
|
||||
private class FooBuilder implements I2NPMessageImpl.Builder {
|
||||
public I2NPMessage build(I2PAppContext ctx) { return new FooMessage(ctx, null); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Just carry some data...
|
||||
*/
|
||||
class FooMessage extends I2NPMessageImpl {
|
||||
private byte[] _data;
|
||||
public static final int MESSAGE_TYPE = 17;
|
||||
public FooMessage(I2PAppContext ctx, byte data[]) {
|
||||
super(ctx);
|
||||
_data = data;
|
||||
}
|
||||
/** pull the read data off */
|
||||
public byte[] getData() { return _data; }
|
||||
/** specify the payload to be sent */
|
||||
public void setData(byte data[]) { _data = data; }
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
protected int calculateWrittenLength() { return _data.length; }
|
||||
public void readMessage(byte[] data, int offset, int dataSize, int type) throws I2NPMessageException, IOException {
|
||||
_data = new byte[dataSize];
|
||||
System.arraycopy(data, offset, _data, 0, dataSize);
|
||||
}
|
||||
|
||||
protected int writeMessageBody(byte[] out, int curIndex) throws I2NPMessageException {
|
||||
System.arraycopy(_data, 0, out, curIndex, _data.length);
|
||||
return curIndex + _data.length;
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// netDb store handling below
|
||||
|
||||
/**
|
||||
* Handle any netDb stores from the peer - they send us their netDb as part of
|
||||
* their SSU establishment (and we send them ours).
|
||||
*/
|
||||
private class DBStoreJobBuilder implements HandlerJobBuilder {
|
||||
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
|
||||
return new HandleJob(_us, receivedMessage, from, fromHash);
|
||||
}
|
||||
}
|
||||
private class HandleJob extends JobImpl {
|
||||
private I2NPMessage _msg;
|
||||
public HandleJob(RouterContext ctx, I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
|
||||
super(ctx);
|
||||
_msg = receivedMessage;
|
||||
}
|
||||
public void runJob() {
|
||||
// we know its a DatabaseStoreMessage, since thats the type of message that the handler
|
||||
// is registered as
|
||||
DatabaseStoreMessage m = (DatabaseStoreMessage)_msg;
|
||||
try {
|
||||
_us.netDb().store(m.getKey(), m.getRouterInfo());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
iae.printStackTrace();
|
||||
}
|
||||
}
|
||||
public String getName() { return "Handle netDb store"; }
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public class Shitlist {
|
||||
Date oldDate = (Date)_shitlist.put(peer, new Date(_context.clock().now() + period));
|
||||
wasAlready = (null == oldDate);
|
||||
if (reason != null) {
|
||||
if (!wasAlready)
|
||||
if (!wasAlready || (!_shitlistCause.containsKey(peer)) )
|
||||
_shitlistCause.put(peer, reason);
|
||||
} else {
|
||||
_shitlistCause.remove(peer);
|
||||
|
||||
@@ -12,6 +12,9 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Build and maintain tunnels throughout the network.
|
||||
*
|
||||
@@ -66,3 +69,31 @@ public interface TunnelManagerFacade extends Service {
|
||||
public void setInboundSettings(Hash client, TunnelPoolSettings settings);
|
||||
public void setOutboundSettings(Hash client, TunnelPoolSettings settings);
|
||||
}
|
||||
|
||||
class DummyTunnelManagerFacade implements TunnelManagerFacade {
|
||||
|
||||
public TunnelInfo getTunnelInfo(TunnelId id) { return null; }
|
||||
public TunnelInfo selectInboundTunnel() { return null; }
|
||||
public TunnelInfo selectInboundTunnel(Hash destination) { return null; }
|
||||
public TunnelInfo selectOutboundTunnel() { return null; }
|
||||
public TunnelInfo selectOutboundTunnel(Hash destination) { return null; }
|
||||
public boolean isInUse(Hash peer) { return false; }
|
||||
public int getParticipatingCount() { return 0; }
|
||||
public int getFreeTunnelCount() { return 0; }
|
||||
public int getOutboundTunnelCount() { return 0; }
|
||||
public long getLastParticipatingExpiration() { return -1; }
|
||||
public void buildTunnels(Destination client, ClientTunnelSettings settings) {}
|
||||
public TunnelPoolSettings getInboundSettings() { return null; }
|
||||
public TunnelPoolSettings getOutboundSettings() { return null; }
|
||||
public TunnelPoolSettings getInboundSettings(Hash client) { return null; }
|
||||
public TunnelPoolSettings getOutboundSettings(Hash client) { return null; }
|
||||
public void setInboundSettings(TunnelPoolSettings settings) {}
|
||||
public void setOutboundSettings(TunnelPoolSettings settings) {}
|
||||
public void setInboundSettings(Hash client, TunnelPoolSettings settings) {}
|
||||
public void setOutboundSettings(Hash client, TunnelPoolSettings settings) {}
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException {}
|
||||
public void restart() {}
|
||||
public void shutdown() {}
|
||||
public void startup() {}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class GarlicMessageBuilder {
|
||||
return buildMessage(ctx, config, new SessionKey(), new HashSet());
|
||||
}
|
||||
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags) {
|
||||
return buildMessage(ctx, config, wrappedKey, wrappedTags, 20);
|
||||
return buildMessage(ctx, config, wrappedKey, wrappedTags, 50);
|
||||
}
|
||||
public static GarlicMessage buildMessage(RouterContext ctx, GarlicConfig config, SessionKey wrappedKey, Set wrappedTags, int numTagsToDeliver) {
|
||||
Log log = ctx.logManager().getLog(GarlicMessageBuilder.class);
|
||||
@@ -68,7 +68,7 @@ public class GarlicMessageBuilder {
|
||||
wrappedTags.add(new SessionTag(true));
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Less than 10 tags are available (" + availTags + "), so we're including more");
|
||||
} else if (ctx.sessionKeyManager().getAvailableTimeLeft(key, curKey) < 30*1000) {
|
||||
} else if (ctx.sessionKeyManager().getAvailableTimeLeft(key, curKey) < 60*1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
for (int i = 0; i < numTagsToDeliver; i++)
|
||||
wrappedTags.add(new SessionTag(true));
|
||||
|
||||
@@ -151,9 +151,6 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Send outbound client message job beginning");
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Clove built to " + _toString);
|
||||
long timeoutMs = _overallExpiration - getContext().clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": preparing to search for the leaseSet for " + _toString);
|
||||
@@ -210,9 +207,9 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
getContext().statManager().addRateData("client.leaseSetFoundRemoteTime", lookupTime, lookupTime);
|
||||
}
|
||||
boolean ok = getNextLease();
|
||||
if (ok)
|
||||
if (ok) {
|
||||
send();
|
||||
else {
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to send on a random lease, as getNext returned null (to=" + _toString + ")");
|
||||
dieFatal();
|
||||
@@ -258,19 +255,23 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
// sort are randomly ordered)
|
||||
Collections.shuffle(leases);
|
||||
|
||||
// ordered by lease number of failures
|
||||
TreeMap orderedLeases = new TreeMap();
|
||||
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
|
||||
Lease lease = (Lease)iter.next();
|
||||
long id = lease.getNumFailure();
|
||||
while (orderedLeases.containsKey(new Long(id)))
|
||||
id++;
|
||||
orderedLeases.put(new Long(id), lease);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
|
||||
if (false) {
|
||||
// ordered by lease number of failures
|
||||
TreeMap orderedLeases = new TreeMap();
|
||||
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
|
||||
Lease lease = (Lease)iter.next();
|
||||
long id = lease.getNumFailure();
|
||||
while (orderedLeases.containsKey(new Long(id)))
|
||||
id++;
|
||||
orderedLeases.put(new Long(id), lease);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": ranking lease we havent sent it down as " + id);
|
||||
}
|
||||
|
||||
_lease = (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
} else {
|
||||
_lease = (Lease)leases.get(0);
|
||||
}
|
||||
|
||||
_lease = (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -320,7 +321,10 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
}
|
||||
|
||||
_inTunnel = selectInboundTunnel();
|
||||
|
||||
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Clove built to " + _toString);
|
||||
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(getContext(), token,
|
||||
_overallExpiration, key,
|
||||
_clove, _from.calculateHash(),
|
||||
@@ -461,12 +465,12 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
|
||||
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
clove.setDeliveryInstructions(instructions);
|
||||
clove.setExpiration(_overallExpiration);
|
||||
clove.setExpiration(OVERALL_TIMEOUT_MS_DEFAULT+getContext().clock().now());
|
||||
clove.setId(getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE));
|
||||
|
||||
DataMessage msg = new DataMessage(getContext());
|
||||
msg.setData(_clientMessage.getPayload().getEncryptedData());
|
||||
msg.setMessageExpiration(_overallExpiration);
|
||||
msg.setMessageExpiration(clove.getExpiration());
|
||||
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
|
||||
@@ -35,7 +35,14 @@ public class FloodfillDatabaseLookupMessageHandler implements HandlerJobBuilder
|
||||
_context.statManager().addRateData("netDb.lookupsReceived", 1, 0);
|
||||
|
||||
if (true || _context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
|
||||
return new HandleFloodfillDatabaseLookupMessageJob(_context, (DatabaseLookupMessage)receivedMessage, from, fromHash);
|
||||
Job j = new HandleFloodfillDatabaseLookupMessageJob(_context, (DatabaseLookupMessage)receivedMessage, from, fromHash);
|
||||
if (true) {
|
||||
// might as well inline it, all the heavy lifting is queued up in later jobs, if necessary
|
||||
j.runJob();
|
||||
return null;
|
||||
} else {
|
||||
return j;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dropping lookup request as throttled");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user