Compare commits

...

737 Commits

Author SHA1 Message Date
bf51741134 * Update versions, package release 2008-08-24 10:28:57 +00:00
zzz
33e8abfc3e * Persistent data store: Increase write limit from 300 to 600
so floodfill routers don't get backed up
2008-08-20 15:02:56 +00:00
zzz
258d01f0d9 * Blocklists: Handle blank lines and \r\n in blocklist.txt
* NTCP: Add connection limit, set by i2np.ntcp.maxConnections,
      default is 500 (very high for now)
2008-08-20 14:58:45 +00:00
zzz
49af13a3ca * i2psnark: Fix OOM vulnerability by checking incoming message length
(thanks devzero!)
2008-08-13 15:59:16 +00:00
zzz
719ba3f66f * Floodfill Peer Selector:
- Avoid peers whose netdb is old, or have a recent failed store,
        or are forever-shitlisted
2008-08-04 19:31:11 +00:00
zzz
9652db9623 * Blocklists:
- New, disabled by default, except for blocking of
        forever-shitlisted peers. See source for instructions
        and file format.
    * Transport - Reject peers from inbound connections:
      - Check IP against blocklist
      - Check router hash against forever-shitlist, then block IP
2008-07-30 03:59:18 +00:00
zzz
481af00bab -9 2008-07-16 15:05:56 +00:00
zzz
11d267bc9a * configpeer.jsp: New 2008-07-16 15:05:07 +00:00
zzz
40f0cb65a1 * SSU:
Don't proactively reconnect until 30m idle, so
        we don't lose introducer tags prematurely
2008-07-16 15:04:02 +00:00
zzz
2ba9929277 * PRNG: Move logging from wrapper to router log 2008-07-16 15:03:00 +00:00
zzz
616abba328 * i2psnark: Open completed files read-only the first time 2008-07-16 15:02:20 +00:00
14a6352d9a Corrected UTF-8 encoding 2008-07-16 14:44:17 +00:00
5782c42d25 Cleaned up all 'imports' in all applications, core and router. 2008-07-16 13:42:54 +00:00
dev
f261deaf16 made code more 1.5 compatible 2008-07-14 15:05:26 +00:00
zzz
5228543236 * SSU:
- Try to pick better introducers by checking shitlist,
        wasUnreachable list, failing list, and idle times
      - To keep introducer connections up and valid,
        periodically send a "ping" (a data packet with no data and no acks)
        to everybody that has been an introducer in the last two hours
      - Add a stat udp.receiveRelayRequestBadTag, make udp.receiveRelayRequest only for good ones
      - Remove some 60s and 5m stats, leave only the 10m ones
      - Narrow the range for the retransmit time after an allocation fail
      - Adjust some logging
2008-07-07 14:18:38 +00:00
zzz
e173a47e01 * Streaming lib - adjust some loggging, cleanup Connection.toString() 2008-07-07 14:18:15 +00:00
zzz
07b895a069 * Router console: Flag placeholder pages as noncacheable 2008-07-07 14:10:10 +00:00
zzz
4d8ffc85e2 * LoadTestManager: Don't instantiate, it's disabled 2008-07-07 14:09:16 +00:00
zzz
53e2e0d1c9 * KeyManager:
- Don't write router key backup when leaseSet keys are updated
      - Synchronize to prevent concurrent writes (thanks Galen!)
      - Backup keys every 7 days instead of every 5 minutes
2008-07-07 14:07:59 +00:00
zzz
e0dcf82697 * HTTP Proxy: Don't show jump links for unknown jump hosts 2008-07-07 14:07:14 +00:00
zzz
0cfac58adb * i2psnark:
- Repair corrupted files with wrong length rather than die
      - Register shutdown hook to properly shutdown torrents when
        the router shuts down, hopefully will reduce corruption
      - Add Galen tracker
      - Add a note about how to chane directory
2008-07-07 14:05:54 +00:00
zzz
2768bef991 * NTCP:
- Try to fix 100% CPU, caused perhaps by JVM NIO bug...
      - Fix failsafe stats
2008-06-30 03:14:32 +00:00
zzz
bae712ad96 * i2psnark:
- Fix NPE caused by race (thanks echelon!)
      - Add mastertracker, remove de-ebook
2008-06-30 03:09:20 +00:00
zzz
49cb4c13b3 * PersistentDataStore: More leaseSet code cleanup 2008-06-30 03:08:16 +00:00
zzz
28da17276c * configstats.jsp: Fix NPE when no stats checked (thanks nothome27!) 2008-06-30 03:07:52 +00:00
zzz
14099ace69 * SimpleTimer: Change congestion message from error to warn 2008-06-30 03:07:00 +00:00
zzz
9289799c97 * FloodfillMonitorJob: Change range from 5-7 to 4-6 2008-06-24 14:41:41 +00:00
zzz
f057666ac2 * PersistentDataStore: Don't try to remove nonexistent leaseSet files 2008-06-24 14:39:14 +00:00
zzz
a11b74b2d8 * NTCP: Remove getIsInbound(), duplicate of isInbound() 2008-06-24 14:38:09 +00:00
zzz
0e018c5b4d * Router console: add placeholder pages for i2psnark, i2ptunnel,
susidns, and susimail for use when the .wars are not running
2008-06-24 14:36:39 +00:00
zzz
107a90fa33 increase max window size to 128 2008-06-24 14:33:30 +00:00
dev
01259cc07d merge of '5c7631359fea237f6aa916acd4f76a8a00d519fb'
and '88d299913d4aeb0ef7e8adabb5c39255e9cca0d2'
2008-06-22 14:08:06 +00:00
dev
4d955f3be5 minor optimization in I2PDatagramDissector(only verfy signature once) 2008-06-22 14:07:30 +00:00
zzz
49e429c166 * PRNG: Add two stats
* Summary bar:
      - Display Warning for TCP private IP address
      - Display PRNG stats
2008-06-20 20:22:38 +00:00
zzz
53c5b1446a * OutNetMessage: Change cache logging from WARN to INFO 2008-06-20 20:21:21 +00:00
zzz
dc68ebbaeb * configclients.jsp: Add start button for clients and webapps. 2008-06-20 20:20:50 +00:00
zzz
f3d73a6c15 * configclients.jsp: Implement saves for clients and webapps. 2008-06-17 13:48:41 +00:00
zzz
91950a37f5 * Comm System: Add new STATUS_HOSED for use when UDP bind fails
* Summary bar: Display helpful errror message when UDP bind fails
    * UDP: Don't bid when UDP bind fails
2008-06-17 13:47:54 +00:00
zzz
a8c266402e * configclients.jsp: New. For both clients and webapps.
Saves are not yet implemented.
2008-06-16 12:31:14 +00:00
zzz
d78fb4df3c * RouterConsoleRunner: Use a new config file, webapps.config,
to control which .wars in webapps/ get run. Apps are enabled
      by default; disable by (e.g.) webapps.syndie.startOnLoad=false
      Config file is written if it does not exist.
      Implement methods for use by upcoming configclients.jsp.
2008-06-16 12:26:36 +00:00
zzz
fb5a8ee0d8 * Refactor LoadClientAppsJob.java, move some functions to new
ClientAppConfig.java, to make them easily available to
      a future configclients.jsp
2008-06-16 12:19:55 +00:00
zzz
7b81062816 * UDP: Prevent 100% CPU when UDP bind fails;
change bind fail message from ERROR to CRIT
2008-06-16 12:18:43 +00:00
zzz
c3a2adc97e minor updates 2008-06-13 17:19:42 +00:00
zzz
bff685f7ca * Throttle: Use BANDWIDTH rather than CRIT as the rejection reason at
startup, so peers don't list us as failing.
2008-06-10 14:05:55 +00:00
zzz
7e51c86c38 * Floodfill: Add new FloodfillMonitorJob, which tracks active
floodfills, and automatically enables/disables floodfill on
      Class O routers to maintain 5-7 total active floodfills
2008-06-10 13:37:27 +00:00
zzz
df069ec9d1 * NetDb Stats:
- Remove several more stats
      - Don't publish bw stats in first hour of uptime
      - Publish floodfill stats even if other stats are disabled
      - Changes not effective until 0.6.2.1 to provide cover.
2008-06-10 13:28:13 +00:00
zzz
eb3164d0e0 * graphs.jsp: Fix a bug where it tries to display the combined
bandwidth graph when it isn't available
2008-06-10 13:17:28 +00:00
zzz
5a69de3650 0.6.2-1 2008-06-09 14:12:09 +00:00
zzz
87b933fd3a propagate from branch 'i2p.i2p.i2p-0.6.2.1-pre' (head 8b23a248995e5c57ccef1c2620a47929f4b257cf)
to branch 'i2p.i2p' (head f65d1f225d8700ea812e1c3cbc0ee9e7a5bbaf98)
2008-06-09 14:00:50 +00:00
zzz
2404078bfa 2008-06-09 zzz
* Reachability: Restrict peers with no SSU address at all from inbound tunnels
    * News:
      - Add display of last updated and last checked time
        on index.jsp and configupdate.jsp
      - Add a function to get update version (unused for now)
    * config.jsp: Add another warning
2008-06-09 13:14:52 +00:00
zzz
6b33378a0a fix date in the xml 2008-06-08 17:37:13 +00:00
c46c9b2b7c * Fix version in news.xml so it could be published 2008-06-07 21:46:33 +00:00
acf22bf8fc * Write announcement and prepare for release 2008-06-07 20:34:07 +00:00
zzz
f3b8c73e96 * NetDb: Tweak some logging on lease problems
* Shitlist:
      - Add shitlistForever() and isShitlistedForever(), unused for now
      - Sort the HTML output by router hash
    * config.jsp: Add another warning
    * netdb.jsp:
      - Sort the lease HTML output by dest hash, local first
      - Sort the router HTML output by router hash
2008-06-07 17:44:13 +00:00
zzz
a8e625072b add de-ebook-archiv.i2p 2008-06-06 21:36:34 +00:00
zzz
88e26224c2 * LeaseSet:
- Sort the leases by expiration date in TunnelPool.locked_buildNewLeaseSet()
        to make later LeaseSet comparisons reliable. This cleans up the code too.
      - Fix broken old vs. new LeaseSet comparison
        in ClientConnectionRunner.requestLeaseSet(),
        so that we only sign and publish a new LeaseSet when it's really new.
        Should reduce outbound overhead both in LeaseSet publishing and LeaseSet bundling,
        and floodfill router load, since locked_buildNewLeaseSet() generates
        the same LeaseSet as before quite frequently, often just seconds apart.
2008-06-06 16:17:07 +00:00
zzz
db9db18bdf * LeaseSet - code cleanup:
- Add exception to enforce max # of leases = 6, should be plenty
      - Rewrite TunnelPool.locked_buildNewLeaseSet() so it doesn't add lots of
        leases and then immediately remove them again, triggering
        the new leaseSet size exception
      - Remove the now unused LeaseSet.removeLease(lease) and
        LeaseSet.removeLease(index)
      - Store first and last expiration for efficiency
2008-06-05 12:30:12 +00:00
zzz
9c06bb3fca * HTTP Proxy error pages: Don't say eepsites are 'temporarily' down since we don't know 2008-06-05 00:46:24 +00:00
zzz
8edfa746e5 * configtunnels.jsp: Add warnings for <= 0 and >= 4 hop configurations 2008-06-04 16:03:34 +00:00
zzz
5729b34f8b * Peer Profiles - Preparation for using bonuses:
- Use CapacityBonus rather than ReachablilityBonus in the Capacity calculation
      - Persist CapacityBonus rather than ReachabilityBonus
      - Include SpeedBonus in the Speed calculation
      - Prevent negative values in Speed and Capacity when using bonuses
      - Clean up SpeedCalculator.java
2008-06-04 15:06:55 +00:00
zzz
2f80f7fa63 Add some config files for a future small distribution 2008-06-04 14:54:05 +00:00
zzz
592e609291 .33-2001 2008-06-01 20:55:32 +00:00
zzz
3396a8813f * Add some compiler flexibility to two obscure SAM makefiles 2008-06-01 20:50:29 +00:00
zzz
6345e669bc * summary bar: Add a warning if you are firewalled and class O 2008-06-01 20:43:51 +00:00
zzz
74a5abbc11 * ProfileOrganizer: Restrict !isSelectable() (i.e. shitlisted) peers from the High Capacity tier,
not just the Fast tier, since we don't use them for tunnels anyway
2008-06-01 20:34:20 +00:00
zzz
19992b1d1b * Logging: Move common WARN output to DEBUG so we can ask users to
set the default log level to WARN without massive spewage
2008-06-01 20:24:43 +00:00
zzz
02e7a19f65 * i2psnark: Change displayed peer idents to match that shown by bytemonsoon 2008-06-01 20:17:48 +00:00
zzz
c6a697df57 * Client Apps: Add new parameter for clients.config,
clientApp.x.startOnLoad=false, to disable loading
        (for SAM for example). Defaults to true of course.
2008-06-01 20:16:17 +00:00
zzz
26bb479957 * summary bar: Hide ident, provide a tooltip and a link 2008-06-01 20:14:00 +00:00
zzz
0c42e7e4b2 make a nicer initialNews.xml, add clarification to config.jsp 2008-05-30 00:08:29 +00:00
zzz
699a62a9b9 Dont bid on private IP addresses in transports 2008-05-27 13:20:56 +00:00
zzz
ffc67d1e5a add a updaterRouter target, containing only i2p.jar and router.jar 2008-05-26 14:39:39 +00:00
zzz
2f72f5ca67 * Throttle: Set a default router.maxParticipatingTunnels = 3000 (was none)
* Stats: Add a fake uptime if not publishing stats, to get participating tunnels
    * build.xml:
      - Add an updateSmall target which includes only the essentials
      - Clean up the build file some 
      - Remove empty eepsite/ and subdirs from i2pupdate.zip
    * configtunnels.jsp: Add warning
    * i2psnark: Catch a bencode exception (bad peer from tracker) earlier
    * i2psnark-standalone: Fix exception http://forum.i2p/viewtopic.php?p=12217
2008-05-26 13:13:26 +00:00
dev
955e7823ad replaced jetty download-url 2008-05-22 20:52:56 +00:00
zzz
7e3800a5cb * Reachability:
- Call the previously unused profile.tunnelTestFailed()
        (redefined to include a probability argument)
        and severely downgrade a peer's capacity upon failures,
        depending on tunnel length and direction.
        This will help push unreachable and malicious peers
        out of the High Capacity tier.
      - Put recent fail rate on profiles.jsp
    * ProfileOrganizer: Logging cleanup
    * eepsite_index.html: Update add-host and jump links
    * HTTP Proxy: Remove trevorreznik jump server from list
2008-05-20 12:48:41 +00:00
dev
6c7691cecb updated history 2008-05-20 11:31:52 +00:00
dev
760c316486 merge of '883c453307272eee439471d4e9da1e120804dac1'
and 'c60598471c5f08b7d7e12e38d39f7a1d4c8ccf63'
2008-05-20 11:30:41 +00:00
dev
5d9d82879f implemented PrivateKeyFile(implements #3) 2008-05-20 11:30:03 +00:00
zzz
9b8772a470 * Throttle: Reject tunnels for first 20m uptime (was 10m)
* TunnelPeerSelectors:
       - Re-enable strict ordering of peers,
         based on XOR distance from a random hash
       - Restrict peers with uptime < 90m from tunnels (was 2h),
         which is really 60m due to rounding in netDb publishing.
    * i2psnark:
       - Limit max pipelined requests from a single peer to 128KB
         (was unlimited; i2p-bt default is 5 * 64KB)
       - Increase max uploaders per torrent to 6 (was 4)
       - Reduce max connections per torrent to 16 (was 24) to increase
         unchoke time and reduce memory consumption
       - Strictly enforce max connections per torrent
       - Choke more gradually when over BW limit
    * help.jsp: Add a link to the FAQ
    * peers.jsp: Fix UDP direction indicators
    * hosts.txt: Add update.postman.i2p
2008-05-18 21:45:54 +00:00
zzz
bc5d87e6f0 * i2psnark:
- Randomize the PeerCheckerTask start times to make global limiting
        work better
      - Calculate bw limits using 40s rather than 4m averages to make
        bw limiting work better
      - Change default bw limit from uplimit/3 to uplimit/2 due to
        overhead reduction from the leaseset bundling change
2008-05-12 13:53:11 +00:00
zzz
d81bff267a * Outbound message:
- Tweak the cache key for efficiency
2008-05-12 13:50:15 +00:00
zzz
042399f293 * Stats:
- Require two udp stats when stats.full=false, caused NPE on peers.jsp
2008-05-12 13:48:41 +00:00
zzz
5be7ea1fc5 * libjbigi:
- Add documentation on dynamic build option
      - Add two speed tests to the build script
      - Clean up the build script, make it easier to build dynamic
2008-05-12 13:47:15 +00:00
zzz
db34665bb1 * Update Handler:
- Add postman to the list
2008-05-12 13:46:23 +00:00
zzz
ed9a03ebc7 * Summary bar:
- Add messages when dropping tunnel requests due to load
2008-05-12 13:45:08 +00:00
zzz
619b5c0e45 * Update Handler:
- Add option to download and verify only
      - Add distinct error message if version check fails
2008-05-10 14:31:18 +00:00
zzz
4c2c5ca232 Simplify oldstats.jsp if no events in a stat 2008-05-10 14:28:37 +00:00
zzz
3a203c3018 Fix the hosed inNetPool.droppedDeliveryStatusDelay stat (caused by an SSU hack) 2008-05-10 14:27:49 +00:00
zzz
3e86ee9746 Dont write out the my.info file 2008-05-10 14:26:24 +00:00
dev
d0855e1fc6 added http://www.i2p2.i2p/_static/i2pupdate.sud as another update-site 2008-05-09 12:37:11 +00:00
zzz
0bde8a24e4 * Reachability:
- Restrict peers requiring introducers from inbound tunnels,
        since it's slow and unreliable... and many of them advertise
        NTCP, which seems unlikely to work
      - Provide warning on summary bar if firewalled with inbound NTCP enabled
    * Stats: Remove the bw.[send,recv]Bps[1,15]s stats unless
      log level net.i2p.router.transport.FIFOBandwidthLimiter >= WARN
      at startup (you didn't get any data unless you set the log level anyway)
    * oldstats.jsp: Don't put 2 decimal places on integer event counts
    * Remove the Internals link from the menu bar
    * i2psnark: Extend startup delay from 1 to 3 minutes
2008-05-07 16:23:54 +00:00
dev
4049ff5167 -2 2008-05-06 20:04:26 +00:00
dev
89389ccc13 added i2jump.i2p as jump-site 2008-05-06 19:44:35 +00:00
zzz
959e308578 Add router version to profiles.jsp 2008-05-05 16:26:15 +00:00
zzz
8d4cbd8556 I2PTunnel: Change default outproxy to false.i2p 2008-05-05 14:42:17 +00:00
zzz
99b9c93636 -1 2008-05-05 14:10:19 +00:00
zzz
47c666c582 * Summary bar:
- Add reachability status
      - Add participating tunnel acceptance status
    * Throttle: Reject tunnels for first 10m uptime
2008-05-05 14:07:40 +00:00
zzz
a3c330fd9d - Restrict <= .32 SSU-only peers from inbound tunnels,
since they don't know if they are unreachable
2008-05-05 14:04:41 +00:00
zzz
a6f3478db3 * Outbound message:
- Fix a couple of tunnel cache cleaning bugs
      - Cache based on source+dest pairs rather than just dest
      - Send the reply leaseSet only when necessary,
        rather than all the time (big savings in overhead)
      - Enable persistent lease selection again
      - Logging tweaks
2008-05-05 14:01:22 +00:00
zzz
b1af22a15e - Have SSU bid aggressively when it has less than 3 peers, so
we can determine our IP address and do peer testing.
        Otherwise a router may never determine its IP address or reachability status.
2008-05-05 13:57:54 +00:00
zzz
65ec41c48f NetDb Stats: Cleanup of commented out stats 2008-05-05 13:55:50 +00:00
zzz
aebf16add7 0.6.1.33 version and news 2008-04-25 13:57:38 +00:00
zzz
1de0563c94 update forum link on susidns page 2008-04-25 13:22:41 +00:00
dev
c6059b9d85 merge of '20e06eb5e2ee39991682cae68511fca1944cd5b0'
and '8adc3b02e35ce3275e813ca9c64d75d02a0d9310'
2008-04-20 19:21:35 +00:00
dev
f68c9242a9 added news-entry for trac 2008-04-20 19:21:13 +00:00
zzz
51838ba051 * Outbound message/Reachability:
- Fix a bug from -19 causing the persistent lease selection
        removed in -17 to be back again
      - Use netDb-listed-unreachable instead of detected-unreachable
        for exclusion of unreachable peers from selected leases,
        as there are potential anonymity problems with using
        detected-unreachable
      - Tweak logging some more
    * NetDb stats: Remove a couple more including the inefficient stat_identities
2008-04-20 18:02:15 +00:00
zzz
cf50b7eac1 * Reachability:
- Track unreachable peers persistently
        (i.e. separately from shitlist, and not cleared when they contact us)
      - Exclude detected unreachable peers from inbound tunnels
      - Exclude detected unreachable peers from selected leases
      - Exclude detected unreachable floodfill peers from lookups
      - Show unreachable status on profiles.jsp
2008-04-17 18:59:15 +00:00
zzz
2edd84e088 * SSU/Reachability:
- Extend shitlist time from 4-8m to 40-60m
      - Add some shitlist logging
      - Don't shitlist twice when unreachable on all transports
      - Exclude netDb-listed unreachable peers from inbound tunnels;
        this won't help much since there are very few of these now
      - Remove 10s delay on inbound UDP connections used for the
        0.6.1.10 transition
      - Track and display UDP connection direction on peers.jsp
      - Show shitlist status in-line on profiles.jsp
2008-04-16 20:51:57 +00:00
zzz
5ba1e458c6 * SSU Reachability/PeerTestManager:
- Back out strict peer ordering until we fix SSU
      - Back out persistent lease selection until we fix SSU
      - Fix detection of UDP REJECT_UNSOLICITED by recording status on expiration
      - Increase known Charlie time to 10m; 3m wasn't enough
      - Don't continue retransmitting peer test if we know Charlie
      - Don't run multiple peer tests at once
      - Tighten test frequency range to 6.5-19.5m, was 0-26m
2008-04-15 18:27:43 +00:00
zzz
0b600669c7 * Addressbook: Disallow '.-' and '-.' in host names
* NTCP: Don't drop a connection unless both directions are idle;
            Fix idle time for outbound connections
    * Outbound message: Make sure cached lease is in current leaseSet
    * Stats: Put all NetworkDatabase stats in same group
    * TunnelPool: Stop building tunnels and leaseSets after client shutdown
    * i2psnark: Add locking to prevent two I2CP connections
2008-04-12 21:34:25 +00:00
dev
215eb14d38 no need anymore to set I2P-Home when using eepget 2008-04-07 18:54:48 +00:00
zzz
20ff2f5bdc Minor cleanups, -15 2008-04-07 17:42:54 +00:00
zzz
edc02e5efa Implement outbound bandwidth limiting for i2psnark 2008-04-07 17:41:58 +00:00
zzz
d57356f807 Reduce priority of participating traffic 2008-04-07 17:41:19 +00:00
zzz
a7a6c75ac5 * ExploratoryPeerSelector: Try NonFailing even more
* HostsTxtNamingService: Add reverse lookup support
    * Outbound message: Minor cleanup
    * i2psnark TrackerCLient: Minor cleanup
    * checklist.txt: Minor edit
    * hosts.txt: Add perv.i2p, false.i2p, mtn.i2p2.i2p
    * i2ptunnel.config: Change CVS client to mtn
    * netdb.jsp: Show leaseSet destinations using reverse lookup
    * profiles.jsp: First cut at showing floodfill data
2008-03-30 21:50:35 +00:00
zzz
b9e2def552 * Send messages for the same destination to the same inbound
lease to reduce out-of-order delivery.
    * ExploratoryPeerSelector: Back out the floodfill peer exclusion
      for now, as it prevents speed rating of those peers
2008-03-27 13:03:16 +00:00
zzz
4d7417401c * ReseedHandler: Support multiple urls,
add netdb.i2p2.de as a 2nd default
2008-03-26 20:15:47 +00:00
zzz
40a9e959e8 * i2psnark:
- Add support for secondary open trackers
      - Refactor and simplify the TrackerClient code
      - Add welterde's tracker to the default list
      - Don't have eepget retry announces
      - Slow down tracker contacts if they've failed for a while
      - Add some debug support showing connections (?p=2)
2008-03-25 21:54:54 +00:00
zzz
42bbb4a9ff * NewsFetcher: Fix bug causing fetch every 10m 2008-03-22 17:10:49 +00:00
zzz
9500a55532 * Tunnel Testing:
- Fix counting so it really takes 4 consecutive failures
        rather than 4 total to remove a tunnel
      - Credit or blame goes to the exploratory tunnel as well
        as the tunnel being tested
      - Adjust tunnel test timeout based on tunnel length
    * ExploratoryPeerSelector: Tweak logging
    * ProfileOrganizer: Adjust integration calculation again
    * build.xml: Add to help
    * checklist.txt: Tweak
    * readme.html: Fix forum links
    * netDb: Remove tunnel.testFailedTime
2008-03-22 13:07:38 +00:00
zzz
0556f15068 remove corrupt sex0r.i2p from hosts.txt 2008-03-21 15:23:10 +00:00
zzz
6e981874a5 * ExploratoryPeerSelector:
- Exclude floodfill peers
      - Tweak the HighCap vs. NonFailing decision
    * i2psnark: Increase retries for .torrent fetch
    * IRC Proxy: Prevent mIRC from sending an alternate DCC request
      containing an IP
    * readme.html: Reorder some items
    * Stats: Add some more required stats
    * Streaming lib: Fix slow start to be exponential growth,
      fix congestion avoidance to be linear growth.
      Should speed up local connections a lot, and remote
      connections a little.
2008-03-19 00:20:15 +00:00
zzz
8886d61caa forum news 2008-03-16 16:12:57 +00:00
zzz
b6fe81a982 Floodfill Search: Prefer heard-from, unfailing, unshitlisted floodfill peers 2008-03-14 22:53:18 +00:00
zzz
e7cdb965ba * ProfileOrganizer:
- Use more recent stats to calculate integrationory.txt
       - Show that fast peers are also high-capacity on profiles.jsp
    * readme.html: Update Syndie link
    * TunnelPool: Update comments
    * netDb: Report 1-2h uptime as 90m to further frustrate tracking,
      get rid of the 60s tunnel stats
      (effective as of .33 to provide cover)
2008-03-13 23:13:32 +00:00
zzz
4fa4357bf1 * Floodfill Search:
- Fix a bug that caused a single FloodfillOnlySearchJob
         instance to be run multiple times, with unpredictable
         results
       - Select ff peers randomly to improve reliability
       - Add some bulletproofing
2008-03-13 20:23:46 +00:00
zzz
46307c60d4 * ProfileOrganizer:
- Don't require a peer to be high-capacity to be
         well-integrated (not used for anything right now,
         but want to get it right for possible floodfill verification)
       - Don't fall back to median for high-capacity threshold
         if the mean is higher than the median, this prevents
         frequent large high-capacity counts
       - Fix high-capacity selector that picked one too many
    * Console: put well-integrated count back in the summary
2008-03-11 22:59:47 +00:00
zzz
e6a0c2f4f0 * EepGet: Fix byte count for bytesTransferred status listeners
(fixes command line status)
    * UpdateHandler:
       - Fix byte count display
       - Display final status on router console
       - Don't allow multiple update jobs to queue up
       - Increase max retries
       - Code cleanup
       - Don't show 'check for update' button when update in progress
       - Enhance error messages
2008-03-10 16:27:40 +00:00
zzz
cb41bf6023 NetDb: Comment out published netDb stats disabled for .32 2008-03-10 16:22:24 +00:00
zzz
d23c8a8331 -2 2008-03-09 14:55:44 +00:00
zzz
b1beb46ca2 propagate from branch 'i2p.i2p.i2p-0.6.1.33-pre' (head adbe93ae091c4ca78306ef94968a0c1d788e2c01)
to branch 'i2p.i2p' (head f541ec6c1ca7ffae49e31ee75559695d64152fa1)
2008-03-09 14:45:31 +00:00
6606c83cb2 2008-03-09 Complication
* Give the Jetty build file ability to ask permission
      before downloading the Jetty archive from the web,
      and to verify its SHA1 + MD5 hashes. Adjust the main build file
      in accordance with this change.
    * Improve the release checklist.
2008-03-08 20:37:45 +00:00
zzz
5998f5c9bd * ClientPeerSelector: Implement strict ordering of peers,
based on XOR distance from a random hash
      separately generated for each tunnel pool
2008-03-07 23:24:49 +00:00
zzz
cffcbe5f94 update news and version numbers to .32 2008-03-07 15:08:26 +00:00
zzz
0c75725f5e * ProfileOrganizer, TunnelPoolSettings, ClientPeerSelector:
- Prevent peers with matching IPs from joining same tunnel.
        Match 0-4 bytes of IP (0=off, 1=most restrictive, 4=least).
        Default is 2 (disallow routers in same /16).
        Set with router.defaultPool.IPRestriction=x
      - Comment out unused RebuildPeriod pool setting
      - Add random key to pool in preparation for XOR peer ordering
2008-03-07 14:36:40 +00:00
zzz
5ef325408c Optimize naming lookups for a destkey 2008-03-07 14:31:36 +00:00
zzz
d957712e88 Change a common wtf error to a warn 2008-03-06 14:03:49 +00:00
zzz
9233c2ed4c Add create account link in susimail 2008-03-06 14:00:33 +00:00
zzz
7ad54eb749 * Stats: Add code to disable most stats to save memory.
Set on configstats.jsp or set stat.full=false to disable the stats.
      (true by default for now)
2008-03-05 18:44:45 +00:00
zzz
5740e20c62 -3201 2008-03-05 14:25:39 +00:00
zzz
0a9114fc2f add a i2psnark StartAll button 2008-03-05 14:20:02 +00:00
zzz
71ddfa42e1 * i2psnark: Don't do a naming lookup for Base64 destkeys 2008-03-05 14:19:19 +00:00
zzz
1ecb84f3fb * Naming: Make HostsTxt the sole default NamingService
(was Meta = PetName + HostsTxt)
    * Naming: Add two new experimental NamingServices, EepGet and Exec,
      not enabled by default -
      see source comments in core/java/src/net/i2p/client/naming
      for configuration instructions
2008-03-05 14:16:04 +00:00
zzz
c46b06fb81 Fix netdb.knownLeaseSets count reported by floodfill routers 2008-03-01 16:13:41 +00:00
zzz
a7397879aa correct instructions 2008-02-29 16:50:37 +00:00
zzz
9b86da7ce5 upcoming .32 news 2008-02-29 14:51:24 +00:00
zzz
c68977ca8c * i2ptunnel: Add 3-hop option to edit.jsp to match configtunnels.jsp
* i2psnark: Remove orion and gaytorrents from default tracker list
    * Remove orion from jump list and from eepsite_index.html
    * Jbigi: Change jbigi version to 4.2.2 in build scripts - tested by amiga
    * Capitalize OutboundMessageDistributor job name
    * TunnelPool: Add a warning if all tunnels are backlogged
2008-02-27 15:18:32 +00:00
zzz
bc7bd628db * Reintroduce NTCP backlog pushback, with switch back to
previous tunnel when no longer backlogged
    * Catch an nio exception in an NTCP logging statement if loglevel is WARN
    * IRC Proxy: terminate all messages with \r\n (thanks TrivialPursuit!)
2008-02-26 19:33:11 +00:00
zzz
bc7ab39131 +x the c build files 2008-02-21 14:53:50 +00:00
zzz
100163e03b * Raise inbound default bandwidth to 32KBps
* Fix config.jsp that showed 0KBps share bandwidth by default
2008-02-21 14:05:33 +00:00
zzz
49c02f13b2 -5 2008-02-19 15:39:44 +00:00
zzz
40f072e25e Display capabilities on profiles.jsp 2008-02-19 15:32:39 +00:00
zzz
918b1acb8f * Tunnels: Enforce max tunnel length of 8, catch an index error
http://forum.i2p/viewtopic.php?t=2561
2008-02-19 15:28:41 +00:00
zzz
bc16078e3f Remove some stats from netDb, effective in .32 2008-02-19 15:22:46 +00:00
zzz
4a8dbd0634 * Addressbook: Disallow '--' in host names except in IDN,
add some reserved host names
2008-02-19 15:21:58 +00:00
zzz
38c0184f95 clarify the i2ptunnel edit page dropdowns 2008-02-19 15:20:42 +00:00
zzz
68829ddb99 merge of 'cc7dee6f711dd10db6c1f42af8dc7ba6f6b0002d'
and 'dc2fa2d01da4c7b3733d4dadb85d757d592c1fa6'
2008-02-19 15:16:53 +00:00
zzz
19089bd6a7 * Fix race in TunnelDispatcher which caused
participating tunnel count to seesaw -
      should increase network capacity
    * Leave participating tunnels in 10s batches for efficiency
    * Update participating tunnel ratestat when leaving a tunnel too,
      to generate a smoother graph
    * Fix tunnel.participatingMessageCount stat to include all
      participating tunnels, not just outbound endpoints
    * Simplify Expire Tunnel job name
2008-02-16 16:25:21 +00:00
zzz
69cc0afd1b * PersistentDataStore: Write out 300 records every 10 min
rather than 1 every 10 sec;
      Don't store leasesets to disk or read them in
    * Combine rates for pools with the same length setting
      in the new tunnel build algorithm
    * Clarify a log message in the UpdateHandler
2008-02-13 21:42:09 +00:00
zzz
d2f3a262db * Make graphs clickable to get larger graphs
* Change SimpleTimer CRIT to a WARN, increase threshold
    * Checklist update
2008-02-13 11:49:24 +00:00
dev
c1703b872d probably fixed a bug in udp-transport 2008-02-11 20:45:33 +00:00
dev
f7b0e8181b merge of '1d753ff05fc1c942cf6ce8feeadfaa577bff27ab'
and 'e20c0075f57440edcebaac0009a0ef0327d563cc'
2008-02-11 20:38:47 +00:00
zzz
43f2695901 * Add new tunnel build algorithm (preliminary)
* Change NTCP backlogged message from error to warning
    * Checklist updates
2008-02-10 18:29:21 +00:00
ea0d4ffd7f * 2008-02-10 0.6.1.31 released
2008-02-10 Complication
    * Update news and version numbers
2008-02-10 13:59:20 +00:00
dev
0ed29573a7 minor 2008-02-09 13:18:22 +00:00
zzz
1365d3e3b9 Remove orion reference in dnfh error page 2008-02-08 21:04:23 +00:00
zzz
40befb5a92 remove comments from hosts.txt 2008-02-08 19:16:48 +00:00
zzz
9c16eec361 checklist updates, +x some files 2008-02-08 00:15:32 +00:00
zzz
093c69637d * build.xml: Add some apps to javadoc
* checklist.txt: Add some things
    * news.xml: Minor edit
    * runplain.sh: Add some comments
2008-02-06 16:38:23 +00:00
zzz
134ec7acea * news.xml: make links relative
* wrapper.config: Add some comments
* checklist.txt: Add some things
2008-02-05 03:14:18 +00:00
0802a5ae40 2008-02-05 Complication
* Change the dates too (sorry for such forgetfulness!)
2008-02-04 23:52:11 +00:00
14ddfb360f 2008-02-04 Complication
* Also use the new key for checking, and add it into news.xml
2008-02-04 23:20:28 +00:00
78ad831028 2008-02-04 Complication
* Added my release signing key into TrustedUpdate.java
2008-02-04 21:34:52 +00:00
zzz
22f1684262 * NewsFetcher: Change fetch failed from error to warning
* installer: Fix URL and "email"
    * checklist.txt: New release checklist
2008-01-31 19:56:00 +00:00
zzz
83f51b4a66 2008-01-29 zzz
* Addressbook: Change default subscription
    * ConfigUpdateHandler: Change default news URL
    * initialNews.xml: Update version to .31
    * news.xml: More updates
    * hosts.txt: Add i2p-projekt.i2p
    * readme.html: More URL updates
    * SusiDNS: Change references to default subscription
2008-01-29 22:20:04 +00:00
zzz
9c28de0704 2008-01-28 zzz
* news.xml: Updates, still preliminary
    * ReseedHandler: Change default URL
    * i2ptunnel.config: Change default outproxies
    * readme.html: Change *.i2p.net URLs
    * help.jsp: Change *.i2p.net URLs
    * eepsite_index.html: Change stats.i2p addressbook subscription URL
    * hosts.txt: Add krabs.i2p, true.i2p, www.i2p2.i2p
2008-01-28 19:24:12 +00:00
zzz
c69fda2298 Drop unused directories 2008-01-27 14:48:50 +00:00
zzz
cabb22331b Drop unused directories 2008-01-27 14:42:04 +00:00
zzz
5b3aca29a8 replace orion.i2p with perv.i2p/stats.cgi 2008-01-09 22:40:33 +00:00
zzz
f35cbf59d8 * NewsFetcher: add last-modified support, reduce number of retries
* Error pages: add icon and logo,
        clarify 'destination not found' and 'proxy not found' pages
2008-01-09 04:11:12 +00:00
zzz
a96119d09b 2008-01-08 zzz
* addressbook: Limit size of subscribed hosts.txt,
        don't save old etag or last-modified data
    * EepGet: Add some logging,
        enforce size limits even when size not in returned header,
        don't return old etag or last-modified data,
        don't call transferFailed listener more than once
2008-01-09 02:15:43 +00:00
zzz
2711294aee (zzz) sign my update signing key 2008-01-09 01:56:38 +00:00
zzz
f838b1828b 2008-01-07 zzz
* profiles.jsp formatting cleanup
    * NTCP: Reduce max idle time from 60m to 20m
    * NTCP: Fix idle time on connections with zero messages,
      correctly drop these connections
2008-01-07 08:09:43 +00:00
zzz
fbf6282c1a 2008-01-03 zzz
* addressbook: Do basic validation of hostnames and destkeys
    * susidns: Add support for the private addressbook,
      update the text and links somewhat
2008-01-04 02:37:24 +00:00
zzz
5195a5c1fc 2008-01-02 zzz
* Add stats.i2p to the jump list
    * Impose 20MB limit on POSTs and catch OOMs in POST
    * eepsite_index.html: add stats.i2p services
    * addressbook: log source of new keys; disallow dests > 516 bytes
    * addressbook: convert hostnames to lower case to prevent duplicates
    * susidns: generalize references to orion
2008-01-02 22:08:48 +00:00
zzz
62b18b18b5 2007-12-29 zzz
* Tweak IRC inbound PONG filtering to fix xchat/BitchX lagometers
2007-12-30 03:45:09 +00:00
zzz
7c8f519b35 2007-12-29 zzz
* Allow commas in router.trustedUpdateKeys and router.updateURL again
2007-12-29 23:59:24 +00:00
zzz
d6fb979616 2007-12-29 zzz
* Change default news host from dev.i2p.net to dev.i2p
2007-12-29 22:15:11 +00:00
zzz
f568d21969 2007-12-29 zzz
* Change jetty timeout from 30 to 60 sec (thanks sponge!)
2007-12-29 21:08:29 +00:00
zzz
7fe9d590f5 2007-12-28 zzz
* Add zzz's update signing key
2007-12-28 20:27:16 +00:00
0a1240ebfd 2007-12-26 Complication
* Improve reseed handler (less repetitive code,
      avoid reporting errors when less than 10% of fetches fail)
2007-12-26 20:55:07 +00:00
4e68f2a157 2007-12-26 Complication
* Escape both CR, LF and CR LF line breaks in Router.saveConfig()
      and unescape them in DataHelper.loadProps() to support
      saving and loading config properties with line breaks
    * Change the update URLs textbox into a textarea like keys have,
      so different URLs go on different lines
    * Modify TrustedUpdate to provide a method which supplies a key list
      delimited with CR LF line breaks
    * Modify DEFAULT_UPDATE_URL to supply a default URL list
      delimited with CR LF line breaks
    * Modify selectUpdateURL() to handle URL lists
      delimited by any kind of line breaks
    * Start saving trusted update keys
    * Improve formatting on configupdate.jsp
2007-12-26 08:14:54 +00:00
zzz
e9bd6907d1 2007-12-22 zzz
* Add support for multiple update URLs
    * Change default for update to use i2p proxy,
      add several URLs as defaults
    * Enable trusted key form on configupdate.jsp
2007-12-22 23:58:46 +00:00
zzz
b20495c39f (zzz) Clarify the 'destination not found' error page 2007-12-22 20:54:44 +00:00
zzz
0ecbc4c27b (zzz) remove anonymitytracker from default tracker list, not seen in over 6 months 2007-12-17 02:33:05 +00:00
zzz
4d5b1d4c3f 2007-12-10 zzz
* Fix NPE in CLI TrustedUpdate keygen
2007-12-10 22:22:59 +00:00
17b719f3f7 2007-12-02 Complication
* Commit SAM v2 patch from mkvore (thank you!)
    * Minor reformatting to preserve consistent whitespace
      in old SAM classes (new classes unaltered)
2007-12-03 04:19:25 +00:00
979a3e98d8 2007-12-01 Complication
* Separate the checks "does Jetty .zip file need downloading"
      and "does Jetty .zip file need extracting" in the Jetty buildfile.
      First download (unless already done), then extract (unless done).
2007-12-02 03:13:15 +00:00
zzz
c6a1112f0a 2007-11-26 zzz
* i2psnark: add timeout for receive inactivity
2007-11-26 21:53:58 +00:00
zzz
4ebcc95d9f 2007-11-24 zzz
* i2psnark: increase streaming lib write timeout to 240 sec and change
      timeout action from "ping" to "disconect", as the fix in .30 to
      honor options on outbound connections led to hung outbound connections
      (bitfield never transmitted, connection never dropped)
2007-11-24 20:22:45 +00:00
zzz
7e59ce27fa (zzz) i2phost.i2p => i2host.i2p 2007-11-06 20:15:25 +00:00
zzz
22345a264e (zzz) fix new jump server typo 2007-11-06 19:53:06 +00:00
03739996da 2007-11-06 jrandom
* add i2phost.i2p to the jump list
2007-11-06 11:26:01 +00:00
zzz
819a72d4f6 2007-10-11 zzz
* IRC Proxy: Fix several possible anonymity holes:
      - Block CTCP in NOTICE messages
      - Block CTCP anywhere in PRIVMSG and NOTICE, not just at first character
      - Check for lower case commands
    (Thanks sponge!)
2007-10-11 06:03:21 +00:00
e480931e20 2007-10-07 jrandom
* back out the NTCP backlog pushback, as it could be used to mount an
      active anonymity attack.
2007-10-08 04:11:36 +00:00
zzz
3f01d0a69e (zzz) .30 details 2007-10-08 04:03:40 +00:00
f67f47f0cd 0.6.1.30 2007-10-08 03:09:35 +00:00
5ad6ee60eb 0.6.1.30 2007-10-08 03:01:47 +00:00
5accba6cdc 2007-10-07 Complication
* Fix an issue in EepGet whereby sending of "etag" and "lastModified" headers
      broke retrying.
2007-10-08 02:36:17 +00:00
zzz
313e1704df 2007-09-27 zzz
* Implement pushback of NTCP transport backlog to the outbound tunnel selection code
    * Clean up the NTCP and UDP tables on peers.jsp to be consistent,
      fix some of the sorting
2007-09-27 03:52:32 +00:00
zzz
cf4d2b17c9 2007-09-22 zzz
* Send messages for the same destination out the same outbound
      tunnel to reduce out-of-order delivery.
2007-09-23 02:44:34 +00:00
zzz
9145eedc35 2007-09-19 zzz
* i2psnark: Fix broken multifile torrent Delete;
        cleanup Storage resources in AddTorrent;
        don't autostart torrent after Create
2007-09-20 01:44:02 +00:00
zzz
b772179077 2007-09-18 zzz
* eepsite_index.html: Add links to trevorreznik address book
    * streaming lib: Fix SocketManagerFactory to honor options on outbound connections
    * streaming lib: Fix setDefaultOptions() when called with a ConnectionOptions parameter
    * i2psnark: Don't make outbound connections to already-connected peers
    * i2psnark: Debug logging cleanup
2007-09-18 19:09:19 +00:00
zzz
9054a196ce 2007-09-14 zzz
* eepget: Increase header timeout to 45s
    * HTTP proxy: Return a better error message for localhost requests
    * tunnels: Fix PooledTunnelCreatorConfig memory leak
2007-09-15 01:58:30 +00:00
zzz
d28a96ac7d 2007-09-09 zzz
* eepget: Add support for Last-Modified and If-Modified-Since
    * addressbook: Finish incomplete support for Last-Modified
2007-09-09 17:38:53 +00:00
zzz
9c73f80ac3 (zzz) Copy over SocketTimeout.java file from syndie for EepGet.java 2007-09-08 20:21:15 +00:00
20c46cff04 synced up with the eepget from the syndie source tree (allowing better
control of timeouts and transparent redirection).  the users of eepget
in this source tree don't necessarily use the timeout controls, though
they can be updated to do so
2007-09-08 02:24:01 +00:00
f332513755 added trevorreznik.i2p 2007-09-02 01:36:54 +00:00
zzz
cb69a66498 (zzz) .29 announcement 2007-08-24 02:34:40 +00:00
1c66543938 0.6.1.29 released 2007-08-24 00:33:28 +00:00
zzz
53ab3c472e 2007-08-13 zzz
* readme.html - Add inproxy.tino.i2p, replace search.i2p with eepsites.i2p,
      tweak the eepsite and troubleshooting sections
2007-08-13 19:42:59 +00:00
zzz
a4b221fa71 2007-08-11 zzz
* Add stats for individual tunnel rates (nice when graphed)
    * i2psnark: Fix outbound tunnel nickname
2007-08-11 20:48:14 +00:00
e3e1d0842d 2007-08-05 Complication
* Update the sharing calculator on config.jsp
      and explain the trade-off even more thoroughly.
2007-08-06 03:35:42 +00:00
99b5bf9609 2007-08-04 Complication
* Lower the threshold between the K and L bandwidth class,
      so that K is now < 12 KB/s, instead of <= 16 KB/s.
      Hopefully this lets people with 128 kbit/s (16 KB/s) upload lines
      participate in routing, if they keep the default share percentage.
2007-08-05 03:25:30 +00:00
zzz
da10fe0df7 (zzz) ask for bandwidth 2007-07-19 18:05:44 +00:00
zzz
9fd5ba7b2d 2007-07-16 zzz
* i2psnark: Add tooltip info for choked/uninterested
2007-07-16 21:15:51 +00:00
zzz
05b5df9d76 2007-07-16 zzz
* Make selection of graphed data configurable via configstats.jsp,
      remove most of the default graphs to save some memory
2007-07-16 20:47:57 +00:00
zzz
5c1dc79767 2007-07-15 zzz
* Add current values to graph legends
    * Fix up previous Rate fix to check for divide by zero
2007-07-15 18:34:33 +00:00
4acd2996c5 2007-07-14 Complication
* Take the post-download routerInfo size check back out of ReseedHandler,
      since it wasn't helpful, and a lower limit caused false warnings.
    * Give EepGet ability to enforce a min/max HTTP response size.
    * Enforce a maximum response size of 8 MB when ReseedHandler
      downloads into a ByteArrayOutputStream.
    * Refactor ReseedHandler/ReseedRunner from static to ordinary classes,
      change invocation from RouterConsoleRunner accordingly.
    * Add an EepGet status listener to ReseedHandler to log causes of reseed failure,
      provide status reports to indicate the progress of reseeding.
    * Enable icon for default eepsite, and the index page
      of the router console (more later).
2007-07-15 00:56:18 +00:00
zzz
16fa6a89bc 2007-07-14 zzz
* Clean up graphs.jsp - set K=1024 where appropriate,
      output image sizes in html, catch ooms, other minor tweaks
    * Fix current event count truncation which fixes graphs with low
      60-sec event counts displaying high values
      (bw.* and router.* graphs for example were 1.5x too high)
      Affects all "events per period" (non-lifetime) counts.
2007-07-14 18:44:11 +00:00
zzz
2a72e8574b 2007-07-09 zzz
* i2psnark: give a better error message for a non-i2p torrent
2007-07-10 01:20:37 +00:00
zzz
d4a1bcf28f 2007-07-07 zzz
* Add auto-detect IP/Port to NTCP. When enabled on config.jsp,
      SSU will notify/restart NTCP when the external address changes.
      Now you can enable inbound TCP without a static IP or dyndns service.
2007-07-07 20:03:50 +00:00
zzz
409b71def5 2007-07-04 zzz
* Display calculated share bandwidth and remove load testing
      on config.jsp
2007-07-04 22:58:48 +00:00
zzz
2dc5fbda02 2007-07-01 zzz
* Replace broken option i2np.udp.alwaysPreferred with
      i2np.udp.preferred and adjust UDP bids; possible settings are
      "false" (default), "true", and "always".
      Default setting results in same behavior as before
      (NTCP is preferred unless it isn't established and UDP is established).
      Use to compare NTCP and UDP transports.
2007-07-01 22:07:52 +00:00
71aaf03d09 2007-06-27 jrandom
* fix for a streaming lib bug that could leave a thread waiting
      indefinitely (thanks Complication!)
2007-06-28 01:51:16 +00:00
30c99e630b 2007-06-16 Complication
* First pass on EepGet and ReseedHandler improvements,
      please avoid use on routers which matter!
    * Give EepGet ability of downloading into an OutputStream,
      such as the ByteArrayOutputStream of ReseedHandler.
    * Detect failure to reseed better, report it persistently
      and more verbosely, provide a link to logs
      and suggest manual reseed.
2007-06-16 23:15:49 +00:00
445b39171a 2007-05-06 Complication
* spelling correction to history.txt
2007-05-06 20:02:04 +00:00
571c2d6047 2007-05-06 Complication
* Fix the build.xml file, so the preppkg build target won't try copying files
      which became deprecated with the old Syndie (thank for alerting, itsu!)
2007-05-06 19:52:39 +00:00
zzz
42ff763933 (zzz) 4-10 2007-04-11 06:01:35 +00:00
zzz
727edc3ff9 (zzz) add 204 log link 2007-04-08 04:18:18 +00:00
zzz
82a4758a0a (zzz) 3-27 and 4-3 2007-04-05 06:11:21 +00:00
zzz
915914ebb3 2007-03-31 zzz
* Add trevorreznik jump server to the http proxy error page
    * Add anonymity to the trackers supporting details links in i2psnark
2007-03-31 21:50:51 +00:00
zzz
c438b56378 (zzz) 3-20 2007-03-25 22:58:18 +00:00
zzz
307ccfb1b4 2007-03-24 zzz
* Remove Syndie from build targets and navbar
2007-03-24 07:57:37 +00:00
zzz
34e23259b4 2007-03-22 zzz
* i2psnark tracker handling tweaks:
    -   Add link to tracker details page (Postman only for now, requires bytemonsoon patch)
    -   Add Base URL to tracker list configuration
    -   Web page links built from tracker list Base URLs
    -   Only build and sort tracker list once
    -   Add anonymityWeb tracker to default list
    -   Add tooltip info for TrackerErrs
    -   Stop torrent if not registered with tracker
    -   Mark temp files as delete on exit
2007-03-22 05:21:25 +00:00
zzz
036802d66a 2007-03-18 zzz
* i2psnark: Cleanup some handling of saved partial pieces
    * i2psnark: Put bit counting in Bitfield.java for efficiency
    * i2psnark: Save torrent completion state in i2psnark.config
2007-03-18 21:57:01 +00:00
zzz
6a7dbc8e3a (zzz) i2psnark: Save torrent completion status in i2psnark.config 2007-03-18 21:43:41 +00:00
zzz
cf4a9ffc27 (zzz) i2psnark: Put bit counting in Bitfield.java for efficiency 2007-03-18 21:28:28 +00:00
zzz
6ef4adf318 (zzz) i2psnark: Remove saved partial when halted, don't save partial when halted 2007-03-18 21:25:18 +00:00
zzz
f84c9bf3b1 (zzz) .28 news 2007-03-18 02:17:16 +00:00
da0837bd58 ugh, jrandom sucks. 0.6.1.28 2007-03-18 01:47:59 +00:00
9094a62273 oops 2007-03-17 21:19:37 +00:00
026183a655 * 2007-03-17 0.6.1.2 released 2007-03-17 21:18:12 +00:00
zzz
b033c7945c (zzz) i2psnark: clarify that total uploader value is peers on UI. (thanks jadeSerpent) 2007-03-15 18:26:06 +00:00
zzz
0c2dcf0845 (zzz) 3-13 mtg 2007-03-15 08:21:35 +00:00
zzz
b6e597e5bf 2007-03-13 zzz
* i2psnark: Make max total uploaders configurable (thanks Amiga4000!)
2007-03-14 04:06:27 +00:00
ae402baa71 2007-03-12 jrandom
* dodge a race on startup (thanks zzz!)
2007-03-12 18:19:57 +00:00
zzz
d6c8a4d9eb 2007-03-10 zzz
* Streaming lib: Change initial RTT deviation from RTT to RTT/2
      to reduce early RTO values
2007-03-10 08:45:27 +00:00
zzz
8e2849b7e5 2007-03-08 zzz
* i2psnark changes to improve upload performance:
    *  Implement total uploader limit (10)
    *  Don't timeout non-piece messages out
    *  Change chunk size to 32K (was 64K)
    *  Change request limit to 64K (was 256K)
    * i2psnark: Disconnect from seeds when complete
2007-03-08 18:55:17 +00:00
zzz
0aa0cd330f (zzz) take dynamic router keys off the config page 2007-03-07 21:05:21 +00:00
zzz
0960cafaf5 2007-03-07 zzz
* Streaming lib changes to improve upstream performance during congestion:
    *   Change min window size from 12 to 1
    *   Change max timeout from 10 to 45 sec
    *   Change initial timeout from 10 to 15 sec
    *   Change intial window size for i2psnark from 12 to 1
    *   Change slow start growth rate for i2psnark from 1/2 to 1
2007-03-07 05:11:45 +00:00
zzz
2088a28053 (zzz) Mar. 04 - More tweaks to the default eepsite start page to help
people start up the tunnel which is now stopped by default
2007-03-04 23:25:18 +00:00
zzz
a5c4ba3bff 2007-03-03 zzz
* Upgrade from Jetty 5.1.6 to 5.1.12 which fixes spaces in URL
    * Add a updaterWithJetty build target
2007-03-03 20:30:52 +00:00
zzz
1bbd2cf52e 2007-03-03 zzz
* Implement priority sending for NTCP
    * Disable trimForOverload() in tunnel BuildExecutor which
      was preventing tunnel builds when outbound traffic was high
      (i.e. most of the time when running i2psnark)
2007-03-03 20:11:05 +00:00
zzz
ce50efa60c (zzz) 02-28 i2psnark file reopen cleanup 2007-03-01 02:29:11 +00:00
zzz
a3c64a9ba3 (zzz) 02-28 add peer details to i2psnark web page 2007-03-01 02:22:14 +00:00
zzz
1447164a8a (zzz) 2-20 2007-02-24 09:15:29 +00:00
zzz
bc0bf8d7ff (zzz) Fix Syndie URL typo 2007-02-22 08:51:14 +00:00
zzz
9f346f488e (zzz) add links 2007-02-17 02:07:45 +00:00
zzz
760d7d9704 (zzz) 0.6.1.27 2007-02-16 19:35:09 +00:00
77310e17d1 * 2007-02-15 0.6.1.27 released
2007-02-15  jrandom
    * Limit the whispering floodfill sends to at most 3 randomly
      chosen from the known floodfill peers
2007-02-15 23:25:04 +00:00
e54b964929 2007-02-14 jrandom
* Don't filter out KICK and H(ide oper status) IRC messages
      (thanks Takk and postman!)
2007-02-14 21:35:43 +00:00
809f3e847b 2007-02-13 jrandom
* Tell our peers about who we know in the floodfill netDb every
      6 hours or so, mitigating the situation where peers lose track
      of floodfill routers.
    * Disable the Syndie updater (people should use the new Syndie,
      not this one)
    * Disable the eepsite tunnel by default
2007-02-14 04:33:36 +00:00
zzz
f4beebe60d (zzz) 02-13 2007-02-14 03:04:11 +00:00
827e427f0b added trac.i2p 2007-02-12 10:26:21 +00:00
zzz
c02125511d (zzz) 02-06 2007-02-08 18:51:58 +00:00
zzz
1aa1069b6f (zzz) 01-30 2007-02-02 02:50:43 +00:00
zzz
91d281077d 2007-01-30 zzz
* i2psnark: Don't hold _snarks lock while checking a snark,
      so web page is responsive at startup
2007-01-30 08:58:19 +00:00
zzz
f339dec024 2007-01-29 zzz
* i2psnark: Add NickyB tracker
2007-01-30 04:05:21 +00:00
zzz
2aeef44f8d 2007-01-28 zzz
* i2psnark: Don't hold sendQueue lock while flushing output,
      to make everything run smoother
2007-01-29 04:03:36 +00:00
zzz
0fd41a9490 2007-01-27 zzz
* i2psnark: Fix orphaned Snark reader tasks leading to OOMs
2007-01-28 02:30:05 +00:00
58f10d14b2 2007-01-20 Complication
* Drop overlooked comment
2007-01-21 03:49:41 +00:00
46ca42ddf8 2007-01-20 Complication
* Modify ReseedHandler to query the "i2p.reseedURL" property from I2PAppContext
      instead of System, so setting a reseed URL in advanced configuration has effect.
    * Clean out obsolete reseed code from ConfigNetHandler.
2007-01-21 03:35:49 +00:00
zzz
e6e6d6f4ee 2007-01-20 zzz
* Improve performance by not reading in the whole
      piece from disk for each request. A huge memory savings
      on 1MB torrents with many peers.
2007-01-21 01:43:31 +00:00
zzz
8a87df605b 2007-01-20 zzz
* i2psnark: More choking rotation tweaks
    * Improve performance by not reading in the whole
      piece from disk for each request. A huge memory savings
      on 1MB torrents with many peers.
2007-01-21 00:35:09 +00:00
zzz
8ca085bceb (zzz) 1/16 2007-01-19 03:04:49 +00:00
zzz
df47587db0 * Add new HTTP Proxy error message for non-http protocols 2007-01-18 01:42:13 +00:00
zzz
d705e0ad04 2007-01-17 zzz
* Add note on Syndie index.html steering people to new Syndie
2007-01-17 05:13:27 +00:00
zzz
40d209dd7c 2007-01-17 zzz
* i2psnark: Fix crash when autostart off and
      tcrrent started manually
2007-01-16 06:20:23 +00:00
zzz
7f2a0457bf 2007-01-16 zzz
* i2psnark: Fix bug caused by last i2psnark checkin
      (ConnectionAcceptor not started)
    * Don't start PeerCoordinator, ConnectionAcceptor,
      and TrackerClient unless starting torrent
2007-01-16 04:47:58 +00:00
f4749f2483 2007-01-15 jrandom
* small guard against unnecessary streaming lib reset packets
      (thanks Complication!)
2007-01-15 06:35:59 +00:00
zzz
9c42830076 2007-01-15 zzz
* i2psnark: Add 'Stop All' link on web page
    * Add some links to trackers and forum on web page
    * Don't start tunnel if 'Autostart' unchecked
    * Fix torrent restart bug by reopening file descriptors
2007-01-15 04:36:03 +00:00
zzz
53ba6c2a64 2007-01-14 zzz
* i2psnark: Improvements for torrents with > 4 leechers:
      choke based on upload rate when seeding, and
      be smarter and fairer about rotating choked peers.
    * Handle two common i2psnark OOM situations rather
      than shutting down the whole thing.
    * Fix reporting to tracker of remaining bytes for
      torrents > 4GB (but ByteMonsoon still has a bug)
2007-01-14 19:49:33 +00:00
zzz
61b3f21f69 (zzz) 01-09 2007-01-13 01:15:04 +00:00
zzz
506fd5f889 (zzz) Jan. 2 2007-01-03 03:19:06 +00:00
zzz
d538f888b4 (zzz) 12-19,12-26 2006-12-28 09:11:30 +00:00
976c5fdd47 new syndie httpserv 2006-12-16 22:31:07 +00:00
zzz
b63f3437f2 (zzz) 12-12 mtg 2006-12-13 06:40:15 +00:00
zzz
e760f2e538 (zzz) 12/5 mtg 2006-12-06 02:52:13 +00:00
zzz
17c8fca779 (zzz) 11-28 mtg 2006-11-29 01:37:18 +00:00
zzz
87fda382c3 (zzz) 11/14 and 11/21 mtgs 2006-11-22 02:31:23 +00:00
zzz
1e404cd7ac 2006-10-29 zzz
* i2psnark: Fix and enable generation of multifile torrents,
      print error if no tracker selected at create-torrent,
      fix stopping a torrent that hasn't started successfully,
      add eBook and GayTorrents trackers to form,
      web page formatting tweaks
2006-11-10 01:44:35 +00:00
zzz
098f99d806 (zzz) 11/7 mtg 2006-11-10 01:34:54 +00:00
zzz
da93f96035 (zzz) 10/31 mtg 2006-11-02 02:39:07 +00:00
ead39cc87e 2006-10-29 Complication
* Ensure we get NTP samples from more diverse sources
      (0.pool.ntp.org, 1.pool.ntp.org, etc)
    * Discard median-based peer skew calculator as framed average works,
      and adjusting its percentage can make it behave median-like
    * Require more data points (from at least 20 peers)
      before considering a peer skew measurement reliable
2006-10-29 19:29:50 +00:00
zzz
e4e3c44459 (zzz) 10/24 2006-10-25 08:22:09 +00:00
zzz
af151e32e5 (zzz) 10/17 mtg 2006-10-21 08:22:24 +00:00
12819a2a17 added mtn.i2p 2006-10-15 02:10:36 +00:00
87eedff254 0.6.1.26 2006-10-09 05:10:22 +00:00
4c59cd7621 * 2006-10-10 0.6.1.26 released
2006-10-10  jrandom
    * Removed the status display from the console, as its more confusing
      than informative (though the content is still displayed in the HTML)
2006-10-09 01:44:47 +00:00
ef707e7956 2006-10-08 Complication
* Update comment to reflect current status
2006-10-08 23:10:58 +00:00
73cf3fb299 2006-10-08 Complication
* Add a framed average peer clock skew calculator
    * Add config property "router.clockOffsetSanityCheck" to determine
      if NTP-suggested clock offsets get sanity checked (default "true")
    * Reject NTP-suggested clock offsets if they'd increase peer clock skew
      by more than 5 seconds, or make it more than 20 seconds total
    * Decrease log level in getMedianPeerClockSkew()
2006-10-08 22:52:59 +00:00
zzz
80b0c97d72 (zzz) 10/3 status notes 2006-10-04 19:41:09 +00:00
zzz
5cf85c1d7b (zzz)
* i2psnark: Second try at synchronization fix - synch addRequest()
      completely rather than just portions of it and requestNextPiece()
2006-09-29 23:54:17 +00:00
c14e52ceb5 2006-09-27 jrandom
* added HMAC-SHA256
    * properly use CRLF with EepPost
    * suppress jbigi/jcpuid messages if jbigi.dontLog/jcpuid.dontLog is set
    * PBE session key generation (with 1000 rounds of SHA256)
    * misc SDK helper functions
2006-09-27 06:00:33 +00:00
32a579e480 2006-09-26 Complication
* Take back another inadverent logging change in NTCPConnection
2006-09-27 04:44:13 +00:00
0a240a4436 2006-09-26 Complication
* Take back an accidental log level change
2006-09-27 04:31:34 +00:00
9325b806e4 2006-09-26 Complication
* Subclass from Clock a RouterClock which can access router transports,
      with the goal of developing it to second-guess NTP results
    * Make transports report clock skew in seconds
    * Adjust renderStatusHTML() methods accordingly
    * Show average for NTCP clock skews too
    * Give transports a getClockSkews() method to report clock skews
    * Give transport manager a getClockSkews() method to aggregate results
    * Give comm system facade a getMedianPeerClockSkew() method which RouterClock calls
      (to observe results, add "net.i2p.router.transport.CommSystemFacadeImpl=WARN" to
logging)
    * Extra explicitness in NTCP classes to denote unit of time.
    * Fix some places in NTCPConnection where milliseconds and seconds were confused
2006-09-27 04:02:13 +00:00
zzz
ef2e24ea11 (zzz)
* i2psnark: Paranoid copy before writing pieces,
      recheck files on completion, redownload bad pieces
    * i2psnark: Don't contact tracker as often when seeding
2006-09-26 03:11:39 +00:00
zzz
373934c6e0 (zzz)
* i2psnark: Add some synchronization to prevent rare problem
      after restoring orphan piece
2006-09-24 18:30:22 +00:00
zzz
e8e8bac694 (zzz)
* i2psnark: Eliminate duplicate requests caused by i2p-bt's
      rapid choke/unchokes
    * i2psnark: Truncate long TrackerErr messages on web page
2006-09-20 22:39:24 +00:00
zzz
23e8a558c2 (zzz)
* i2psnark: Implement retransmission of requests. This
      eliminates one cause of complete stalls with a peer.
      This problem is common on torrents with a small number of
      active peers where there are no choke/unchokes to kickstart things.
2006-09-16 21:07:28 +00:00
46f2645834 2006-09-14 Complication
* news.xml update
2006-09-14 03:16:53 +00:00
zzz
2329439034 (zzz)
* i2psnark: Fix restoral of partial pieces broken by last patch
2006-09-14 02:37:32 +00:00
zzz
6d400368b9 (zzz) changelog date fix 2006-09-13 23:24:14 +00:00
zzz
26c13b40fe (zzz)
* i2psnark: Mark a peer's requests as unrequested on disconnect,
      preventing premature end game
    * i2psnark: Randomize selection of next piece during end game
    * i2psnark: Don't restore a partial piece to a peer that is already working on it
    * i2psnark: strip ".torrent" on web page
    * i2psnark: Limit piece size in generated torrent to 1MB max
2006-09-13 23:02:07 +00:00
zzz
9fd0e95fe8 (zzz) 9/12 status 2006-09-13 17:34:58 +00:00
zzz
7e21f2c92b (zzz)
* i2psnark: Add "Stalled" indication and stat totals on web page
2006-09-10 01:55:37 +00:00
zzz
c9d8e796c6 (zzz)
* i2psnark: Fix bug where new peers would always be set to "interested"
      regardless of actual interest
    * i2psnark: Reduce max piece size from 10MB to 1MB; larger may have severe
      memory and efficiency problems
2006-09-09 22:15:05 +00:00
zzz
e7203f5d46 (zzz) 0.6.1.25 2006-09-09 21:19:49 +00:00
22d76a1b64 * 2006-09-09 0.6.1.25 released 2006-09-09 17:46:21 +00:00
0903dc46c6 2006-09-08 jrandom
* Tweak the PRNG logging so it only displays error messages if there are
      problems
    * Disable dynamic router keys for the time being, as they don't offer
      meaningful security, may hurt the router, and makes it harder to
      determine the network health.  The code to restart on SSU IP change is
      still enabled however.
    * Disable tunnel load testing, leaning back on the tiered selection for
      the time being.
    * Spattering of bugfixes
2006-09-09 01:41:57 +00:00
zzz
0f56ec8078 (zzz) oops remove duplicate 2006-09-07 23:26:53 +00:00
zzz
70ee1df2bf (zzz)
* i2psnark: Increase output timeout from 2 min to 4 min
    * i2psnark: Orphan debug msg cleanup
    * i2psnark: More web rate report cleanup
2006-09-07 23:22:12 +00:00
zzz
61a6a29bec cCVS: ---------------------------------------------------------------------- 2006-09-07 23:03:18 +00:00
zzz
678f7d8f72 (zzz)
* i2psnark: Implement basic partial-piece saves across connections
    * i2psnark: Implement keep-alive sending. This will keep non-i2psnark clients
      from dropping us for inactivity but also renders the 2-minute transmit-inactivity
      code in i2psnark ineffective. Will have to research why there is transmit but
      not receive inactivity code. With the current connection limit of 24 peers
      we aren't in any danger of keeping out new peers by keeping inactive ones.
    * i2psnark: Increase CHECK_PERIOD from 20 to 40 since nothing happens in 20 seconds
    * i2psnark: Fix dropped chunk handling
    * i2psnark: Web rate report cleanup
2006-09-06 06:32:53 +00:00
zzz
b92ee364bc (zzz)
* i2psnark: Report cleared trackerErr immediately
    * i2psnark: Add trackerErr reporting after previous success; retry more quickly
    * i2psnark: Set up new connections more quickly
    * i2psnark: Don't delay tracker fetch when setting up lots of connections
    * i2psnark: Reduce MAX_UPLOADERS from 12 to 4
2006-09-04 08:26:21 +00:00
zzz
aef19fcd38 (zzz) i2psnark: enable pipelining, set tunnel length default to 1 + 0-1 2006-09-04 06:01:53 +00:00
zzz
3b01df1d2c (zzz) Add rate reporting to i2psnark 2006-09-03 09:12:22 +00:00
4aed23b198 2006-09-03 Complication
* Limit form size in SusiDNS to avoid exceeding a POST size limit on postback
    * Print messages about addressbook size to give better overview
    * Enable delete function in published addressbook
2006-09-03 06:37:46 +00:00
03e8875c27 2006-08-21 Complication
* Fix error reporting discrepancy (thanks for helping notice, yojoe!)
2006-08-21 05:55:33 +00:00
48921a0875 2006-08-03 Complication
* news.xml update
2006-08-04 00:49:40 +00:00
633fabb09e 2006-08-03 jrandom
* Decrease the recently modified tunnel building timeout, though keep
      the scaling on their processing
2006-08-03 22:34:24 +00:00
bc42c26d94 2006-07-31 jrandom
* Increase the tunnel building timeout
    * Avoid a rare race (thanks bar!)
    * Fix the bandwidth capacity publishing code to factor in share percentage
      and outbound throttling (oops)
2006-08-01 02:26:52 +00:00
3c09ca3359 2006-07-29 Complication
* Treat NTP responses from unexpected stratums like failures
2006-07-30 05:08:20 +00:00
zzz
1e9e7dd345 (zzz) 0.6.1.24 2006-07-29 23:02:57 +00:00
034803add7 * 2006-07-28 0.6.1.24 released 2006-07-29 18:03:14 +00:00
b25bb053bb 2006-07-28 jrandom
* Don't try to reverify too many netDb entries at once (thanks
      cervantes and Complication!)
2006-07-29 04:41:15 +00:00
9bd0c79441 2006-07-28 jrandom
* Actually fix the threading deadlock issue in the netDb (removing
      the synchronized access to individual kbuckets while validating
      individual entries) (thanks cervantes, postman, frosk, et al!)
2006-07-29 01:11:50 +00:00
06b8670410 * 2006-07-27 0.6.1.23 released 2006-07-28 03:34:59 +00:00
6577ae499f 2006-07-27 jrandom
* Cut down NTCP connection establishments once we know the peer is skewed
      (rather than wait for full establishment before verifying)
    * Removed a lock on the stats framework when accessing rates, which
      shouldn't be a problem, assuming rates are created (pretty much) all at
      once and merely updated during the lifetime of the jvm.
2006-07-27 23:40:00 +00:00
54bc5485ec oops, thanks bar! 2006-07-27 07:06:55 +00:00
84b741ac98 2006-07-27 jrandom
* Further NTCP write status cleanup
    * Handle more oddly-timed NTCP disconnections (thanks bar!)
2006-07-27 06:20:25 +00:00
c48c419d74 quick prng workaround 2006-07-27 01:34:31 +00:00
fb2e795add 2006-07-26 jrandom
* When dropping a netDb router reference, only accept newer
      references as part of the update check
    * If we have been up for a while, don't accept really old
      router references (published 2 or more days ago)
    * Drop router references once they are no longer valid, even if
      they were allowed in due to the lax restrictions on startup
2006-07-27 01:04:59 +00:00
ec215777ec 2006-07-26 jrandom
* When dropping a netDb router reference, only accept newer
      references as part of the update check
    * If we have been up for a while, don't accept really old
      router references (published 2 or more days ago)
    * Drop router references once they are no longer valid, even if
      they were allowed in due to the lax restrictions on startup
2006-07-27 00:56:49 +00:00
d4e0f27c56 2006-07-26 jrandom
* Every time we create a new router identity, add an entry to the
      new "identlog.txt" text file in the I2P install directory.  For
      debugging purposes, publish the count of how many identities the
      router has cycled through, though not the identities itself.
    * Cleaned up the way the multitransport shitlisting worked, and
      added per-transport shitlists
    * When dropping a router reference locally, first fire a netDb
      lookup for the entry
    * Take the peer selection filters into account when organizing the
      profiles (thanks Complication!)
    * Avoid some obvious configuration errors for the NTCP transport
      (invalid ports, "null" ip, etc)
    * Deal with some small NTCP bugs found in the wild (unresolveable
      hosts, strange network discons, etc)
    * Send our netDb info to peers we have direct NTCP connections to
      after each 6-12 hours of connection uptime
    * Clean up the NTCP reading and writing queue logic to avoid some
      potential delays
    * Allow people to specify the IP that the SSU transport binds on
      locally, via the advanced config "i2np.udp.bindInterface=1.2.3.4"
2006-07-26 06:36:18 +00:00
e1c686baa6 2006-07-18 Complication
* URL and date fix in news.xml
2006-07-18 23:35:54 +00:00
d57af1aef4 remove 1.5ism 2006-07-18 20:20:08 +00:00
a52dd57215 * 2006-07-18 0.6.1.22 released
2006-07-18  jrandom
    * Add a failsafe to the NTCP transport to make sure we keep
      pumping writes when we should.
    * Properly reallow 16-32KBps routers in the default config
      (thanks Complication!)
2006-07-18 20:08:00 +00:00
65138357d3 2006-07-16 Complication
* Collect tunnel build agree/reject/expire statistics
      for each bandwidth tier of peers (and peers of unknown tiers,
      even if those shouldn't exist)
2006-07-16 17:20:46 +00:00
f6320696dd 2006-07-14 jrandom
* Improve the multitransport shitlisting (thanks Complication!)
    * Allow routers with a capacity of 16-32KBps to be used in tunnels under
      the default configuration (thanks for the stats Complication!)
    * Properly allow older router references to load on startup
      (thanks bar, Complication, et al!)
    * Add a new "i2p.alwaysAllowReseed" advanced config property, though
      hopefully today's changes should make this unnecessary (thanks void!)
    * Improved NTCP buffering
    * Close NTCP connections if we are too backlogged when writing to them
2006-07-14 18:08:44 +00:00
900d8a2026 oops, test method. thanks cervantes 2006-07-07 19:04:24 +00:00
ccc9a87e8c unnecessary 2006-07-07 18:59:16 +00:00
208634e5de 2006-07-04 jrandom
* New NIO-based tcp transport (NTCP), enabled by default for outbound
      connections only.  Those who configure their NAT/firewall to allow
      inbound connections and specify the external host and port
      (dyndns/etc is ok) on /config.jsp can receive inbound connections.
      SSU is still enabled for use by default for all users as a fallback.
    * Substantial bugfix to the tunnel gateway processing to transfer
      messages sequentially instead of interleaved
    * Renamed GNU/crypto classes to avoid name clashes with kaffe and other
      GNU/Classpath based JVMs
    * Adjust the Fortuna PRNG's pooling system to reduce contention on
      refill with a background thread to refill the output buffer
    * Add per-transport support for the shitlist
    * Add a new async pumped tunnel gateway to reduce tunnel dispatcher
      contention
2006-07-04 21:17:44 +00:00
3d07205c9d 2006-07-01 Complication
* Ensure that the I2PTunnel web interface won't update tunnel settings
      for shared clients when a non-shared client is modified
      (thanks for spotting, BarkerJr!)
2006-07-01 22:44:34 +00:00
zzz
f0a424a93f (zzz) .21, 6-13 mtg 2006-06-15 22:15:09 +00:00
f9b59ee07d 2006-06-14 cervantes
* Small tweak to I2PTunnel CSS, so it looks better with desktops
      that use Bitstream Vera fonts @ 96 dpi
2006-06-14 05:24:34 +00:00
b92b9d2618 * 2006-06-14 0.6.1.21 released 2006-06-14 02:17:40 +00:00
a3db9429a7 2006-06-13 jrandom
* Use a minimum uptime of 2 hours, not 4 (oops)
2006-06-13 23:29:51 +00:00
291a5c9578 2006-06-13 jrandom
* Cut down the proactive rejections due to queue size - if we are
      at the point of having decrypted the request off the queue, might
      as well let it through, rather than waste that decryption
2006-06-13 09:38:48 +00:00
0a3281c279 2006-06-11 Kloug
* Bugfix to the I2PTunnel IRC filter to support multiple concurrent
      outstanding pings/pongs
2006-06-11 19:48:37 +00:00
23f30ba576 2006-06-10 jrandom
* Further reduction in proactive rejections
2006-06-10 20:14:57 +00:00
f3de85c4de 2006-06-09 jrandom
* Don't let the pending tunnel request queue grow beyond reason
      (letting things sit for up to 30s when they fail after 10s
      seems a bit... off)
2006-06-10 00:34:44 +00:00
a3a4888e0b 2006-06-08 jrandom
* Be more conservative in the proactive rejections
2006-06-09 01:02:40 +00:00
6fd7881f8e thanks bar 2006-06-05 06:41:11 +00:00
381f716769 2006-06-04 Complication
* Stop sending a blank line before USER in susimail.
      Seemed to break in rare cases, thanks for reporting, Brachtus!
2006-06-05 01:33:03 +00:00
f2078e1523 * 2006-06-04 0.6.1.20 released
2006-06-04  jrandom
    * Reduce the SSU ack frequency
    * Tweaked the tunnel rejection settings to reject less aggressively
2006-06-04 22:25:08 +00:00
f2fb87c88b 2006-05-31 jrandom
* Only send netDb searches to the floodfill peers for the time being
    * Add some proof of concept filters for tunnel participation.  By default,
      it will skip peers with an advertised bandwith of less than 32KBps or
      an advertised uptime of less than 2 hours.  If this is sufficient, a
      safer implementation of these filters will be implemented.
2006-05-31 23:23:37 +00:00
fcbea19478 2006-05-30 Complication
* weekly news.xml update
2006-05-31 03:19:24 +00:00
92f25bd4fa 2006-05-18 Complication
* news.xml update
2006-05-19 00:17:10 +00:00
85c2c11217 * 2006-05-18 0.6.1.19 released
2006-05-18  jrandom
    * Made the SSU ACKs less frequent when possible
2006-05-18 22:31:06 +00:00
de1ca4aea4 2006-05-17 Complication
* Fix some oversights in my previous changes:
      adjust some loglevels, make a few statements less wasteful,
      make one comparison less confusing and more likely to log unexpected values
2006-05-18 03:42:55 +00:00
a0f865fb99 2006-05-17 jrandom
* Make the peer page sortable
    * SSU modifications to cut down on unnecessary connection failures
2006-05-18 03:00:48 +00:00
2c3fea5605 2006-05-16 jrandom
* Further shitlist randomizations
    * Adjust the stats monitored for detecting cpu overload when dropping new
      tunnel requests
2006-05-16 18:34:08 +00:00
ba1d88b5c9 2006-05-15 jrandom
* Add a load dependent throttle on the pending inbound tunnel request
      backlog
    * Increased the tunnel test failure slack before killing a tunnel
2006-05-15 17:33:02 +00:00
2ad715c668 2006-05-13 Complication
* Update the build number too
2006-05-14 05:11:36 +00:00
5f17557e54 2006-05-13 Complication
* Separate growth factors for tunnel count and tunnel test time
    * Reduce growth factors, so probabalistic throttle would activate
    * Square probAccept values to decelerate stronger when far from average
    * Create a bandwidth stat with approximately 15-second half life
    * Make allowTunnel() check the 1-second bandwidth for overload
      before doing allowance calculations using 15-second bandwidth
    * Tweak the overload detector in BuildExecutor to be more sensitive
      for rising edges, add ability to initiate tunnel drops
    * Add a function to seek and drop the highest-rate participating tunnel,
      keeping a fixed+random grace period between such drops.
      It doesn't seem very effective, so disabled by default
      ("router.dropTunnelsOnOverload=true" to enable)
2006-05-14 04:52:44 +00:00
2ad5a6f907 2006-05-11 jrandom
* PRNG bugfix (thanks cervantes and Complication!)
2006-05-12 03:31:44 +00:00
0920462060 2006-05-09 Complication
* weekly news.xml update
2006-05-10 02:38:42 +00:00
870e94e184 * 2006-05-09 0.6.1.18 released
2006-05-09  jrandom
    * Further tunnel creation timeout revamp
2006-05-09 21:17:17 +00:00
6b0d507644 2006-05-07 Complication
* Fix problem whereby repeated calls to allowed() would make
      the 1-tunnel exception permit more than one concurrent build
2006-05-08 03:19:46 +00:00
70cf9e4ca7 2006-05-06 jrandom
* Readjust the tunnel creation timeouts to reject less but fail earlier,
      while tracking the extended timeout events.
2006-05-06 20:27:34 +00:00
2a3974c71d 2006-05-04 jrandom
* Short circuit a highly congested part of the stat logging unless its
      required (may or may not help with a synchronization issue reported by
      andreas)
2006-05-04 23:08:48 +00:00
46ac9292e8 2006-05-03 Complication
* Allow a single build attempt to proceed despite 1-minute overload
      only if the 1-second rate shows enough spare bandwidth
      (e.g. overload has already eased)
2006-05-03 11:13:26 +00:00
4307097472 2006-05-02 Complication
* Correct a misnamed property in SummaryHelper.java
      to avoid confusion
    * Make the maximum allowance of our own concurrent
      tunnel builds slightly adaptive: one concurrent build per 6 KB/s
      within the fixed range 2..10
    * While overloaded, try to avoid completely choking our own build attempts,
      instead prefer limiting them to 1
2006-05-03 04:30:26 +00:00
ed3fdaf4f1 2006-05-02 Complication
* Fixed URL in previous update, sorry
2006-05-03 02:11:06 +00:00
378a9a8f5c 2006-05-02 Complication
* Weekly news.xml update
2006-05-03 02:03:01 +00:00
4ef6180455 2006-05-01 jrandom
* Adjust the tunnel build timeouts to cut down on expirations, and
      increased the SSU connection establishment retransmission rate to
      something less glacial.
    * For the first 5 minutes of uptime, be less aggressive with tunnel
      exploration, opting for more reliable peers to start with.
2006-05-01 22:40:21 +00:00
d4970e23c0 2006-05-01 jrandom
* Fix for a netDb lookup race (thanks cervantes!)
2006-05-01 19:09:02 +00:00
0c9f165016 fix typos 2006-05-01 15:39:37 +00:00
be3a899ecb 2006-04-27 jrandom
* Avoid a race in the message reply registry (thanks cervantes!)
2006-04-28 00:31:20 +00:00
7a6a749004 2006-04-27 jrandom
* Fixed the tunnel expiration desync code (thanks Complication!)
2006-04-28 00:08:40 +00:00
17271ee3f0 2006-04-25 Complication
* weekly news.xml update
2006-04-26 02:30:05 +00:00
99bcfa90df 2006-04-24 Complication
* Update news.xml to reflect 0.6.1.17
2006-04-24 12:43:25 +00:00
eb36e993c1 * 2006-04-23 0.6.1.17 released 2006-04-23 21:06:12 +00:00
zzz
e5eca5fa45 zzz update 2006-04-22 20:37:21 +00:00
8cba2f4236 2006-04-19 jrandom
* Adjust how we pick high capacity peers to allow the inclusion of fast
      peers (the previous filter assumed an old usage pattern)
    * New set of stats to help track per-packet-type bandwidth usage better
    * Cut out the proactive tail drop from the SSU transport, for now
    * Reduce the frequency of tunnel build attempts while we're saturated
    * Don't drop tunnel requests as easily - prefer to explicitly reject them
2006-04-19 17:46:51 +00:00
40d5ed31ac 2006-04-15 Complication
* Update news.xml to reflect 0.6.1.16
2006-04-15 17:25:50 +00:00
181275fe35 * 2006-04-15 0.6.1.16 released 2006-04-15 07:58:12 +00:00
23d8c01ce7 2006-04-15 jrandom
* Adjust the proactive tunnel request dropping so we will reject what we
      can instead of dropping so much (but still dropping if we get too far
      overloaded)
2006-04-15 07:15:19 +00:00
de83944486 2006-04-14 jrandom
* 0 isn't very random
    * Adjust the tunnel drop to be more reasonable
2006-04-14 20:24:07 +00:00
90cd7ff23a 2006-04-14 jrandom
* -28.00230115311259 is not between 0 and 1 in any universe I know.
    * Made the bw-related tunnel join throttle much simpler
2006-04-14 18:04:11 +00:00
8d0a9b4ccd 2006-04-14 jrandom
* Make some more stats graphable, and allow some internal tweaking on the
      tunnel pairing for creation and testing.
2006-04-14 11:40:35 +00:00
230d4cd23f * 2006-04-13 0.6.1.15 released 2006-04-13 12:40:21 +00:00
e9b6fcc0a4 2006-04-12 jrandom
* Added a further failsafe against trying to queue up too many messages to
      a peer.
2006-04-13 04:22:06 +00:00
8fcb871409 2006-04-12 jrandom
* Watch out for failed syndie index fetches (thanks bar!)
2006-04-12 06:49:01 +00:00
83bef43fd5 2006-04-11 jrandom
* Throttling improvements on SSU - throttle all transmissions to a peer
      when we are retransmitting, not just retransmissions.  Also, if
      we're already retransmitting to a peer, probabalistically tail drop new
      messages targetting that peer, based on the estimated wait time before
      transmission.
    * Fixed the rounding error in the inbound tunnel drop probability.
2006-04-11 13:39:06 +00:00
b4fc6ca31b 2006-04-10 jrandom
* Include a combined send/receive graph (good idea cervantes!)
    * Proactively drop inbound tunnel requests probabalistically as the
      estimated queue time approaches our limit, rather than letting them all
      through up to that limit.
2006-04-10 05:37:28 +00:00
ab3f1b708d 2006-04-08 jrandom
* Stat summarization fix (removing the occational holes in the jrobin
      graphs)
2006-04-09 01:14:08 +00:00
c76402a160 2006-04-08 jrandom
* Process inbound tunnel requests more efficiently
    * Proactively drop inbound tunnel requests if the queue before we'd
      process it in is too long (dynamically adjusted by cpu load)
    * Adjust the tunnel rejection throttle to reject requeusts when we have to
      proactively drop too many requests.
    * Display the number of pending inbound tunnel join requests on the router
      console (as the "handle backlog")
    * Include a few more stats in the default set of graphs
2006-04-08 06:15:43 +00:00
a50c73aa5e 2006-04-06 jrandom
* Fix for a bug in the new irc ping/pong filter (thanks Complication!)
2006-04-07 01:26:32 +00:00
5aa66795d2 2006-04-06 jrandom
* Fixed a typo in the reply cleanup code
2006-04-06 10:33:44 +00:00
ac3c2d2b15 * 2006-04-05 0.6.1.14 released 2006-04-05 17:08:04 +00:00
072a45e5ce 2006-04-05 jrandom
* Cut down on the time that we allow a tunnel creation request to sit by
      without response, and reject tunnel creation requests that are lagged
      locally.  Also switch to a bounded FIFO instead of a LIFO
    * Threading tweaks for the message handling (thanks bar!)
    * Don't add addresses to syndie with blank names (thanks Complication!)
    * Further ban clearance
2006-04-05 04:40:00 +00:00
1ab14e52d2 2006-04-04 Complication
* weekly news.xml update
2006-04-05 03:06:00 +00:00
9a820961a2 2006-04-05 jrandom
* Fix during the ssu handshake to avoid an unnecessary failure on
      packet retransmission (thanks ripple!)
    * Fix during the SSU handshake to use the negotiated session key asap,
      rather than using the intro key for more than we should (thanks ripple!)
    * Fixes to the message reply registry (thanks Complication!)
    * More comprehensive syndie banning (for repeated pushes)
    * Publish the router's ballpark bandwidth limit (w/in a power of 2), for
      testing purposes
    * Put a floor back on the capacity threshold, so too many failing peers
      won't cause us to pick very bad peers (unless we have very few good
      ones)
    * Bugfix to cut down on peers using introducers unneessarily (thanks
      Complication!)
    * Reduced the default streaming lib message size to fit into a single
      tunnel message, rather than require 5 tunnel messages to be transferred
      without loss before recomposition.  This reduces throughput, but should
      increase reliability, at least for the time being.
    * Misc small bugfixes in the router (thanks all!)
    * More tweaking for Syndie's CSS (thanks Doubtful Salmon!)
2006-04-04 12:20:32 +00:00
764149aef3 2006-04-01 jrandom
* Take out the router watchdog's teeth (don't restart on leaseset failure)
    * Filter the IRC ping/pong messages, as some clients send unsafe
      information in them (thanks aardvax and dust!)
2006-04-03 10:07:22 +00:00
1b3ad31bff 2006-04-01 jrandom
* Take out the router watchdog's teeth (don't restart on leaseset failure)
2006-04-01 19:05:35 +00:00
15e6c27c04 2006-03-30 jrandom
* Substantially reduced the lock contention in the message registry (a
      major hotspot that can choke most threads).  Also reworked the locking
      so we don't need per-message timer events
    * No need to have additional per-peer message clearing, as they are
      either unregistered individually or expired.
    * Include some of the more transient tunnel throttling
2006-03-30 07:26:43 +00:00
8b707e569f 2006-03-28 Complication
* weekly news.xml update
2006-03-29 02:09:23 +00:00
e4c4b24c61 2006-03-26 Complication
* announce 0.6.1.3
2006-03-27 03:24:38 +00:00
031636e607 * 2006-03-26 0.6.1.13 released 2006-03-26 23:23:49 +00:00
b5c0d77c69 2006-03-25 jrandom
* Added a simple purge and ban of syndie authors, shown as the
      "Purge and ban" button on the addressbook for authors that are already
      on the ignore list.  All of their entries and metadata are deleted from
      the archive, and the are transparently filtered from any remote
      syndication (so no user on the syndie instance will pull any new posts
      from them)
    * More strict tunnel join throtting when congested
2006-03-25 23:50:48 +00:00
d489caa88c 2006-03-24 jrandom
* Try to desync tunnel building near startup (thanks Complication!)
    * If we are highly congested, fall back on only querying the floodfill
      netDb peers, and only storing to those peers too
    * Cleaned up the floodfill-only queries
2006-03-24 20:53:28 +00:00
2a24029acf 2006-03-21 Complication
* Weekly news.xml update
2006-03-22 02:15:13 +00:00
c5aab8c750 2006-03-21 jrandom
* Avoid a very strange (unconfirmed) bug that people using the systray's
      browser picker dialog could cause by disabling the GUI-based browser
      picker.
    * Cut down on subsequent streaming lib reset packets transmitted
    * Use a larger MTU more often
    * Allow netDb searches to query shitlisted peers, as the queries are
      indirect.
    * Add an option to disable non-floodfill netDb searches (non-floodfill
      searches are used by default, but can be disabled by adding
      netDb.floodfillOnly=true to the advanced config)
2006-03-21 23:11:32 +00:00
343748111a 2006-03-20 jrandom
* Fix to allow for some slack when coalescing stats
    * Workaround some oddball errors
2006-03-20 05:39:54 +00:00
c5ddfabfe9 2006-03-20 jrandom
* Fix to allow for some slack when coalescing stats
    * Workaround some oddball errors
2006-03-20 05:31:09 +00:00
1ef33906ed 2006-03-18 jrandom
* Added a new graphs.jsp page to show all of the stats being harvested
2006-03-19 00:23:23 +00:00
f3849a22ad 2006-03-18 jrandom
* Made the netDb search load limitations a little less stringent
    * Add support for specifying the number of periods to be plotted on the
      graphs - e.g. to plot only the last hour of a stat that is averaged at
      the 60 second period, add &periodCount=60
2006-03-18 23:09:35 +00:00
b03ff21d3b 2006-03-17 jrandom
* Add support for graphing the event count as well as the average stat
      value (done by adding &showEvents=true to the URL).  Also supports
      hiding the legend (&hideLegend=true), the grid (&hideGrid=true), and
      the title (&hideTitle=true).
    * Removed an unnecessary arbitrary filter on the profile organizer so we
      can pick high capacity and fast peers more appropriately
2006-03-17 23:46:00 +00:00
52094b10c9 aych tee emm ell smells 2006-03-16 22:37:57 +00:00
fc927efaa3 2006-03-16 jrandom
* Integrate basic hooks for jrobin (http://jrobin.org) into the router
      console.  Selected stats can be harvested automatically and fed into
      in-memory RRD databases, and those databases can be served up either as
      PNG images or as RRDtool compatible XML dumps (see oldstats.jsp for
      details).  A base set of stats are harvested by default, but an
      alternate list can be specified by setting the 'stat.summaries' list on
      the advanced config.  For instance:
      stat.summaries=bw.recvRate.60000,bw.sendRate.60000
    * HTML tweaking for the general config page (thanks void!)
    * Odd NPE fix (thanks Complication!)
2006-03-16 21:52:09 +00:00
65dc803fb7 2006-03-16 jrandom
* Integrate basic hooks for jrobin (http://jrobin.org) into the router
      console.  Selected stats can be harvested automatically and fed into
      in-memory RRD databases, and those databases can be served up either as
      PNG images or as RRDtool compatible XML dumps (see oldstats.jsp for
      details).  A base set of stats are harvested by default, but an
      alternate list can be specified by setting the 'stat.summaries' list on
      the advanced config.  For instance:
      stat.summaries=bw.recvRate.60000,bw.sendRate.60000
    * HTML tweaking for the general config page (thanks void!)
    * Odd NPE fix (thanks Complication!)
2006-03-16 21:45:17 +00:00
349adf6690 2006-03-15 Complication
* Trim out an old, inactive IP second-guessing method
      (thanks for spotting, Anonymous!)
2006-03-16 00:49:22 +00:00
2c843fd818 2006-03-15 jrandom
* Further stat cleanup
    * Keep track of how many peers we are actively trying to communicate with,
      beyond those who are just trying to communicate with us.
    * Further router tunnel participation throttle revisions to avoid spurious
      rejections
    * Rate stat display cleanup (thanks ripple!)
    * Don't even try to send messages that have been queued too long
2006-03-15 22:36:10 +00:00
863b511cde 2006-03-15 jrandom
* Further stat cleanup
    * Keep track of how many peers we are actively trying to communicate with,
      beyond those who are just trying to communicate with us.
    * Further router tunnel participation throttle revisions to avoid spurious
      rejections
    * Rate stat display cleanup (thanks ripple!)
    * Don't even try to send messages that have been queued too long
2006-03-15 22:26:42 +00:00
zzz
c417e7c237 2006-03-14 zzz update 2006-03-15 06:02:07 +00:00
zzz
1822c0d7d8 2006-03-07 zzz update 2006-03-09 02:19:42 +00:00
zzz
94c1c32b51 2006-03-05 zzz
* Remove the +++--- from the logs on i2psnark startup
2006-03-06 01:57:47 +00:00
deb35f4af4 2006-03-05 jrandom
* HTML fixes in Syndie to work better with opera (thanks shaklen!)
    * Give netDb lookups to floodfill peers more time, as they are much more
      likely to succeed (thereby cutting down on the unnecessary netDb
      searches outside the floodfill set)
    * Fix to the SSU IP detection code so we won't use introducers when we
      don't need them (thanks Complication!)
    * Add a brief shitlist to i2psnark so it doesn't keep on trying to reach
      peers given to it
    * Don't let netDb searches wander across too many peers
    * Don't use the 1s bandwidth usage in the tunnel participation throttle,
      as its too volatile to have much meaning.
    * Don't bork if a Syndie post is missing an entry.sml
2006-03-05 17:07:07 +00:00
883150f943 2006-03-05 Complication
* Reduce exposed statistical information,
      to make build and uptime tracking more expensive
2006-03-05 07:44:59 +00:00
717d1b97b2 2006-03-04 Complication
* Fix the announce URL of orion's tracker in Snark sources
2006-03-04 23:50:01 +00:00
e62135eacc 2006-03-03 Complication
* Explicit check for an index out of bounds exception while parsing
      an inbound IRC command (implicit check was there already)
2006-03-04 03:04:06 +00:00
2c6d953359 2006-03-01 jrandom
* More aggressive tunnel throttling as we approach our bandwidth limit,
      and throttle based off periods wider than 1 second.
    * Included Doubtful Salmon's syndie stylings (thanks!)
2006-03-01 23:01:20 +00:00
zzz
2b79e2df3f 2006-02-28 zzz update 2006-03-01 04:11:16 +00:00
zzz
fab6e421b8 2006-02-27 zzz
* Update error page templates to add \r, Connection: close, and
      Proxy-connection: close.
2006-02-28 03:55:18 +00:00
589cbd675a * 2006-02-27 0.6.1.12 released
2006-02-27  jrandom
    * Adjust the jbigi.jar to use the athlon-optimized jbigi on windows/amd64
      machines, rather than the generic jbigi (until we have an athlon64
      optimized version)
2006-02-27 19:05:40 +00:00
c486f5980a * 2006-02-27 0.6.1.12 released
2006-02-27  jrandom
    * Adjust the jbigi.jar to use the athlon-optimized jbigi on windows/amd64
      machines, rather than the generic jbigi (until we have an athlon64
      optimized version)
2006-02-27 18:51:31 +00:00
eee21aa301 2006-02-26 jrandom
* Switch from the bouncycastle to the gnu-crypto implementation for
      SHA256, as benchmarks show a 10-30% speedup.
    * Removed some unnecessary object caches
    * Don't close i2psnark streams prematurely
2006-02-26 21:30:56 +00:00
zzz
a2854cf6f6 2006-02-25 zzz spelling fix 2006-02-25 21:51:46 +00:00
62b7cf64da 2006-02-25 jrandom
* Made the Syndie permalinks in the thread view point to the blog view
    * Disabled TCP again (since the live net seems to be doing well w/out it)
    * Fix the message time on inbound SSU establishment (thanks zzz!)
    * Don't be so aggressive with parallel tunnel creation when a tunnel pool
      just starts up
2006-02-25 20:41:51 +00:00
7b2a435aad 2006-02-24 jrandom
* Rounding calculation cleanup in the stats, and avoid an uncontested
      mutex (thanks ripple!)
    * SSU handshake cleanup to help force incompatible peers to stop nagging
      us by both not giving them an updated reference to us and by dropping
      future handshake packets from them.
2006-02-24 09:35:52 +00:00
3d8d21e543 2006-02-23 jrandom
* Increase the SSU retransmit ceiling (for slow links)
    * Estimate the sender's SSU MTU (to help see if we agree)
2006-02-23 14:38:39 +00:00
8b7958cff2 2006-02-22 jrandom
* Fix to properly profile tunnel joins (thanks Ragnarok, frosk, et al!)
    * More aggressive poor-man's PMTU, allowing larger MTUs on less reliable
      links
    * Further class validator refactorings
2006-02-23 08:08:37 +00:00
7bb792836d 2006-02-22 jrandom
* Fix to properly profile tunnel joins (thanks Ragnarok, frosk, et al!)
    * More aggressive poor-man's PMTU, allowing larger MTUs on less reliable
      links
    * Further class validator refactorings
2006-02-23 01:48:47 +00:00
03f509ca54 2006-02-22 jrandom
* Handle a rare race under high bandwidth situations in the SSU transport
    * Minor refactoring so we don't confuse sun's 1.6.0-b2 validator
2006-02-22 14:54:22 +00:00
5f05631936 2006-02-21 Complication
* Reactivate TCP tranport by default, in addition to re-allowing
2006-02-22 06:19:19 +00:00
zzz
5cfedd4c8b 2006-02-21 zzz update 2006-02-22 03:34:02 +00:00
zzz
269fec64a5 2006-02-21 zzz
announce 0.6.1.11
2006-02-21 20:12:14 +00:00
f63c6f4717 * 2006-02-21 0.6.1.11 released 2006-02-21 15:20:17 +00:00
b4c495531a 2006-02-21 jrandom
* Throttle the outbound SSU establishment queue, so it doesn't fill up the
      heap when backlogged (and so that the messages queued up on it don't sit
      there forever)
    * Further SSU memory cleanup
    * Clean up the address regeneration code so it knows when to rebuild the
      local info more precisely.
2006-02-21 13:31:18 +00:00
9990126e3e 2006-02-20 jrandom
* Throttle the outbound SSU establishment queue, so it doesn't fill up the
      heap when backlogged (and so that the messages queued up on it don't sit
      there forever)
    * Further SSU memory cleanup
2006-02-21 02:02:48 +00:00
ac8436a8eb 2006-02-20 jrandom
* Properly enable TCP this time (oops)
    * Deal with multiple form handlers on the same page in the console without
      being too annoying (thanks blubb and bd_!)
2006-02-20 18:12:47 +00:00
dee79dfb1c 2006-02-20 jrandom
* Reenable the TCP transport as a fallback (we'll continue to muck with
      debugging SSU-only elsewhere)
2006-02-20 16:42:29 +00:00
9b4e6f475d no need to include this stuff in the updates (they havent changed) 2006-02-20 14:28:09 +00:00
7672ba23d1 2006-02-20 jrandom
* Major SSU and router tuning to reduce contention, memory usage, and GC
      churn.  There are still issues to be worked out, but this should be a
      substantial improvement.
    * Modified the optional netDb harvester task to support choosing whether
      to use (non-anonymous) direct connections or (anonymous) exploratory
      tunnels to do the harvesting.  Harvesting itself is enabled via the
      advanced config "netDb.shouldHarvest=true" (default is false) and the
      connection type can be chosen via "netDb.harvestDirectly=false" (default
      is false).
2006-02-20 14:27:38 +00:00
4b77ddedcc 2006-02-20 jrandom
* Major SSU and router tuning to reduce contention, memory usage, and GC
      churn.  There are still issues to be worked out, but this should be a
      substantial improvement.
    * Modified the optional netDb harvester task to support choosing whether
      to use (non-anonymous) direct connections or (anonymous) exploratory
      tunnels to do the harvesting.  Harvesting itself is enabled via the
      advanced config "netDb.shouldHarvest=true" (default is false) and the
      connection type can be chosen via "netDb.harvestDirectly=false" (default
      is false).
2006-02-20 14:19:52 +00:00
222af6c090 * Added pruning of suckers history (it used to grow indefinitely). 2006-02-20 05:08:59 +00:00
8e879cb646 2006-02-19 jrandom
* Moved the current net's reseed URL to a different location than where
      the old net looks (dev.i2p.net/i2pdb2/ vs .../i2pdb/)
    * More aggressively expire inbound messages (on receive, not just on send)
    * Add in a hook for breaking backwards compatibility in the SSU wire
      protocol directly by including a version as part of the handshake.  The
      version is currently set to 0, however, so the wire protocol from this
      build is compatible with all earlier SSU implementations.
    * Increased the number of complete message readers, cutting down
      substantially on the delay processing inbound messages.
    * Delete the message history file on startup
    * Reworked the restart/shutdown display on the console (thanks bd_!)
2006-02-19 12:40:03 +00:00
65975df1be 2006-02-19 jrandom
* Moved the current net's reseed URL to a different location than where
      the old net looks (dev.i2p.net/i2pdb2/ vs .../i2pdb/)
    * More aggressively expire inbound messages (on receive, not just on send)
    * Add in a hook for breaking backwards compatibility in the SSU wire
      protocol directly by including a version as part of the handshake.  The
      version is currently set to 0, however, so the wire protocol from this
      build is compatible with all earlier SSU implementations.
    * Increased the number of complete message readers, cutting down
      substantially on the delay processing inbound messages.
    * Delete the message history file on startup
    * Reworked the restart/shutdown display on the console (thanks bd_!)
2006-02-19 12:29:57 +00:00
c94de2fbb5 2006-02-18 jrandom
* Migrate the outbound packets from a central component to the individual
      per-peer components, substantially cutting down on lock contention when
      dealing with higher degrees.
    * Load balance the outbound SSU transfers evenly across peers, rather than
      across messages (so peers with few messages won't be starved by peers
      with many).
    * Reduce the frequency of router info rebuilds (thanks bar!)
2006-02-19 03:58:08 +00:00
5aa335740a 2006-02-18 jrandom
* Migrate the outbound packets from a central component to the individual
      per-peer components, substantially cutting down on lock contention when
      dealing with higher degrees.
    * Load balance the outbound SSU transfers evenly across peers, rather than
      across messages (so peers with few messages won't be starved by peers
      with many).
    * Reduce the frequency of router info rebuilds (thanks bar!)
2006-02-19 03:22:31 +00:00
1202751359 2006-02-18 jrandom
* Add a new AIMD throttle in SSU to control the number of concurrent
      messages being sent to a given peer, in addition to the throttle on the
      number of concurrent bytes to that peer.
    * Adjust the existing SSU outbound queue to throttle based on the queue's
      lag, not an arbitrary number of packets.
2006-02-18 06:48:48 +00:00
34fcf53d87 2006-02-18 jrandom
* Add a new AIMD throttle in SSU to control the number of concurrent
      messages being sent to a given peer, in addition to the throttle on the
      number of concurrent bytes to that peer.
    * Adjust the existing SSU outbound queue to throttle based on the queue's
      lag, not an arbitrary number of packets.
2006-02-18 06:38:30 +00:00
9ddc632b9f 2006-02-17 jrandom
* Properly fix the build request queue throttling, using queue age to
      detect congestion, rather than queue size.
2006-02-17 22:29:28 +00:00
941b65eb32 2006-02-17 jrandom
* Disable the message history log file by default (duh - feel free to
      delete messageHistory.txt after upgrading.  thanks deathfatty!)
    * Limit the size of the inbound tunnel build request queue so we don't
      get an insane backlog of requests that we're bound to reject, and adjust
      the queue processing so we keep on churning through them when we've got
      a backlog.
    * Small fixes for the multiuser syndie operation (thanks Complication!)
    * Renamed modified PRNG classes that were imported from gnu-crypto so we
      don't conflict with JVMs using that as a JCE provider (thanks blx!)
2006-02-17 09:16:00 +00:00
8c9167464b 2006-02-17 jrandom
* Disable the message history log file by default (duh - feel free to
      delete messageHistory.txt after upgrading.  thanks deathfatty!)
    * Limit the size of the inbound tunnel build request queue so we don't
      get an insane backlog of requests that we're bound to reject, and adjust
      the queue processing so we keep on churning through them when we've got
      a backlog.
    * Small fixes for the multiuser syndie operation (thanks Complication!)
    * Renamed modified PRNG classes that were imported from gnu-crypto so we
      don't conflict with JVMs using that as a JCE provider (thanks blx!)
2006-02-17 09:07:53 +00:00
5b94965983 * 2006-02-16 0.6.1.10 released 2006-02-16 20:44:07 +00:00
3226ea5bf6 (1.188) added downloads.legion.i2p, politguy.i2p, ninja.i2p 2006-02-16 20:24:20 +00:00
84a24784e4 so old its not funny 2006-02-16 11:30:46 +00:00
71d3fa6b8c disable by default 2006-02-16 11:20:32 +00:00
9e00dbaafd 2006-02-16 jrandom
* Add a new toggle to the web config to enable/disable the load testing
2006-02-16 10:33:29 +00:00
2e9e0c64d4 2006-02-16 jrandom
* Dropped much of the abandonware from the apps/ directory
2006-02-16 09:45:22 +00:00
fde3f1ce7d not used 2006-02-16 09:40:12 +00:00
321c560648 drop most of the abandonware 2006-02-16 09:33:53 +00:00
d2ddca7d64 *cough* 2006-02-16 09:13:48 +00:00
fb17e70f12 2006-02-16 jrandom
* Bugfix to the I2PTunnel web config to properly accept i2cp port settings
    * Initial sucker refactoring to simplify reuse of the html parsing
    * Beginnings of hooks to push imported rss/atom out to remote syndie
      archives automatically (though not enabled currently)
    * Further SSU peer test cleanup
2006-02-16 08:36:22 +00:00
79f934fe17 2006-02-16 jrandom
* Bugfix to the I2PTunnel web config to properly accept i2cp port settings
    * Initial sucker refactoring to simplify reuse of the html parsing
    * Beginnings of hooks to push imported rss/atom out to remote syndie
      archives automatically (though not enabled currently)
    * Further SSU peer test cleanup
2006-02-16 08:24:07 +00:00
3d76df6af3 thanks zzz 2006-02-16 06:16:37 +00:00
zzz
d5c36f7f4e 2006-02-15 zzz fix release # 2006-02-15 17:56:59 +00:00
zzz
41ac628744 2006-02-15 zzz update 2006-02-15 17:49:40 +00:00
41e5e1a094 "&amp;amp;" sucks 2006-02-15 14:16:16 +00:00
74edc3fa7d 2006-02-15 jrandom
* Add in per-blog RSS feeds to Syndie
    * Upgraded sucker's ROME dependency to 0.8, bundling sucked enclosures
      with the posts, marking additional attachments as Media RSS enclosures
      (http://search.yahoo.com/mrss/), since RSS only supports one enclosure
      per item.
    * Don't allow the default syndie user to be set to something invalid if
      its in single user mode.
2006-02-15 13:42:20 +00:00
3a26218b5a just for clarity 2006-02-15 13:41:47 +00:00
687abd9427 2006-02-15 jrandom
* Add in per-blog RSS feeds to Syndie
    * Upgraded sucker's ROME dependency to 0.8, bundling sucked enclosures
      with the posts, marking additional attachments as Media RSS enclosures
      (http://search.yahoo.com/mrss/), since RSS only supports one enclosure
      per item.
    * Don't allow the default syndie user to be set to something invalid if
      its in single user mode.
2006-02-15 13:36:28 +00:00
113fbc1df3 2006-02-15 jrandom
* Merged in the i2p_0_6_1_10_PRE branch to the trunk, so CVS HEAD is no
      longer backwards compatible (and should not be used until 0.6.1.1 is
      out)
2006-02-15 05:33:17 +00:00
zzz
1374ea0ea1 2006-02-07 zzz update 2006-02-08 05:59:10 +00:00
zzz
424e55d3b2 2006-02-01 (zzz) 1-31 update 2006-02-02 02:59:43 +00:00
fde4f579f4 file BuildRequestor.java was initially added on branch i2p_0_6_1_10_PRE. 2006-02-02 01:32:33 +00:00
2d651a41f0 2006-01-25 jrandom
* Run the peer profile coalescing/reorganization outside the job queue
      (on one of the timers), to cut down on some job queue congestion.  Also,
      trim old profiles while running, not just when starting up.
    * Slightly more sane intra-floodfill-node netDb activity (only flood new
      entries)
    * Workaround in the I2PTunnelHTTPServer for some bad requests (though the
      source of the bug is not yet addressed)
    * Better I2PSnark reconnection handling
    * Further cleanup in the new tunnel build process
    * Make sure we expire old participants properly
    * Remove much of the transient overload throttling (it wasn't using a good
      metric)
2006-01-26 04:47:12 +00:00
1eebd5463f 2006-01-25 jrandom
* Run the peer profile coalescing/reorganization outside the job queue
      (on one of the timers), to cut down on some job queue congestion.  Also,
      trim old profiles while running, not just when starting up.
    * Slightly more sane intra-floodfill-node netDb activity (only flood new
      entries)
    * Workaround in the I2PTunnelHTTPServer for some bad requests (though the
      source of the bug is not yet addressed)
    * Better I2PSnark reconnection handling
    * Further cleanup in the new tunnel build process
    * Make sure we expire old participants properly
    * Remove much of the transient overload throttling (it wasn't using a good
      metric)
2006-01-26 04:41:44 +00:00
f22601b477 2006-01-25 jrandom
* Run the peer profile coalescing/reorganization outside the job queue
      (on one of the timers), to cut down on some job queue congestion.  Also,
      trim old profiles while running, not just when starting up.
    * Slightly more sane intra-floodfill-node netDb activity (only flood new
      entries)
    * Workaround in the I2PTunnelHTTPServer for some bad requests (though the
      source of the bug is not yet addressed)
    * Better I2PSnark reconnection handling
    * Further cleanup in the new tunnel build process
    * Make sure we expire old participants properly
    * Remove much of the transient overload throttling (it wasn't using a good
      metric)
2006-01-26 04:28:15 +00:00
ab8e11657f 2006-01-25 dust
* Fix IRC client proxy to use ISO-8859-1.
2006-01-25 15:34:28 +00:00
zzz
17eb7fa983 2006-1-24 zzz update 2006-01-25 07:43:02 +00:00
d1134f9704 added hidden.i2p, bk1k.i2p, antipiracyagency.i2p 2006-01-23 15:29:36 +00:00
13fe45b489 2006-01-22 jrandom
* New tunnel build process - does not use the new crypto or new peer
      selection strategies.  However, it does drop the fallback tunnel
      procedure, except for tunnels who are configured to allow them, or for
      the exploratory pool during bootstrapping or after a catastrophic
      failure.  This new process prefers to fail rather than use too-short
      tunnels, so while it can do some pretty aggressive tunnel rebuilding,
      it may expose more tunnel failures to the user.
    * Always prefer normal tunnels to fallback tunnels.
    * Potential fix for a bug while changing i2cp settings on I2PSnark (thanks
      bar!)
    * Do all of the netDb entry writing in a separate thread, avoiding
      duplicates and batching them up.
2006-01-23 00:51:54 +00:00
cd235e5902 2006-01-19 Complication
* Explain better where eepsite's destkey can be found
2006-01-20 04:40:24 +00:00
b727d868fb 2006-01-18 cervantes
* Add title attributes to all external links in Syndie, so we can rollover
      and quickly see if it's worth clicking on.
    * Fixed a minor compiler warning.
2006-01-18 06:37:52 +00:00
zzz
cd2609b34a (zzz) 1-17 update 2006-01-18 03:45:37 +00:00
a12ede096a 2006-01-17 jrandom
* First pass of the new tunnel creation crypto, specified in the new
      router/doc/tunnel-alt-creation.html (referenced in the current
      router/doc/tunnel-alt.html).  It isn't actually used anywhere yet, other
      than in the test code, but the code verifies the technical viability, so
      further scrutiny would be warranted.
2006-01-17 22:56:15 +00:00
eb3442823e 2006-01-16 cervantes
* Dragged I2P kicking and screaming into 2006 (Oops)
2006-01-16 22:00:04 +00:00
211f37c207 2005-01-14 cervantes
* Removed entirely misleading memory status from the console summary.
2006-01-14 20:14:47 +00:00
d60d0923c0 2005-01-13 cervantes
* Further Syndie layout hardening and typeface balancing.
2006-01-13 06:27:43 +00:00
zzz
0448203ede (zzz) spelling fix and tweaks 2006-01-13 02:51:27 +00:00
00c97dbf92 0.6.1.9 2006-01-13 01:13:05 +00:00
d07342e3e3 * 2005-01-12 0.6.1.9 released 2006-01-12 21:18:38 +00:00
205d8de281 2005-01-12 jrandom
* Only create the loadtest.log if requested to do so (thanks zzz!)
    * Make sure we cleanly take into consideration the appropriate data
      points when filtering out duplicate messages in the message validator,
      and report the right bloom filter false positives rate (not used for
      anything except debugging)
2006-01-12 19:35:54 +00:00
a638301b5c 2005-01-12 cervantes
* Syndie CSS tweaks to removed some redundant declarations, improve font
      scaling and layout robustness. Improved cross browser compatibility
      (in other words "kicked IE"). Tightened the look of the blog template
      a little.
2006-01-12 09:59:55 +00:00
4f51ad492b 2005-01-11 Complication
* CSS comment fixes
2006-01-11 23:19:38 +00:00
zzz
c3a9f72d41 (zzz) 1-10-06 update 2006-01-11 23:12:31 +00:00
79476d3609 2005-01-11 jrandom
* Include the attachments/blogs/etc for comments on the blog view
    * Syndie HTML fixes (thanks cervantes!)
    * Make sure we fully reset the objects going into our cache before we
      reuse them (thanks zzz!)
2006-01-11 20:32:34 +00:00
zzz
97a206fcda (zzz) add step-by-step eepsite announce instructions 2006-01-11 05:31:05 +00:00
f783e65a4c added decadence.i2p, freedomarchives.i2p, closedshop.i2p 2006-01-10 20:53:10 +00:00
dbd1f65864 2005-01-10 jrandom
* Added the per-post list of attachments/blogs/etc to the blog view in
      Syndie (though this does not yet include comments or some further
      refinements)
    * Have the I2P shortcut launch i2p.exe instead of i2psvc.exe on windows,
      removing the dox box (though also removes the restart functionality...)
    * Give the i2p.exe the correct java.library.path to support the systray
      dll (thanks Bobcat, Sugadude, anon!)
2006-01-10 06:59:07 +00:00
5c78d8108f 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-10 02:12:54 +00:00
1b273bdf43 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-09 23:18:15 +00:00
934f4082f1 2005-01-09 jrandom
* Removed a longstanding bug that had caused unnecessary router identity
      churn due to clock skew
    * Temporarily sanity check within the streaming lib for long pending
      writes
    * Added support for a blog-wide logo to Syndie, and automated the pushing
      of updated extended blog info data along side the metadata.
2006-01-09 22:22:41 +00:00
002aed145f 2005-01-09 jrandom
* Bugfix for a rare SSU error (thanks cervantes!)
    * More progress on the blog interface, allowing customizable blog-wide
      links.
2006-01-09 07:02:01 +00:00
1ca27ffd39 2005-01-09 jrandom
* Bugfix for a rare SSU error (thanks cervantes!)
    * More progress on the blog interface, allowing customizable blog-wide
      links.
2006-01-09 06:33:29 +00:00
66e6dbec33 2006-01-08 jrandom
* First pass of the new blog interface, though without much of the useful
      customization features (coming soon)
2006-01-08 21:50:54 +00:00
894caaa63c 2006-01-08 jrandom
* First pass of the new blog interface, though without much of the useful
      customization features (coming soon)
2006-01-08 20:54:36 +00:00
zzz
97dae94b46 (zzz) update 2006-01-05 06:29:55 +00:00
c00488afeb 2006-01-04 jrandom
* Rather than profile individual tunnels for throughput over their
      lifetime, do so at 1 minute intervals (allowing less frequently active
      tunnels to be more fairly measured).
    * Run the live tunnel load test across two tunnels at a time, by default.
      The load test runs for a random period from 90s to the tunnel lifetime,
      self paced.  This should help gathering data for profiling peers that
      are in exploratory tunnels.
2006-01-03  jrandom
    * Calculate the overall peer throughput across the 3 fastest one minute
      tunnel throughput values, rather than the single fastest throughput.
    * Degrade the profiled throughput data over time (cutting the profiled
      peaks in half once a day, on average)
    * Enable yet another new speed calculation for profiling peers, using the
      peak throughput from individual tunnels that a peer is participating in,
      rather than across all tunnels they are participating in.  This helps
      gather a fairer peer throughput measurement, since it won't allow a slow
      high capacity peer seem to have a higher throughput (pushing a little
      data across many tunnels at once, as opposed to lots of data across a
      single tunnel).  This degrades over time like the other.
    * Add basic OS/2 support to the jbigi code (though we do not bundle a
      precompiled OS/2 library)
2006-01-05 02:48:13 +00:00
23723b56ca 2006-01-01 jrandom
* Disable multifile torrent creation in I2PSnark's web UI for the moment
      (though it can still seed and participate in multifile swarms)
    * Enable a new speed calculation for profiling peers, using their peak
      1 minute average tunnel throughput as their speed.
2006-01-01 17:23:26 +00:00
76f89ac93c 2005-12-31 jrandom
* Include a simple torrent creator in the I2PSnark web UI
    * Further streaming lib closing improvements
    * Refactored the load test components to run off live tunnels (though,
      still not safe for normal/anonymous load testing)
2005-12-31 23:40:20 +00:00
0f8611e465 2005-12-30 jrandom
* Close streams more gracefully
2005-12-30 23:33:52 +00:00
8e87ae08fb 2005-12-30 jrandom
* Small streaming lib bugfixes for the modified timeouts
    * Minor Syndie/Sucker RSS html fix
    * Small synchronization fix in I2PSnark (thanks fsm!)
2005-12-30 20:57:53 +00:00
5b1a6391f3 2005-12-30 jrandom
* Replaced the bundled linux jcpuid (written in C++) with scintilla's
      jcpuid (written in C), removing the libg++.so.5 dependency that has bit
      some distros (e.g. mandriva)
2005-12-30 18:16:46 +00:00
728f177473 2005-12-29 jrandom
* Minor fix to the new ERR-ClockSkew to deal with people whose clocks are
      actually correct
2005-12-29 13:07:22 +00:00
1d0d0d9c69 2005-12-27 jrandom
* Add a new Status: line on the router console - "ERR-ClockSkew", in case
      the clock is too skewed to do anything useful (check the year and month,
      not just the hour and minute).
    * Fixed the read/write timeouts in the streaming lib (so that it actually
      honors them now)
    * Minor I2PSnark cleanups (no read timeout, more careful shutdown and
      torrent closing)
    * Handle an oddball tunnel creation failure (thanks Xunk)
2005-12-27 13:20:50 +00:00
9b7e5d1817 2005-12-26 Complication
* Fix some integer typecasting in I2PSnark (caused >2GB torrents to fail)
    * HTML readability cosmetics on "Peers" page
2005-12-27 04:20:29 +00:00
dc0485b526 fix ugliness in release history of help.jsp
[yes, i am still alive *g*]
2005-12-23 04:36:31 +00:00
784d465d17 * 2005-12-22 0.6.1.8 released
2005-12-22  jrandom
    * Bundle the standalone I2PSnark launcher in the installer and update
      process (launch as "java -jar launch-i2psnark.jar", viewing the
      interface on http://localhost:8002/)
    * Don't autostart swarming torrents by default so that you can run a
      standalone I2PSnark from the I2P install dir and not have the embedded
      I2PSnark autolaunch the torrents that the standalone instance is running
    * Fixed a rare streaming lib bug that could let a blocking call wait
      forever.
2005-12-22 12:49:09 +00:00
327089c9d1 4matting 2005-12-22 10:10:39 +00:00
148dd99c86 2005-12-22 jrandom
* Cleaned up some buffer synchronization issues in I2PSnark that could
       cause blockage.
2005-12-22 10:04:12 +00:00
98277d3b64 2005-12-21 jrandom
* Adjusted I2PSnark's usage of the streaming lib (tweaking it for BT's
      behavior)
    * Fixed the I2PSnark bug that would lose track of live peers
2005-12-21 12:04:54 +00:00
702e5a5eab 2005-12-19 cervantes
* Silly RDF syntax
    * Oops...
2005-12-21 04:15:36 +00:00
54c91731c0 rdf updates for easier fire2pe/xul handling 2005-12-20 10:22:24 +00:00
3bfc109476 (cough. not often a problem though) 2005-12-20 02:29:09 +00:00
3989638f2d 2005-12-19 jrandom
* Fix for old Syndie blog bookmarks (thanks Complication!)
    * Fix for I2PSnark to accept incoming connections again (oops)
    * Randomize the order that peers from the tracker are contacted
2005-12-20 02:01:37 +00:00
4a65fd4f46 2005-12-19 jrandom
* I2PSnark logging, disconnect old inactive peers rather than new ones,
      memory usage reduction, better OOM handling, and a shared connection
      acceptor.
    * Cleaned up the Syndie blog page and the resulting filters (viewing a
      blog from the blog page shows threads started by the selected author,
      not those that they merely participate in)
2005-12-19 13:34:52 +00:00
d525c49d45 2005-12-18 jrandom
* Added a standalone runner for the I2PSnark web ui (build with the
      command "ant i2psnark", unzip i2psnark-standalone.zip somewhere, run
      with "java -jar launch-i2psnark.jar", and go to http://localhost:8002/).
    * Further I2PSnark error handling
2005-12-17  jrandom
    * Let multiuser accounts authorize themselves to access the remote
      functionality again (thanks Ch0Hag!)
    * Adjust the JVM heap size to 128MB for new installs (existing users can
      accomplish this by editing wrapper.config, adding the line
      "wrapper.java.maxmemory=128", and then doing a full shutdown and startup
      of the router).  This is relevent for heavy usage of I2PSnark in the
      router console.
2005-12-18 05:52:19 +00:00
c287bace0f 2005-12-18 jrandom
* Added a standalone runner for the I2PSnark web ui (build with the
      command "ant i2psnark", unzip i2psnark-standalone.zip somewhere, run
      with "java -jar launch-i2psnark.jar", and go to http://localhost:8002/).
    * Further I2PSnark error handling
2005-12-18 05:39:52 +00:00
ee0951b5b2 2005-12-17 jrandom
* Use our faster SHA1, rather than the JVM's within I2PSnark, and let
      'piece' sizes grow larger than before.
2005-12-17 09:22:07 +00:00
1eb3ae5e1b 2005-12-16 jrandom
* Added some I2PSnark sanity checks, an OOMListener when running
      standalone, and a guard against keeping memory tied up indefinitely.
    * Sanity check on the watchdog (thanks zzz!)
    * Handle invalid HTTP requests in I2PTunnel a little better
2005-12-17 03:47:02 +00:00
7d234b1978 2005-12-16 jrandom
* Moved I2PSnark from using Threads to I2PThreads, so we handle OOMs
      properly (thanks Complication!)
    * More guards in I2PSnark for zany behavior (I2PSession recon w/ skew,
      b0rking in the DirMonitor, etc)
2005-12-16 23:18:56 +00:00
6f424fa751 2005-12-16 jrandom
* Try to run a torrent in readonly mode if we can't write to the file, and
      handle failures a little more gracefully (thanks polecat!)
2005-12-16 11:01:20 +00:00
7726bd1a5c 2005-12-16 jrandom
* Refuse torrents with too many files (128), avoiding ulimit errors.
    * Remove an fd leak in I2PSnark
    * Further I2PSnark web UI cleanup
2005-12-16 08:24:21 +00:00
2a922098d6 refresh link (good idea Complication) 2005-12-16 04:01:05 +00:00
3ec92c8b62 2005-12-15 jrandom
* Added a first pass to the I2PSnark web UI (see /i2psnark/)
2005-12-16 03:00:48 +00:00
b37bb9372e 2005-12-15 jrandom
* Added multitorrent support to I2PSnark, accessible currently by running
      "i2psnark.jar --config i2psnark.config" (which may or may not exist).
      It then joins the swarm for any torrents in ./i2psnark/*.torrent, saving
      their data in that directory as well.  Removing the .torrent file stops
      participation, and it is currently set to seed indefinitely.  Completion
      is logged to the logger and standard output, with further UI interaction
      left to the (work in progress) web UI.
2005-12-15 08:58:30 +00:00
369b6930e5 2005-12-14 jrandom
* Fix to drop peer references when we shitlist people again (thanks zzz!)
    * Further I2PSnark fixes to deal with arbitrary torrent info attributes
      (thanks Complication!)
2005-12-14 09:32:50 +00:00
5033a22a9b 2005-12-13 zzz
* Don't test tunnels expiring within 90 seconds
    * Defer Test Tunnel jobs if job lag too large
    * Use JobQueue.getMaxLag() rather than the jobQueue.jobLag stat to measure
      job lag for tunnel build backoff, allowing for more agile handling
      (since the stat is only updated once a minute)
    * Use tunnel length override if all tunnels are expiring within one
      minute.
2005-12-13 21:56:41 +00:00
6e5114a4c2 multihop load tests (testing everyone with a fixed set of fast other peers).
not for general purpose use, so you can probably safely ignore this code, but its
useful for testing
2005-12-13 21:32:50 +00:00
77c818a0b1 toolbar.html isn't in cvs yet (polecat - please check this diff :) 2005-12-13 09:43:41 +00:00
7ac673cea4 2005-12-13 jrandom
* Fixed I2PSnark's handling of some torrent files to deal with those
      created by Azureus and I2PRufus (it didn't know how to deal with
      additional meta info, such as path.utf-8 or name.utf-8).
2005-12-13 09:38:51 +00:00
e5fa7e0ae4 Navbar is now customizeable via docs/toolbar.html. There is a default should that file not be there. And... wtf, didn't my syndie thumbnail patch take? Well, no conflicts reported so, here it goes again. 2005-12-13 08:18:59 +00:00
ab4f3008cb 2005-12-09 zzz
* Create different strategies for exploratory tunnels (which are difficult
      to create) and client tunnels (which are much easier)
    * Gradually increase number of parallel build attempts as tunnel expiry
      nears.
    * Temporarily shorten attempted build tunnel length if builds using
      configured tunnel length are unsuccessful
    * React more aggressively to tunnel failure than routine tunnel
      replacement
    * Make tunnel creation times randomized - there is existing code to
      randomize the tunnels but it isn't effective due to the tunnel creation
      strategy. Currently, most tunnels get built all at once, at about 2 1/2
      to 3 minutes before expiration. The patch fixes this by fixing the
      randomization, and by changing the overlap time (with old tunnels) to a
      range of 2 to 4 minutes.
    * Reduce number of excess tunnels. Lots of excess tunnels get created due
      to overlapping calls. Just about anything generated a call which could
      build many tunnels all at once, even if tunnel building was already in
      process.
    * Miscellaneous router console enhancements
2005-12-09 08:05:44 +00:00
f738a02760 link to the filtered blog, not to the current viewBlogs page 2005-12-08 21:01:04 +00:00
7d64ecb628 2005-12-08 jrandom
* Minor bugfix in SSU for dealing with corrupt packets
    * Added some hooks for load testing
2005-12-08 20:53:41 +00:00
7beacff028 2005-12-07 jrandom
* Added a first pass at a blog view in Syndie
2005-12-08 00:50:32 +00:00
952bcc696a 2005-12-07 jrandom
* Expand the thread we're viewing to its leaf
    * Bugfix on intraday ordering (children are always newer than parents)
2005-12-07 20:19:42 +00:00
19bba048f2 2005-12-05 jrandom
* Added an RDF and XML thread export to Syndie, reachable at
      .../threadnav/rdf or .../threadnav/xml, accepting the parameters
      count=$numThreads and offset=$threadIndex.  If the $numThreads is -1, it
      displays all threads.
2005-12-05 06:14:15 +00:00
5966fcf5ff 2005-12-04 TLorD
* Patch for the C SAM library to null terminate strings on copy (thanks!)
2005-12-04 20:12:19 +00:00
fbd7feee61 2005-12-04 jrandom
* Bugfix in Syndie for a problem in the threaded indexer (thanks CofE!)
    * Always include ourselves in the favorite authors (since we don't
      bookmark ourselves)
2005-12-04 20:02:24 +00:00
5faca98176 I am such a useless bitch. 2005-12-04 17:04:10 +00:00
99951bf815 Adding a schema for [link] to handle if you want to display links directly to your attachments within the context of the blog itself. Some redundant code here (3 files modified with cut & paste) so we may want to further abstract the External links: HTML generation code. 2005-12-04 13:55:27 +00:00
024b1a04e2 no message 2005-12-04 03:30:32 +00:00
a4cc18df76 2005-12-03 jrandom
* Use newgroup-like tags by default in Syndie's interface
2005-12-04 03:18:08 +00:00
fef578973a no message 2005-12-03 22:09:01 +00:00
35b75a7300 2005-12-03 jrandom
* Added support for a 'most recent posts' view that CofE requested, which
      includes the ability to filter by age (e.g. posts by your favorite
      authors in the last 5 days).
2005-12-03 19:03:44 +00:00
1c6c397913 2005-12-03 jrandom
* Adjusted Syndie to use the threaded view that cervantes suggested, which
      displays a a single thread path at a time - from root to leaf - rather
      than a depth first traversal.
2005-12-03 17:33:35 +00:00
c96965d364 2005-12-03 jrandom
* Package up a standalone Syndie install into a "syndie-standalone.zip",
      buildable with "ant syndie".  It extracts into ./syndie/, launches with
      "java -jar launchsyndie.jar" (or javaw, on windows, to avoid a dos box),
      running a single user Syndie instance (by default).  It also creates a
      default subscription to syndiemedia without any anonymity (using no
      proxy).  Upgrades can be done by just replacing the syndie.war with the
      one from I2P.
2005-12-03 05:41:25 +00:00
12900ca709 * 2005-12-01 0.6.1.7 released
2005-12-01  jrandom
    * Add a new criteria to the tunnel join throttle, backing off people if we
      are failing to talk to our peers more than usual.
2005-12-01 17:16:53 +00:00
f5b829a124 2005-11-30 jrandom
* Cleaned up the build process to deal with Jetty 5.1.6 and rename the
      new commons-logging-api.jar to commons-logging.jar, which it replaces.
      Jetty 5.1.6 is pushed with all updates.  Also, no need to push a
      separate jdom or rome, as they're inside syndie.war.
2005-12-01 01:13:44 +00:00
f62a6d3ce6 2005-11-30 jrandom
* Don't let the TCP transport alone shitlist a peer, since other
      transports may be working.  Also display whether TCP connections are
      inbound or outbound on the peers page.
    * Fixed some substantial bugs in the SSU introducers where we wouldn't
      talk to anyone who didn't expose an IP (even if they had introducers),
      among other goofy things.
    * When dealing with SSU introducers, send them all a packet at 3s/6s/9s,
      rather than sending one a packet at 3s, then another a packet at 6s,
      and a third a packet at 9s.
    * Fixed Syndie attachments (oops)
2005-11-30 20:48:25 +00:00
3d18bf870b 2005-11-29 zzz
* Added a link to orion's jump page on the 'key not found' error page.
2005-11-29 17:56:02 +00:00
d8071296eb 2005-11-29 jrandom
* Further Syndie UI cleanup
    * Bundled our patched MultiPartRequest code from jetty (APL2 licensed),
      since it hasn't been applied to the jetty CVS yet [1].  Its packaged
      into syndie.jar and renamed to net.i2p.syndie.web.MultiPartRequest, but
      will be removed as soon as its integrated into Jetty.  This patch allows
      posting content in various character sets.
      [1] http://article.gmane.org/gmane.comp.java.jetty.general/6031
    * Upgraded new installs to the latest stable jetty (5.1.6), though this
      isn't pushed as part of the update yet, as there aren't any critical
      bugs.
2005-11-29 16:58:01 +00:00
c66e3256aa 2005-11-29 jrandom
* Added back in the OSX jbigi, which was accidentally removed a few revs
      back (thanks for the bug report stoerte!)  New installs will get the
      full jbigi, or you can pull the jbigi.jar from CVS by going to
      http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/installer/lib/jbigi/jbigi.jar
      and clicking on the first "download" link, saving that jbigi.jar to
      lib/jbigi.jar in your I2P installation directory.  After restarting your
      router, it should load up fine.
2005-11-29 12:46:34 +00:00
686742a67b 2005-11-27 jrandom
* Inlined the Syndie CSS to reduce the number of HTTP requests (and
      because firefox [and others?] delay rendering until they fetch the css).
    * Make sure we fire the shutdown tasks when regenerating a new identity
      (thanks picsou!)
    * Cleaned up some of the things I b0rked in the 'dynamic keys' mode
    * Don't drop SSU sessions if they're still transmitting data successfully,
      even if there are transmission failures
    * Adjusted the time summarization to display hours after 119m, not 90m
    * Further EepGet cleanup (grr)
2005-11-28 16:02:38 +00:00
cdf94295f3 (1.185) added TheBreton.i2p adab.i2p awup.i2p china.i2p davidkra.i2p
comwiz.i2p dust.i2p eepsites.i2p jmg.i2p kuroneko.i2p
               mywastedlife.i2p site.games.i2p squid2.i2p striker.i2p
               tracker.awup.i2p zzz.i2p
2005-11-26 18:32:22 +00:00
fbf1705c4e * 2005-11-26 0.6.1.6 released 2005-11-26 18:26:22 +00:00
453ecc4208 Further improvements, and works fine for ubergeeks, but not yet for normal
geeks (aka no router console)
2005-11-26 17:19:29 +00:00
d1f2b447ac 2005-11-26 jrandom
* Update the sorting in Syndie to consider children 'newer' than parents,
      even if they have the same message ID (duh)
    * Cleaned up some nav links in Syndie (good idea gloin, spaetz!)
    * Added a bunch of tooltips to Syndie's fields (thanks polecat!)
    * Force support for nonvalidating XML in Jetty (so we can handle GCJ/etc
      better)
2005-11-26 16:51:16 +00:00
70c4560f02 2005-11-26 jrandom
* Be more explicit about what messages we will handle through a client
      tunnel, and how we will handle them.  This cuts off a set of attacks
      that an active adversary could mount, though they're probably nonobvious
      and would require at least some sophistication.
2005-11-26 11:39:32 +00:00
9089fdd2d5 2005-11-26 Raccoon23
* Added support for 'dynamic keys' mode, where the router creates a new
      router identity whenever it detects a substantial change in its public
      address (read: SSU IP or port).  This only offers minimal additional
      protection against trivial attackers, but should provide functional
      improvement for people who have periodic IP changes, since their new
      router address would not be shitlisted while their old one would be.
    * Added further infrastructure for restricted route operation, but its use
      is not recommended.
2005-11-26 09:16:11 +00:00
ef82cc4f20 2005-11-25 jrandom
* Further Syndie UI cleanups
    * Logging cleanup
    * Fixed link to fproxy.tino.i2p (thanks zzz!)
2005-11-26 05:05:52 +00:00
f2c2a5b386 2005-11-25 jrandom
* Don't publish stats for periods we haven't reached yet (thanks zzz!)
    * Cleaned up the syndie threaded display to show the last updated date for
      a subthread, and to highlight threads updated in the last two days.
2005-11-25 11:06:00 +00:00
fc858bc950 replaced the old nonfunctional perl SAM lib with postman's new
implementation (thanks postman!)
2005-11-24 09:41:01 +00:00
dbb4b3d0c2 2005-11-24 jrandom
* Fix to save syndication settings in Syndie (thanks spaetz!)
2005-11-24 08:45:54 +00:00
2b841ad667 2005-11-23 jrandom
* Removed spurious streaming lib RTO increase (it wasn't helpful)
    * Streamlined the tunnel batching to schedule batch transmissions more
      appropriately.
    * Default tunnel pool variance to 2 +0-1 hops
2005-11-23 16:04:52 +00:00
5e094b43b3 2005-11-21 jrandom
* IE doesn't strip SPAN from <button> form fields, so add in a workaround
      within I2PTunnel.
    * Increase the maximum SSU retransmission timeout to accomodate slower or
      more congested links (though SSU's RTO calculation will usually use a
      much lower timeout)
    * Moved the streaming lib timed events off the main timer queues and onto
      a streaming lib specific set of timer queues.  Streaming lib timed
      events are more likely to have lock contention on the I2CP socket while
      other timed events in the router are (largely) independent.
    * Fixed a case sensitive lookup bug (thanks tino!)
    * Syndie cleanup - new edit form on the preview page, and fixed some blog
      links (thanks tino!)
2005-11-21 14:45:04 +00:00
33d57dd545 2005-11-21 jrandom
* IE doesn't strip SPAN from <button> form fields, so add in a workaround
      within I2PTunnel.
    * Increase the maximum SSU retransmission timeout to accomodate slower or
      more congested links (though SSU's RTO calculation will usually use a
      much lower timeout)
    * Moved the streaming lib timed events off the main timer queues and onto
      a streaming lib specific set of timer queues.  Streaming lib timed
      events are more likely to have lock contention on the I2CP socket while
      other timed events in the router are (largely) independent.
    * Fixed a case sensitive lookup bug (thanks tino!)
    * Syndie cleanup - new edit form on the preview page, and fixed some blog
      links (thanks tino!)
2005-11-21 14:37:06 +00:00
61f75b5f09 2005-11-19 jrandom
* Implemented a trivial pure java PMTU backoff strategy, switching between
      a 608 byte MTU and a 1350 byte MTU, depending upon retransmission rates.
    * Fixed new user registration in Syndie (thanks Complication!)
2005-11-20 04:42:16 +00:00
3f65e53592 2005-11-17 jrandom
* More cautious file handling in Syndie
2005-11-17 08:23:45 +00:00
99ae3ee459 2005-11-16 jrandom
* More aggressive I2PTunnel content encoding munging to work around some
      rare HTTP behavior (ignoring q values on Accept-encoding, using gzip
      even when only identity is specified, etc).  I2PTunnelHTTPServer now
      sends "Accept-encoding: \r\n" plus "X-Accept-encoding: x-i2p-gzip\r\n",
      and I2PTunnelHTTPServer handles x-i2p-gzip in either the Accept-encoding
      or X-Accept-encoding headers.  Eepsite operators who do not know to
      check for X-Accept-encoding will simply use the identity encoding.
2005-11-16 11:50:56 +00:00
f7236d7d58 * 2005-11-15 0.6.1.5 released 2005-11-16 03:20:20 +00:00
5182008b38 more stuff 2005-11-16 03:12:04 +00:00
9d030327e6 put rome and jdom in the syndie.war, and fix some deprecation warnings 2005-11-15 12:46:54 +00:00
a15c90d2cc add in a basic limit on the attachment size (trivially circumvented, but for now it'll do)
fixed some links
2005-11-15 11:17:04 +00:00
84c2a713e1 update the post 2005-11-15 10:23:29 +00:00
7db9ce6e5b update the default syndie post, and add in some html hooks for it
only allow 40 chars in the subject within the thread tree
2005-11-15 10:22:57 +00:00
da42f5717b logging cleanup 2005-11-15 06:38:00 +00:00
5241953e5d logging 2005-11-15 06:37:20 +00:00
b1a5f61ba2 cleanup and logging 2005-11-15 06:34:04 +00:00
024a5a1ad4 deal with spaces in filenames cleanly 2005-11-15 06:29:51 +00:00
b031de5404 more cleanup 2005-11-15 06:24:17 +00:00
dceac73951 2005-11-14 jrandom
* Migrate to the new Syndie interface
2005-11-15 00:45:36 +00:00
8f95143488 more syndie cleanup 2005-11-15 00:24:35 +00:00
a8f3043aae * move over the rssimport, and default it to the proxy @ localhost:4444 2005-11-14 05:40:18 +00:00
6c91b2d4a9 migrate to the new system 2005-11-14 02:09:23 +00:00
ddd438de35 protect against spoofing in syndie with a per-jvm-instance nonce (which should prevent the spurious "are you being spoofed" things)
fixed the syndie previewing
2005-11-13 14:15:26 +00:00
16fd46db2b move the admin page over to the new system 2005-11-13 11:04:17 +00:00
1159c155a4 * migrate the post/preview over to the new system
* honor the "force new thread" and "refuse replies (from everyone but me)" functionality
2005-11-13 07:55:38 +00:00
30b4e2aa2a change one's password 2005-11-13 04:47:15 +00:00
ae46fa2e6d allow the user to change their password 2005-11-13 04:46:28 +00:00
9fc34895c9 more careful ops 2005-11-13 03:46:20 +00:00
08be4c7b3d migrated the syndie addressbook 2005-11-13 03:42:52 +00:00
7443457af4 * Ignore <ul>. Add <strong>, <p> and <li>.
* change to make [ and ] use their &#n;.
2005-11-12 17:10:56 +00:00
979a4cfb69 migrate the switchuser and register 2005-11-12 13:01:32 +00:00
ed285871bf migrate the profile page to the new format, and refactor the new format's servlets 2005-11-12 12:14:23 +00:00
8c70b8b32a ircclient too :) 2005-11-12 05:06:57 +00:00
14134694d7 2005-11-11 jrandom
* Add filtering threads by author to Syndie, populated with authors in the
      user's addressbook
    * When creating the default user, add
      "http://syndiemedia.i2p/archive/archive.txt" to their addressbook,
      configured to automatically pull updates.  (what other archives should
      be included?)
    * Tiny servlet to help dole out the new routerconsole themes, and bundle
      the installer/resources/themes/** into ./docs/themes/** on both install
      and update.
2005-11-12 05:03:51 +00:00
807d2d3509 2005-11-11 cervantes
* Initial pass of the routerconsole revamp, starting with I2PTunnel and
	  being progressively rolled out to other sections at later dates.
	  Featuring abstracted W3C strict XHTML1.0 markup, with CSS providing
	  layout and styling.
	* Implemented console themes. Users can create their own themes by
	  creating css files in: {i2pdir}/docs/themes/console/{themename}/
	  and activating it using the routerconsole.theme={themename} advanced
	  config property. Look at the example incomplete "defCon1" theme.
	  Note: This is very much a work in progress. Folks might want to hold-off
	  creating their own skins until the markup has solidified.
	* Added "routerconsole.javascript.disabled=true" to disable console
	  client-side scripting and "routerconsole.css.disabled=true" to remove
	  css styling (only rolled out in the i2ptunnel interface currently)
	* Fixed long standing bug with i2ptunnel client and server edit screens
	  where tunnel count and depth properties would fail to save. Added
	  backup quantity and variance configuration options.
	* Added basic accessibility support (key shortcuts, linear markup, alt and
	  title information and form labels).
	* So far only tested on IE6, Firefox 1.0.6, Opera 8 and lynx.
2005-11-12 02:52:40 +00:00
b222cd43f4 2005-11-11 cervantes
* Initial pass of the routerconsole revamp, starting with I2PTunnel and
	  being progressively rolled out to other sections at later dates.
	  Featuring abstracted W3C strict XHTML1.0 markup, with CSS providing
	  layout and styling.
	* Implemented console themes. Users can create their own themes by
	  creating css files in: {i2pdir}/docs/themes/console/{themename}/
	  and activating it using the routerconsole.theme={themename} advanced
	  config property. Look at the example incomplete "defCon1" theme.
	  Note: This is very much a work in progress. Folks might want to hold-off
	  creating their own skins until the markup has solidified.
	* Added "routerconsole.javascript.disabled=true" to disable console
	  client-side scripting and "routerconsole.css.disabled=true" to remove
	  css styling (only rolled out in the i2ptunnel interface currently)
	* Fixed long standing bug with i2ptunnel client and server edit screens
	  where tunnel count and depth properties would fail to save. Added
	  backup quantity and variance configuration options.
	* Added basic accessibility support (key shortcuts, linear markup, alt and
	  title information and form labels).
	* So far only tested on IE6, Firefox 1.0.6, Opera 8 and lynx.
2005-11-12 02:38:55 +00:00
7f6aa327f2 allow the user to override what login/pass is used for the default user in single user mode 2005-11-11 12:26:17 +00:00
49564a3878 2005-11-11 jrandom
* Default Syndie to single user mode, and automatically log into a default
      user account (additional accounts can be logged into with the 'switch'
      or login pages, and new accounts can be created with the register page).
    * Disable the 'automated' column on the Syndie addressbook unless the user
      is appropriately authorized (good idea Polecat!)
2005-11-11 11:29:15 +00:00
12ddaff0ce .. 2005-11-11 10:09:04 +00:00
a8ea239dcc *cough* 2005-11-11 09:58:03 +00:00
ca391097a9 onwards, christian soldiers 2005-11-11 09:39:48 +00:00
4297edc88f wait until we build the tree before sorting the threads, so we can have the most recently updated thread first 2005-11-11 05:10:38 +00:00
6de4673e9e 2005-11-10 jrandom
* First pass to a new threaded Syndie interface, which isn't enabled by
      default, as its not done yet.
2005-11-11 03:46:36 +00:00
f6979c811f 2005-11-10 jrandom
* First pass to a new threaded Syndie interface, which isn't enabled by
      default, as its not done yet.
2005-11-11 03:41:16 +00:00
bd86483204 2005-11-06 jrandom
* Include SSU establishment failure in the peer profile as a commError,
      as we do for TCP establishment failures.
    * Don't throttle the initial transmission of a message because of ongoing
      retransmissions to a peer, since the initial transmission of a message
      is more valuable than a retransmission (since it has less latency).
    * Cleaned up links to SusiDNS and I2PTunnel (thanks zzz!)
2005-11-06 22:25:17 +00:00
53cf03cec6 no need for ant's IgnoreBlank (and its not in the classpath anyway) 2005-11-05 22:50:14 +00:00
e284a8878b 2005-11-05 jrandom
* Include the most recent ACKs with packets, rather than only sending an
      ack exactly once.  SSU differs from TCP in this regard, as TCP has ever
      increasing sequence numbers, while each message ID in SSU is random, so
      we don't get the benefit of later ACKs implicitly ACKing earlier
      messages.
    * Reduced the max retransmission timeout for SSU
    * Don't try to send messages queued up for a long time waiting for
      establishment.
2005-11-05 21:19:05 +00:00
14cd469c6d 2005-11-05 jrandom
* Include the most recent ACKs with packets, rather than only sending an
      ack exactly once.  SSU differs from TCP in this regard, as TCP has ever
      increasing sequence numbers, while each message ID in SSU is random, so
      we don't get the benefit of later ACKs implicitly ACKing earlier
      messages.
    * Reduced the max retransmission timeout for SSU
    * Don't try to send messages queued up for a long time waiting for
      establishment.
2005-11-05 21:12:57 +00:00
9050d7c218 2005-11-05 dust
* Fix sucker to delete its temporary files.
    * Improve sucker's sml output some.
    * Fix Exception in SMLParser for weird sml.
2005-11-05 11:01:57 +00:00
0ad18cd0ba 2005-10-31 jrandom
* Fix for some syndie reply scenarios (thanks identiguy and CofE!)
    * Removed a potentially infinitely recursive call (oops)
(forgot to commit this file before.  oops)
2005-11-04 03:08:00 +00:00
ca0af146b7 2005-11-03 zzz
* Added a new error page to the eepproxy to differentiate the full 60
      second timeout from the immediate "I don't know this base64" failure.
2005-11-04 01:20:17 +00:00
a2d2b031f4 2005-11-01 jrandom
* Added a few more css elements (thanks identiguy!)
2005-11-02 00:35:21 +00:00
2f36912ac0 2005-10-31 jrandom
* Fix for some syndie reply scenarios (thanks identiguy and CofE!)
    * Removed a potentially infinitely recursive call (oops)
2005-10-31 20:03:11 +00:00
53e32c8e64 *** empty log message *** 2005-10-30 07:38:42 +00:00
10dde610dc 2005-10-30 dust
* Merge sucker into syndie with a rssimport.jsp page.
    * Add getContentType() to EepGet.
    * Make chunked transfer work (better) with EepGet.
    * Do replaceAll("<","&lt;") for logs.
2005-10-30 05:47:55 +00:00
f7c2ae9a3b adjust the exe build filter to only run on linux or windows 2005-10-30 01:07:09 +00:00
ac3b88b9e9 0.6.1.4 2005-10-29 23:22:11 +00:00
60124cdcdc * 2005-10-29 0.6.1.4 released 2005-10-29 23:20:04 +00:00
e7d2281772 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-29 21:43:41 +00:00
52ace2d695 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-29 21:35:24 +00:00
b5a25801b4 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-28 22:26:47 +00:00
3fc0558810 added ttc.i2p 2005-10-28 01:13:10 +00:00
bd9c6ff463 oops (allow cwin=1 for interactive streams) 2005-10-25 20:19:49 +00:00
a0c822af96 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-25 19:23:17 +00:00
4de302101d 2005-10-24 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-25 19:13:53 +00:00
84383c3dab * Enforce delay >= 1 in BlogManager.getUpdateDelay() 2005-10-25 01:24:32 +00:00
a94abb13a4 * Factor archive fetching into a seperate method from the update loop. 2005-10-25 01:03:55 +00:00
ad59ab691b (sun.* is sun specific) 2005-10-24 09:45:00 +00:00
ee9ac31c8b * Instantiate new RemoteArchiveBean for each archive fetched by the updater, to prevent weirdness if the index fetch for archive n+1 fails.
* Add a blocking fetch to EepGetScheduler and RemoteArchiveBean and use them from the updater, to prevent race conditions with multiple archive fetches.
2005-10-24 06:27:56 +00:00
788998307a * Fixed bugs preventing the use of fetchSelectedBulk in the updater 2005-10-24 05:27:22 +00:00
2f8a2879bb no more -Di2p.weakPRNG :) 2005-10-22 18:12:09 +00:00
c7b9525d2c 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-22 18:06:02 +00:00
3fbc6f41af first pass gcj makefile
I have no idea what I'm doing, this just shows you how to do it ;)
2005-10-21 01:30:13 +00:00
6534c84578 Cought a few places where peers could be taken off the list but not removed for the purposes of rarest-first calculations. 2005-10-21 01:09:31 +00:00
3816c79193 Clean up some possible thread safety issues. 2005-10-21 00:10:13 +00:00
8458e4e0af Don't count peers we can't connect to for rarest-first calculations. 2005-10-20 23:28:32 +00:00
0b9e4967a0 The rarest-first sort is stable, so randomize the wantedPieces list to ensure a healthy swarm when you have mostly seeders. 2005-10-20 22:31:28 +00:00
05e2da7c22 Request pieces in rarest-first order, instead of randomly. 2005-10-20 22:05:51 +00:00
13bda1f6d7 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 19:42:13 +00:00
ea22c73a73 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-20 08:56:39 +00:00
aa5f1cb18d 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 23:23:19 +00:00
ab9c6d59cf remove jbigi.jar from the classpath so we don't mistakenly extract & overwrite the
libjbigi.so.  Yeah, this means that i2psnark uses pure java modPow, but it doesn't do
any real heavy lifting anyway, except a DSA signature every 5-10 minutes.  whoop de do.
2005-10-19 23:18:29 +00:00
76655d01d0 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 22:38:45 +00:00
138f7d3b8d 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
2005-10-19 22:02:37 +00:00
df4b998a6a 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-19 05:15:12 +00:00
2d70103f88 2005-10-17 dust
* Exchange the remaining URL with EepGet in Sucker.
    * Allow /TOPIC irc command.
2005-10-18 03:14:01 +00:00
731e26e7d6 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-18 00:39:46 +00:00
f9d3b157f0 Emergency patch to I2PTunnelIRCClient, not sure why zero length strings are getting passed to it. Oh, and my first patch\! 2005-10-15 22:55:14 +00:00
12775c416d * Don't filter IRC "MAP" messages (not critical, but it doesn't hurt) 2005-10-14 16:26:31 +00:00
2ca4e63216 * Fixed Syndie's Sucker to not explicitly reference something only found
in sun's JVM (thanks cervantes!)
2005-10-14 16:02:38 +00:00
918d8f851f 2005-10-14 jrandom
* More explicit filter for linux/PPC building (thanks anon!)
2005-10-14 15:05:26 +00:00
20ea680ff0 * 2005-10-14 0.6.1.3 released
2005-10-14  jrandom
    * Added a key explaining peers.jsp a bit (thanks tethra!)
2005-10-14 13:48:04 +00:00
cabb607211 2005-10-13 dust
* Bundled dust's Sucker for pulling RSS/Atom content into SML, which can
      then be injected into Syndie with the Syndie CLI.
    * Bundled ROME and JDOM (BSD and Apache licensed, respectively) for
      RSS/Atom parsing.
2005-10-13  jrandom
    * SSU retransmission choke bugfix (== != !=)
    * Include initial transmissions in the retransmission choke, so that
      if we are already retransmitting a message, we won't send anything
      to that peer other than that message (or ACKs, if necessary)
2005-10-14 02:15:39 +00:00
00a4761b5e 2005-10-13 jrandom
* SSU retransmission choke bugfix (== != !=)
    * Include initial transmissions in the retransmission choke, so that
      if we are already retransmitting a message, we won't send anything
      to that peer other than that message (or ACKs, if necessary)
2005-10-13 09:18:35 +00:00
3516701272 2005-10-12 jrandom
* Choke SSU retransmissions to a peer while there is already a
      retransmission in flight to them.  This currently lets other initial
      transmissions through, since packet loss is often sporadic, but maybe
      this should block initial transmissions as well?
    * Display the retransmission bytes stat on peers.jsp (thanks bar!)
    * Filter QUIT messages in the I2PTunnelIRCClient proxy
2005-10-13 03:54:54 +00:00
c4d785667a 2005-10-11 jrandom
* Piggyback the SSU partial ACKs with data packets.  This is backwards
      compatible.
    * Syndie RSS renderer bugfix, plus now include the full entry instead of
      just the blurb before the cut.
2005-10-11 21:05:14 +00:00
123e0ba589 2005-10-11 jrandom
* Piggyback the SSU explicit ACKs with data packets (partial ACKs aren't
      yet piggybacked).  This is backwards compatible.
    * SML parser cleanup in Syndie
2005-10-11 07:07:39 +00:00
197237aa32 2005-10-10 dust
* Implemented a new I2PTunnelIRCClient which locally filters inbound and
      outbound IRC commands for anonymity and security purposes, removing all
      CTCP messages except ACTION, as well as stripping the hostname from the
      USER message (while leaving the nick and 'full name').  The IRC proxy
      doesn't use this by default, but you can enable it by creating a new
      "IRC proxy" tunnel on the web interface, or by changing the tunnel type
      to "ircclient" in i2ptunnel.config.
2005-10-10  jrandom
    * I2PTunnel http client config cleanup and stats
    * Minor SSU congestion tweaks and stats
    * Reduced netDb exploration period
2005-10-10 23:05:18 +00:00
f30dc2b480 2005-10-10 dust
* Implemented a new I2PTunnelIRCClient which locally filters inbound and
      outbound IRC commands for anonymity and security purposes, removing all
      CTCP messages except ACTION, as well as stripping the hostname from the
      USER message (while leaving the nick and 'full name').  The IRC proxy
      doesn't use this by default, but you can enable it by creating a new
      "IRC proxy" tunnel on the web interface, or by changing the tunnel type
      to "ircclient" in i2ptunnel.config.
2005-10-10  jrandom
    * I2PTunnel http client config cleanup and stats
    * Minor SSU congestion tweaks and stats
    * Reduced netDb exploration period
2005-10-10 22:58:18 +00:00
d4ff34eacb * Treat petname names as being case insensitive. 2005-10-09 22:56:02 +00:00
978769a05d * Finished syndie address auto-import. If syndie.importAddresses=true in syndie.config, then new addresses will automatically be imported to the router's petname db. If importaddresses=true in a user's config file, then new addresses will automatically be imported to that users pername db, when they're logged in. 2005-10-09 20:16:30 +00:00
993c70f600 2005-10-09 jrandom
* Syndie CLI cleanup for simpler CLI posting.  Usage shown with
      java -jar lib/syndie.jar
    * Beginnings of the Syndie logging cleanup
    * Delete corrupt Syndie posts
2005-10-09 11:35:13 +00:00
5dfa9ad7f6 2005-10-09 jrandom
* Now that the streaming lib works reasonably, set the default inactivity
      event to send a 0 byte keepalive payload, rather than disconnecting the
      stream.  This should cut the irc netsplits and help out with other long
      lived streams.  The default timeout is now less than the old timeout as
      well, so the keepalive will be sent before earlier builds fire their
      fatal timeouts.
2005-10-09 05:46:57 +00:00
f282fe3854 * Fix probable NPE. 2005-10-09 04:01:29 +00:00
9297564555 * getName, getLocation -> getByName, getByLocation 2005-10-09 03:47:12 +00:00
e7ad516685 * Beautify PetNameDB API.
* Start of syndie auto address export.
2005-10-09 03:32:34 +00:00
ad574c8504 2005-10-08 jrandom
* Use the OS clock for stat timing, since it doesn't jump around (though
      still use the NTP'ed clock for display)
    * Added new DH stats
2005-10-08 22:05:46 +00:00
38617fe0a7 fixed link (thanks Jazzy) 2005-10-07 23:45:48 +00:00
cdee5b2c31 * 2005-10-07 0.6.1.2 released
2005-10-07  jrandom
    * Include the 1 second bandwidth usage on the console rather than the
      1 minute rate, as the 1 second value doesn't have the 1m/5m quantization
      issues.
2005-10-07 20:19:04 +00:00
7f6e65c76f 2005-10-07 jrandom
* Allow the I2PTunnelHTTPServer to send back the first few packets of an
      HTTP response quicker, and initialize the streaming lib's cwin more
      carefully.
    * Added a small web UI to the new Syndie scheduled updater.  If you log in
      as a user authorized to use the remote archive funtionality, you can
      request remote archives in your address book to be automatically pulled
      down by checking the "scheduled?" checkbox.
2005-10-07 10:22:59 +00:00
4dd628dbc8 2005-10-05 jrandom
* Allow the first few packets in the stream to fill in their IDs during
      handshake (thanks cervantes, Complication, et al!)  This should fix at
      least some of the intermittent HTTP POST issues.
2005-10-05 23:24:33 +00:00
3b5b48ad8a more cleanup (thanks bar) 2005-10-05 03:48:56 +00:00
c4cac3f3f1 we dont need no grammar 2005-10-05 01:45:21 +00:00
4a49e98c31 caps (thanks bar) 2005-10-05 01:11:25 +00:00
0c0e269e72 youspell (thanks bar) 2005-10-05 00:52:27 +00:00
70b6f97abe 2005-10-04 jrandom
* Syndie patch for single user remote archives (thanks nickless_head!)
    * Handle an invalid netDb store (thanks Complication!)
2005-10-04 23:43:05 +00:00
0013677b83 v2mail, not v2mail2 2005-10-04 23:34:19 +00:00
a98ceda64d postmans changes for i2pmail stuff 2005-10-04 23:33:15 +00:00
91ea1d0395 include two specific issues with freenet 2005-10-04 20:18:18 +00:00
4aa65c3bb3 2005-10-04 jrandom
* Further reduction in unnecessary streaming packets.
2005-10-04 07:36:25 +00:00
0a1f59940a 2005-10-03 jrandom
* Properly reject unroutable IP addresses *cough*
2005-10-04 02:05:52 +00:00
f540dc798b * Changed default update delay to twelve hours, and enforced a minimum
delay of one hour.
2005-10-04 00:27:34 +00:00
30f6f26a68 * Implemented a Syndie auto-updater. 2005-10-03 18:55:09 +00:00
ea3bf3ffc8 might as well commit this draft 2005-10-03 06:21:08 +00:00
831d5ac70c not used 2005-10-03 06:20:47 +00:00
1962867ad9 * Actually implement the bit that returns a 304 if archive.txt hasn't changed. 2005-10-03 02:54:34 +00:00
914 changed files with 60708 additions and 24962 deletions

101
Makefile.gcj Normal file
View File

@ -0,0 +1,101 @@
# 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
JAR_ROUTER=router.jar
JAR_JBIGI=jbigi.jar
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
JAR_CONSOLE=\
i2psnark.jar \
javax.servlet.jar \
commons-el.jar \
commons-logging.jar \
jasper-runtime.jar \
ant-apache-bcel.jar \
ant.jar \
jasper-compiler.jar \
org.mortbay.jetty.jar \
routerconsole.jar
JAR_SUCKER=jdom.jar rome-0.7.jar sucker.jar
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
# unfortunately, its not quite ready for most end users, as the
# ${JAR_CONSOLE} fails to compile with:
# org/apache/commons/logging/impl/LogKitLogger.java: In class 'org.apache.commons.logging.impl.LogKitLogger':
# .../LogKitLogger.java: In constructor '(java.lang.String)':
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:104: error: cannot find file for class org.apache.log.Hierarchy
# .../LogKitLogger.java:104: confused by earlier errors, bailing out
#${JAR_CONSOLE}\
#${JAR_XML} \
#${JAR_SUCKER}
#${JAR_CONSOLE}
SYSTEM_PROPS=-DloggerFilenameOverride=logs/log-router-@.txt \
-Dorg.mortbay.http.Version.paranoid=true \
-Dorg.mortbay.util.FileResource.checkAliases=false \
-Dorg.mortbay.xml.XmlParser.NotValidating=true
#SYSTEM_PROPS=-Di2p.weakPRNG=true
OPTIMIZE=-O2
#OPTIMIZE=-O3
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} ${OPTIMIZE} -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} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.FortunaStandalone
@cd build ; ${GCJ} ${OPTIMIZE} -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} ${OPTIMIZE} -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} ${OPTIMIZE} -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} ${OPTIMIZE} -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 ; time ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
@ls -l ${NATIVE_DIR}/libi2p.so
@echo "* libi2p.so built"

View File

@ -17,6 +17,10 @@
# Contains the addresses from your master address book
# and your subscribed address books. (../userhosts.txt)
#
# private_addressbook The path to the private address book used by the router.
# This is used only by the router and SusiDNS.
# It is not published by addressbook. (../privatehosts.txt)
#
# published_addressbook The path to the copy of your address book made
# available on i2p. (../eepsite/docroot/hosts.txt)
#
@ -35,6 +39,7 @@ proxy_host=localhost
proxy_port=4444
master_addressbook=myhosts.txt
router_addressbook=../userhosts.txt
private_addressbook=../privatehosts.txt
published_addressbook=../eepsite/docroot/hosts.txt
log=log.txt
subscriptions=subscriptions.txt

View File

@ -21,11 +21,11 @@
package addressbook;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.i2p.I2PAppContext;
import net.i2p.util.EepGet;
@ -66,6 +66,7 @@ public class AddressBook {
* where key is a human readable name, and value is a base64 i2p
* destination.
*/
/* unused
public AddressBook(String url, String proxyHost, int proxyPort) {
this.location = url;
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
@ -79,22 +80,26 @@ public class AddressBook {
}
new File("addressbook.tmp").delete();
}
*/
/**
* Construct an AddressBook from the Subscription subscription. If the
* address book at subscription has not changed since the last time it was
* read or cannot be read, return an empty AddressBook.
* Set a maximum size of the remote book to make it a little harder for a malicious book-sender.
*
* @param subscription
* A Subscription instance pointing at a remote address book.
*/
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
this.location = subscription.getLocation();
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
proxyHost, proxyPort, 0, "addressbook.tmp",
subscription.getLocation(), true, subscription.getEtag());
get.fetch();
subscription.setEtag(get.getETag());
proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, "addressbook.tmp", null,
subscription.getLocation(), true, subscription.getEtag(), subscription.getLastModified(), null);
if (get.fetch()) {
subscription.setEtag(get.getETag());
subscription.setLastModified(get.getLastModified());
}
try {
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
} catch (IOException exp) {
@ -151,6 +156,37 @@ public class AddressBook {
return this.addresses.toString();
}
/**
* Do basic validation of the hostname and dest
* hostname was already converted to lower case by ConfigParser.parse()
*/
private static boolean valid(String host, String dest) {
return
host.endsWith(".i2p") &&
host.length() > 4 &&
host.length() <= 67 && // 63 + ".i2p"
(! host.startsWith(".")) &&
(! host.startsWith("-")) &&
host.indexOf(".-") < 0 &&
host.indexOf("-.") < 0 &&
host.indexOf("..") < 0 &&
// IDN - basic check, not complete validation
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
// some reserved names that may be used for local configuration someday
(! host.equals("proxy.i2p")) &&
(! host.equals("router.i2p")) &&
(! host.equals("console.i2p")) &&
(! host.endsWith(".proxy.i2p")) &&
(! host.endsWith(".router.i2p")) &&
(! host.endsWith(".console.i2p")) &&
dest.length() == 516 &&
dest.endsWith("AAAA") &&
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
;
}
/**
* Merge this AddressBook with AddressBook other, writing messages about new
* addresses or conflicts to log. Addresses in AddressBook other that are
@ -169,7 +205,7 @@ public class AddressBook {
String otherKey = (String) otherIter.next();
String otherValue = (String) other.addresses.get(otherKey);
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
if (valid(otherKey, otherValue)) {
if (this.addresses.containsKey(otherKey) && !overwrite) {
if (!this.addresses.get(otherKey).equals(otherValue)
&& log != null) {
@ -184,7 +220,7 @@ public class AddressBook {
this.modified = true;
if (log != null) {
log.append("New address " + otherKey
+ " added to address book.");
+ " added to address book. From: " + other.location);
}
}
}

View File

@ -21,12 +21,19 @@
package addressbook;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Utility class providing methods to parse and write files in config file
@ -60,6 +67,7 @@ public class ConfigParser {
* a single key, value pair on each line, in the format: key=value. Lines
* starting with '#' or ';' are considered comments, and ignored. Lines that
* are obviously not in the format key=value are also ignored.
* The key is converted to lower case.
*
* @param input
* A BufferedReader with lines in key=value format to parse into
@ -77,7 +85,7 @@ public class ConfigParser {
inputLine = ConfigParser.stripComments(inputLine);
String[] splitLine = inputLine.split("=");
if (splitLine.length == 2) {
result.put(splitLine[0].trim(), splitLine[1].trim());
result.put(splitLine[0].trim().toLowerCase(), splitLine[1].trim());
}
inputLine = input.readLine();
}
@ -301,4 +309,4 @@ public class ConfigParser {
new FileWriter(file, false)));
}
}
}

View File

@ -21,12 +21,12 @@
package addressbook;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Main class of addressbook. Performs updates, and runs the main loop.
@ -98,7 +98,8 @@ public class Daemon {
AddressBook router = new AddressBook(routerFile);
List defaultSubs = new LinkedList();
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
defaultSubs.add("http://www.i2p2.i2p/hosts.txt");
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
etagsFile, lastModifiedFile, defaultSubs, (String) settings
@ -143,7 +144,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 +189,4 @@ public class Daemon {
_instance.notifyAll();
}
}
}
}

View File

@ -22,10 +22,10 @@
package addressbook;
import javax.servlet.GenericServlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* A wrapper for addressbook to allow it to be started as a web application.

View File

@ -21,13 +21,13 @@
package addressbook;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* A list of Subscriptions loaded from a file.

View File

@ -10,16 +10,14 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Properties;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;
import org.jibble.pircbot.PircBot;

View File

@ -1,39 +0,0 @@
The Heartbeat GUI loads up the stat files generated by the Heartbeat
engine and renders them visually, offering a way to drill through different
data points and take snapshots as things change (by saving particular stat
files for later). The GUI itself doesn't need to be on the same machine
as the Heartbeat engine - it pulls the stat files through any URL - even
through the EepProxy.
An example Heartbeat GUI config file follows
# how often do we want to pull new data to render
refreshFrequency=60
## for each peer test we may want to include in the GUI:
# where to find the current stat file (URL or filename)
stat.0.location=http://dev.i2p.net/stats/heartbeatStat_khWY_30s_1kb.txt
## optional entries for each peer test describing what we want shown
## (and how we want it shown)
# do we want to plot the send time (from when the ping was sent until the pong server got it)?
stat.0.plot.current.send=true
# do we want to plot the receive time (from when the pong was sent until reception)?
stat.0.plot.current.receive=true
# do we want to plot the lost messages?
stat.0.plot.current.lost=true
# what color should the current lines be rendered in?
stat.0.plot.current.color=BLUE
## optional entries for each peer test describing what averages we want
## rendered
# plot 1 minute send average?
stat.0.plot.1m.send=true
# plot 1 minute receive average?
stat.0.plot.1m.receive=true
# plot 1 minute lost message average?
stat.0.plot.1m.lost=true
# what color should the 1 minute averages be rendered as?
stat.0.plot.1m.color=GREEN
## repeated for all of the averaged periods, e.g.
## stat.0.plot.30m, .60m, 1440m (1 day)
There may be some other options, such as where to store snapshot files, whether
to generate PNG images, etc.

View File

@ -1,122 +0,0 @@
Heartbeat
Application layer tool for monitoring the long term health of the
network by periodically testing peers, generating stats, and
rendering them visually. The engine (both server and client) should
work headless and seperate from the GUI, exposing the data in a simple
to parse (and human readable) text file for each peer being tested.
The GUI then periodically refreshes itself by loading those files (
either locally or from a URL) and renders the current state accordingly,
giving users a way to check that the network is alive, devs a tool to
both monitor the state of the network and to debug different situations (by
accessing the stat file - either live or archived).
The heartbeat configuration file is organized as a standard properties
file (by default located at heartbeat.config, but that can be overridden by
passing a filename as the first argument to the Heartbeat command):
# where the router is located (default is localhost)
i2cpHost=localhost
# I2CP port for the router (default is 7654)
i2cpPort=4001
# How many hops we want the router to put in our tunnels (default is 2)
numHops=2
# where our private destination keys are located - if this doesn't exist,
# a new one will be created and saved there (by default, heartbeat.keys)
privateDestinationFile=heartbeat_r2.keys
## peer tests configured below:
# destination peer for test 0
peer.0.peer=[destination in base64]
# where will we write out the stat data?
peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
# how many minutes will we keep stats for?
peer.0.statDuration=30
# how often will we write out new stat data (in seconds)?
peer.0.statFrequency=60
# how often will we send a ping to the peer (in seconds)?
peer.0.sendFrequency=30
# how many bytes will be included in the ping?
peer.0.sendSize=1024
# take a guess...
peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
# we can keep track of a few moving averages - this value includes a whitespace
# delimited list of numbers, each specifying a period to calculate the average
# over (in minutes)
peer.0.averagePeriods=1 5 30
## repeat the peer.0.* for as many tests as desired, incrementing as necessary
If there are no peer.* lines, it will simply run a pong server. If any data is
missing, it will use the defaults (though there are no defaults for peer.* lines) -
running the Heartbeat app with no heartbeat configuration file whatsoever will create
a new pong server (storing its keys at heartbeat.keys) and using the I2P router at
localhost:7654.
The stat file generated for each set of peer.n.* lines contains the current state
of the test, its averages, as well as any other interesting data points. An example
stat file follows (hopefully it is self explanatory):
peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
peerDest [base 64 of the full destination]
localDest [base 64 of the full destination]
numTunnelHops 2
comment Test with localhost sending 30KB every 20 seconds
sendFrequency 20
sendSize 30720
sessionStart 20040409.22:51:10.915
currentTime 20040409.23:31:39.607
numPending 2
lifetimeSent 118
lifetimeRecv 113
#averages minutes sendMs recvMs numLost
periodAverage 1 1843 771 0
periodAverage 5 786 752 1
periodAverage 30 855 735 3
#action status date and time sent sendMs replyMs
EVENT OK 20040409.23:21:44.742 691 670
EVENT OK 20040409.23:22:05.201 671 581
EVENT OK 20040409.23:22:26.301 1182 1452
EVENT OK 20040409.23:22:47.322 24304 1723
EVENT OK 20040409.23:23:08.232 2293 1081
EVENT OK 20040409.23:23:29.332 1392 641
EVENT OK 20040409.23:23:50.262 641 761
EVENT OK 20040409.23:24:11.102 651 701
EVENT OK 20040409.23:24:31.401 841 621
EVENT OK 20040409.23:24:52.061 651 681
EVENT OK 20040409.23:25:12.480 701 1623
EVENT OK 20040409.23:25:32.990 1442 1212
EVENT OK 20040409.23:25:54.230 591 631
EVENT OK 20040409.23:26:14.620 620 691
EVENT OK 20040409.23:26:35.199 1793 1432
EVENT OK 20040409.23:26:56.570 661 641
EVENT OK 20040409.23:27:17.200 641 660
EVENT OK 20040409.23:27:38.120 611 921
EVENT OK 20040409.23:27:58.699 831 621
EVENT OK 20040409.23:28:19.559 801 661
EVENT OK 20040409.23:28:40.279 601 611
EVENT OK 20040409.23:29:00.648 601 621
EVENT OK 20040409.23:29:21.288 701 661
EVENT LOST 20040409.23:29:41.828
EVENT LOST 20040409.23:30:02.327
EVENT LOST 20040409.23:30:22.656
EVENT OK 20040409.23:31:24.305 1843 771
The actual ping and pong messages sent are formatted trivially -
ping messages contain
$from $series $type $sentOn $size $payload
while pong messages contain
$from $series $type $sentOn $receivedOn $size $payload
$series is a number describing the sending client's test (so that you can
ping the same peer with different configurations concurrently, varying things
like the frequency and size of the message, window, etc).
They are sent as raw binary messages though, so see I2PAdapter.sendPing(..)
and I2PAdapter.sendPong(..) for the details.
To get valid measurements, of course, you will want to make sure that
both the heartbeat client and pong server have synchronized clocks (even
more so than I2P requires). It is highly recommended that only NTP
synchronized peers be used for heartbeat tests.

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="heartbeat">
<target name="all" depends="clean, buildGUI" />
<target name="build" depends="builddep, jar" />
<target name="buildGUI" depends="build, jarGUI" />
<target name="builddep">
<ant dir="../../../core/java/" target="build" />
</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" includes="**/*.java" excludes="net/i2p/heartbeat/gui/**" classpath="../../../core/java/build/i2p.jar" />
</target>
<target name="compileGUI">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
<src path="src/" />
<classpath path="../../../core/java/build/i2p.jar" />
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
</javac>
</target>
<target name="jar" depends="compile">
<jar destfile="./build/heartbeat.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="net.i2p.heartbeat.Heartbeat" />
<attribute name="Class-Path" value="i2p.jar heartbeat.jar" />
</manifest>
</jar>
</target>
<target name="jarGUI" depends="compileGUI">
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
<jar destfile="./build/heartbeatGUI.jar" basedir="./build/obj" includes="**">
<manifest>
<attribute name="Main-Class" value="net.i2p.heartbeat.gui.HeartbeatMonitor" />
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar heartbeatGUI.jar i2p.jar" />
</manifest>
</jar>
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
packagenames="*"
use="true"
access="package"
splitindex="true"
windowtitle="I2P heartbeat monitor" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../../core/java/" target="cleandep" />
<ant dir="../../../core/java/" target="cleandep" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../../core/java/" target="distclean" />
</target>
</project>

View File

@ -1,468 +0,0 @@
package net.i2p.heartbeat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Define the configuration for testing against one particular peer as a client
*/
public class ClientConfig {
private static final Log _log = new Log(ClientConfig.class);
private Destination _peer;
private Destination _us;
private String _statFile;
private int _statDuration;
private int _statFrequency;
private int _sendFrequency;
private int _sendSize;
private int _numHops;
private String _comment;
private int _averagePeriods[];
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_PREFIX = "peer.";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_PEER = ".peer";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_STATFILE = ".statFile";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_STATDURATION = ".statDuration";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_STATFREQUENCY = ".statFrequency";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_SENDFREQUENCY = ".sendFrequency";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_SENDSIZE = ".sendSize";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_COMMENT = ".comment";
/**
* @seeRoutine ClientConfig#load
* @seeRoutine ClientConfig#store
*/
public static final String PROP_AVERAGEPERIODS = ".averagePeriods";
/**
* Default constructor...
*/
public ClientConfig() {
this(null, null, null, -1, -1, -1, -1, 0, null, null);
}
/**
* Create a dummy client config to be fetched from the specified location
* @param location the location to fetch from
*/
public ClientConfig(String location) {
this(null, null, location, -1, -1, -1, -1, 0, null, null);
}
/**
* @param peer who we will test against
* @param us who we are
* @param statLocation where the stat data should be stored/fetched
* @param duration how many minutes to keep events for
* @param statFreq how often to write out stats
* @param sendFreq how often to send pings
* @param sendSize how large the pings should be
* @param numHops how many hops is the current Heartbeat app using
* @param comment describe this test
* @param averagePeriods list of minutes to summarize over
*/
public ClientConfig(Destination peer, Destination us, String statLocation, int duration, int statFreq, int sendFreq,
int sendSize, int numHops, String comment, int averagePeriods[]) {
_peer = peer;
_us = us;
_statFile = statLocation;
_statDuration = duration;
_statFrequency = statFreq;
_sendFrequency = sendFreq;
_sendSize = sendSize;
_numHops = numHops;
_comment = comment;
_averagePeriods = averagePeriods;
}
/**
* Retrieves the peer to test against
*
* @return the Destination (peer)
*/
public Destination getPeer() {
return _peer;
}
/**
* Sets the peer to test against
*
* @param peer the Destination (peer)
*/
public void setPeer(Destination peer) {
_peer = peer;
}
/**
* Retrieves who we are when we test
*
* @return the Destination (us)
*/
public Destination getUs() {
return _us;
}
/**
* Sets who we are when we test
*
* @param us the Destination (us)
*/
public void setUs(Destination us) {
_us = us;
}
/**
* Retrieves the location to write the current stats to
*
* @return the name of the file
*/
public String getStatFile() {
return _statFile;
}
/**
* Sets the name of the location we write the current stats to
*
* @param statFile the name of the file
*/
public void setStatFile(String statFile) {
_statFile = statFile;
}
/**
* Retrieves how many minutes of statistics should be maintained within the window for this client
*
* @return the number of minutes
*/
public int getStatDuration() {
return _statDuration;
}
/**
* Sets how many minutes of statistics should be maintained within the window for this client
*
* @param durationMinutes the number of minutes
*/
public void setStatDuration(int durationMinutes) {
_statDuration = durationMinutes;
}
/**
* Retrieves how frequently the stats are written out (in seconds)
*
* @return the frequency in seconds
*/
public int getStatFrequency() {
return _statFrequency;
}
/**
* Sets how frequently the stats are written out (in seconds)
*
* @param freqSeconds the frequency in seconds
*/
public void setStatFrequency(int freqSeconds) {
_statFrequency = freqSeconds;
}
/**
* Retrieves how frequenty we send messages to the peer (in seconds)
*
* @return the frequency in seconds
*/
public int getSendFrequency() {
return _sendFrequency;
}
/**
* Sets how frequenty we send messages to the peer (in seconds)
*
* @param freqSeconds the frequency in seconds
*/
public void setSendFrequency(int freqSeconds) {
_sendFrequency = freqSeconds;
}
/**
* Retrieves how many bytes the ping messages should be (min values ~700, max ~32KB)
*
* @return the size in bytes
*/
public int getSendSize() {
return _sendSize;
}
/**
* Sets how many bytes the ping messages should be (min values ~700, max ~32KB)
*
* @param numBytes the size in bytes
*/
public void setSendSize(int numBytes) {
_sendSize = numBytes;
}
/**
* Retrieves the brief, 1 line description of the test. Useful comments are along the lines of "The peer is located on a fast router and connection with 2
* hop tunnels".
*
* @return the brief comment
*/
public String getComment() {
return _comment;
}
/**
* Sets a brief, 1 line description (comment) of the test.
*
* @param comment the brief comment
*/
public void setComment(String comment) {
_comment = comment;
}
/**
* Retrieves the periods that the client's tests should be averaged over.
*
* @return list of periods (in minutes) that the data should be averaged over, or null
*/
public int[] getAveragePeriods() {
return _averagePeriods;
}
/**
* Sets the periods that the client's tests should be averaged over.
*
* @param periods the list of periods (in minutes) that the data should be averaged over, or null
*/
public void setAveragePeriods(int periods[]) {
_averagePeriods = periods;
}
/**
* Make sure we're keeping track of the average over the given time period.
*
* @param minutes how many minutes to monitor
*/
public void addAveragePeriod(int minutes) {
if (_averagePeriods != null) {
for (int i = 0; i < _averagePeriods.length; i++) {
if (_averagePeriods[i] == minutes)
return;
}
}
int numPeriods = 1;
if (_averagePeriods != null)
numPeriods += _averagePeriods.length;
int periods[] = new int[numPeriods];
if (_averagePeriods != null)
System.arraycopy(_averagePeriods, 0, periods, 0, _averagePeriods.length);
periods[periods.length-1] = minutes;
Arrays.sort(periods);
_averagePeriods = periods;
}
/**
* Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels
*
* @return the number of hops
*/
public int getNumHops() {
return _numHops;
}
/**
* Sets how many hops this test engine is configured to use for its outbound and inbound tunnels
*
* @param numHops the number of hops
*/
public void setNumHops(int numHops) {
_numHops = numHops;
}
/**
* Load the client config from the properties specified, deriving the current config entry from the peer number.
*
* @param clientConfig the properties to load from
* @param peerNum the number associated with the peer
* @return true if it was loaded correctly, false if there were errors
*/
public boolean load(Properties clientConfig, int peerNum) {
if ((clientConfig == null) || (peerNum < 0)) return false;
String peerVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_PEER);
String statFileVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFILE);
String statDurationVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATDURATION);
String statFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY);
String sendFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY);
String sendSizeVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE);
String commentVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_COMMENT);
String periodsVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS);
if ((peerVal == null) || (statFileVal == null) || (statDurationVal == null) || (statFrequencyVal == null)
|| (sendFrequencyVal == null) || (sendSizeVal == null)) {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Peer number " + peerNum + " does not exist");
}
return false;
}
try {
int duration = getInt(statDurationVal);
int statFreq = getInt(statFrequencyVal);
int sendFreq = getInt(sendFrequencyVal);
int sendSize = getInt(sendSizeVal);
if ((duration <= 0) || (statFreq <= 0) || (sendFreq < 0) || (sendSize <= 0)) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("Invalid client config: duration [" + statDurationVal + "] stat frequency ["
+ statFrequencyVal + "] send frequency [" + sendFrequencyVal + "] send size ["
+ sendSizeVal + "]");
}
return false;
}
statFileVal = statFileVal.trim();
if (statFileVal.length() <= 0) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("Stat file is blank for peer " + peerNum);
}
return false;
}
Destination d = new Destination();
d.fromBase64(peerVal);
if (commentVal == null) {
commentVal = "";
}
commentVal = commentVal.trim();
commentVal = commentVal.replace('\n', '_');
List periods = new ArrayList(4);
if (periodsVal != null) {
StringTokenizer tok = new StringTokenizer(periodsVal);
while (tok.hasMoreTokens()) {
String periodVal = tok.nextToken();
int minutes = getInt(periodVal);
if (minutes > 0) {
periods.add(new Integer(minutes));
}
}
}
int avgPeriods[] = new int[periods.size()];
for (int i = 0; i < periods.size(); i++) {
avgPeriods[i] = ((Integer) periods.get(i)).intValue();
}
_comment = commentVal;
_statDuration = duration;
_statFrequency = statFreq;
_sendFrequency = sendFreq;
_sendSize = sendSize;
_statFile = statFileVal;
_peer = d;
_averagePeriods = avgPeriods;
return true;
} catch (DataFormatException dfe) {
_log.error("Peer destination for " + peerNum + " was invalid: " + peerVal);
return false;
}
}
/**
* Store the client config to the properties specified, deriving the current config entry from the peer number.
*
* @param clientConfig the properties to store to
* @param peerNum the number associated with the peer
* @return true if it was stored correctly, false if there were errors
*/
public boolean store(Properties clientConfig, int peerNum) {
if ((_peer == null) || (_sendFrequency < 0) || (_sendSize <= 0) || (_statDuration <= 0)
|| (_statFrequency <= 0) || (_statFile == null)) { return false; }
String comment = _comment;
if (comment == null) {
comment = "";
}
comment = comment.trim();
comment = comment.replace('\n', '_');
StringBuffer buf = new StringBuffer(32);
if (_averagePeriods != null) {
for (int i = 0; i < _averagePeriods.length; i++) {
buf.append(_averagePeriods[i]).append(' ');
}
}
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_PEER, _peer.toBase64());
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFILE, _statFile);
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATDURATION, _statDuration + "");
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY, _statFrequency + "");
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY, _sendFrequency + "");
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE, _sendSize + "");
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_COMMENT, comment);
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS, buf.toString());
return true;
}
private static final int getInt(String val) {
if (val == null) return -1;
try {
int i = Integer.parseInt(val);
return i;
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Value [" + val + "] is not a valid integer");
}
return -1;
}
}
}

View File

@ -1,133 +0,0 @@
package net.i2p.heartbeat;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Responsible for actually conducting the tests, coordinating the storing of the
* stats, and the management of the rates. This has its own thread specific for
* pumping data around as well.
*
*/
class ClientEngine {
private static final Log _log = new Log(ClientEngine.class);
/** who can send our pings? */
private Heartbeat _heartbeat;
/** actual test state */
private PeerData _data;
/** have we been stopped? */
private boolean _active;
/** used to generate engine IDs */
private static int __id = 0;
/** this engine's id, unique to the {test,sendingClient,startTime} */
private int _id;
private static PeerDataWriter writer = new PeerDataWriter();
/**
* Create a new engine that will send its pings through the given heartbeat
* system, and will coordinate the test according to the configuration specified.
* @param heartbeat the Heartbeat to send pings through
* @param config the Configuration to load configuration from =p
*/
public ClientEngine(Heartbeat heartbeat, ClientConfig config) {
_heartbeat = heartbeat;
_data = new PeerData(config);
_active = false;
_id = ++__id;
}
/** stop sending any more pings or writing any more state */
public void stopEngine() {
_active = false;
if (_log.shouldLog(Log.INFO))
_log.info("Stopping engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
}
/** start up the test (this does not block, as it fires up the test thread) */
public void startEngine() {
_active = true;
I2PThread t = new I2PThread(new ClientRunner());
t.setName("HeartbeatClient " + _id);
t.start();
}
/**
* Who are we testing?
* @return the Destination (peer) we're testing
*/
public Destination getPeer() {
return _data.getConfig().getPeer();
}
/**
* What is our series identifier (used to locally identify a test)
* @return the series identifier
*/
public int getSeriesNum() {
return _id;
}
/**
* receive notification from the heartbeat system that a pong was received in
* reply to a ping we have sent.
*
* @param sentOn when did we send the ping?
* @param replyOn when did the peer send the pong?
*/
public void receivePong(long sentOn, long replyOn) {
_data.pongReceived(sentOn, replyOn);
}
/** fire off a new ping */
private void doSend() {
long now = Clock.getInstance().now();
_data.addPing(now);
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
}
/** our actual heartbeat pumper - this drives the test */
private class ClientRunner implements Runnable {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Starting engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
// when do we need to send the next PING?
long nextSend = Clock.getInstance().now();
// when do we need to write out the next state data?
long nextWrite = Clock.getInstance().now();
while (_active) {
if (Clock.getInstance().now() >= nextSend) {
doSend();
nextSend = Clock.getInstance().now() + _data.getConfig().getSendFrequency() * 1000;
}
if (Clock.getInstance().now() >= nextWrite) {
boolean written = writer.persist(_data);
if (!written) {
if (_log.shouldLog(Log.ERROR)) _log.error("Unable to write the client state data");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Client state data written");
}
}
_data.cleanup();
long timeToWait = nextSend - Clock.getInstance().now();
if (timeToWait > 0) {
try {
Thread.sleep(timeToWait);
} catch (InterruptedException ie) {
}
}
}
}
}
}

View File

@ -1,254 +0,0 @@
package net.i2p.heartbeat;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import net.i2p.data.Destination;
import net.i2p.util.Log;
/**
* Main driver for the heartbeat engine, loading 0 or more tests, firing
* up a ClientEngine for each, and serving as a pong server. If there isn't
* a configuration file, or if the configuration file doesn't specify any tests,
* it simply sits around as a pong server, passively responding to whatever is
* sent its way. <p />
*
* The config file format is examplified below:
* <pre>
* # where the router is located (default is localhost)
* i2cpHost=localhost
* # I2CP port for the router (default is 7654)
* i2cpPort=4001
* # How many hops we want the router to put in our tunnels (default is 2)
* numHops=2
* # where our private destination keys are located - if this doesn't exist,
* # a new one will be created and saved there (by default, heartbeat.keys)
* privateDestinationFile=heartbeat_r2.keys
* # where do we want to export the plain base64 of our destination?
* publicDestinationFile=heartbeat_r2.txt
*
* ## peer tests configured below:
*
* # destination peer for test 0
* peer.0.peer=[destination in base64]
* # where will we write out the stat data?
* peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
* # how many minutes will we keep stats for?
* peer.0.statDuration=30
* # how often will we write out new stat data (in seconds)?
* peer.0.statFrequency=60
* # how often will we send a ping to the peer (in seconds)?
* peer.0.sendFrequency=30
* # how many bytes will be included in the ping?
* peer.0.sendSize=1024
* # take a guess...
* peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
* # we can keep track of a few moving averages - this value includes a whitespace
* # delimited list of numbers, each specifying a period to calculate the average
* # over (in minutes)
* peer.0.averagePeriods=1 5 30
* ## repeat the peer.0.* for as many tests as desired, incrementing as necessary
* </pre>
*
*/
public class Heartbeat {
private static final Log _log = new Log(Heartbeat.class);
/** location containing this heartbeat's config */
private String _configFile;
/** clientNum (Integer) to ClientConfig mapping */
private Map _clientConfigs;
/** series num (Integer) to ClientEngine mapping */
private Map _clientEngines;
/** helper class for managing our I2P send/receive and message formatting */
private I2PAdapter _adapter;
/** our own callback that the I2PAdapter notifies on ping or pong messages */
private PingPongAdapter _eventAdapter;
/** if there are no command line arguments, load the config from "heartbeat.config" */
public static final String CONFIG_FILE_DEFAULT = "heartbeat.config";
/**
* build up a new heartbeat manager, but don't actually do anything
* @param configFile the name of the configuration file
*/
public Heartbeat(String configFile) {
_configFile = configFile;
_clientConfigs = new HashMap();
_clientEngines = new HashMap();
_eventAdapter = new PingPongAdapter();
_adapter = new I2PAdapter();
_adapter.setListener(_eventAdapter);
}
private Heartbeat() {
}
/** load up the config data (but don't build any engines or start them up) */
public void loadConfig() {
Properties props = new Properties();
FileInputStream fin = null;
File configFile = new File(_configFile);
if (configFile.exists()) {
try {
fin = new FileInputStream(_configFile);
props.load(fin);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error reading the config data", ioe);
}
} finally {
if (fin != null) try {
fin.close();
} catch (IOException ioe) {
}
}
}
loadBaseConfig(props);
loadClientConfigs(props);
}
/**
* send a ping message to the peer
*
* @param peer peer to ping
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
* @param now current time to be sent in the ping (so we can watch for it in the pong)
* @param size total message size to send
*/
void sendPing(Destination peer, int seriesNum, long now, int size) {
if (_adapter.getIsConnected()) _adapter.sendPing(peer, seriesNum, now, size);
}
/**
* load up the base data (I2CP config, etc)
* @param props the properties to load from
*/
private void loadBaseConfig(Properties props) {
_adapter.loadConfig(props);
}
/**
* load up all of the test config data
* @param props the properties to load from
* */
private void loadClientConfigs(Properties props) {
int i = 0;
while (true) {
ClientConfig config = new ClientConfig();
if (!config.load(props, i)) {
break;
}
_clientConfigs.put(new Integer(i), config);
i++;
}
}
/** connect to the network */
private void connect() {
boolean connected = _adapter.connect();
if (!connected) _log.error("Unable to connect to the router");
}
/** disconnect from the network */
private void disconnect() { /* UNUSED */
_adapter.disconnect();
}
/** start up all of the tests */
public void startEngines() {
for (Iterator iter = _clientConfigs.values().iterator(); iter.hasNext();) {
ClientConfig config = (ClientConfig) iter.next();
ClientEngine engine = new ClientEngine(this, config);
config.setUs(_adapter.getLocalDestination());
config.setNumHops(_adapter.getNumHops());
_clientEngines.put(new Integer(engine.getSeriesNum()), engine);
engine.startEngine();
}
}
/** stop all of the tests */
public void stopEngines() {
for (Iterator iter = _clientEngines.values().iterator(); iter.hasNext();) {
ClientEngine engine = (ClientEngine) iter.next();
engine.stopEngine();
}
_clientEngines.clear();
}
/**
* Fire up a new heartbeat system, waiting until, well, forever. Builds
* a new heartbeat system, loads the config, connects to the network, starts
* the engines, and then sits back and relaxes, responding to any pings and
* running any tests. <p />
*
* <code> <b>Usage: </b> Heartbeat [<i>configFileName</i>]</code> <p />
* @param args the list of args passed to the program from the command-line
*/
public static void main(String args[]) {
String configFile = CONFIG_FILE_DEFAULT;
if (args.length == 1) {
configFile = args[0];
}
if (_log.shouldLog(Log.INFO)) {
_log.info("Starting up with config file " + configFile);
}
Heartbeat heartbeat = new Heartbeat(configFile);
heartbeat.loadConfig();
heartbeat.connect();
heartbeat.startEngines();
Object o = new Object();
while (true) {
try {
synchronized (o) {
o.wait();
}
} catch (InterruptedException ie) {
}
}
}
/**
* Receive event notification from the I2PAdapter
*
*/
private class PingPongAdapter implements I2PAdapter.PingPongEventListener {
/**
* We were pinged, so always just send a pong back.
*
* @param from who sent us the ping?
* @param seriesNum what series did the sender specify?
* @param sentOn when did the sender say they sent their ping?
* @param data arbitrary payload data
*/
public void receivePing(Destination from, int seriesNum, Date sentOn, byte[] data) {
if (_adapter.getIsConnected()) {
_adapter.sendPong(from, seriesNum, sentOn, data);
}
}
/**
* We received a pong, so find the right client engine and tell it about the pong.
*
* @param from who sent us the pong
* @param seriesNum our client ID
* @param sentOn when did we send the ping?
* @param replyOn when did they send their pong?
* @param data the arbitrary data we sent in the ping (that they sent back in the pong)
*/
public void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte[] data) {
ClientEngine engine = (ClientEngine) _clientEngines.get(new Integer(seriesNum));
if (engine.getPeer().equals(from)) {
engine.receivePong(sentOn.getTime(), replyOn.getTime());
}
}
}
}

View File

@ -1,604 +0,0 @@
package net.i2p.heartbeat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.Log;
/**
* Tie-in to the I2P SDK for the Heartbeat system, talking to the I2PSession and
* dealing with the raw ping and pong messages.
*
*/
class I2PAdapter {
private final static Log _log = new Log(I2PAdapter.class);
/** I2CP host */
private String _i2cpHost;
/** I2CP port */
private int _i2cpPort;
/** how long do we want our tunnels to be? */
private int _numHops;
/** filename containing the heartbeat engine's private destination info */
private String _privateDestFile;
/** filename to store the heartbeat engine's public destination in base64*/
private String _publicDestFile;
/** our destination */
private Destination _localDest;
/** who do we tell? */
private PingPongEventListener _listener;
/** how do we talk to the router */
private I2PSession _session;
/** object that receives our i2cp notifications from the session and tells us */
private I2PListener _i2pListener; /* UNUSED */
/**
* This config property tells us where the private destination data for our
* connection (or if it doesn't exist, where will we save it)
*/
private static final String DEST_FILE_PROP = "privateDestinationFile";
/** by default, the private destination data is in "heartbeat.keys" */
private static final String DEST_FILE_DEFAULT = "heartbeat.keys";
/** where will we export the public destination in base 64? */
private static final String PUBLIC_DEST_FILE_PROP = "publicDestinationFile";
/** where will we export the public destination in base 64? */
private static final String PUBLIC_DEST_FILE_DEFAULT = "heartbeat.txt";
/** This config property defines where the I2P router is */
private static final String I2CP_HOST_PROP = "i2cpHost";
/** by default, the I2P host is "localhost" */
private static final String I2CP_HOST_DEFAULT = "localhost";
/** This config property defines the I2CP port on the router */
private static final String I2CP_PORT_PROP = "i2cpPort";
/** by default, the I2CP port is 7654 */
private static final int I2CP_PORT_DEFAULT = 7654;
/** This property defines how many hops we want in our tunnels. */
public static final String NUMHOPS_PROP = "numHops";
/** by default, use 2 hop tunnels */
public static final int NUMHOPS_DEFAULT = 2;
/**
* Constructs an I2PAdapter . . .
*/
public I2PAdapter() {
_privateDestFile = null;
_publicDestFile = null;
_i2cpHost = null;
_i2cpPort = -1;
_localDest = null;
_listener = null;
_session = null;
_numHops = 0;
}
/**
* who are we?
* @return the destination (us)
*/
public Destination getLocalDestination() {
return _localDest;
}
/**
* who gets notified when we receive a ping or a pong?
* @return the event listener who gets notified
*/
public PingPongEventListener getListener() {
return _listener;
}
/**
* Sets who gets notified when we receive a ping or a pong
* @param listener the event listener to get notified
*/
public void setListener(PingPongEventListener listener) {
_listener = listener;
}
/**
* how many hops do we want in our tunnels?
* @return the number of hops
*/
public int getNumHops() {
return _numHops;
}
/**
* are we connected?
* @return true or false . . .
*/
public boolean getIsConnected() {
return _session != null;
}
/**
* Read in all of the config data
* @param props the properties to load from
*/
void loadConfig(Properties props) {
String privDestFile = props.getProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
String pubDestFile = props.getProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
String host = props.getProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
String port = props.getProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
String numHops = props.getProperty(NUMHOPS_PROP, "" + NUMHOPS_DEFAULT);
int portNum = -1;
try {
portNum = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("Invalid I2CP port specified [" + port + "]");
}
portNum = I2CP_PORT_DEFAULT;
}
int hops = -1;
try {
hops = Integer.parseInt(numHops);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN)) {
_log.warn("Invalid # hops specified [" + numHops + "]");
}
hops = NUMHOPS_DEFAULT;
}
_numHops = hops;
_privateDestFile = privDestFile;
_publicDestFile = pubDestFile;
_i2cpHost = host;
_i2cpPort = portNum;
}
/**
* write out the config to the props
* @param props the properties to write to
*/
void storeConfig(Properties props) {
if (_privateDestFile != null) {
props.setProperty(DEST_FILE_PROP, _privateDestFile);
} else {
props.setProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
}
if (_publicDestFile != null) {
props.setProperty(PUBLIC_DEST_FILE_PROP, _publicDestFile);
} else {
props.setProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
}
if (_i2cpHost != null) {
props.setProperty(I2CP_HOST_PROP, _i2cpHost);
} else {
props.setProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
}
if (_i2cpPort > 0) {
props.setProperty(I2CP_PORT_PROP, "" + _i2cpPort);
} else {
props.setProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
}
props.setProperty(NUMHOPS_PROP, "" + _numHops);
}
private static final int TYPE_PING = 0;
private static final int TYPE_PONG = 1;
/**
* send a ping message to the peer
*
* @param peer peer to ping
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
* @param now current time to be sent in the ping (so we can watch for it in the pong)
* @param size total message size to send
*
* @throws IllegalStateException if we are not connected to the router
*/
public void sendPing(Destination peer, int seriesNum, long now, int size) {
if (_session == null) throw new IllegalStateException("Not connected to the router");
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
try {
_localDest.writeBytes(baos);
DataHelper.writeLong(baos, 2, seriesNum);
DataHelper.writeLong(baos, 1, TYPE_PING);
DataHelper.writeDate(baos, new Date(now));
int padding = size - baos.size();
byte paddingData[] = new byte[padding];
I2PAppContext.getGlobalContext().random().nextBytes(paddingData);
//Arrays.fill(paddingData, (byte) 0x2A);
DataHelper.writeLong(baos, 2, padding);
baos.write(paddingData);
boolean sent = _session.sendMessage(peer, baos.toByteArray());
if (!sent) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error sending the ping to " + peer.calculateHash().toBase64() + " for series "
+ seriesNum);
}
} else {
if (_log.shouldLog(Log.INFO)) {
_log.info("Ping sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
}
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error sending the ping", ioe);
}
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error writing out the ping message", dfe);
}
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error writing out the ping message", ise);
}
}
}
/**
* send a pong message to the peer
*
* @param peer peer to pong
* @param seriesNum id given to us in the ping
* @param sentOn date the peer said they sent us the message
* @param data payload the peer sent us in the ping
*
* @throws IllegalStateException if we are not connected to the router
*/
public void sendPong(Destination peer, int seriesNum, Date sentOn, byte data[]) {
if (_session == null) throw new IllegalStateException("Not connected to the router");
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length + 768);
try {
_localDest.writeBytes(baos);
DataHelper.writeLong(baos, 2, seriesNum);
DataHelper.writeLong(baos, 1, TYPE_PONG);
DataHelper.writeDate(baos, sentOn);
DataHelper.writeDate(baos, new Date(Clock.getInstance().now()));
DataHelper.writeLong(baos, 2, data.length);
baos.write(data);
boolean sent = _session.sendMessage(peer, baos.toByteArray());
if (!sent) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error sending the pong to " + peer.calculateHash().toBase64() + " for series "
+ seriesNum + " which was sent on " + sentOn);
}
} else {
if (_log.shouldLog(Log.INFO)) {
_log.info("Pong sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum
+ " which was sent on " + sentOn);
}
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error sending the ping", ioe);
}
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error writing out the pong message", dfe);
}
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error writing out the pong message", ise);
}
}
}
/**
* We've received this data from I2P - parse it into a ping or a pong
* and notify accordingly
* @param data the data to handle
*/
private void handleMessage(byte data[]) {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
try {
Destination from = new Destination();
from.readBytes(bais);
int series = (int) DataHelper.readLong(bais, 2);
long type = DataHelper.readLong(bais, 1);
Date sentOn = DataHelper.readDate(bais);
Date receivedOn = null;
if (type == TYPE_PONG) {
receivedOn = DataHelper.readDate(bais);
}
int size = (int) DataHelper.readLong(bais, 2);
byte payload[] = new byte[size];
int read = DataHelper.read(bais, payload);
if (read != size) { throw new IOException("Malformed payload - read " + read + " instead of " + size); }
if (_listener == null) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Listener isn't set, but we received a valid message of type " + type + " sent from "
+ from.calculateHash().toBase64());
}
return;
}
if (type == TYPE_PING) {
if (_log.shouldLog(Log.INFO)) {
_log.info("Ping received from " + from.calculateHash().toBase64() + " on series " + series
+ " sent on " + sentOn + " containing " + size + " bytes");
}
_listener.receivePing(from, series, sentOn, payload);
} else if (type == TYPE_PONG) {
if (_log.shouldLog(Log.INFO)) {
_log.info("Pong received from " + from.calculateHash().toBase64() + " on series " + series
+ " sent on " + sentOn + " with pong sent on " + receivedOn + " containing " + size
+ " bytes");
}
_listener.receivePong(from, series, sentOn, receivedOn, payload);
} else {
throw new IOException("Invalid message type " + type);
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error handling the message", ioe);
}
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error parsing the message", dfe);
}
}
}
/**
* connect to the I2P router and either authenticate ourselves with the
* destination we're given, or create a new one and write that to the
* destination file.
*
* @return true if we connect successfully, false otherwise
*/
boolean connect() {
I2PClient client = I2PClientFactory.createClient();
Destination us = null;
File destFile = new File(_privateDestFile);
us = verifyDestination(client, destFile);
if (us == null) return false;
// if we're here, we got a destination. lets connect
FileInputStream fin = null;
try {
fin = new FileInputStream(destFile);
Properties options = getOptions();
I2PSession session = client.createSession(fin, options);
I2PListener lsnr = new I2PListener();
session.setSessionListener(lsnr);
session.connect();
_localDest = session.getMyDestination();
if (_log.shouldLog(Log.INFO)) {
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
}
_session = session;
_i2pListener = lsnr;
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error connecting", ise);
}
return false;
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error loading the destionation", ioe);
}
return false;
} finally {
if (fin != null) try {
fin.close();
} catch (IOException ioe) {
}
}
return true;
}
/**
* load, verify, or create a destination
*
* @param client the client
* @param destFile the file holding the destination
* @return the destination loaded, or null if there was an error
*/
private Destination verifyDestination(I2PClient client, File destFile) {
Destination us = null;
FileInputStream fin = null;
if (destFile.exists()) {
try {
fin = new FileInputStream(destFile);
us = new Destination();
us.readBytes(fin);
if (_log.shouldLog(Log.INFO)) {
_log.info("Existing destination loaded: [" + us.toBase64() + "]");
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(_publicDestFile);
fos.write(us.toBase64().getBytes());
fos.flush();
} catch (IOException fioe) {
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
} finally {
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
}
} catch (IOException ioe) {
if (fin != null) try {
fin.close();
} catch (IOException ioe2) {
}
fin = null;
destFile.delete();
us = null;
} catch (DataFormatException dfe) {
if (fin != null) try {
fin.close();
} catch (IOException ioe2) {
}
fin = null;
destFile.delete();
us = null;
} finally {
if (fin != null) try {
fin.close();
} catch (IOException ioe2) {
}
fin = null;
}
}
if (us == null) {
// need to create a new one
FileOutputStream fos = null;
try {
fos = new FileOutputStream(destFile);
us = client.createDestination(fos);
if (_log.shouldLog(Log.INFO)) {
_log.info("New destination created: [" + us.toBase64() + "]");
}
fos.close();
try {
fos = new FileOutputStream(_publicDestFile);
fos.write(us.toBase64().getBytes());
fos.flush();
} catch (IOException fioe) {
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
} finally {
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
fos = null;
}
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error writing out the destination keys being created", ioe);
}
return null;
} catch (I2PException ie) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error creating the destination", ie);
}
return null;
} finally {
if (fos != null) try {
fos.close();
} catch (IOException ioe) {
}
}
}
return us;
}
/**
* I2PSession connect options
* @return the options as Properties
*/
private Properties getOptions() {
Properties props = new Properties();
// this should be BEST_EFFORT, but i'm too lazy to update the code to handle tracking
// sessionTags and sessionKeys, marking them as delivered on pong.
props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
props.setProperty(I2PClient.PROP_TCP_HOST, _i2cpHost);
props.setProperty(I2PClient.PROP_TCP_PORT, _i2cpPort + "");
props.setProperty("tunnels.depthInbound", "" + _numHops);
props.setProperty("tunnels.depthOutbound", "" + _numHops);
return props;
}
/** disconnect from the I2P router */
void disconnect() {
if (_session != null) {
try {
_session.destroySession();
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Error destroying the session", ise);
}
}
_session = null;
}
}
/**
* Defines an event notification system for receiving pings and pongs
*
*/
public interface PingPongEventListener {
/**
* receive a ping message from the peer
*
* @param from peer that sent us the ping
* @param seriesNum id the peer sent us in the ping
* @param sentOn date the peer said they sent us the message
* @param data payload from the ping
*/
void receivePing(Destination from, int seriesNum, Date sentOn, byte data[]);
/**
* receive a pong message from the peer
*
* @param from peer that sent us the pong
* @param seriesNum id the peer sent us in the pong (that we sent them in the ping)
* @param sentOn when we sent out the ping
* @param replyOn when they sent out the pong
* @param data payload from the ping/pong
*/
void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte data[]);
}
/**
* Receive data from the session and pass it along to handleMessage for parsing/dispersal
*
*/
private class I2PListener implements I2PSessionListener {
/* (non-Javadoc)
* @see net.i2p.client.I2PSessionListener#disconnected(net.i2p.client.I2PSession)
*/
public void disconnected(I2PSession session) {
if (_log.shouldLog(Log.ERROR)) {
_log.error("Session disconnected");
}
disconnect();
}
/* (non-Javadoc)
* @see net.i2p.client.I2PSessionListener#errorOccurred(net.i2p.client.I2PSession, java.lang.String, java.lang.Throwable)
*/
public void errorOccurred(I2PSession session, String message, Throwable error) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred: " + message, error);
}
/* (non-Javadoc)
* @see net.i2p.client.I2PSessionListener#reportAbuse(net.i2p.client.I2PSession, int)
*/
public void reportAbuse(I2PSession session, int severity) {
if (_log.shouldLog(Log.ERROR)) _log.error("Abuse reported with severity " + String.valueOf(severity));
}
/* (non-Javadoc)
* @see net.i2p.client.I2PSessionListener#messageAvailable(net.i2p.client.I2PSession, int, long)
*/
public void messageAvailable(I2PSession session, int msgId, long size) {
try {
byte data[] = session.receiveMessage(msgId);
handleMessage(data);
} catch (I2PSessionException ise) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error receiving the message", ise);
disconnect();
}
}
}
}

View File

@ -1,412 +0,0 @@
package net.i2p.heartbeat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.util.Clock;
import net.i2p.util.Log;
/**
* Contain the current window of data for a particular series of ping/pong stats
* sent to a peer. This should be periodically kept clean by calling cleanup()
* to timeout expired pings and to drop data outside the window.
*
*/
public class PeerData {
private final static Log _log = new Log(PeerData.class);
/** peer / sequence / config in this data series */
private ClientConfig _peer;
/** date sent (Long) to EventDataPoint containing the datapoints sent in the current period */
private Map _dataPoints;
/** date sent (Long) to EventDataPoint containing pings that haven't yet timed out or been ponged */
private TreeMap _pendingPings;
private long _sessionStart;
private long _lifetimeSent;
private long _lifetimeReceived;
/** rate averaging the time to send over a variety of periods */
private RateStat _sendRate;
/** rate averaging the time to receive over a variety of periods */
private RateStat _receiveRate;
/** rate averaging the frequency of lost messages over a variety of periods */
private RateStat _lostRate;
/** how long we wait before timing out pending pings (30 seconds) */
private static final long TIMEOUT_PERIOD = 60 * 1000;
/** synchronize on this when updating _dataPoints or _pendingPings */
private Object _updateLock = new Object();
/**
* Creates a PeerData . . .
* @param config configuration to load from
*/
public PeerData(ClientConfig config) {
_peer = config;
_dataPoints = new TreeMap();
_pendingPings = new TreeMap();
_sessionStart = Clock.getInstance().now();
_lifetimeSent = 0;
_lifetimeReceived = 0;
_sendRate = new RateStat("sendRate", "How long it takes to send", "peer",
getPeriods(config.getAveragePeriods()));
_receiveRate = new RateStat("receiveRate", "How long it takes to receive", "peer",
getPeriods(config.getAveragePeriods()));
_lostRate = new RateStat("lostRate", "How frequently we lose messages", "peer",
getPeriods(config.getAveragePeriods()));
}
/**
* turn the periods (# minutes) into rate periods (# milliseconds)
* @param periods (in minutes)
* @return an array of periods (in milliseconds)
*/
private static long[] getPeriods(int periods[]) {
long rv[] = null;
if (periods == null) periods = new int[0];
rv = new long[periods.length];
for (int i = 0; i < periods.length; i++)
rv[i] = (long) periods[i] * 60 * 1000; // they're in minutes
Arrays.sort(rv);
return rv;
}
/**
* how many pings are still outstanding?
* @return the number of pings outstanding
*/
public int getPendingCount() {
synchronized (_updateLock) {
return _pendingPings.size();
}
}
/**
* how many data points are available in the current window?
* @return the number of datapoints available
*/
public int getDataPointCount() {
synchronized (_updateLock) {
return _dataPoints.size();
}
}
/**
* when did this test begin?
* @return when the test began
*/
public long getSessionStart() { return _sessionStart; }
/**
* sets when the test began
* @param when when it began
*/
public void setSessionStart(long when) { _sessionStart = when; }
/**
* how many pings have we sent for this test?
* @return the number of pings sent
*/
public long getLifetimeSent() { return _lifetimeSent; }
/**
* how many pongs have we received for this test?
* @return the number of pings received
*/
public long getLifetimeReceived() { return _lifetimeReceived; }
/**
* @return the client configuration
*/
public ClientConfig getConfig() {
return _peer;
}
/**
* What periods are we averaging the data over (in minutes)?
* @return the periods as an array of ints (in minutes)
*/
public int[] getAveragePeriods() {
return (_peer.getAveragePeriods() != null ? _peer.getAveragePeriods() : new int[0]);
}
/**
* average time to send over the given period.
*
* @param period number of minutes to retrieve the average for
* @return milliseconds average, or -1 if we dont track that period
*/
public double getAverageSendTime(int period) {
return getAverage(_sendRate, period);
}
/**
* average time to receive over the given period.
*
* @param period number of minutes to retrieve the average for
* @return milliseconds average, or -1 if we dont track that period
*/
public double getAverageReceiveTime(int period) {
return getAverage(_receiveRate, period);
}
/**
* number of lost messages over the given period.
*
* @param period number of minutes to retrieve the average for
* @return number of lost messages in the period, or -1 if we dont track that period
*/
public double getLostMessages(int period) {
Rate rate = _lostRate.getRate(period * 60 * 1000);
if (rate == null) return -1;
return rate.getCurrentTotalValue();
}
private double getAverage(RateStat stat, int period) {
Rate rate = stat.getRate(period * 60 * 1000);
if (rate == null) return -1;
return rate.getAverageValue();
}
/**
* Return an ordered list of data points in the current window (after doing a cleanup)
*
* @return list of EventDataPoint objects
*/
public List getDataPoints() {
cleanup();
synchronized (_updateLock) {
return new ArrayList(_dataPoints.values());
}
}
/**
* We have sent the peer a ping on this series (using the send time as given)
* @param dateSent when the ping was sent
*/
public void addPing(long dateSent) {
EventDataPoint sent = new EventDataPoint(dateSent);
synchronized (_updateLock) {
_pendingPings.put(new Long(dateSent), sent);
}
_lifetimeSent++;
}
/**
* we have received a pong from the peer on this series
*
* @param dateSent when we sent the ping
* @param pongSent when the peer received the ping and sent the pong
*/
public void pongReceived(long dateSent, long pongSent) {
long now = Clock.getInstance().now();
synchronized (_updateLock) {
if (_pendingPings.size() <= 0) {
_log.warn("Pong received (sent at " + dateSent + ", " + (now-dateSent)
+ "ms ago, pong delay " + (pongSent-dateSent) + "ms, pong receive delay "
+ (now-pongSent) + "ms)");
return;
}
Long first = (Long)_pendingPings.firstKey();
EventDataPoint data = (EventDataPoint)_pendingPings.remove(new Long(dateSent));
if (data != null) {
data.setPongReceived(now);
data.setPongSent(pongSent);
data.setWasPonged(true);
locked_addDataPoint(data);
if (dateSent != first.longValue()) {
_log.error("Out of order delivery: received " + dateSent
+ " but the first pending is " + first.longValue()
+ " (delta " + (dateSent - first.longValue()) + ")");
} else {
_log.info("In order delivery for " + dateSent + " in ping "
+ _peer.getComment());
}
} else {
_log.warn("Pong received, but no matching ping? ping sent at = " + dateSent);
return;
}
}
_sendRate.addData(pongSent - dateSent, 0);
_receiveRate.addData(now - pongSent, 0);
_lifetimeReceived++;
}
protected void addDataPoint(EventDataPoint data) {
synchronized (_updateLock) {
locked_addDataPoint(data);
}
}
private void locked_addDataPoint(EventDataPoint data) {
Object val = _dataPoints.put(new Long(data.getPingSent()), data);
if (val != null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Duplicate data point received: " + data);
}
}
/**
* drop all datapoints outside the window we're watching, and timeout all
* pending pings not ponged in the TIMEOUT_PERIOD, both updating the lost message
* rate and coallescing all of the rates.
*
*/
public void cleanup() {
long dropBefore = Clock.getInstance().now() - _peer.getStatDuration() * 60 * 1000;
long timeoutBefore = Clock.getInstance().now() - TIMEOUT_PERIOD;
long numDropped = 0;
long numTimedOut = 0;
synchronized (_updateLock) {
numDropped = locked_dropExpired(dropBefore);
numTimedOut = locked_timeoutPending(timeoutBefore);
}
_lostRate.addData(numTimedOut, 0);
_receiveRate.coalesceStats();
_sendRate.coalesceStats();
_lostRate.coalesceStats();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
+ " old entries");
}
/**
* Drop all data points that are already too old for us to be interested in
*
* @param when the earliest ping send time we care about
* @return number of data points dropped
*/
private int locked_dropExpired(long when) {
Set toDrop = new HashSet(4);
// drop the failed and really old
for (Iterator iter = _dataPoints.keySet().iterator(); iter.hasNext(); ) {
Long pingTime = (Long)iter.next();
if (pingTime.longValue() < when)
toDrop.add(pingTime);
}
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
_dataPoints.remove(iter.next());
}
return toDrop.size();
}
/**
* timeout and remove all pings that were sent before the given time,
* moving them from the set of pending pings to the set of data points
*
* @param when the earliest ping send time we care about
* @return number of pings timed out
*/
private int locked_timeoutPending(long when) {
Set toDrop = new HashSet(4);
for (Iterator iter = _pendingPings.keySet().iterator(); iter.hasNext(); ) {
Long pingTime = (Long)iter.next();
if (pingTime.longValue() < when) {
toDrop.add(pingTime);
EventDataPoint point = (EventDataPoint)_pendingPings.get(pingTime);
point.setWasPonged(false);
locked_addDataPoint(point);
}
}
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
_pendingPings.remove(iter.next());
}
return toDrop.size();
}
/** actual data point for the peer */
public class EventDataPoint {
private boolean _wasPonged;
private long _pingSent;
private long _pongSent;
private long _pongReceived;
/**
* Creates an EventDataPoint
*/
public EventDataPoint() { this(-1); }
/**
* Creates an EventDataPoint with pingtime associated with it =)
* @param pingSentOn the time a ping was sent
*/
public EventDataPoint(long pingSentOn) {
_wasPonged = false;
_pingSent = pingSentOn;
_pongSent = -1;
_pongReceived = -1;
}
/**
* when did we send this ping?
* @return the time the ping was sent
*/
public long getPingSent() { return _pingSent; }
/**
* sets when we sent this ping
* @param when when we sent the ping
*/
public void setPingSent(long when) { _pingSent = when; }
/**
* when did the peer receive the ping?
* @return the time the ping was receieved
*/
public long getPongSent() {
return _pongSent;
}
/**
* Set the time the peer received the ping
* @param when the time to set
*/
public void setPongSent(long when) {
_pongSent = when;
}
/**
* when did we receive the peer's pong?
* @return the time we receieved the pong
*/
public long getPongReceived() {
return _pongReceived;
}
/**
* Set the time the peer's pong was receieved
* @param when the time to set
*/
public void setPongReceived(long when) {
_pongReceived = when;
}
/**
* did the peer reply in time?
* @return true or false, whether we got a reply in time */
public boolean getWasPonged() {
return _wasPonged;
}
/**
* Set whether we receieved the peer's reply in time
* @param pong true or false
*/
public void setWasPonged(boolean pong) {
_wasPonged = pong;
}
}
}

View File

@ -1,142 +0,0 @@
package net.i2p.heartbeat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import net.i2p.util.Clock;
import net.i2p.util.Log;
/**
* Actually write out the stats for peer test
*
*/
public class PeerDataWriter {
private final static Log _log = new Log(PeerDataWriter.class);
/**
* persist the peer state to the location specified in the peer config
*
* @param data the peer data to persist
* @return true if it was persisted correctly, false on error
*/
public boolean persist(PeerData data) {
String filename = data.getConfig().getStatFile();
File statFile = new File(filename);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(statFile);
persist(data, fos);
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error persisting the peer data for "
+ data.getConfig().getPeer().calculateHash().toBase64(), ioe);
return false;
} finally {
if (fos != null) try {
fos.close();
} catch (IOException ioe) {
}
}
return true;
}
/**
* persists the peer state to the output stream
* @param data the peer data to persist
* @param out where to persist the data
* @return true if it was persisted correctly [always (as implemented)], false on error
* @throws IOException
*/
public boolean persist(PeerData data, OutputStream out) throws IOException {
String header = getHeader(data);
out.write(header.getBytes());
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\troundTrip\n".getBytes());
for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) {
PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next();
String line = getEvent(point);
out.write(line.getBytes());
}
return true;
}
private String getHeader(PeerData data) {
StringBuffer buf = new StringBuffer(1024);
buf.append("peer \t").append(data.getConfig().getPeer().calculateHash().toBase64()).append('\n');
buf.append("local \t").append(data.getConfig().getUs().calculateHash().toBase64()).append('\n');
buf.append("peerDest \t").append(data.getConfig().getPeer().toBase64()).append('\n');
buf.append("localDest \t").append(data.getConfig().getUs().toBase64()).append('\n');
buf.append("numTunnelHops\t").append(data.getConfig().getNumHops()).append('\n');
buf.append("comment \t").append(data.getConfig().getComment()).append('\n');
buf.append("sendFrequency\t").append(data.getConfig().getSendFrequency()).append('\n');
buf.append("sendSize \t").append(data.getConfig().getSendSize()).append('\n');
buf.append("sessionStart \t").append(getTime(data.getSessionStart())).append('\n');
buf.append("currentTime \t").append(getTime(Clock.getInstance().now())).append('\n');
buf.append("numPending \t").append(data.getPendingCount()).append('\n');
buf.append("lifetimeSent \t").append(data.getLifetimeSent()).append('\n');
buf.append("lifetimeRecv \t").append(data.getLifetimeReceived()).append('\n');
int periods[] = data.getAveragePeriods();
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\troundTrip\n");
for (int i = 0; i < periods.length; i++) {
buf.append("periodAverage\t").append(periods[i]).append('\t');
buf.append(getNum(data.getAverageSendTime(periods[i]))).append('\t');
buf.append(getNum(data.getAverageReceiveTime(periods[i]))).append('\t');
buf.append(getNum(data.getLostMessages(periods[i]))).append('\t');
double rtt = data.getAverageSendTime(periods[i])
+ data.getAverageReceiveTime(periods[i]);
buf.append(getNum(rtt)).append('\n');
}
return buf.toString();
}
private String getEvent(PeerData.EventDataPoint point) {
StringBuffer buf = new StringBuffer(128);
buf.append("EVENT\t");
if (point.getWasPonged())
buf.append("OK\t");
else
buf.append("LOST\t");
buf.append(getTime(point.getPingSent())).append('\t');
if (point.getWasPonged()) {
buf.append(point.getPongSent() - point.getPingSent()).append('\t');
buf.append(point.getPongReceived() - point.getPongSent()).append('\t');
buf.append(point.getPongReceived() - point.getPingSent()).append('\t');
}
buf.append('\n');
return buf.toString();
}
private final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
/**
* Converts a time (long) to text
* @param when the time to convert
* @return the textual representation
*/
public String getTime(long when) {
synchronized (_fmt) {
return _fmt.format(new Date(when));
}
}
private final DecimalFormat _numFmt = new DecimalFormat("#0", new DecimalFormatSymbols(Locale.UK));
/**
* Converts a number (double) to text
* @param val the number to convert
* @return the textual representation
*/
public String getNum(double val) {
synchronized (_numFmt) {
return _numFmt.format(val);
}
}
}

View File

@ -1,108 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import net.i2p.util.Log;
/**
* Render the control widgets (refresh/load/snapshot and the
* tabbed panel with the plot config data)
*
*/
class HeartbeatControlPane extends JPanel {
private final static Log _log = new Log(HeartbeatControlPane.class);
private HeartbeatMonitorGUI _gui;
private JTabbedPane _configPane;
private final static Color WHITE = new Color(255, 255, 255);
private final static Color LIGHT_BLUE = new Color(180, 180, 255); /* UNUSED */
private final static Color BLACK = new Color(0, 0, 0);
private Color _background = WHITE;
private Color _foreground = BLACK;
/**
* Constructs a control panel onto the gui
* @param gui the gui the panel is associated with
*/
public HeartbeatControlPane(HeartbeatMonitorGUI gui) {
_gui = gui;
initializeComponents();
}
/**
* Adds a test to the panel
* @param config the configuration for the test
*/
public void addTest(PeerPlotConfig config) {
_configPane.addTab(config.getTitle(), null, new JScrollPane(new PeerPlotConfigPane(config, this)), config.getSummary());
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
_gui.pack();
}
/**
* Removes a test from the panel
* @param config the configuration for the test
*/
public void removeTest(PeerPlotConfig config) {
_gui.getMonitor().getState().removeTest(config);
int index = _configPane.indexOfTab(config.getTitle());
if (index >= 0)
_configPane.removeTabAt(index);
}
/**
* Callback: when tests have changed
*/
public void testsUpdated() {
List knownNames = new ArrayList(8);
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
PeerPlotState state = _gui.getMonitor().getState().getTest(i);
String title = state.getPlotConfig().getTitle();
knownNames.add(state.getPlotConfig().getTitle());
if (_configPane.indexOfTab(title) >= 0) {
_log.debug("We already know about [" + title + "]");
} else {
_log.info("The test [" + title + "] is new to us");
PeerPlotConfigPane pane = new PeerPlotConfigPane(state.getPlotConfig(), this);
_configPane.addTab(state.getPlotConfig().getTitle(), null, new JScrollPane(pane), state.getPlotConfig().getSummary());
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
}
}
List toRemove = new ArrayList(4);
for (int i = 0; i < _configPane.getTabCount(); i++) {
if (knownNames.contains(_configPane.getTitleAt(i))) {
// noop
} else {
toRemove.add(_configPane.getTitleAt(i));
}
}
for (int i = 0; i < toRemove.size(); i++) {
String title = (String)toRemove.get(i);
_log.info("Removing test [" + title + "]");
_configPane.removeTabAt(_configPane.indexOfTab(title));
}
}
private void initializeComponents() {
if (_gui != null)
setBackground(_gui.getBackground());
else
setBackground(_background);
setLayout(new BorderLayout());
HeartbeatMonitorCommandBar bar = new HeartbeatMonitorCommandBar(_gui);
bar.setBackground(getBackground());
add(bar, BorderLayout.NORTH);
_configPane = new JTabbedPane(JTabbedPane.LEFT);
_configPane.setBackground(_background);
//add(_configPane, BorderLayout.CENTER);
add(_configPane, BorderLayout.SOUTH);
}
}

View File

@ -1,116 +0,0 @@
package net.i2p.heartbeat.gui;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* The HeartbeatMonitor, complete with main()! Act now, and it's only 5 easy
* payments of $19.95 (plus shipping and handling)! You heard me, only _5_
* easy payments of $19.95 (plus shipping and handling)! <p />
*
* (fine print: something about some states in the US requiring the addition
* of sales tax... or something) <p />
*
* (finer print: Satan owns you. Deal with it.) <p />
*
* (even finer print: usage: <code>HeartbeatMonitor [configFilename]</code>)
*/
public class HeartbeatMonitor implements PeerPlotStateFetcher.FetchStateReceptor, PeerPlotConfig.UpdateListener {
private final static Log _log = new Log(HeartbeatMonitor.class);
private HeartbeatMonitorState _state;
private HeartbeatMonitorGUI _gui;
/**
* Delegating constructor.
* @see HeartbeatMonitor#HeartbeatMonitor(String)
*/
public HeartbeatMonitor() { this(null); }
/**
* Creates a HeartbeatMonitor . . .
* @param configFilename the configuration file to read from
*/
public HeartbeatMonitor(String configFilename) {
_state = new HeartbeatMonitorState(configFilename);
_gui = new HeartbeatMonitorGUI(this);
}
/**
* Starts the game rollin'
*/
public void runMonitor() {
loadConfig();
I2PThread t = new I2PThread(new HeartbeatMonitorRunner(this));
t.setName("HeartbeatMonitor");
t.setDaemon(false);
t.start();
_log.debug("Monitor started");
}
/**
* give us all the data/config available
* @return the current state (data/config)
*/
HeartbeatMonitorState getState() {
return _state;
}
/** for all of the peer tests being monitored, refetch the data and rerender */
void refetchData() {
_log.debug("Refetching data");
for (int i = 0; i < _state.getTestCount(); i++)
PeerPlotStateFetcher.fetchPeerPlotState(this, _state.getTest(i));
}
/** (re)load the config defining what peer tests we are monitoring (and how to render) */
void loadConfig() {
//for (int i = 0; i < 10; i++) {
// load("fake" + i);
//}
}
/**
* Loads config data
* @param location the name of the location to load data from
*/
public void load(String location) {
PeerPlotConfig cfg = new PeerPlotConfig(location);
cfg.addListener(this);
PeerPlotState state = new PeerPlotState(cfg);
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
}
/* (non-Javadoc)
* @see PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched
*/
public synchronized void peerPlotStateFetched(PeerPlotState state) {
_state.addTest(state);
_gui.stateUpdated();
}
/**
* store the config defining what peer tests we are monitoring (and how to render)
*/
void storeConfig() {}
/**
* And now, the main function, the one you've all been waiting for! . . .
* @param args da args. Should take 1, which is the location to load config data from
*/
public static void main(String args[]) {
Thread.currentThread().setName("HeartbeatMonitor.main");
if (args.length == 1)
new HeartbeatMonitor(args[0]).runMonitor();
else
new HeartbeatMonitor().runMonitor();
}
/**
* Called when the config is updated
* @param config the updated config
*/
public void configUpdated(PeerPlotConfig config) {
_log.debug("Config updated, revamping the gui");
_gui.stateUpdated();
}
}

View File

@ -1,67 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
class HeartbeatMonitorCommandBar extends JPanel {
private HeartbeatMonitorGUI _gui;
private JComboBox _refreshRate;
private JTextField _location;
/**
* Constructs a command bar onto the gui
* @param gui the gui the command bar is associated with
*/
public HeartbeatMonitorCommandBar(HeartbeatMonitorGUI gui) {
_gui = gui;
initializeComponents();
}
private void refreshChanged(ItemEvent evt) {}
private void loadCalled() {
_gui.getMonitor().load(_location.getText());
}
private void browseCalled() {
JFileChooser chooser = new JFileChooser(_location.getText());
chooser.setBackground(_gui.getBackground());
chooser.setMultiSelectionEnabled(false);
int rv = chooser.showDialog(this, "Load");
if (rv == JFileChooser.APPROVE_OPTION)
_gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
}
private void initializeComponents() {
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
_refreshRate.setEnabled(false);
_refreshRate.setBackground(_gui.getBackground());
//add(_refreshRate);
JLabel loadLabel = new JLabel("Load from: ");
loadLabel.setBackground(_gui.getBackground());
add(loadLabel);
_location = new JTextField(20);
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
_location.setBackground(_gui.getBackground());
add(_location);
JButton browse = new JButton("Browse...");
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
browse.setBackground(_gui.getBackground());
add(browse);
JButton load = new JButton("Load");
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
load.setBackground(_gui.getBackground());
add(load);
setBackground(_gui.getBackground());
}
}

View File

@ -1,98 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
class HeartbeatMonitorGUI extends JFrame {
private HeartbeatMonitor _monitor;
private HeartbeatPlotPane _plotPane;
private HeartbeatControlPane _controlPane;
private final static Color WHITE = new Color(255, 255, 255);
private Color _background = WHITE;
/**
* Creates the GUI for all youz who be too shoopid for text based shitz
* @param monitor the monitor the gui operates over
*/
public HeartbeatMonitorGUI(HeartbeatMonitor monitor) {
super("Heartbeat Monitor");
_monitor = monitor;
initializeComponents();
pack();
//setResizable(false);
setVisible(true);
}
HeartbeatMonitor getMonitor() { return _monitor; }
/** build up all our widgets */
private void initializeComponents() {
getContentPane().setLayout(new BorderLayout());
setBackground(_background);
_plotPane = new JFreeChartHeartbeatPlotPane(this); // new HeartbeatPlotPane(this);
_plotPane.setBackground(_background);
//JScrollPane pane = new JScrollPane(_plotPane);
//pane.setBackground(_background);
getContentPane().add(new JScrollPane(_plotPane), BorderLayout.CENTER);
_controlPane = new HeartbeatControlPane(this);
_controlPane.setBackground(_background);
getContentPane().add(_controlPane, BorderLayout.SOUTH);
//JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(_plotPane), new JScrollPane(_controlPane));
//getContentPane().add(pane, BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initializeMenus();
}
/**
* Callback: when the state of the world changes . . .
*/
public void stateUpdated() {
_controlPane.testsUpdated();
_plotPane.stateUpdated();
}
private void exitCalled() {
_monitor.getState().setWasKilled(true);
setVisible(false);
System.exit(0);
}
private void loadConfigCalled() {}
private void saveConfigCalled() {}
private void loadSnapshotCalled() {}
private void saveSnapshotCalled() {}
private void initializeMenus() {
JMenuBar bar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem loadConfig = new JMenuItem("Load config");
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
JMenuItem saveConfig = new JMenuItem("Save config");
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
JMenuItem exit = new JMenuItem("Exit");
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
fileMenu.add(loadConfig);
fileMenu.add(saveConfig);
fileMenu.add(loadSnapshot);
fileMenu.add(saveSnapshot);
fileMenu.add(exit);
bar.add(fileMenu);
setJMenuBar(bar);
}
}

View File

@ -1,32 +0,0 @@
package net.i2p.heartbeat.gui;
import net.i2p.util.Log;
/**
* Periodically fire off necessary events (instructing the heartbeat monitor when
* to refetch the data, etc). This is the only active thread in the heartbeat
* monitor (outside the swing/jvm threads)
*/
class HeartbeatMonitorRunner implements Runnable {
private final static Log _log = new Log(HeartbeatMonitorRunner.class);
private HeartbeatMonitor _monitor;
/**
* Creates the thread . . .
* @param monitor the monitor the thread runs over
*/
public HeartbeatMonitorRunner(HeartbeatMonitor monitor) {
_monitor = monitor;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
while (!_monitor.getState().getWasKilled()) {
_monitor.refetchData();
try { Thread.sleep(_monitor.getState().getRefreshRateMs()); } catch (InterruptedException ie) {}
}
_log.info("Stopping the heartbeat monitor runner");
}
}

View File

@ -1,129 +0,0 @@
package net.i2p.heartbeat.gui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* manage the current state of the GUI - all data points, as well as any
* rendering or configuration options.
*/
class HeartbeatMonitorState {
private String _configFile;
private List _peerPlotState;
private int _currentPeerPlotConfig;
private int _refreshRateMs;
private boolean _killed;
/** by default, refresh every 30 seconds */
private final static int DEFAULT_REFRESH_RATE = 30*1000;
/** where do we load/store config info from? */
private final static String DEFAULT_CONFIG_FILE = "heartbeatMonitor.config";
/**
* A delegating constructor.
* @see HeartbeatMonitorState#HeartbeatMonitorState(String)
*/
public HeartbeatMonitorState() { this(DEFAULT_CONFIG_FILE); }
/**
* Constructs the state, loading from the specified location
* @param configFile the name of the file to load info from
*/
public HeartbeatMonitorState(String configFile) {
_peerPlotState = Collections.synchronizedList(new ArrayList());
_refreshRateMs = DEFAULT_REFRESH_RATE;
_configFile = configFile;
_killed = false;
_currentPeerPlotConfig = 0;
}
/**
* how many tests are we monitoring?
* @return the number of tests
*/
public int getTestCount() { return _peerPlotState.size(); }
/**
* Retrieves the current info of a test for a certain peer . . .
* @param peer a number associated with a certain peer
* @return the test data
*/
public PeerPlotState getTest(int peer) { return (PeerPlotState)_peerPlotState.get(peer); }
/**
* Adds a test . . .
* @param peerState the test (by state) to add . . .
*/
public void addTest(PeerPlotState peerState) {
if (!_peerPlotState.contains(peerState))
_peerPlotState.add(peerState);
}
/**
* Removes a test . . .
* @param peerState the test (by state) to remove . . .
*/
public void removeTest(PeerPlotState peerState) { _peerPlotState.remove(peerState); }
/**
* Removes a test . . .
* @param peerConfig the test (by config) to remove . . .
*/
public void removeTest(PeerPlotConfig peerConfig) {
for (int i = 0; i < getTestCount(); i++) {
PeerPlotState state = getTest(i);
if (state.getPlotConfig() == peerConfig) {
removeTest(state);
return;
}
}
}
/**
* which of the tests are we currently editing/viewing?
* @return the number associated with the test
*/
public int getPeerPlotConfig() { return _currentPeerPlotConfig; }
/**
* Sets the test we are currently editting/viewing
* @param whichTest the number associated with the test
*/
public void setPeerPlotConfig(int whichTest) { _currentPeerPlotConfig = whichTest; }
/**
* how frequently should we update the data?
* @return the current frequency (in milliseconds)
*/
public int getRefreshRateMs() { return _refreshRateMs; }
/**
* Sets how frequently we should update data
* @param ms the frequency (in milliseconds)
*/
public void setRefreshRateMs(int ms) { _refreshRateMs = ms; }
/**
* where is our config stored?
* @return the name of the config file
*/
public String getConfigFile() { return _configFile; }
/**
* Sets where our config is stored
* @param filename the name of the config file
*/
public void setConfigFile(String filename) { _configFile = filename; }
/**
* have we been shut down?
* @return true if we have, false otherwise
*/
public boolean getWasKilled() { return _killed; }
/**
* Sets if we have been shutdown or not
* @param killed true if we've been shutdown, false otherwise
*/
public void setWasKilled(boolean killed) { _killed = killed; }
}

View File

@ -1,62 +0,0 @@
package net.i2p.heartbeat.gui;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import net.i2p.heartbeat.PeerDataWriter;
import net.i2p.util.Log;
/**
* Render the graph and legend
*/
class HeartbeatPlotPane extends JPanel {
private final static Log _log = new Log(HeartbeatPlotPane.class);
protected HeartbeatMonitorGUI _gui;
private JTextArea _text;
/**
* Constructs the plot pane
* @param gui the gui the pane is attached to
*/
public HeartbeatPlotPane(HeartbeatMonitorGUI gui) {
_gui = gui;
initializeComponents();
}
/**
* Callback: when things change . . .
*/
public void stateUpdated() {
StringBuffer buf = new StringBuffer(32*1024);
PeerDataWriter writer = new PeerDataWriter();
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
StaticPeerData data = _gui.getMonitor().getState().getTest(i).getCurrentData();
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
try {
writer.persist(data, baos);
} catch (IOException ioe) {
_log.error("wtf, error writing to a byte array?", ioe);
}
buf.append(new String(baos.toByteArray())).append("\n\n\n");
}
_text.setText(buf.toString());
}
protected void initializeComponents() {
setBackground(_gui.getBackground());
//Dimension size = new Dimension(800, 600);
_text = new JTextArea("",30,80); // 16, 60);
_text.setAutoscrolls(true);
_text.setEditable(false);
// _text.setLineWrap(true);
// add(new JScrollPane(_text));
add(_text);
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
// setPreferredSize(size);
}
}

View File

@ -1,233 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.Color;
import java.awt.Font;
import java.util.List;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.XYItemRenderer;
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
import org.jfree.data.XYSeries;
import org.jfree.data.XYSeriesCollection;
import net.i2p.heartbeat.PeerData;
import net.i2p.util.Log;
class JFreeChartAdapter {
private final static Log _log = new Log(JFreeChartAdapter.class); /* UNUSED */
private final static Color WHITE = new Color(255, 255, 255);
ChartPanel createPanel(HeartbeatMonitorState state) {
ChartPanel panel = new ChartPanel(createChart(state));
panel.setDisplayToolTips(true);
panel.setEnforceFileExtensions(true);
panel.setHorizontalZoom(true);
panel.setVerticalZoom(true);
panel.setMouseZoomable(true, true);
panel.getChart().setBackgroundPaint(WHITE);
return panel;
}
JFreeChart createChart(HeartbeatMonitorState state) {
Plot plot = createPlot(state);
JFreeChart chart = new JFreeChart("I2P Heartbeat performance", Font.getFont("arial"), plot, true);
return chart;
}
void updateChart(ChartPanel panel, HeartbeatMonitorState state) {
XYPlot plot = (XYPlot)panel.getChart().getPlot();
plot.setDataset(getCollection(state));
updateLines(plot, state);
}
private long getFirst(HeartbeatMonitorState state) { /* UNUSED */
long first = -1;
for (int i = 0; i < state.getTestCount(); i++) {
List dataPoints = state.getTest(i).getCurrentData().getDataPoints();
if ( (dataPoints != null) && (dataPoints.size() > 0) ) {
PeerData.EventDataPoint data = (PeerData.EventDataPoint)dataPoints.get(0);
if ( (first < 0) || (first > data.getPingSent()) )
first = data.getPingSent();
}
}
return first;
}
Plot createPlot(HeartbeatMonitorState state) {
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("ms"), renderer);
updateLines(plot, state);
return plot;
}
private void updateLines(XYPlot plot, HeartbeatMonitorState state) {
if (true) return;
if (state == null) return;
for (int i = 0; i < state.getTestCount(); i++) {
PeerPlotConfig config = state.getTest(i).getPlotConfig();
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
XYSeriesCollection col = ((XYSeriesCollection)plot.getDataset());
for (int j = 0; j < col.getSeriesCount(); j++) {
//XYItemRenderer renderer = plot.getRendererForDataset(col.getSeries(j));
XYItemRenderer renderer = plot.getRendererForDataset(col);
if (col.getSeriesName(j).startsWith(config.getTitle() + " send")) {
if (curConfig.getPlotSendTime()) {
//renderer.setPaint(curConfig.getPlotLineColor());
}
}
if (col.getSeriesName(j).startsWith(config.getTitle() + " receive")) {
if (curConfig.getPlotReceiveTime()) {
//renderer.setPaint(curConfig.getPlotLineColor());
}
}
if (col.getSeriesName(j).startsWith(config.getTitle() + " lost")) {
if (curConfig.getPlotLostMessages()) {
//renderer.setPaint(curConfig.getPlotLineColor());
}
}
}
}
}
XYSeriesCollection getCollection(HeartbeatMonitorState state) {
XYSeriesCollection col = new XYSeriesCollection();
if (state != null) {
for (int i = 0; i < state.getTestCount(); i++) {
addTest(col, state.getTest(i));
}
} else {
XYSeries series = new XYSeries("latency", false, false);
series.add(System.currentTimeMillis(), 0);
col.addSeries(series);
}
return col;
}
void addTest(XYSeriesCollection col, PeerPlotState state) {
PeerPlotConfig config = state.getPlotConfig();
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
addLines(col, curConfig, config.getTitle(), state.getCurrentData().getDataPoints());
addAverageLines(col, config, config.getTitle(), state.getCurrentData());
}
/**
* @param col the collection of xy series to add to
* @param config preferences for how to display this test
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
* @param data List of PeerData.EventDataPoint describing all of the events in the test
*/
void addLines(XYSeriesCollection col, PeerPlotConfig.PlotSeriesConfig config, String lineName, List data) {
if (config.getPlotSendTime()) {
XYSeries sendSeries = getSendSeries(data);
sendSeries.setName(lineName + " send");
sendSeries.setDescription("milliseconds for the ping to reach the peer");
col.addSeries(sendSeries);
}
if (config.getPlotReceiveTime()) {
XYSeries recvSeries = getReceiveSeries(data);
recvSeries.setName(lineName + " receive");
recvSeries.setDescription("milliseconds for the peer's pong to reach the sender");
col.addSeries(recvSeries);
}
if (config.getPlotLostMessages()) {
XYSeries lostSeries = getLostSeries(data);
lostSeries.setName(lineName + " lost");
lostSeries.setDescription("number of ping/pong messages lost");
col.addSeries(lostSeries);
}
}
/**
* Add a data series for each average that we're configured to render
*
* @param col the collection of xy series to add to
* @param config preferences for how to display this test
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
* @param data List of PeerData.EventDataPoint describing all of the events in the test
*/
void addAverageLines(XYSeriesCollection col, PeerPlotConfig config, String lineName, StaticPeerData data) {
if (data.getDataPointCount() <= 0) return;
PeerData.EventDataPoint start = (PeerData.EventDataPoint)data.getDataPoints().get(0);
PeerData.EventDataPoint finish = (PeerData.EventDataPoint)data.getDataPoints().get(data.getDataPointCount()-1);
List configs = config.getAverageSeriesConfigs();
for (int i = 0; i < configs.size(); i++) {
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
int minutes = (int)cfg.getPeriod()/(60*1000);
if (cfg.getPlotSendTime()) {
double time = data.getAverageSendTime(minutes);
if (time > 0) {
XYSeries series = new XYSeries(lineName + " send " + minutes + "m avg [" + time + "]", false, false);
series.add(start.getPingSent(), time);
series.add(finish.getPingSent(), time);
series.setDescription("send time, averaged over the last " + minutes + " minutes");
col.addSeries(series);
}
}
if (cfg.getPlotReceiveTime()) {
double time = data.getAverageReceiveTime(minutes);
if (time > 0) {
XYSeries series = new XYSeries(lineName + " receive " + minutes + "m avg[" + time + "]", false, false);
series.add(start.getPingSent(), time);
series.add(finish.getPingSent(), time);
series.setDescription("receive time, averaged over the last " + minutes + " minutes");
col.addSeries(series);
}
}
if (cfg.getPlotLostMessages()) {
double num = data.getLostMessages(minutes);
if (num > 0) {
XYSeries series = new XYSeries(lineName + " lost messages (" + num + " in " + minutes + "m)", false, false);
series.add(start.getPingSent(), num);
series.add(finish.getPingSent(), num);
series.setDescription("number of messages lost in the last " + minutes + " minutes");
col.addSeries(series);
}
}
}
}
XYSeries getSendSeries(List data) {
XYSeries series = new XYSeries("sent", false, false);
for (int i = 0; i < data.size(); i++) {
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
if (point.getWasPonged()) {
series.add(point.getPingSent(), point.getPongSent()-point.getPingSent());
} else {
// series.add(data.getPingSent(), 0);
}
}
return series;
}
XYSeries getReceiveSeries(List data) {
XYSeries series = new XYSeries("receive", false, false);
for (int i = 0; i < data.size(); i++) {
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
if (point.getWasPonged()) {
series.add(point.getPingSent(), point.getPongReceived()-point.getPongSent());
} else {
// series.add(data.getPingSent(), 0);
}
}
return series;
}
XYSeries getLostSeries(List data) {
XYSeries series = new XYSeries("lost", false, false);
for (int i = 0; i < data.size(); i++) {
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
if (point.getWasPonged()) {
//series.add(point.getPingSent(), 0);
} else {
series.add(point.getPingSent(), 1);
}
}
return series;
}
}

View File

@ -1,58 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import org.jfree.chart.ChartPanel;
import net.i2p.util.Log;
/**
* Render the graph and legend
*
*/
class JFreeChartHeartbeatPlotPane extends HeartbeatPlotPane {
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class); /* UNUSED */
private ChartPanel _panel;
private JFreeChartAdapter _adapter;
/**
* Creates a JFreeChart plot pane for the given gui
* @param gui the heartbeat monitor gui
*/
public JFreeChartHeartbeatPlotPane(HeartbeatMonitorGUI gui) {
super(gui);
}
/**
* Called when the state is updated
*/
public void stateUpdated() {
if (_panel == null) {
remove(0); // remove the dummy
_adapter = new JFreeChartAdapter();
_panel = _adapter.createPanel(_gui.getMonitor().getState());
_panel.setBackground(_gui.getBackground());
add(new JScrollPane(_panel), BorderLayout.CENTER);
_gui.pack();
} else {
_adapter.updateChart(_panel, _gui.getMonitor().getState());
//_gui.pack();
}
}
protected void initializeComponents() {
// noop
setLayout(new BorderLayout());
add(new JLabel(), BorderLayout.CENTER);
//dummy.setBackground(_gui.getBackground());
//dummy.setPreferredSize(new Dimension(800,600));
//add(dummy);
//add(_panel);
}
}

View File

@ -1,367 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.Destination;
import net.i2p.heartbeat.ClientConfig;
import net.i2p.util.Log;
/**
* Configure how we want to render a particular clientConfig in the GUI
*/
class PeerPlotConfig {
private final static Log _log = new Log(PeerPlotConfig.class); /* UNUSED */
/** where can we find the current state/data (either as a filename or a URL)? */
private String _location;
/** what test are we defining the plot data for? */
private ClientConfig _config;
/** how should we render the current data set? */
private PlotSeriesConfig _currentSeriesConfig;
/** how should we render the various averages available? */
private List _averageSeriesConfigs;
private Set _listeners;
private boolean _disabled;
/**
* Delegating constructor . . .
* @param location the name of the file/URL to get the data from
*/
public PeerPlotConfig(String location) {
this(location, null, null, null);
}
/**
* Constructs a config =)
* @param location the location of the file/URL to get the data from
* @param config the client's configuration
* @param currentSeriesConfig the series config
* @param averageSeriesConfigs the average
*/
public PeerPlotConfig(String location, ClientConfig config, PlotSeriesConfig currentSeriesConfig, List averageSeriesConfigs) {
_location = location;
if (config == null)
config = new ClientConfig(location);
_config = config;
if (currentSeriesConfig != null)
_currentSeriesConfig = currentSeriesConfig;
else
_currentSeriesConfig = new PlotSeriesConfig(0);
if (averageSeriesConfigs != null) {
_averageSeriesConfigs = averageSeriesConfigs;
} else {
rebuildAverageSeriesConfigs();
}
_listeners = Collections.synchronizedSet(new HashSet(2));
_disabled = false;
}
/**
* 'Rebuilds' the average series stuff from the client configuration
*/
public void rebuildAverageSeriesConfigs() {
int periods[] = _config.getAveragePeriods();
if (periods == null) {
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(0));
} else {
Arrays.sort(periods);
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(periods.length));
for (int i = 0; i < periods.length; i++) {
_averageSeriesConfigs.add(new PlotSeriesConfig(periods[i]*60*1000));
}
}
}
/**
* Adds an average period
* @param minutes the number of minutes averaged over
*/
public void addAverage(int minutes) {
_config.addAveragePeriod(minutes);
TreeMap ordered = new TreeMap();
for (int i = 0; i < _averageSeriesConfigs.size(); i++) {
PlotSeriesConfig cfg = (PlotSeriesConfig)_averageSeriesConfigs.get(i);
ordered.put(new Long(cfg.getPeriod()), cfg);
}
Long period = new Long(minutes*60*1000);
if (!ordered.containsKey(period))
ordered.put(period, new PlotSeriesConfig(minutes*60*1000));
List cfgs = Collections.synchronizedList(new ArrayList(ordered.size()));
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); )
cfgs.add(iter.next());
_averageSeriesConfigs = cfgs;
}
/**
* Where is the current state data supposed to be found? This must either be a
* local file path or a URL
* @return the current location
*/
public String getLocation() { return _location; }
/**
* The location the current state data is supposed to be found. This must either be
* a local file path or a URL
* @param location the location
*/
public void setLocation(String location) {
_location = location;
fireUpdate();
}
/**
* What are we configuring?
* @return the client configuration
*/
public ClientConfig getClientConfig() { return _config; }
/**
* Sets what we are currently configuring
* @param config the new config
*/
public void setClientConfig(ClientConfig config) {
_config = config;
fireUpdate();
}
/**
* How do we want to render the current data set?
* @return the way we currently render the data
*/
public PlotSeriesConfig getCurrentSeriesConfig() { return _currentSeriesConfig; }
/**
* Sets how we want to render the current data set.
* @param config the new config
*/
public void setCurrentSeriesConfig(PlotSeriesConfig config) {
_currentSeriesConfig = config;
fireUpdate();
}
/**
* How do we want to render the averages?
* @return the way we currently render the averages
*/
public List getAverageSeriesConfigs() { return _averageSeriesConfigs; }
/**
* Sets how we want to render the averages
* @param configs the new configs
*/
public void setAverageSeriesConfigs(List configs) { _averageSeriesConfigs = configs; }
/**
* four char description of the peer
* @return the name
*/
public String getPeerName() {
Destination peer = getClientConfig().getPeer();
if (peer == null)
return "????";
return peer.calculateHash().toBase64().substring(0, 4);
}
/**
* title: name.packetsize.sendfrequency
* @return the title
*/
public String getTitle() {
return getPeerName() + '.' + getSize() + '.' + getClientConfig().getSendFrequency();
}
/**
* summary. includes:name, size, sendfrequency, and # of hops
* @return the summary
*/
public String getSummary() {
return "Send peer " + getPeerName() + ' ' + getSize() + " every " +
getClientConfig().getSendFrequency() + " seconds through " +
getClientConfig().getNumHops() + "-hop tunnels";
}
private String getSize() {
int bytes = getClientConfig().getSendSize();
if (bytes < 1024)
return bytes + "b";
return bytes/1024 + "kb";
}
/**
* we've got someone who wants to be notified of changes to the plot config
* @param lsnr the listener to be added
*/
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
/**
* remove a listener
* @param lsnr the listener to remove
*/
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
void fireUpdate() {
if (_disabled) return;
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
((UpdateListener)iter.next()).configUpdated(this);
}
}
/**
* Disables notification of events listeners
* @see PeerPlotConfig#fireUpdate()
*/
public void disableEvents() { _disabled = true; }
/**
* Enables notification of events listeners
* @see PeerPlotConfig#fireUpdate()
*/
public void enableEvents() { _disabled = false; }
/**
* How do we want to render a particular dataset (either the current or the averaged values)?
*/
public class PlotSeriesConfig {
private long _period;
private boolean _plotSendTime;
private boolean _plotReceiveTime;
private boolean _plotLostMessages;
private Color _plotLineColor;
/**
* Delegating constructor . . .
* @param period the period for the config
* (0 for current, otherwise # of milliseconds being averaged over)
*/
public PlotSeriesConfig(long period) {
this(period, false, false, false, null);
if (period <= 0) {
_plotSendTime = true;
_plotReceiveTime = true;
_plotLostMessages = true;
}
}
/**
* Creates a config for the rendering of a particular dataset)
* @param period the period for the config
* (0 for current, otherwise # of milliseconds being averaged over)
* @param plotSend do we plot send times?
* @param plotReceive do we plot receive times?
* @param plotLost do we plot lost packets?
* @param plotColor in what color?
*/
public PlotSeriesConfig(long period, boolean plotSend, boolean plotReceive, boolean plotLost, Color plotColor) {
_period = period;
_plotSendTime = plotSend;
_plotReceiveTime = plotReceive;
_plotLostMessages = plotLost;
_plotLineColor = plotColor;
}
/**
* Retrieves the plot config this plot series config is a part of
* @return the plot config
*/
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
/**
* What period is this series config describing?
* @return 0 for current, otherwise # milliseconds that are being averaged over
*/
public long getPeriod() { return _period; }
/**
* Sets the period this series config is describing
* @param period the period
* (0 for current, otherwise # milliseconds that are being averaged over)
*/
public void setPeriod(long period) {
_period = period;
fireUpdate();
}
/**
* Should we render the time to send (ping to peer)?
* @return true or false . . .
*/
public boolean getPlotSendTime() { return _plotSendTime; }
/**
* Sets whether we render the time to send (ping to peer) or not
* @param shouldPlot true or false
*/
public void setPlotSendTime(boolean shouldPlot) {
_plotSendTime = shouldPlot;
fireUpdate();
}
/**
* Should we render the time to receive (peer pong to us)?
* @return true or false . . .
*/
public boolean getPlotReceiveTime() { return _plotReceiveTime; }
/**
* Sets whether we render the time to receive (peer pong to us)
* @param shouldPlot true or false
*/
public void setPlotReceiveTime(boolean shouldPlot) {
_plotReceiveTime = shouldPlot;
fireUpdate();
}
/**
* Should we render the number of messages lost (ping sent, no pong received in time)?
* @return true or false . . .
*/
public boolean getPlotLostMessages() { return _plotLostMessages; }
/**
* Sets whether we render the number of messages lost (ping sent, no pong received in time) or not
* @param shouldPlot true or false
*/
public void setPlotLostMessages(boolean shouldPlot) {
_plotLostMessages = shouldPlot;
fireUpdate();
}
/**
* What color should we plot the data with?
* @return the color
*/
public Color getPlotLineColor() { return _plotLineColor; }
/**
* Sets the color we should plot the data with
* @param color the color to use
*/
public void setPlotLineColor(Color color) {
_plotLineColor = color;
fireUpdate();
}
}
/**
* An interface for listening to updates . . .
*/
public interface UpdateListener {
/**
* @param config the peer plot config that changes
* @see PeerPlotConfig#fireUpdate()
*/
void configUpdated(PeerPlotConfig config);
}
}

View File

@ -1,371 +0,0 @@
package net.i2p.heartbeat.gui;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import net.i2p.util.Log;
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
private final static Log _log = new Log(PeerPlotConfigPane.class);
private PeerPlotConfig _config;
private HeartbeatControlPane _parent;
private JLabel _title;
private JButton _delete;
private JLabel _fromLabel;
private JTextField _from;
private JTextArea _comments;
private JLabel _peerLabel;
private JTextField _peerKey;
private JLabel _localLabel;
private JTextField _localKey;
private OptionLine _options[];
private Random _rnd = new Random();
private final static Color WHITE = new Color(255, 255, 255);
private Color _background = WHITE;
/**
* Constructs a pane
* @param config the plot config it represents
* @param pane the pane this one is attached to
*/
public PeerPlotConfigPane(PeerPlotConfig config, HeartbeatControlPane pane) {
_config = config;
_parent = pane;
if (_parent != null)
_background = _parent.getBackground();
_config.addListener(this);
initializeComponents();
}
/** called when the user wants to stop monitoring this test */
private void delete() {
_parent.removeTest(_config);
}
private void initializeComponents() {
buildComponents();
placeComponents(this);
refreshView();
//setBorder(new BevelBorder(BevelBorder.RAISED));
setBackground(_background);
}
/**
* place all the gui components onto the given panel
* @param body the panel to place the components on
*/
private void placeComponents(JPanel body) {
body.setLayout(new GridBagLayout());
GridBagConstraints cts = new GridBagConstraints();
// row 0: title + delete
cts.gridx = 0;
cts.gridy = 0;
cts.gridwidth = 5;
cts.anchor = GridBagConstraints.WEST;
cts.fill = GridBagConstraints.NONE;
body.add(_title, cts);
cts.gridx = 5;
cts.gridwidth = 1;
cts.anchor = GridBagConstraints.NORTHWEST;
cts.fill = GridBagConstraints.BOTH;
body.add(_delete, cts);
// row 1: from + location
cts.gridx = 0;
cts.gridy = 1;
cts.gridwidth = 1;
cts.fill = GridBagConstraints.NONE;
body.add(_fromLabel, cts);
cts.gridx = 1;
cts.gridwidth = 5;
cts.fill = GridBagConstraints.BOTH;
body.add(_from, cts);
// row 2: comment
cts.gridx = 0;
cts.gridy = 2;
cts.gridwidth = 6;
cts.fill = GridBagConstraints.BOTH;
body.add(_comments, cts);
// row 3: peer + peerKey
cts.gridx = 0;
cts.gridy = 3;
cts.gridwidth = 1;
cts.fill = GridBagConstraints.NONE;
body.add(_peerLabel, cts);
cts.gridx = 1;
cts.gridwidth = 5;
cts.fill = GridBagConstraints.BOTH;
body.add(_peerKey, cts);
// row 4: local + localKey
cts.gridx = 0;
cts.gridy = 4;
cts.gridwidth = 1;
cts.fill = GridBagConstraints.NONE;
body.add(_localLabel, cts);
cts.gridx = 1;
cts.gridwidth = 5;
cts.fill = GridBagConstraints.BOTH;
body.add(_localKey, cts);
// row 5-N: data row
for (int i = 0; i < _options.length; i++) {
cts.gridx = 0;
cts.gridy = 5 + i;
cts.gridwidth = 1;
cts.fill = GridBagConstraints.NONE;
cts.anchor = GridBagConstraints.WEST;
if (_options[i]._durationMinutes <= 0)
body.add(new JLabel("Data: "), cts);
else
body.add(new JLabel(_options[i]._durationMinutes + "m avg: "), cts);
cts.gridx = 1;
body.add(_options[i]._send, cts);
cts.gridx = 2;
body.add(_options[i]._recv, cts);
cts.gridx = 3;
body.add(_options[i]._lost, cts);
cts.gridx = 4;
body.add(_options[i]._all, cts);
cts.gridx = 5;
body.add(_options[i]._color, cts);
}
}
/** build all of the gui components */
private void buildComponents() {
_title = new JLabel(_config.getSummary());
_title.setBackground(_background);
_delete = new JButton("Delete");
_delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { delete(); } });
_delete.setEnabled(false);
_delete.setBackground(_background);
_fromLabel = new JLabel("Location: ");
_fromLabel.setBackground(_background);
_from = new JTextField(_config.getLocation());
_from.setEditable(false);
_from.setBackground(_background);
_comments = new JTextArea(_config.getClientConfig().getComment(), 2, 20);
// _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 40);
_comments.setEditable(false);
_comments.setBackground(_background);
_peerLabel = new JLabel("Peer: ");
_peerLabel.setBackground(_background);
_peerKey = new JTextField(_config.getClientConfig().getPeer().toBase64(), 8);
_peerKey.setBackground(_background);
_localLabel = new JLabel("Local: ");
_localLabel.setBackground(_background);
_localKey = new JTextField(_config.getClientConfig().getUs().toBase64(), 8);
_localKey.setBackground(_background);
int averagedPeriods[] = _config.getClientConfig().getAveragePeriods();
if (averagedPeriods == null)
averagedPeriods = new int[0];
_options = new OptionLine[1 + averagedPeriods.length];
_options[0] = new OptionLine(0);
for (int i = 0; i < averagedPeriods.length; i++) {
_options[1+i] = new OptionLine(averagedPeriods[i]);
}
}
/** the settings have changed - revise */
private void refreshView() {
for (int i = 0; i < _options.length; i++) {
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_options[i]._durationMinutes);
if (cfg == null) {
_log.warn("Config for minutes " + _options[i]._durationMinutes + " was not found?");
continue;
}
//_log.debug("Refreshing view for minutes ["+ _options[i]._durationMinutes + "]: send [" +
// _options[i]._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
// _options[i]._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
// _options[i]._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]");
_options[i]._send.setSelected(cfg.getPlotSendTime());
_options[i]._recv.setSelected(cfg.getPlotReceiveTime());
_options[i]._lost.setSelected(cfg.getPlotLostMessages());
if (cfg.getPlotLineColor() != null)
_options[i]._color.setBackground(cfg.getPlotLineColor());
}
}
/**
* find the right config for the given period
* @param minutes the minutes to locate the config by
* @return the config for the given period, or null
*/
private PeerPlotConfig.PlotSeriesConfig getConfig(int minutes) {
if (minutes <= 0)
return _config.getCurrentSeriesConfig();
List configs = _config.getAverageSeriesConfigs();
for (int i = 0; i < configs.size(); i++) {
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
if (cfg.getPeriod() == minutes * 60*1000)
return cfg;
}
return null;
}
/**
* notified that the config has been updated
* @param config the config that was been updated
*/
public void configUpdated(PeerPlotConfig config) { refreshView(); }
private class ChooseColor implements ActionListener {
private int _minutes;
private JButton _button;
/**
* @param minutes the minutes (line) to change the color of...
* @param button the associated button
*/
public ChooseColor(int minutes, JButton button) {
_minutes = minutes;
_button = button;
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent evt) {
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
Color origColor = null;
if (cfg != null)
origColor = cfg.getPlotLineColor();
Color color = JColorChooser.showDialog(PeerPlotConfigPane.this, "What color should this line be?", origColor);
if (color != null) {
if (cfg != null)
cfg.setPlotLineColor(color);
_button.setBackground(color);
}
}
}
private class OptionLine {
int _durationMinutes;
JCheckBox _send;
JCheckBox _recv;
JCheckBox _lost;
JCheckBox _all;
JButton _color;
/**
* Creates an OptionLine.
* @param durationMinutes the minutes =)
*/
public OptionLine(int durationMinutes) {
_durationMinutes = durationMinutes;
_send = new JCheckBox("send time");
_send.setBackground(_background);
_recv = new JCheckBox("receive time");
_recv.setBackground(_background);
_lost = new JCheckBox("lost messages");
_lost.setBackground(_background);
_all = new JCheckBox("all");
_all.setBackground(_background);
_color = new JButton("color");
int r = _rnd.nextInt(255);
if (r < 0) r = -r;
int g = _rnd.nextInt(255);
if (g < 0) g = -g;
int b = _rnd.nextInt(255);
if (b < 0) b = -b;
//_color.setBackground(new Color(r, g, b));
_color.setBackground(_background);
_send.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
_recv.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
_lost.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
_all.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
_color.addActionListener(new ChooseColor(durationMinutes, _color));
_color.setEnabled(false);
}
}
private class UpdateListener implements ActionListener {
private OptionLine _line;
private int _minutes;
/**
* Update Listener constructor . . .
* @param line the line
* @param minutes the minutes
*/
public UpdateListener(OptionLine line, int minutes) {
_line = line;
_minutes = minutes;
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent evt) {
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
cfg.getPlotConfig().disableEvents();
_log.debug("Updating data for minutes ["+ _line._durationMinutes + "]: send [" +
_line._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
_line._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
_line._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]: config = " + cfg);
boolean force = _line._all.isSelected();
cfg.setPlotSendTime(_line._send.isSelected() || force);
cfg.setPlotReceiveTime(_line._recv.isSelected() || force);
cfg.setPlotLostMessages(_line._lost.isSelected() || force);
cfg.getPlotConfig().enableEvents();
cfg.getPlotConfig().fireUpdate();
}
}
/**
* Unit test stuff
* @param args da arsg
*/
public final static void main(String args[]) {
Test t = new Test();
t.runTest();
}
private final static class Test implements PeerPlotStateFetcher.FetchStateReceptor {
/**
* Runs da test
*/
public void runTest() {
PeerPlotConfig cfg = new PeerPlotConfig("C:\\testnet\\r2\\heartbeatStat_10s_30kb.txt");
PeerPlotState state = new PeerPlotState(cfg);
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
System.exit(-1);
}
/* (non-Javadoc)
* @see net.i2p.heartbeat.gui.PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched(net.i2p.heartbeat.gui.PeerPlotState)
*/
public void peerPlotStateFetched(PeerPlotState state) {
javax.swing.JFrame f = new javax.swing.JFrame("Test");
f.getContentPane().add(new JScrollPane(new PeerPlotConfigPane(state.getPlotConfig(), null)));
f.pack();
f.setVisible(true);
}
}
}

View File

@ -1,95 +0,0 @@
package net.i2p.heartbeat.gui;
/**
* Current data + plot config for a particular test
*
*/
class PeerPlotState {
private StaticPeerData _currentData;
private PeerPlotConfig _plotConfig;
/**
* Delegating constructor . . .
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
*/
public PeerPlotState() {
this(null, null);
}
/**
* Delegating constructor . . .
* @param config plot config
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
*/
public PeerPlotState(PeerPlotConfig config) {
this(config, new StaticPeerData(config.getClientConfig()));
}
/**
* Creates a PeerPlotState
* @param config plot config
* @param data peer data
*/
public PeerPlotState(PeerPlotConfig config, StaticPeerData data) {
_plotConfig = config;
_currentData = data;
}
/**
* Add an average
* @param minutes mins averaged over
* @param sendMs how much later did the peer receieve
* @param recvMs how much later did we receieve
* @param lost how many were lost
*/
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
// make sure we've got the config entry for the average
_plotConfig.addAverage(minutes);
// add the data point...
_currentData.addAverage(minutes, sendMs, recvMs, lost);
}
/**
* we successfully got a ping/pong through
*
* @param sendTime when did the ping get sent?
* @param sendMs how much later did the peer receive the ping?
* @param recvMs how much later than that did we receive the pong?
*/
public void addSuccess(long sendTime, int sendMs, int recvMs) {
_currentData.addData(sendTime, sendMs, recvMs);
}
/**
* we lost a ping/pong
*
* @param sendTime when did we send the ping?
*/
public void addLost(long sendTime) {
_currentData.addData(sendTime);
}
/**
* data set to render
* @return the data set
*/
public StaticPeerData getCurrentData() { return _currentData; }
/**
* Sets the data set to render
* @param data the data set
*/
public void setCurrentData(StaticPeerData data) { _currentData = data; }
/**
* configuration options on how to render the data set
* @return the config options
*/
public PeerPlotConfig getPlotConfig() { return _plotConfig; }
/**
* Sets the configuration options on how to render the data
* @param config the config options
*/
public void setPlotConfig(PeerPlotConfig config) { _plotConfig = config; }
}

View File

@ -1,363 +0,0 @@
package net.i2p.heartbeat.gui;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.StringTokenizer;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
class PeerPlotStateFetcher {
private final static Log _log = new Log(PeerPlotStateFetcher.class);
/**
* Fetch and fill the specified state structure
* @param receptor the 'receptor' (callbacks)
* @param state the state
*/
public static void fetchPeerPlotState(FetchStateReceptor receptor, PeerPlotState state) {
I2PThread t = new I2PThread(new Fetcher(receptor, state));
t.setDaemon(true);
t.setName("Fetch state from " + state.getPlotConfig().getLocation());
t.start();
}
/**
* Callback stuff . . .
*/
public interface FetchStateReceptor {
/**
* Called when a peer plot state is fetched
* @param state state that was fetched
*/
void peerPlotStateFetched(PeerPlotState state);
}
private static class Fetcher implements Runnable {
private PeerPlotState _state;
private FetchStateReceptor _receptor;
/**
* Creates a Fetcher thread
* @param receptor the 'receptor' (callbacks)
* @param state the state
*/
public Fetcher(FetchStateReceptor receptor, PeerPlotState state) {
_state = state;
_receptor = receptor;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
String loc = _state.getPlotConfig().getLocation();
_log.debug("Load called [" + loc + "]");
InputStream in = null;
try {
try {
URL location = new URL(loc);
in = location.openStream();
} catch (MalformedURLException mue) {
_log.debug("Not a url [" + loc + "]");
in = null;
}
if (in == null)
in = new FileInputStream(loc);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ( (line = reader.readLine()) != null) {
handleLine(line);
}
if (valid())
_receptor.peerPlotStateFetched(_state);
} catch (IOException ioe) {
_log.error("Error retrieving from the location [" + loc + "]", ioe);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
/**
* check to make sure we've got everything we need
* @return true [always]
*/
boolean valid() {
return true;
}
/**
* handle a line from the data set - these can be formatted in one of the
* following ways. <p />
*
* <pre>
* peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
* local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
* peerDest [base 64 of the full destination]
* localDest [base 64 of the full destination]
* numTunnelHops 2
* comment Test with localhost sending 30KB every 20 seconds
* sendFrequency 20
* sendSize 30720
* sessionStart 20040409.22:51:10.915
* currentTime 20040409.23:31:39.607
* numPending 2
* lifetimeSent 118
* lifetimeRecv 113
* #averages minutes sendMs recvMs numLost
* periodAverage 1 1843 771 0
* periodAverage 5 786 752 1
* periodAverage 30 855 735 3
* #action status date and time sent sendMs replyMs
* EVENT OK 20040409.23:21:44.742 691 670
* EVENT OK 20040409.23:22:05.201 671 581
* EVENT OK 20040409.23:22:26.301 1182 1452
* EVENT OK 20040409.23:22:47.322 24304 1723
* EVENT OK 20040409.23:23:08.232 2293 1081
* EVENT OK 20040409.23:23:29.332 1392 641
* EVENT OK 20040409.23:23:50.262 641 761
* EVENT OK 20040409.23:24:11.102 651 701
* EVENT OK 20040409.23:24:31.401 841 621
* EVENT OK 20040409.23:24:52.061 651 681
* EVENT OK 20040409.23:25:12.480 701 1623
* EVENT OK 20040409.23:25:32.990 1442 1212
* EVENT OK 20040409.23:25:54.230 591 631
* EVENT OK 20040409.23:26:14.620 620 691
* EVENT OK 20040409.23:26:35.199 1793 1432
* EVENT OK 20040409.23:26:56.570 661 641
* EVENT OK 20040409.23:27:17.200 641 660
* EVENT OK 20040409.23:27:38.120 611 921
* EVENT OK 20040409.23:27:58.699 831 621
* EVENT OK 20040409.23:28:19.559 801 661
* EVENT OK 20040409.23:28:40.279 601 611
* EVENT OK 20040409.23:29:00.648 601 621
* EVENT OK 20040409.23:29:21.288 701 661
* EVENT LOST 20040409.23:29:41.828
* EVENT LOST 20040409.23:30:02.327
* EVENT LOST 20040409.23:30:22.656
* EVENT OK 20040409.23:31:24.305 1843 771
* </pre>
*
* @param line (see above)
*/
private void handleLine(String line) {
if (line.startsWith("peerDest"))
handlePeerDest(line);
else if (line.startsWith("localDest"))
handleLocalDest(line);
else if (line.startsWith("numTunnelHops"))
handleNumTunnelHops(line);
else if (line.startsWith("comment"))
handleComment(line);
else if (line.startsWith("sendFrequency"))
handleSendFrequency(line);
else if (line.startsWith("sendSize"))
handleSendSize(line);
else if (line.startsWith("periodAverage"))
handlePeriodAverage(line);
else if (line.startsWith("EVENT"))
handleEvent(line);
else if (line.startsWith("numPending"))
handleNumPending(line);
else if (line.startsWith("sessionStart"))
handleSessionStart(line);
else
_log.debug("Not handled: " + line);
}
private void handlePeerDest(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String destKey = tok.nextToken();
try {
Destination d = new Destination();
d.fromBase64(destKey);
_state.getPlotConfig().getClientConfig().setPeer(d);
_log.debug("Setting the peer to " + d.calculateHash().toBase64());
} catch (DataFormatException dfe) {
_log.error("Unable to parse the peerDest line: [" + line + "]", dfe);
}
}
private void handleLocalDest(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String destKey = tok.nextToken();
try {
Destination d = new Destination();
d.fromBase64(destKey);
_state.getPlotConfig().getClientConfig().setUs(d);
} catch (DataFormatException dfe) {
_log.error("Unable to parse the localDest line: [" + line + "]", dfe);
}
}
private void handleComment(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
StringBuffer buf = new StringBuffer(line.length()-32);
while (tok.hasMoreTokens())
buf.append(tok.nextToken()).append(' ');
_state.getPlotConfig().getClientConfig().setComment(buf.toString());
}
private void handleNumTunnelHops(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String num = tok.nextToken();
try {
int val = Integer.parseInt(num);
_state.getPlotConfig().getClientConfig().setNumHops(val);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the numTunnelHops line: [" + line + "]", nfe);
}
}
private void handleNumPending(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String num = tok.nextToken();
try {
int val = Integer.parseInt(num);
_state.getCurrentData().setPendingCount(val);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the numPending line: [" + line + "]", nfe);
}
}
private void handleSendFrequency(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String num = tok.nextToken();
try {
int val = Integer.parseInt(num);
_state.getPlotConfig().getClientConfig().setSendFrequency(val);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the sendFrequency line: [" + line + "]", nfe);
}
}
private void handleSendSize(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String num = tok.nextToken();
try {
int val = Integer.parseInt(num);
_state.getPlotConfig().getClientConfig().setSendSize(val);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
}
}
private void handleSessionStart(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
String date = tok.nextToken();
try {
long when = getDate(date);
_state.getCurrentData().setSessionStart(when);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the sessionStart line: [" + line + "]", nfe);
}
}
private void handlePeriodAverage(String line) {
StringTokenizer tok = new StringTokenizer(line);
tok.nextToken(); // ignore;
try {
// periodAverage minutes sendMs recvMs numLost
int min = Integer.parseInt(tok.nextToken());
int send = Integer.parseInt(tok.nextToken());
int recv = Integer.parseInt(tok.nextToken());
int lost = Integer.parseInt(tok.nextToken());
_state.addAverage(min, send, recv, lost);
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
}
}
private void handleEvent(String line) {
StringTokenizer tok = new StringTokenizer(line);
// * EVENT OK 20040409.23:29:21.288 701 661
// * EVENT LOST 20040409.23:29:41.828
tok.nextToken(); // ignore first two
tok.nextToken();
try {
long when = getDate(tok.nextToken());
if (when < 0) {
_log.error("Invalid EVENT line: [" + line + "]");
return;
}
if (tok.hasMoreTokens()) {
int sendMs = Integer.parseInt(tok.nextToken());
int recvMs = Integer.parseInt(tok.nextToken());
_state.addSuccess(when, sendMs, recvMs);
} else {
_state.addLost(when);
}
} catch (NumberFormatException nfe) {
_log.error("Unable to parse the EVENT line: [" + line + "]", nfe);
}
}
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
private long getDate(String date) {
synchronized (_fmt) {
try {
return _fmt.parse(date).getTime();
} catch (ParseException pe) {
_log.error("Unable to parse the date [" + date + "]", pe);
return -1;
}
}
}
private void fakeRun() { /* UNUSED */
try {
Destination peer = new Destination();
Destination us = new Destination();
peer.fromBase64("3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJ" +
"cS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7g" +
"fqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teM" +
"d5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA");
us.fromBase64("W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYpp" +
"K9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63" +
"rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HC" +
"VilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA");
_state.getPlotConfig().getClientConfig().setPeer(peer);
_state.getPlotConfig().getClientConfig().setUs(us);
_state.getPlotConfig().getClientConfig().setNumHops(2);
_state.getPlotConfig().getClientConfig().setComment("we do stuff\nreally nifty stuff. really");
_state.getPlotConfig().getClientConfig().setAveragePeriods(new int[] { 1, 5, 30, 60 });
int rnd = new java.util.Random().nextInt();
if (rnd > 0)
rnd = rnd % 10;
else
rnd = (-rnd) % 10;
_state.getPlotConfig().getClientConfig().setSendFrequency(rnd);
_state.getPlotConfig().getClientConfig().setSendSize(16*1024);
_state.getPlotConfig().getClientConfig().setStatDuration(10);
_state.getPlotConfig().rebuildAverageSeriesConfigs();
_state.setCurrentData(new StaticPeerData(_state.getPlotConfig().getClientConfig()));
_receptor.peerPlotStateFetched(_state);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@ -1,134 +0,0 @@
package net.i2p.heartbeat.gui;
import java.util.HashMap;
import java.util.Map;
import net.i2p.heartbeat.ClientConfig;
import net.i2p.heartbeat.PeerData;
/**
* Raw data points for a test
*/
class StaticPeerData extends PeerData {
private int _pending;
/** Integer (period, in minutes) to Integer (milliseconds) for sending a ping */
private Map _averageSendTimes;
/** Integer (period, in minutes) to Integer (milliseconds) for receiving a pong */
private Map _averageReceiveTimes;
/** Integer (period, in minutes) to Integer (num messages) of how many messages were lost on average */
private Map _lostMessages;
/**
* Creates a static peer data with a specified client config ... duh
* @param config the client config
*/
public StaticPeerData(ClientConfig config) {
super(config);
_averageSendTimes = new HashMap(4);
_averageReceiveTimes = new HashMap(4);
_lostMessages = new HashMap(4);
}
/**
* Adds averaged data
* @param minutes the minutes (averaged over)
* @param sendMs the send time (ping) in milliseconds
* @param recvMs the receive time (pong) in milliseconds
* @param lost the number lost
*/
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
_averageSendTimes.put(new Integer(minutes), new Integer(sendMs));
_averageReceiveTimes.put(new Integer(minutes), new Integer(recvMs));
_lostMessages.put(new Integer(minutes), new Integer(lost));
}
/**
* Sets the number pending
* @param numPending the number pending
*/
public void setPendingCount(int numPending) { _pending = numPending; }
/* (non-Javadoc)
* @see net.i2p.heartbeat.PeerData#setSessionStart(long)
*/
public void setSessionStart(long when) { super.setSessionStart(when); }
/**
* Adds data
* @param sendTime the time it was sent
* @param sendMs the send time (ping) in milliseconds
* @param recvMs the receive time (pong) in milliseconds
*/
public void addData(long sendTime, int sendMs, int recvMs) {
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
dataPoint.setPongSent(sendTime + sendMs);
dataPoint.setPongReceived(sendTime + sendMs + recvMs);
dataPoint.setWasPonged(true);
addDataPoint(dataPoint);
}
/**
* Adds data
* @param sendTime the time it was sent
*/
public void addData(long sendTime) {
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
dataPoint.setWasPonged(false);
addDataPoint(dataPoint);
}
/**
* how many pings are still outstanding?
* @return the number of pings outstanding
*/
public int getPendingCount() { return _pending; }
/**
* average time to send over the given period.
*
* @param period number of minutes to retrieve the average for
* @return milliseconds average, or -1 if we dont track that period
*/
public double getAverageSendTime(int period) {
Integer i = (Integer)_averageSendTimes.get(new Integer(period));
if (i == null)
return -1;
return i.doubleValue();
}
/**
* average time to receive over the given period.
*
* @param period number of minutes to retrieve the average for
* @return milliseconds average, or -1 if we dont track that period
*/
public double getAverageReceiveTime(int period) {
Integer i = (Integer)_averageReceiveTimes.get(new Integer(period));
if (i == null)
return -1;
return i.doubleValue();
}
/**
* number of lost messages over the given period.
*
* @param period number of minutes to retrieve the average for
* @return number of lost messages in the period, or -1 if we dont track that period
*/
public double getLostMessages(int period) {
Integer i = (Integer)_lostMessages.get(new Integer(period));
if (i == null)
return -1;
return i.doubleValue();
}
/* (non-Javadoc)
* @see net.i2p.heartbeat.PeerData#cleanup()
*/
public void cleanup() {}
}

View File

@ -1,278 +0,0 @@
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.

View File

@ -1,11 +0,0 @@
$Id$
the i2p/apps/httptunnel module is the root of the
HTTPTunnel application, and everything within it
is released according to the terms of the I2P
license policy. That means everything contained
within the i2p/apps/httptunnel module is released
under the GPL plus the java exception unless
otherwise marked. Alternate licenses that may be
used include BSD, Cryptix, and MIT, as well as
code granted into the public domain.

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="httptunnel">
<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="compile">
<jar destfile="./build/httptunnel.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="net.i2p.httptunnel.HTTPTunnel" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
</manifest>
</jar>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
packagenames="*"
use="true"
splitindex="true"
windowtitle="HTTPTunnel" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<!-- ministreaming will clean core -->
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
<target name="distclean" depends="clean">
<!-- ministreaming will clean core -->
<ant dir="../../ministreaming/java/" target="distclean" />
</target>
</project>

View File

@ -1,87 +0,0 @@
package net.i2p.httptunnel;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.util.Log;
/**
* Listens on a port for HTTP connections.
*/
public class HTTPListener extends Thread {
private static final Log _log = new Log(HTTPListener.class);
private int port;
private String listenHost;
private SocketManagerProducer smp;
/**
* A public constructor. It contstructs things. In this case,
* it constructs a nice HTTPListener, for all your listening on
* HTTP needs. Yep. That's right.
* @param smp A SocketManagerProducer, producing Sockets, no doubt
* @param port A port, to connect to.
* @param listenHost A host, to connect to.
*/
public HTTPListener(SocketManagerProducer smp, int port, String listenHost) {
this.smp = smp;
this.port = port;
start();
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
public void run() {
try {
InetAddress lh = listenHost == null ? null : InetAddress.getByName(listenHost);
ServerSocket ss = new ServerSocket(port, 0, lh);
while (true) {
Socket s = ss.accept();
new HTTPSocketHandler(this, s);
}
} catch (IOException ex) {
_log.error("Error while accepting connections", ex);
}
}
private boolean proxyUsed = false;
/**
* Query whether this is the first use of the proxy or not
* @return Whether this is the first proxy use, no doubt.
*/
public boolean firstProxyUse() {
if (true) return false; // FIXME: check a config option here
if (proxyUsed) {
return false;
}
proxyUsed = true;
return true;
}
/**
* @return The SocketManagerProducer being used.
*/
public SocketManagerProducer getSMP() {
return smp;
}
/**
* Outputs with HTTP 1.1 flair that a feature isn't implemented.
* @param out The stream the text goes to.
* @deprecated
* @throws IOException
*/
public void handleNotImplemented(OutputStream out) throws IOException {
out.write(("HTTP/1.1 200 Document following\n\n" + "<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
out.flush();
}
}

View File

@ -1,62 +0,0 @@
package net.i2p.httptunnel;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.httptunnel.handler.RootHandler;
import net.i2p.util.Log;
/**
* Handles a single HTTP socket connection.
*/
public class HTTPSocketHandler extends Thread {
private static final Log _log = new Log(HTTPSocketHandler.class);
private Socket s;
private HTTPListener httpl;
private RootHandler h;
/**
* A public constructor.
* @param httpl An HTTPListener, to listen for HTTP, no doubt
* @param s A socket.
*/
public HTTPSocketHandler(HTTPListener httpl, Socket s) {
this.httpl = httpl;
this.s = s;
h = RootHandler.getInstance();
start();
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
public void run() {
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(s.getInputStream());
out = new BufferedOutputStream(s.getOutputStream());
Request req = new Request(in);
h.handle(req, httpl, out);
} catch (IOException ex) {
_log.error("Error while handling data", ex);
} finally {
try {
if (in != null) in.close();
if (out != null) {
out.flush();
out.close();
}
s.close();
} catch (IOException ex) {
_log.error("IOException in finalizer", ex);
}
}
}
}

View File

@ -1,108 +0,0 @@
/*
* HTTPTunnel
* (c) 2003 - 2004 mihi
*
* 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* In addition, as a special exception, mihi gives permission to link
* the code of this program with the proprietary Java implementation
* provided by Sun (or other vendors as well), and distribute linked
* combinations including the two. You must obey the GNU General
* Public License in all respects for all of the code used other than
* the proprietary Java implementation. If you modify this file, you
* may extend this exception to your version of the file, but you are
* not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package net.i2p.httptunnel;
import net.i2p.client.streaming.I2PSocketManager;
/**
* HTTPTunnel main class.
*/
public class HTTPTunnel {
/**
* Create a HTTPTunnel instance.
*
* @param initialManagers a list of socket managers to use
* @param maxManagers how many managers to have in the cache
* @param shouldThrowAwayManagers whether to throw away a manager after use
* @param listenPort which port to listen on
*/
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
int listenPort) {
this(initialManagers, maxManagers, shouldThrowAwayManagers, listenPort, "127.0.0.1", 7654);
}
/**
* Create a HTTPTunnel instance.
*
* @param initialManagers a list of socket managers to use
* @param maxManagers how many managers to have in the cache
* @param shouldThrowAwayManagers whether to throw away a manager after use
* @param listenPort which port to listen on
* @param i2cpAddress the I2CP address
* @param i2cpPort the I2CP port
*/
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
int listenPort, String i2cpAddress, int i2cpPort) {
SocketManagerProducer smp = new SocketManagerProducer(initialManagers, maxManagers, shouldThrowAwayManagers,
i2cpAddress, i2cpPort);
new HTTPListener(smp, listenPort, "127.0.0.1");
}
/**
* The all important main function, allowing HTTPTunnel to be
* stand-alone, a program in it's own right, and all that jazz.
* @param args A list of String passed to the program
*/
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 7654, max = 1;
boolean throwAwayManagers = false;
if (args.length > 1) {
if (args.length == 4) {
host = args[2];
port = Integer.parseInt(args[3]);
} else if (args.length != 2) {
showInfo();
return;
}
max = Integer.parseInt(args[1]);
} else if (args.length != 1) {
showInfo();
return;
}
if (max == 0) {
max = 1;
} else if (max < 0) {
max = -max;
throwAwayManagers = true;
}
new HTTPTunnel(null, max, throwAwayManagers, Integer.parseInt(args[0]), host, port);
}
private static void showInfo() {
System.out.println("Usage: java HTTPTunnel <listenPort> [<max> " + "[<i2cphost> <i2cpport>]]\n"
+ " <listenPort> port to listen for browsers\n"
+ " <max> max number of SocketMangers in pool, " + "use neg. number\n"
+ " to use each SocketManager only once " + "(default: 1)\n"
+ " <i2cphost> host to connect to the router " + "(default: 127.0.0.1)\n"
+ " <i2cpport> port to connect to the router " + "(default: 7654)");
}
}

View File

@ -1,153 +0,0 @@
package net.i2p.httptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import net.i2p.util.Log;
/**
* A HTTP request (GET or POST). This will be passed to a hander for
* handling it.
*/
public class Request {
private static final Log _log = new Log(Request.class);
// all strings are forced to be ISO-8859-1 encoding
private String method;
private String url;
private String proto;
private String params;
private String postData;
/**
* A constructor, creating a request from an InputStream
* @param in InputStream from which we "read-in" a Request
* @throws IOException
*/
public Request(InputStream in) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
String line = br.readLine();
if (line == null) { // no data at all
method = null;
_log.error("Connection but no data");
return;
}
int pos = line.indexOf(" ");
if (pos == -1) {
method = line;
url = "";
_log.error("Malformed HTTP request: " + line);
} else {
method = line.substring(0, pos);
url = line.substring(pos + 1);
}
proto = "";
pos = url.indexOf(" ");
if (pos != -1) {
proto = url.substring(pos); // leading space intended
url = url.substring(0, pos);
}
StringBuffer sb = new StringBuffer(512);
while ((line = br.readLine()) != null) {
if (line.length() == 0) break;
sb.append(line).append("\r\n");
}
params = sb.toString(); // no leading empty line!
sb = new StringBuffer();
// hack for POST requests, ripped from HttpClient
// this won't work for large POSTDATA
// FIXME: do this better, please.
if (!method.equals("GET")) {
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
sb.append((char) i);
}
}
postData = sb.toString();
} else {
postData = "";
}
}
/**
* @return A Request as an array of bytes of a String in ISO-8859-1 format
* @throws IOException
*/
public byte[] toByteArray() throws IOException {
if (method == null) return null;
return toISO8859_1String().getBytes("ISO-8859-1");
}
private String toISO8859_1String() throws IOException {
if (method == null) return null;
return method + " " + url + proto + "\r\n" + params + "\r\n" + postData;
}
/**
* @return the URL of the request
*/
public String getURL() {
return url;
}
/**
* Sets the URL of the Request
* @param newURL the new URL
*/
public void setURL(String newURL) {
url = newURL;
}
/**
* Retrieves the value of a param.
* @param name The name of the param
* @return The value of the param, or null
*/
public String getParam(String name) {
try {
BufferedReader br = new BufferedReader(new StringReader(params));
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith(name)) { return line.substring(name.length()); }
}
return null;
} catch (IOException ex) {
_log.error("Error getting parameter", ex);
return null;
}
}
/**
* Sets the value of a param.
* @param name the name of the param
* @param value the value to be set
*/
public void setParam(String name, String value) {
try {
StringBuffer sb = new StringBuffer(params.length() + value.length());
BufferedReader br = new BufferedReader(new StringReader(params));
String line;
boolean replaced = false;
while ((line = br.readLine()) != null) {
if (line.startsWith(name)) {
replaced = true;
if (value == null) continue; // kill param
line = name + value;
}
sb.append(line).append("\r\n");
}
if (!replaced && value != null) {
sb.append(name).append(value).append("\r\n");
}
params = sb.toString();
} catch (IOException ex) {
_log.error("Error getting parameter", ex);
}
}
}

View File

@ -1,120 +0,0 @@
package net.i2p.httptunnel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
/**
* Produces SocketManagers in a thread and gives them to those who
* need them.
*/
public class SocketManagerProducer extends Thread {
private ArrayList myManagers = new ArrayList();
private HashMap usedManagers = new HashMap();
private int port;
private String host;
private int maxManagers;
private boolean shouldThrowAwayManagers;
/**
* Public constructor creating a SocketManagerProducer
* @param initialManagers a list of socket managers to use
* @param maxManagers how many managers to have in the cache
* @param shouldThrowAwayManagers whether to throw away a manager after use
* @param host which host to listen on
* @param port which port to listen on
*/
public SocketManagerProducer(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
String host, int port) {
if (maxManagers < 1) { throw new IllegalArgumentException("maxManagers < 1"); }
this.host = host;
this.port = port;
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
if (initialManagers != null) {
myManagers.addAll(Arrays.asList(initialManagers));
}
this.maxManagers = maxManagers;
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
setDaemon(true);
start();
}
/**
* Thread producing new SocketManagers.
*/
public void run() {
while (true) {
synchronized (this) {
// without mcDonalds mode, we most probably need no
// new managers.
while (!shouldThrowAwayManagers && myManagers.size() == maxManagers) {
myWait();
}
}
// produce a new manager, regardless whether it is needed
// or not. Do not synchronized this part, since it can be
// quite time-consuming.
I2PSocketManager newManager = I2PSocketManagerFactory.createManager(host, port, new Properties());
// when done, check if it is needed.
synchronized (this) {
while (myManagers.size() == maxManagers) {
myWait();
}
myManagers.add(newManager);
notifyAll();
}
}
}
/**
* Get a manager for connecting to a given destination. Each
* destination will always get the same manager.
*
* @param dest the destination to connect to
* @return the SocketManager to use
*/
public synchronized I2PSocketManager getManager(String dest) {
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
if (result == null) {
result = getManager();
usedManagers.put(dest, result);
}
return result;
}
/**
* Get a "new" SocketManager. Depending on the anonymity settings,
* this can be a completely new one or one randomly selected from
* a pool.
*
* @return the SocketManager to use
*/
public synchronized I2PSocketManager getManager() {
while (myManagers.size() == 0) {
myWait(); // no manager here, so wait until one is produced
}
int which = (int) (Math.random() * myManagers.size());
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
if (shouldThrowAwayManagers) {
myManagers.remove(which);
notifyAll();
}
return result;
}
/**
* Wait until InterruptedException
*/
public void myWait() {
try {
wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}

View File

@ -1,61 +0,0 @@
package net.i2p.httptunnel.filter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import net.i2p.util.Log;
/**
* Chain multiple filters. Decorator pattern...
*/
public class ChainFilter implements Filter {
private static final Log _log = new Log(ChainFilter.class);
private Collection filters; // perhaps protected?
/**
* @param filters A collection (list) of filters to chain to
*/
public ChainFilter(Collection filters) {
this.filters = filters;
}
/* (non-Javadoc)
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
*/
public byte[] filter(byte[] toFilter) {
byte[] buf = toFilter;
for (Iterator it = filters.iterator(); it.hasNext();) {
Filter f = (Filter) it.next();
buf = f.filter(buf);
}
return buf;
}
/* (non-Javadoc)
* @see net.i2p.httptunnel.filter.Filter#finish()
*/
public byte[] finish() {
// this is a bit complicated. Think about it...
try {
byte[] buf = EMPTY;
for (Iterator it = filters.iterator(); it.hasNext();) {
Filter f = (Filter) it.next();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (buf.length != 0) {
baos.write(f.filter(buf));
}
baos.write(f.finish());
buf = baos.toByteArray();
}
return buf;
} catch (IOException ex) {
_log.error("Error chaining filters", ex);
return EMPTY;
}
}
}

View File

@ -1,25 +0,0 @@
package net.i2p.httptunnel.filter;
/**
* A generic filtering interface.
*/
public interface Filter {
/**
* An empty byte array.
*/
public static final byte[] EMPTY = new byte[0];
/**
* Filter some data. Not all filtered data need to be returned.
* @param toFilter the bytes that are to be filtered.
* @return the filtered data
*/
public byte[] filter(byte[] toFilter);
/**
* Data stream has finished. Return all of the rest data.
* @return the rest of the data
*/
public byte[] finish();
}

View File

@ -1,21 +0,0 @@
package net.i2p.httptunnel.filter;
/**
* A filter letting everything pass as is.
*/
public class NullFilter implements Filter {
/* (non-Javadoc)
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
*/
public byte[] filter(byte[] toFilter) {
return toFilter;
}
/* (non-Javadoc)
* @see net.i2p.httptunnel.filter.Filter#finish()
*/
public byte[] finish() {
return EMPTY;
}
}

View File

@ -1,113 +0,0 @@
package net.i2p.httptunnel.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Destination;
import net.i2p.httptunnel.HTTPListener;
import net.i2p.httptunnel.Request;
import net.i2p.httptunnel.SocketManagerProducer;
import net.i2p.httptunnel.filter.Filter;
import net.i2p.httptunnel.filter.NullFilter;
import net.i2p.util.Log;
/**
* Handler for browsing Eepsites.
*/
public class EepHandler {
private static final Log _log = new Log(EepHandler.class);
private static I2PAppContext _context = new I2PAppContext();
protected ErrorHandler errorHandler;
/* package private */EepHandler(ErrorHandler eh) {
errorHandler = eh;
}
/**
* @param req the Request
* @param httpl an HTTPListener
* @param out where to write the results
* @param destination destination as a string, (subject to naming
* service lookup)
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out,
/* boolean fromProxy, */String destination) throws IOException {
SocketManagerProducer smp = httpl.getSMP();
Destination dest = _context.namingService().lookup(destination);
if (dest == null) {
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
return;
}
I2PSocketManager sm = smp.getManager(destination);
Filter f = new NullFilter(); //FIXME: use other filter
req.setParam("Host: ", dest.toBase64());
if (!handle(req, f, out, dest, sm)) {
errorHandler.handle(req, httpl, out, "Unable to reach peer");
}
}
/**
* @param req the Request to send out
* @param f a Filter to apply to the bytes retrieved from the Destination
* @param out where to write the results
* @param dest the Destination of the Request
* @param sm an I2PSocketManager, to get a socket for the Destination
* @return boolean, true if something was written, false otherwise.
* @throws IOException
*/
public boolean handle(Request req, Filter f, OutputStream out, Destination dest,
I2PSocketManager sm) throws IOException {
I2PSocket s = null;
boolean written = false;
try {
synchronized (sm) {
s = sm.connect(dest, new I2PSocketOptions());
}
InputStream in = new BufferedInputStream(s.getInputStream());
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
sout.write(req.toByteArray());
sout.flush();
byte[] buffer = new byte[16384], filtered;
int len;
while ((len = in.read(buffer)) != -1) {
if (len != buffer.length) {
byte[] b2 = new byte[len];
System.arraycopy(buffer, 0, b2, 0, len);
filtered = f.filter(b2);
} else {
filtered = f.filter(buffer);
}
written = true;
out.write(filtered);
}
filtered = f.finish();
written = true;
out.write(filtered);
out.flush();
} catch (SocketException ex) {
_log.error("Error while handling eepsite request");
return written;
} catch (IOException ex) {
_log.error("Error while handling eepsite request");
return written;
} catch (I2PException ex) {
_log.error("Error while handling eepsite request");
return written;
} finally {
if (s != null) s.close();
}
return true;
}
}

View File

@ -1,41 +0,0 @@
package net.i2p.httptunnel.handler;
import java.io.IOException;
import java.io.OutputStream;
import net.i2p.httptunnel.HTTPListener;
import net.i2p.httptunnel.Request;
import net.i2p.util.Log;
/**
* Handler for general error messages.
*/
public class ErrorHandler {
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
/* package private */ErrorHandler() {
}
/**
* @param req the Request
* @param httpl an HTTPListener
* @param out where to write the results
* @param error the error that happened
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out, String error) throws IOException {
// FIXME: Make nicer messages for more likely errors.
out
.write(("HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
.getBytes("ISO-8859-1"));
out
.write(("<html><head><title>" + error + "</title></head><body><h1>" + error
+ "</h1>An internal error occurred while " + "handling a request by HTTPTunnel:<br><b>" + error + "</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
.getBytes("ISO-8859-1"));
out.write(req.toByteArray());
out.write(("</pre></i><br><b>---</b></body></html>").getBytes("ISO-8859-1"));
out.flush();
}
}

View File

@ -1,67 +0,0 @@
package net.i2p.httptunnel.handler;
import java.io.IOException;
import java.io.OutputStream;
import net.i2p.httptunnel.HTTPListener;
import net.i2p.httptunnel.Request;
import net.i2p.util.Log;
/**
* Handler for requests that do not require any connection to anyone
* (except errors).
*/
public class LocalHandler {
private static final Log _log = new Log(LocalHandler.class); /* UNUSED */
/* package private */LocalHandler() {
}
/**
* @param req the Request
* @param httpl an HTTPListener
* @param out where to write the results
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out
/*, boolean fromProxy */) throws IOException {
//FIXME: separate multiple pages, not only a start page
//FIXME: provide some info on this page
out
.write(("HTTP/1.1 200 Document following\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html><head><title>Welcome to I2P HTTPTunnel</title>"
+ "</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "
+ "browse Eepsites by adding an eepsite name to the request." + "</body></html>")
.getBytes("ISO-8859-1"));
out.flush();
}
/**
* Currently always throws an IO Exception
* @param req the Request
* @param httpl an HTTPListener
* @param out where to write the results
* @throws IOException
*/
public void handleProxyConfWarning(Request req, HTTPListener httpl, OutputStream out) throws IOException {
//FIXME
throw new IOException("jrandom ate the deprecated method. mooo");
//httpl.handleNotImplemented(out);
}
/**
* Currently always throws an IO Exception
* @param req the Request
* @param httpl an HTTPListener
* @param out where to write the results
* @throws IOException
*/
public void handleHTTPWarning(Request req, HTTPListener httpl, OutputStream out /*, boolean fromProxy */)
throws IOException {
// FIXME
throw new IOException("jrandom ate the deprecated method. mooo");
//httpl.handleNotImplemented(out);
}
}

View File

@ -1,54 +0,0 @@
package net.i2p.httptunnel.handler;
import java.io.IOException;
import java.io.OutputStream;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.Destination;
import net.i2p.httptunnel.HTTPListener;
import net.i2p.httptunnel.Request;
import net.i2p.httptunnel.SocketManagerProducer;
import net.i2p.httptunnel.filter.Filter;
import net.i2p.httptunnel.filter.NullFilter;
import net.i2p.util.Log;
/**
* Handler for proxying "normal" HTTP requests.
*/
public class ProxyHandler extends EepHandler {
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
private static I2PAppContext _context = new I2PAppContext();
/* package private */ProxyHandler(ErrorHandler eh) {
super(eh);
}
/**
* @param req a Request
* @param httpl an HTTPListener
* @param out where to write the results
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out
/*, boolean fromProxy */) throws IOException {
SocketManagerProducer smp = httpl.getSMP();
Destination dest = findProxy();
if (dest == null) {
errorHandler.handle(req, httpl, out, "Could not find proxy");
return;
}
// one manager for all proxy requests
I2PSocketManager sm = smp.getManager("--proxy--");
Filter f = new NullFilter(); //FIXME: use other filter
if (!handle(req, f, out, dest, sm)) {
errorHandler.handle(req, httpl, out, "Unable to reach peer");
}
}
private Destination findProxy() {
//FIXME!
return _context.namingService().lookup("squid.i2p");
}
}

View File

@ -1,116 +0,0 @@
package net.i2p.httptunnel.handler;
import java.io.IOException;
import java.io.OutputStream;
import net.i2p.httptunnel.HTTPListener;
import net.i2p.httptunnel.Request;
import net.i2p.util.Log;
/**
* Main handler for all requests. Dispatches requests to other handlers.
*/
public class RootHandler {
private static final Log _log = new Log(RootHandler.class); /* UNUSED */
private RootHandler() {
errorHandler = new ErrorHandler();
localHandler = new LocalHandler();
proxyHandler = new ProxyHandler(errorHandler);
eepHandler = new EepHandler(errorHandler);
}
private ErrorHandler errorHandler;
private ProxyHandler proxyHandler;
private LocalHandler localHandler;
private EepHandler eepHandler;
private static RootHandler instance;
/**
* Singleton stuff
* @return the one and only instance, yay!
*/
public static synchronized RootHandler getInstance() {
if (instance == null) {
instance = new RootHandler();
}
return instance;
}
/**
* The _ROOT_ handler: it passes its workload off to the other handlers.
* @param req a Request
* @param httpl an HTTPListener
* @param out where to write the results
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out) throws IOException {
String url = req.getURL();
System.out.println(url);
/* boolean byProxy = false; */
int pos;
if (url.startsWith("http://")) { // access via proxy
/* byProxy=true; */
if (httpl.firstProxyUse()) {
localHandler.handleProxyConfWarning(req, httpl, out);
return;
}
url = url.substring(7);
pos = url.indexOf("/");
String host;
if (pos == -1) {
errorHandler.handle(req, httpl, out, "No host end in URL");
return;
}
host = url.substring(0, pos);
url = url.substring(pos);
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
// normal request; go on below...
} else if (host.endsWith(".i2p")) {
// "old" service request, send a redirect...
out.write(("HTTP/1.1 302 Moved\r\nLocation: " + "http://i2p.i2p/" + host + url + "\r\n\r\n").getBytes("ISO-8859-1"));
return;
} else {
// this is for proxying to the real web
proxyHandler.handle(req, httpl, out /*, true */);
return;
}
}
if (url.equals("/")) { // main page
url = "/_/local/index";
} else if (!url.startsWith("/")) {
errorHandler.handle(req, httpl, out, "No leading slash in URL: " + url);
return;
}
String dest;
url = url.substring(1);
pos = url.indexOf("/");
if (pos == -1) {
dest = url;
url = "/";
} else {
dest = url.substring(0, pos);
url = url.substring(pos);
}
req.setURL(url);
if (dest.equals("_")) { // no eepsite
if (url.startsWith("/local/")) { // local request
req.setURL(url.substring(6));
localHandler.handle(req, httpl, out /*, byProxy */);
} else if (url.startsWith("/http/")) { // http warning
localHandler.handleHTTPWarning(req, httpl, out /*, byProxy */);
} else if (url.startsWith("/proxy/")) { // http proxying
req.setURL("http://" + url.substring(7));
proxyHandler.handle(req, httpl, out /*, byProxy */);
} else {
errorHandler.handle(req, httpl, out, "No local handler for this URL: " + url);
}
} else {
eepHandler.handle(req, httpl, out, /* byProxy, */dest);
}
}
}

View File

@ -2,7 +2,7 @@
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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.
@ -276,3 +276,65 @@ 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
View 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.

View File

@ -0,0 +1 @@
Mark Wielaard <mark@klomp.org>

View 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.

View File

@ -0,0 +1,93 @@
<?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, war" />
<target name="builddep">
<ant dir="../../jetty/" target="build" />
<ant dir="../../streaming/java/" target="build" />
<!-- streaming will build ministreaming and 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:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.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="war" depends="jar">
<war destfile="../i2psnark.war" webxml="../web.xml">
<classes dir="./build/obj" includes="**/*" />
</war>
</target>
<target name="standalone" depends="standalone_prep">
<zip destfile="i2psnark-standalone.zip">
<zipfileset dir="./dist/" prefix="i2psnark/" />
</zip>
</target>
<target name="standalone_prep" depends="war">
<javac debug="true" deprecation="on" source="1.3" target="1.3"
destdir="./build" srcdir="src/" includes="org/klomp/snark/web/RunStandalone.java" >
<classpath>
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
<pathelement location="../../jetty/jettylib/commons-el.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<pathelement location="../../../core/java/build/i2p.jar" />
</classpath>
</javac>
<jar destfile="./build/launch-i2psnark.jar" basedir="./build/" includes="org/klomp/snark/web/RunStandalone.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.web.RunStandalone" />
<attribute name="Class-Path" value="lib/i2p.jar lib/mstreaming.jar lib/streaming.jar lib/commons-el.jar lib/commons-logging.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/javax.servlet.jar lib/org.mortbay.jetty.jar" />
</manifest>
</jar>
<delete dir="./dist" />
<mkdir dir="./dist" />
<copy file="./build/launch-i2psnark.jar" tofile="./dist/launch-i2psnark.jar" />
<mkdir dir="./dist/webapps" />
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
<mkdir dir="./dist/lib" />
<copy file="../../../core/java/build/i2p.jar" tofile="./dist/lib/i2p.jar" />
<copy file="../../jetty/jettylib/commons-el.jar" tofile="./dist/lib/commons-el.jar" />
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
<copy file="../../ministreaming/java/build/mstreaming.jar" tofile="./dist/lib/mstreaming.jar" />
<copy file="../../streaming/java/build/streaming.jar" tofile="./dist/lib/streaming.jar" />
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
<mkdir dir="./dist/work" />
<mkdir dir="./dist/logs" />
<zip destfile="i2psnark-standalone.zip">
<zipfileset dir="./dist/" prefix="i2psnark/" />
</zip>
</target>
<target name="clean">
<delete dir="./build" />
<delete file="../i2psnark.war" />
<delete file="./i2psnark-standalone.zip" />
</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>

View File

@ -0,0 +1,156 @@
/* 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;
/**
* Container of a byte array representing set and unset bits.
*/
public class BitField
{
private final byte[] bitfield;
private final int size;
private int count;
/**
* 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];
this.count = 0;
}
/**
* 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.count = 0;
for (int i = 0; i < size; i++)
if (get(i))
this.count++;
}
/**
* 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);
if ((bitfield[index] & mask) == 0) {
count++;
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;
}
/**
* Return the number of set bits.
*/
public int count()
{
return count;
}
/**
* Return true if all bits are set.
*/
public boolean complete()
{
return count >= size;
}
public String toString()
{
// Not very efficient
StringBuffer sb = new StringBuffer("BitField(");
sb.append(size).append(")[");
for (int i = 0; i < size; i++)
if (get(i))
{
sb.append(' ');
sb.append(i);
}
sb.append(" ]");
return sb.toString();
}
}

View File

@ -0,0 +1,189 @@
/* 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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Accepts connections on a TCP port and routes them to sub-acceptors.
*/
public class ConnectionAcceptor implements Runnable
{
private static final ConnectionAcceptor _instance = new ConnectionAcceptor();
public static final ConnectionAcceptor instance() { return _instance; }
private Log _log = new Log(ConnectionAcceptor.class);
private I2PServerSocket serverSocket;
private PeerAcceptor peeracceptor;
private Thread thread;
private boolean stop;
private boolean socketChanged;
private ConnectionAcceptor() {}
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
if (serverSocket != socket) {
if ( (peeracceptor == null) || (peeracceptor.coordinators != set) )
peeracceptor = new PeerAcceptor(set);
serverSocket = socket;
stop = false;
socketChanged = true;
if (thread == null) {
thread = new I2PThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
}
}
public ConnectionAcceptor(I2PServerSocket serverSocket,
PeerAcceptor peeracceptor)
{
this.serverSocket = serverSocket;
this.peeracceptor = peeracceptor;
socketChanged = false;
stop = false;
thread = new I2PThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
public void halt()
{
if (true) throw new RuntimeException("wtf");
stop = true;
I2PServerSocket ss = serverSocket;
if (ss != null)
try
{
ss.close();
}
catch(I2PException ioe) { }
Thread t = thread;
if (t != null)
t.interrupt();
}
public void restart() {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
socketChanged = true;
Thread t = thread;
if (t != null)
t.interrupt();
}
public int getPort()
{
return 6881; // serverSocket.getLocalPort();
}
public void run()
{
while(!stop)
{
if (socketChanged) {
// ok, already updated
socketChanged = false;
}
while ( (serverSocket == null) && (!stop)) {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
if (serverSocket == null)
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
try
{
I2PSocket socket = serverSocket.accept();
if (socket == null) {
if (socketChanged) {
continue;
} else {
I2PServerSocket ss = I2PSnarkUtil.instance().getServerSocket();
if (ss != serverSocket) {
serverSocket = ss;
socketChanged = true;
}
}
} else {
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
t.start();
}
}
catch (I2PException ioe)
{
if (!socketChanged) {
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
catch (IOException ioe)
{
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
try
{
if (serverSocket != null)
serverSocket.close();
}
catch (I2PException ignored) { }
throw new RuntimeException("wtf");
}
private class Handler implements Runnable {
private I2PSocket _socket;
public Handler(I2PSocket socket) {
_socket = socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
OutputStream out = _socket.getOutputStream();
if (true) {
in = new BufferedInputStream(in);
//out = new BufferedOutputStream(out);
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
peeracceptor.connection(_socket, in, out);
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
try { _socket.close(); } catch (IOException ignored) { }
}
}
}
}

View File

@ -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);
}

View File

@ -0,0 +1,299 @@
package org.klomp.snark;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
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.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.EepGet;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
/**
* 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 Map _opts;
private I2PSocketManager _manager;
private boolean _configured;
private Set _shitlist;
private int _maxUploaders;
private int _maxUpBW;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(Snark.class);
_opts = new HashMap();
setProxy("127.0.0.1", 4444);
setI2CPConfig("127.0.0.1", 7654, null);
_shitlist = new HashSet(64);
_configured = false;
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
}
/**
* 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;
}
_configured = true;
}
public boolean configured() { return _configured; }
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
if (opts != null)
_opts.putAll(opts);
_configured = true;
}
public void setMaxUploaders(int limit) {
_maxUploaders = limit;
_configured = true;
}
public void setMaxUpBW(int limit) {
_maxUpBW = limit;
_configured = true;
}
public String getI2CPHost() { return _i2cpHost; }
public int getI2CPPort() { return _i2cpPort; }
public Map getI2CPOptions() { return _opts; }
public String getEepProxyHost() { return _proxyHost; }
public int getEepProxyPort() { return _proxyPort; }
public boolean getEepProxySet() { return _shouldProxy; }
public int getMaxUploaders() { return _maxUploaders; }
public int getMaxUpBW() { return _maxUpBW; }
/**
* Connect to the router, if we aren't already
*/
synchronized public boolean connect() {
if (_manager == null) {
Properties opts = new Properties();
if (_opts != null) {
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
opts.setProperty(key, _opts.get(key).toString());
}
}
if (opts.getProperty("inbound.nickname") == null)
opts.setProperty("inbound.nickname", "I2PSnark");
if (opts.getProperty("outbound.nickname") == null)
opts.setProperty("outbound.nickname", "I2PSnark");
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
opts.setProperty("i2p.streaming.inactivityAction", "1"); // 1 == disconnect, 2 == ping
if (opts.getProperty("i2p.streaming.initialWindowSize") == null)
opts.setProperty("i2p.streaming.initialWindowSize", "1");
if (opts.getProperty("i2p.streaming.slowStartGrowthRateFactor") == null)
opts.setProperty("i2p.streaming.slowStartGrowthRateFactor", "1");
//if (opts.getProperty("i2p.streaming.writeTimeout") == null)
// opts.setProperty("i2p.streaming.writeTimeout", "90000");
//if (opts.getProperty("i2p.streaming.readTimeout") == null)
// opts.setProperty("i2p.streaming.readTimeout", "120000");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
}
return (_manager != null);
}
public boolean connected() { return _manager != null; }
/**
* Destroy the destination itself
*/
public void disconnect() {
I2PSocketManager mgr = _manager;
_manager = null;
_shitlist.clear();
mgr.destroySocketManager();
}
/** connect to the given destination */
I2PSocket connect(PeerID peer) throws IOException {
Hash dest = peer.getAddress().calculateHash();
synchronized (_shitlist) {
if (_shitlist.contains(dest))
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
}
try {
I2PSocket rv = _manager.connect(peer.getAddress());
if (rv != null) synchronized (_shitlist) { _shitlist.remove(dest); }
return rv;
} catch (I2PException ie) {
synchronized (_shitlist) {
_shitlist.add(dest);
}
SimpleTimer.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
}
}
private class Unshitlist implements SimpleTimer.TimedEvent {
private Hash _dest;
public Unshitlist(Hash dest) { _dest = dest; }
public void timeReached() { synchronized (_shitlist) { _shitlist.remove(_dest); } }
}
/**
* fetch the given URL, returning the file it is stored in, or null on error
*/
public File get(String url) { return get(url, true, 0); }
public File get(String url, boolean rewrite) { return get(url, rewrite, 0); }
public File get(String url, int retries) { return get(url, true, retries); }
public File get(String url, boolean rewrite, int retries) {
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
File out = null;
try {
out = File.createTempFile("i2psnark", "url", new File("."));
} catch (IOException ioe) {
ioe.printStackTrace();
out.delete();
return null;
}
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);
//_log.debug("Rewritten url [" + fetchURL + "]");
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, retries, out.getAbsolutePath(), fetchURL);
if (get.fetch()) {
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
return out;
} else {
_log.warn("Fetch failed [" + url + "]");
out.delete();
return null;
}
}
public I2PServerSocket getServerSocket() {
I2PSocketManager mgr = _manager;
if (mgr != null)
return mgr.getServerSocket();
else
return null;
}
String getOurIPString() {
if (_manager == null)
return "unknown";
I2PSession sess = _manager.getSession();
if (sess != null) {
Destination dest = sess.getMyDestination();
if (dest != null)
return dest.toBase64();
}
return "unknown";
}
Destination getDestination(String ip) {
if (ip == null) return null;
if (ip.endsWith(".i2p")) {
if (ip.length() < 520) { // key + ".i2p"
Destination dest = _context.namingService().lookup(ip);
if (dest != null)
return dest;
}
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;
}
}
}
public String lookup(String name) {
Destination dest = getDestination(name);
if (dest == null)
return null;
return dest.toBase64();
}
/**
* Given http://KEY.i2p/foo/announce turn it into http://i2p/KEY/foo/announce
* Given http://tracker.blah.i2p/foo/announce leave it alone
*/
String rewriteAnnounce(String origAnnounce) {
int destStart = "http://".length();
int destEnd = origAnnounce.indexOf(".i2p");
if (destEnd < destStart + 516)
return origAnnounce;
int pathStart = origAnnounce.indexOf('/', destEnd);
String rv = "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
//_log.debug("Rewriting [" + origAnnounce + "] as [" + rv + "]");
return rv;
}
/** hook between snark's logger and an i2p log */
void debug(String msg, int snarkDebugLevel, Throwable t) {
if (t instanceof OutOfMemoryError) {
try { Thread.sleep(100); } catch (InterruptedException ie) {}
try {
t.printStackTrace();
} catch (Throwable tt) {}
try {
System.out.println("OOM thread: " + Thread.currentThread().getName());
} catch (Throwable tt) {}
}
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;
}
}
}

View File

@ -0,0 +1,141 @@
/* 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;
import net.i2p.util.SimpleTimer;
// 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;
SimpleTimer.TimedEvent expireEvent;
/** 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>";
}
}
}

View File

@ -0,0 +1,463 @@
/* 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.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.i2p.crypto.SHA1;
import net.i2p.data.Base64;
import net.i2p.util.Log;
import org.klomp.snark.bencode.BDecoder;
import org.klomp.snark.bencode.BEValue;
import org.klomp.snark.bencode.BEncoder;
import org.klomp.snark.bencode.InvalidBEncodingException;
/**
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
* info data, and from there to the info_hash. At the moment, though, it seems to work with
* torrents created by I2P-BT, I2PRufus and Azureus.
*
*/
public class MetaInfo
{
private static final Log _log = new Log(MetaInfo.class);
private final String announce;
private final byte[] info_hash;
private final String name;
private final String name_utf8;
private final List files;
private final List files_utf8;
private final List lengths;
private final int piece_length;
private final byte[] piece_hashes;
private final long length;
private final Map infoMap;
private byte[] torrentdata;
MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
int piece_length, byte[] piece_hashes, long length)
{
this.announce = announce;
this.name = name;
this.name_utf8 = name_utf8;
this.files = files;
this.files_utf8 = null;
this.lengths = lengths;
this.piece_length = piece_length;
this.piece_hashes = piece_hashes;
this.length = length;
this.info_hash = calculateInfoHash();
infoMap = null;
}
/**
* 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
{
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
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();
infoMap = info;
val = (BEValue)info.get("name");
if (val == null)
throw new InvalidBEncodingException("Missing name string");
name = val.getString();
val = (BEValue)info.get("name.utf-8");
if (val != null)
name_utf8 = val.getString();
else
name_utf8 = null;
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;
files_utf8 = 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);
files_utf8 = 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);
val = (BEValue)desc.get("path.utf-8");
if (val != null) {
path_list = val.getList();
path_length = path_list.size();
if (path_length > 0) {
file = new ArrayList(path_length);
it = path_list.iterator();
while (it.hasNext())
file.add(((BEValue)it.next()).getString());
files_utf8.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)
{
if (true)
return fast_checkPiece(piece, bs, off, length);
else
return orig_checkPiece(piece, bs, off, length);
}
private boolean orig_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;
}
private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
SHA1 sha1 = new SHA1();
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, name_utf8, 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();
if (infoMap != null) {
info.putAll(infoMap);
return info;
}
info.put("name", name);
if (name_utf8 != null)
info.put("name.utf-8", name_utf8);
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));
if ( (files_utf8 != null) && (files_utf8.size() > i) )
file.put("path.utf-8", files_utf8.get(i));
file.put("length", lengths.get(i));
l.add(file);
}
info.put("files", l);
}
return info;
}
private byte[] calculateInfoHash()
{
Map info = createInfoMap();
StringBuffer buf = new StringBuffer(128);
buf.append("info: ");
for (Iterator iter = info.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
Object val = info.get(key);
buf.append(key).append('=');
if (val instanceof byte[])
buf.append(Base64.encode((byte[])val, true));
else
buf.append(val.toString());
}
_log.debug(buf.toString());
byte[] infoBytes = BEncoder.bencode(info);
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
try
{
MessageDigest digest = MessageDigest.getInstance("SHA");
byte hash[] = digest.digest(infoBytes);
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
return hash;
}
catch(NoSuchAlgorithmException nsa)
{
throw new InternalError(nsa.toString());
}
}
}

View File

@ -0,0 +1,573 @@
/* 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.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.util.Log;
public class Peer implements Comparable
{
private Log _log = new Log(Peer.class);
// Identifying property, the peer id of the other side.
private final PeerID peerID;
private final byte[] my_id;
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 I2PSocket sock;
private boolean deregister = true;
private static long __id;
private long _id;
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
/**
* 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;
_id = ++__id;
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
}
/**
* 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, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
throws IOException
{
this.my_id = my_id;
this.metainfo = metainfo;
this.sock = sock;
byte[] id = handshake(in, out);
this.peerID = new PeerID(id, sock.getPeerDestination());
_id = ++__id;
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
}
/**
* Returns the id of the peer.
*/
public PeerID getPeerID()
{
return peerID;
}
/**
* Returns the String representation of the peerID.
*/
public String toString()
{
if (peerID != null)
return peerID.toString() + ' ' + _id;
else
return "[unknown id] " + _id;
}
/**
* Returns socket (for debug printing)
*/
public String getSocket()
{
return sock.toString();
}
/**
* The hash code of a Peer is the hash code of the peerID.
*/
public int hashCode()
{
return peerID.hashCode() ^ (2 << _id);
}
/**
* 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 _id == p._id && peerID.equals(p.peerID);
}
else
return false;
}
/**
* Compares the PeerIDs.
*/
public int compareTo(Object o)
{
Peer p = (Peer)o;
int rv = peerID.compareTo(p.peerID);
if (rv == 0) {
if (_id > p._id) return 1;
else if (_id < p._id) return -1;
else return 0;
} else {
return rv;
}
}
/**
* 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");
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
try
{
// Do we need to handshake?
if (din == null)
{
sock = I2PSnarkUtil.instance().connect(peerID);
_log.debug("Connected to " + peerID + ": " + sock);
if ((sock == null) || (sock.isClosed())) {
throw new IOException("Unable to reach " + peerID);
}
InputStream in = sock.getInputStream();
OutputStream out = sock.getOutputStream(); //new BufferedOutputStream(sock.getOutputStream());
if (true) {
// buffered output streams are internally synchronized, so we can't get through to the underlying
// I2PSocket's MessageOutputStream to close() it if we are blocking on a write(...). Oh, and the
// buffer is unnecessary anyway, as unbuffered access lets the streaming lib do the 'right thing'.
//out = new BufferedOutputStream(out);
in = new BufferedInputStream(sock.getInputStream());
}
//BufferedInputStream bis
// = new BufferedInputStream(sock.getInputStream());
//BufferedOutputStream bos
// = new BufferedOutputStream(sock.getOutputStream());
byte [] id = handshake(in, out); //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) + "'");
_log.debug("Handshake got matching IDs with " + toString());
} else {
_log.debug("Already have din [" + sock + "] with " + toString());
}
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);
_log.debug("Start running the reader with " + toString());
// Use this thread for running the incomming connection.
// The outgoing connection creates its own Thread.
out.startup();
Thread.currentThread().setName("Snark reader from " + peerID);
s.in.run();
}
catch(IOException eofe)
{
// Ignore, probably just the other side closing the connection.
// Or refusing the connection, timing out, etc.
if (_log.shouldLog(Log.DEBUG))
_log.debug(this.toString(), eofe);
}
catch(Throwable t)
{
_log.error(this + ": " + t.getMessage(), t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
if (deregister) listener.disconnected(this);
disconnect();
}
}
/**
* Sets DataIn/OutputStreams, does the handshake and returns the id
* reported by the other side.
*/
private byte[] handshake(InputStream in, OutputStream out) //BufferedInputStream bis, BufferedOutputStream bos)
throws IOException
{
din = new DataInputStream(in);
dout = new DataOutputStream(out);
// 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();
_log.debug("Wrote my shared hash and ID to " + toString());
// Handshake read - header
byte b = din.readByte();
if (b != 19)
throw new IOException("Handshake failure, expected 19, got "
+ (b & 0xff) + " on " + sock);
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);
_log.debug("Read the remote side's hash and peerID fully from " + toString());
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)
{
// try to save partial piece
if (this.deregister) {
PeerListener p = s.listener;
if (p != null) {
p.savePeerPartial(s);
p.markUnrequested(this);
}
}
state = null;
PeerConnectionIn in = s.in;
if (in != null)
in.disconnect();
PeerConnectionOut out = s.out;
if (out != null)
out.disconnect();
PeerListener pl = s.listener;
if (pl != null)
pl.disconnected(this);
}
I2PSocket csock = sock;
sock = null;
if ( (csock != null) && (!csock.isClosed()) ) {
try {
csock.close();
} catch (IOException ioe) {
_log.warn("Error disconnecting " + toString(), ioe);
}
}
}
/**
* 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;
}
}
public long getInactiveTime() {
PeerState s = state;
if (s != null) {
PeerConnectionIn in = s.in;
PeerConnectionOut out = s.out;
if (in != null && out != null) {
long now = System.currentTimeMillis();
return Math.max(now - out.lastSent, now - in.lastRcvd);
} else
return -1; //"state, no out";
} else {
return -1; //"no state";
}
}
/**
* Send keepalive
*/
public void keepAlive()
{
PeerState s = state;
if (s != null)
s.keepAlive();
}
/**
* Retransmit outstanding requests if necessary
*/
public void retransmitRequests()
{
PeerState s = state;
if (s != null)
s.retransmitRequests();
}
/**
* Return how much the peer has
*/
public int completed()
{
PeerState s = state;
if (s == null || s.bitfield == null)
return 0;
return s.bitfield.count();
}
/**
* Return if a peer is a seeder
*/
public boolean isCompleted()
{
PeerState s = state;
if (s == null || s.bitfield == null)
return false;
return s.bitfield.complete();
}
/**
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
*/
public void setRateHistory(long up, long down)
{
setRate(up, uploaded_old);
setRate(down, downloaded_old);
}
private void setRate(long val, long array[])
{
synchronized(array) {
for (int i = RATE_DEPTH-1; i > 0; i--)
array[i] = array[i-1];
array[0] = val;
}
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getUploadRate()
{
return getRate(uploaded_old);
}
public long getDownloadRate()
{
return getRate(downloaded_old);
}
private long getRate(long array[])
{
long rate = 0;
int i = 0;
synchronized(array) {
for ( ; i < RATE_DEPTH; i++){
if (array[i] < 0)
break;
rate += array[i];
}
}
if (i == 0)
return 0;
return rate / (i * CHECK_PERIOD / 1000);
}
}

View File

@ -0,0 +1,149 @@
/* 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.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.Iterator;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* 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 static final Log _log = new Log(PeerAcceptor.class);
private final PeerCoordinator coordinator;
final PeerCoordinatorSet coordinators;
public PeerAcceptor(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
this.coordinators = null;
}
public PeerAcceptor(PeerCoordinatorSet coordinators)
{
this.coordinators = coordinators;
this.coordinator = null;
}
public void connection(I2PSocket socket,
InputStream in, OutputStream out)
throws IOException
{
// 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, but because of how the protocol works, we can get away with just reading
// ahead the first $LOOKAHEAD_SIZE bytes to figure out which infohash they want to
// talk about, and we can just look for that in our list of active torrents.
byte peerInfoHash[] = null;
if (in instanceof BufferedInputStream) {
in.mark(LOOKAHEAD_SIZE);
peerInfoHash = readHash(in);
in.reset();
} else {
// is this working right?
try {
peerInfoHash = readHash(in);
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
+ ": " + Base64.encode(peerInfoHash));
} catch (IOException ioe) {
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
throw ioe;
}
in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in);
}
if (coordinator != null) {
// single torrent capability
MetaInfo meta = coordinator.getMetaInfo();
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
if (coordinator.needPeers())
{
Peer peer = new Peer(socket, in, out, coordinator.getID(),
coordinator.getMetaInfo());
coordinator.addPeer(peer);
}
else
socket.close();
} else {
// its for another infohash, but we are only single torrent capable. b0rk.
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
+ ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
}
} else {
// multitorrent capable, so lets see what we can handle
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
PeerCoordinator cur = (PeerCoordinator)iter.next();
MetaInfo meta = cur.getMetaInfo();
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
if (cur.needPeers())
{
Peer peer = new Peer(socket, in, out, cur.getID(),
cur.getMetaInfo());
cur.addPeer(peer);
return;
}
else
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Rejecting new peer for " + cur.snark.torrent);
socket.close();
return;
}
}
}
// this is only reached if none of the coordinators match the infohash
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
+ ") while we don't support that hash");
}
}
private static final int LOOKAHEAD_SIZE = "19".length() +
"BitTorrent protocol".length() +
8 + // blank, reserved
20; // infohash
/**
* Read ahead to the infohash, throwing an exception if there isn't enough data
*/
private byte[] readHash(InputStream in) throws IOException {
byte buf[] = new byte[LOOKAHEAD_SIZE];
int read = DataHelper.read(in, buf);
if (read != buf.length)
throw new IOException("Unable to read the hash (read " + read + ")");
byte rv[] = new byte[20];
System.arraycopy(buf, buf.length-rv.length-1, rv, 0, rv.length);
return rv;
}
}

View File

@ -0,0 +1,252 @@
/* 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TimerTask;
/**
* 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;
}
private Random random = new Random();
public void run()
{
synchronized(coordinator.peers)
{
Iterator it = coordinator.peers.iterator();
if ((!it.hasNext()) || coordinator.halted()) {
coordinator.peerCount = 0;
coordinator.interestedAndChoking = 0;
coordinator.setRateHistory(0, 0);
coordinator.uploaders = 0;
if (coordinator.halted())
cancel();
return;
}
// Calculate total uploading and worst downloader.
long worstdownload = Long.MAX_VALUE;
Peer worstDownloader = null;
int peers = 0;
int uploaders = 0;
int downloaders = 0;
int removedCount = 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();
int uploadLimit = coordinator.allowedUploaders();
boolean overBWLimit = coordinator.overUpBWLimit();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
// Remove dying peers
if (!peer.isConnected())
{
it.remove();
coordinator.removePeerFromPieces(peer);
coordinator.peerCount = coordinator.peers.size();
continue;
}
peers++;
if (!peer.isChoking())
uploaders++;
if (!peer.isChoked() && peer.isInteresting())
downloaders++;
long upload = peer.getUploaded();
uploaded += upload;
long download = peer.getDownloaded();
downloaded += download;
peer.setRateHistory(upload, download);
peer.resetCounters();
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);
// Choke half of them rather than all so it isn't so drastic...
// unless this torrent is over the limit all by itself.
boolean overBWLimitChoke = upload > 0 &&
((overBWLimit && random.nextBoolean()) ||
(coordinator.overUpBWLimit(uploaded)));
// 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 == uploadLimit
&& coordinator.interestedAndChoking > 0)
|| coordinator.uploaders > uploadLimit
|| overBWLimitChoke)
&& !peer.isChoking())
{
// Check if it still wants pieces from us.
if (!peer.isInterested())
{
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 (overBWLimitChoke)
{
Snark.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
Snark.INFO);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list for fairness, even though we won't be unchoking this time
it.remove();
removed.add(peer);
}
else if (peer.isInteresting() && peer.isChoked())
{
// If they are choking us make someone else a downloader
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (!peer.isInteresting() && !coordinator.completed())
{
// If they aren't interesting make someone else a downloader
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// 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...
Snark.debug("Choke downloader that doesn't deliver:"
+ peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
it.remove();
removed.add(peer);
}
else if (peer.isInteresting() && !peer.isChoked() &&
download < worstdownload)
{
// Make sure download is good if we are uploading
worstdownload = download;
worstDownloader = peer;
}
else if (upload < worstdownload && coordinator.completed())
{
// Make sure upload is good if we are seeding
worstdownload = upload;
worstDownloader = peer;
}
}
peer.retransmitRequests();
peer.keepAlive();
}
// Resync actual uploaders value
// (can shift a bit by disconnecting peers)
coordinator.uploaders = uploaders;
// Remove the worst downloader if needed. (uploader if seeding)
if (((uploaders == uploadLimit
&& coordinator.interestedAndChoking > 0)
|| uploaders > uploadLimit)
&& worstDownloader != null)
{
Snark.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
worstDownloader.setChoking(true);
coordinator.uploaders--;
removedCount++;
// Put it at the back of the list
coordinator.peers.remove(worstDownloader);
coordinator.peerCount = coordinator.peers.size();
removed.add(worstDownloader);
}
// Optimistically unchoke a peer
if ((!overBWLimit) && !coordinator.overUpBWLimit(uploaded))
coordinator.unchokePeer();
// Put peers back at the end of the list that we removed earlier.
coordinator.peers.addAll(removed);
coordinator.peerCount = coordinator.peers.size();
coordinator.interestedAndChoking += removedCount;
// store the rates
coordinator.setRateHistory(uploaded, downloaded);
}
}
}

View File

@ -0,0 +1,199 @@
/* 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.DataInputStream;
import java.io.IOException;
import net.i2p.util.Log;
class PeerConnectionIn implements Runnable
{
private Log _log = new Log(PeerConnectionIn.class);
private final Peer peer;
private final DataInputStream din;
private Thread thread;
private volatile boolean quit;
long lastRcvd;
public PeerConnectionIn(Peer peer, DataInputStream din)
{
this.peer = peer;
this.din = din;
lastRcvd = System.currentTimeMillis();
quit = false;
}
void disconnect()
{
if (quit == true)
return;
quit = true;
Thread t = thread;
if (t != null)
t.interrupt();
if (din != null) {
try {
din.close();
} catch (IOException ioe) {
_log.warn("Error closing the stream from " + peer, ioe);
}
}
}
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.
// The biggest is the piece message, for which the length is the
// request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
// in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
int i = din.readInt();
lastRcvd = System.currentTimeMillis();
if (i < 0 || i > PeerState.PARTSIZE + 9)
throw new IOException("Unexpected length prefix: " + i);
if (i == 0)
{
ps.keepAliveMessage();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
continue;
}
byte b = din.readByte();
Message m = new Message();
m.type = b;
switch (b)
{
case 0:
ps.chokeMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
break;
case 1:
ps.chokeMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
break;
case 2:
ps.interestedMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
break;
case 3:
ps.interestedMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
break;
case 4:
piece = din.readInt();
ps.haveMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
break;
case 5:
byte[] bitmap = new byte[i-1];
din.readFully(bitmap);
ps.bitfieldMessage(bitmap);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) + ": " + ps.bitfield);
break;
case 6:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.requestMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
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);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
else
{
// XXX - Consume but throw away afterwards.
piece_bytes = new byte[len];
din.readFully(piece_bytes);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
break;
case 8:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.cancelMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
break;
default:
byte[] bs = new byte[i-1];
din.readFully(bs);
ps.unknownMessage(b, bs);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
}
}
}
catch (IOException ioe)
{
// Ignore, probably the other side closed connection.
if (_log.shouldLog(Log.INFO))
_log.info("IOError talking with " + peer, ioe);
}
catch (Throwable t)
{
_log.error("Error talking with " + peer, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
peer.disconnect();
}
}
}

View File

@ -0,0 +1,472 @@
/* 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.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
class PeerConnectionOut implements Runnable
{
private Log _log = new Log(PeerConnectionOut.class);
private final Peer peer;
private final DataOutputStream dout;
private Thread thread;
private boolean quit;
// Contains Messages.
private List sendQueue = new ArrayList();
private static long __id = 0;
private long _id;
long lastSent;
public PeerConnectionOut(Peer peer, DataOutputStream dout)
{
this.peer = peer;
this.dout = dout;
_id = ++__id;
lastSent = System.currentTimeMillis();
quit = false;
}
public void startup() {
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
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 && peer.isConnected())
{
Message m = null;
PeerState state = null;
boolean shouldFlush;
synchronized(sendQueue)
{
shouldFlush = !quit && peer.isConnected() && sendQueue.isEmpty();
}
if (shouldFlush)
// Make sure everything will reach the other side.
// flush while not holding lock, could take a long time
dout.flush();
synchronized(sendQueue)
{
while (!quit && peer.isConnected() && sendQueue.isEmpty())
{
try
{
// Make sure everything will reach the other side.
// don't flush while holding lock, could take a long time
// dout.flush();
// Wait till more data arrives.
sendQueue.wait(60*1000);
}
catch (InterruptedException ie)
{
/* ignored */
}
}
state = peer.state;
if (!quit && state != null && peer.isConnected())
{
// 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.
// this should get fixed for starvation
Iterator it = sendQueue.iterator();
while (m == null && it.hasNext())
{
Message nm = (Message)it.next();
if (nm.type == Message.PIECE)
{
if (state.choking) {
it.remove();
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
}
nm = null;
}
else if (nm.type == Message.REQUEST && state.choked)
{
it.remove();
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
nm = null;
}
if (m == null && nm != null)
{
m = nm;
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
it.remove();
}
}
if (m == null && sendQueue.size() > 0) {
m = (Message)sendQueue.remove(0);
SimpleTimer.getInstance().removeEvent(m.expireEvent);
}
}
}
if (m != null)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
m.sendMessage(dout);
lastSent = System.currentTimeMillis();
// 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.
if (_log.shouldLog(Log.INFO))
_log.info("IOError sending to " + peer, ioe);
}
catch (Throwable t)
{
_log.error("Error sending to " + peer, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
quit = true;
peer.disconnect();
}
}
public void disconnect()
{
synchronized(sendQueue)
{
//if (quit == true)
// return;
quit = true;
if (thread != null)
thread.interrupt();
sendQueue.clear();
sendQueue.notify();
}
if (dout != null) {
try {
dout.close();
} catch (IOException ioe) {
_log.warn("Error closing the stream to " + peer, ioe);
}
}
}
/**
* Adds a message to the sendQueue and notifies the method waiting
* on the sendQueue to change.
* If a PIECE message only, add a timeout.
*/
private void addMessage(Message m)
{
if (m.type == Message.PIECE)
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
synchronized(sendQueue)
{
sendQueue.add(m);
sendQueue.notifyAll();
}
}
/** remove messages not sent in 3m */
private static final int SEND_TIMEOUT = 3*60*1000;
private class RemoveTooSlow implements SimpleTimer.TimedEvent {
private Message _m;
public RemoveTooSlow(Message m) {
_m = m;
m.expireEvent = RemoveTooSlow.this;
}
public void timeReached() {
boolean removed = false;
synchronized (sendQueue) {
removed = sendQueue.remove(_m);
sendQueue.notifyAll();
}
if (removed)
_log.info("Took too long to send " + _m + " to " + peer);
}
}
/**
* 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;
}
}
sendQueue.notifyAll();
}
return removed;
}
void sendAlive()
{
Message m = new Message();
m.type = Message.KEEP_ALIVE;
// addMessage(m);
synchronized(sendQueue)
{
if(sendQueue.isEmpty())
sendQueue.add(m);
sendQueue.notifyAll();
}
}
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);
}
/** reransmit requests not received in 7m */
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
void retransmitRequests(List requests)
{
long now = System.currentTimeMillis();
Iterator it = requests.iterator();
while (it.hasNext())
{
Request req = (Request)it.next();
if(now > req.sendTime + REQ_TIMEOUT) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Retransmit request " + req + " to peer " + peer);
sendRequest(req);
}
}
}
void sendRequests(List requests)
{
Iterator it = requests.iterator();
while (it.hasNext())
{
Request req = (Request)it.next();
sendRequest(req);
}
}
void sendRequest(Request req)
{
// Check for duplicate requests to deal with fibrillating i2p-bt
// (multiple choke/unchokes received cause duplicate requests in the 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)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Discarding duplicate request " + req + " to peer " + peer);
return;
}
}
}
Message m = new Message();
m.type = Message.REQUEST;
m.piece = req.piece;
m.begin = req.off;
m.length = req.len;
addMessage(m);
req.sendTime = System.currentTimeMillis();
}
// Used by PeerState to limit pipelined requests
int queuedBytes()
{
int total = 0;
synchronized(sendQueue)
{
Iterator it = sendQueue.iterator();
while (it.hasNext())
{
Message m = (Message)it.next();
if (m.type == Message.PIECE)
total += m.length;
}
}
return total;
}
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 = 0;
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();
}
}
}
}

View File

@ -0,0 +1,869 @@
/* 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.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Coordinates what peer does what.
*/
public class PeerCoordinator implements PeerListener
{
private final Log _log = new Log(PeerCoordinator.class);
final MetaInfo metainfo;
final Storage storage;
final Snark snark;
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 40*1000; // 40 seconds
final static int MAX_CONNECTIONS = 16;
final static int MAX_UPLOADERS = 6;
// Approximation of the number of current uploaders.
// Resynced by PeerChecker once in a while.
int uploaders = 0;
int interestedAndChoking = 0;
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
// int downloaders = 0;
private long uploaded;
private long downloaded;
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
// synchronize on this when changing peers or downloaders
final List peers = new ArrayList();
/** estimate of the peers, without requiring any synchronization */
volatile int peerCount;
/** Timer to handle all periodical tasks. */
private final Timer timer = new Timer(true);
private final byte[] id;
// Some random wanted pieces
private List wantedPieces;
private boolean halted = false;
private final CoordinatorListener listener;
public String trackerProblems = null;
public int trackerSeenPeers = 0;
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
CoordinatorListener listener, Snark torrent)
{
this.id = id;
this.metainfo = metainfo;
this.storage = storage;
this.listener = listener;
this.snark = torrent;
setWantedPieces();
// Install a timer to check the uploaders.
// Randomize the first start time so multiple tasks are spread out,
// this will help the behavior with global limits
Random r = new Random();
timer.schedule(new PeerCheckerTask(this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
}
// only called externally from Storage after the double-check fails
public void setWantedPieces()
{
// 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);
}
public Storage getStorage() { return storage; }
public CoordinatorListener getListener() { return listener; }
// for web page detailed stats
public List peerList()
{
synchronized(peers)
{
return new ArrayList(peers);
}
}
public byte[] getID()
{
return id;
}
public boolean completed()
{
return storage.complete();
}
public int getPeerCount() { return peerCount; }
public int getPeers()
{
synchronized(peers)
{
int rv = peers.size();
peerCount = rv;
return rv;
}
}
/**
* Returns how many bytes are still needed to get the complete file.
*/
public long getLeft()
{
// XXX - Only an approximation.
return ((long) 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;
}
/**
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
*/
public void setRateHistory(long up, long down)
{
setRate(up, uploaded_old);
setRate(down, downloaded_old);
}
private static void setRate(long val, long array[])
{
synchronized(array) {
for (int i = RATE_DEPTH-1; i > 0; i--)
array[i] = array[i-1];
array[0] = val;
}
}
/**
* Returns the 4-minute-average rate in Bps
*/
public long getDownloadRate()
{
return getRate(downloaded_old);
}
public long getUploadRate()
{
return getRate(uploaded_old);
}
public long getCurrentUploadRate()
{
// no need to synchronize, only one value
long r = uploaded_old[0];
if (r <= 0)
return 0;
return (r * 1000) / CHECK_PERIOD;
}
private long getRate(long array[])
{
long rate = 0;
int i = 0;
synchronized(array) {
for ( ; i < RATE_DEPTH; i++) {
if (array[i] < 0)
break;
rate += array[i];
}
}
if (i == 0)
return 0;
return rate / (i * CHECK_PERIOD / 1000);
}
public MetaInfo getMetaInfo()
{
return metainfo;
}
public boolean needPeers()
{
synchronized(peers)
{
return !halted && peers.size() < MAX_CONNECTIONS;
}
}
public boolean halted() { return halted; }
public void halt()
{
halted = true;
List removed = new ArrayList();
synchronized(peers)
{
// Stop peer checker task.
timer.cancel();
// Stop peers.
removed.addAll(peers);
peers.clear();
peerCount = 0;
}
while (removed.size() > 0) {
Peer peer = (Peer)removed.remove(0);
peer.disconnect();
removePeerFromPieces(peer);
}
// delete any saved orphan partial piece
savedRequest = null;
}
public void connected(Peer peer)
{
if (halted)
{
peer.disconnect(false);
return;
}
Peer toDisconnect = null;
synchronized(peers)
{
Peer old = peerIDInList(peer.getPeerID(), peers);
if ( (old != null) && (old.getInactiveTime() > 8*60*1000) ) {
// idle for 8 minutes, kill the old con (32KB/8min = 68B/sec minimum for one block)
if (_log.shouldLog(Log.WARN))
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
peers.remove(old);
toDisconnect = old;
old = null;
}
if (old != null)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
// toDisconnect = peer to get out of synchronized(peers)
peer.disconnect(false); // Don't deregister this connection/peer.
}
// This is already checked in addPeer() but we could have gone over the limit since then
else if (peers.size() >= MAX_CONNECTIONS)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Already at MAX_CONNECTIONS in connected() with peer: " + peer);
// toDisconnect = peer to get out of synchronized(peers)
peer.disconnect(false);
}
else
{
if (_log.shouldLog(Log.INFO))
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
// Add it to the beginning of the list.
// And try to optimistically make it a uploader.
peers.add(0, peer);
peerCount = peers.size();
unchokePeer();
if (listener != null)
listener.peerChange(this, peer);
}
}
if (toDisconnect != null) {
toDisconnect.disconnect(false);
removePeerFromPieces(toDisconnect);
}
}
// caller must synchronize on peers
private static Peer peerIDInList(PeerID pid, List peers)
{
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
if (pid.sameID(cur.getPeerID()))
return cur;
}
return null;
}
// returns true if actual attempt to add peer occurs
public boolean addPeer(final Peer peer)
{
if (halted)
{
peer.disconnect(false);
return false;
}
boolean need_more;
int peersize = 0;
synchronized(peers)
{
peersize = peers.size();
// This isn't a strict limit, as we may have several pending connections;
// thus there is an additional check in connected()
need_more = (!peer.isConnected()) && peersize < MAX_CONNECTIONS;
// Check if we already have this peer before we build the connection
Peer old = peerIDInList(peer.getPeerID(), peers);
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
}
if (need_more)
{
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
// 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 I2PThread(r, threadName).start();
return true;
}
if (_log.shouldLog(Log.DEBUG)) {
if (peer.isConnected())
_log.info("Add peer already connected: " + peer);
else
_log.info("Connections: " + peersize + "/" + MAX_CONNECTIONS
+ " not accepting extra peer: " + peer);
}
return false;
}
// (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();
synchronized (peers) {
int count = 0;
int unchokedCount = 0;
int maxUploaders = allowedUploaders();
Iterator it = peers.iterator();
while (it.hasNext())
{
Peer peer = (Peer)it.next();
if (peer.isChoking() && peer.isInterested())
{
count++;
if (uploaders < maxUploaders)
{
if (!peer.isChoked())
interested.add(unchokedCount++, peer);
else
interested.add(peer);
}
}
}
while (uploaders < maxUploaders && interested.size() > 0)
{
Peer peer = (Peer)interested.remove(0);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Unchoke: " + peer);
peer.setChoking(false);
uploaders++;
count--;
// Put peer back at the end of the list.
peers.remove(peer);
peers.add(peer);
peerCount = peers.size();
}
interestedAndChoking = count;
}
}
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) {
if (_log.shouldLog(Log.WARN))
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
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) {
// let's not all get on the same piece
Collections.shuffle(requested);
Iterator it2 = requested.iterator();
while (piece == null && it2.hasNext())
{
Piece p = (Piece)it2.next();
if (havePieces.get(p.getId()))
{
piece = p;
}
}
if (piece == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
return -1; //If we still can't find a piece we want, so be it.
} else {
// Should be a lot smarter here - limit # of parallel attempts and
// share blocks rather than starting from 0 with each peer.
// This is where the flaws of the snark data model are really exposed.
// Could also randomize within the duplicate set rather than strict rarest-first
if (_log.shouldLog(Log.DEBUG))
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
}
}
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, int off, int len)
{
if (halted)
return null;
try
{
return storage.getPiece(piece, off, len);
}
catch (IOException ioe)
{
snark.stopTorrent();
_log.error("Error reading the storage for " + metainfo.getName(), ioe);
throw new RuntimeException("B0rked");
}
}
/**
* 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) {
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
return true; // We don't actually care anymore.
}
synchronized(wantedPieces)
{
Piece p = new Piece(piece);
if (!wantedPieces.contains(p))
{
_log.info("Got unwanted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
// 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))
{
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
}
else
{
// Oops. We didn't actually download this then... :(
downloaded -= metainfo.getPieceLength(piece);
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
return false; // No need to announce BAD piece to peers.
}
}
catch (IOException ioe)
{
snark.stopTorrent();
_log.error("Error writing storage for " + metainfo.getName(), ioe);
throw new RuntimeException("B0rked");
}
wantedPieces.remove(p);
}
// Announce to the world we have it!
// Disconnect from other seeders when we get the last piece
synchronized(peers)
{
List toDisconnect = new ArrayList();
Iterator it = peers.iterator();
while (it.hasNext())
{
Peer p = (Peer)it.next();
if (p.isConnected())
{
if (completed() && p.isCompleted())
toDisconnect.add(p);
else
p.have(piece);
}
}
it = toDisconnect.iterator();
while (it.hasNext())
{
Peer p = (Peer)it.next();
p.disconnect(true);
}
}
return true;
}
public void gotChoke(Peer peer, boolean choke)
{
if (_log.shouldLog(Log.INFO))
_log.info("Got choke(" + choke + "): " + peer);
if (listener != null)
listener.peerChange(this, peer);
}
public void gotInterest(Peer peer, boolean interest)
{
if (interest)
{
synchronized(peers)
{
if (uploaders < allowedUploaders())
{
if(peer.isChoking())
{
uploaders++;
peer.setChoking(false);
if (_log.shouldLog(Log.INFO))
_log.info("Unchoke: " + peer);
}
}
}
}
if (listener != null)
listener.peerChange(this, peer);
}
public void disconnected(Peer peer)
{
if (_log.shouldLog(Log.INFO))
_log.info("Disconnected " + peer, new Exception("Disconnected by"));
synchronized(peers)
{
// Make sure it is no longer in our lists
if (peers.remove(peer))
{
// Unchoke some random other peer
unchokePeer();
removePeerFromPieces(peer);
}
peerCount = peers.size();
}
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);
}
}
}
/** Simple method to save a partial piece on peer disconnection
* and hopefully restart it later.
* Only one partial piece is saved at a time.
* Replace it if a new one is bigger or the old one is too old.
* Storage method is private so we can expand to save multiple partials
* if we wish.
*/
private Request savedRequest = null;
private long savedRequestTime = 0;
public void savePeerPartial(PeerState state)
{
if (halted)
return;
Request req = state.getPartialRequest();
if (req == null)
return;
if (savedRequest == null ||
req.off > savedRequest.off ||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(" Saving orphaned partial piece " + req);
if (savedRequest != null)
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
}
}
savedRequest = req;
savedRequestTime = System.currentTimeMillis();
} else {
if (req.piece != savedRequest.piece)
if (_log.shouldLog(Log.DEBUG))
_log.debug(" Discarding orphaned partial piece " + req);
}
}
/** Return partial piece if it's still wanted and peer has it.
*/
public Request getPeerPartial(BitField havePieces) {
if (savedRequest == null)
return null;
if (! havePieces.get(savedRequest.piece)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
return null;
}
synchronized(wantedPieces)
{
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
Piece piece = (Piece)iter.next();
if (piece.getId() == savedRequest.piece) {
Request req = savedRequest;
piece.setRequested(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Restoring orphaned partial piece " + req);
savedRequest = null;
return req;
}
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("We no longer want orphaned piece " + savedRequest);
savedRequest = null;
return null;
}
/** Clear the requested flag for a piece if the peer
** is the only one requesting it
*/
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
{
// see if anybody else is requesting
synchronized (peers)
{
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer p = (Peer)it.next();
if (p.equals(peer))
continue;
if (p.state == null)
continue;
int[] arr = p.state.getRequestedPieces();
for (int i = 0; arr[i] >= 0; i++)
if(arr[i] == piece) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Another peer is requesting piece " + piece);
return;
}
}
}
// nobody is, so mark unrequested
synchronized(wantedPieces)
{
Iterator it = wantedPieces.iterator();
while (it.hasNext()) {
Piece p = (Piece)it.next();
if (p.getId() == piece) {
p.setRequested(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing from request list piece " + piece);
return;
}
}
}
}
/** Mark a peer's requested pieces unrequested when it is disconnected
** Once for each piece
** This is enough trouble, maybe would be easier just to regenerate
** the requested list from scratch instead.
*/
public void markUnrequested(Peer peer)
{
if (halted || peer.state == null)
return;
int[] arr = peer.state.getRequestedPieces();
for (int i = 0; arr[i] >= 0; i++)
markUnrequestedIfOnlyOne(peer, arr[i]);
}
/** Return number of allowed uploaders for this torrent.
** Check with Snark to see if we are over the total upload limit.
*/
public int allowedUploaders()
{
if (Snark.overUploadLimit(uploaders)) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Over limit, uploaders was: " + uploaders);
return uploaders - 1;
} else if (uploaders < MAX_UPLOADERS)
return uploaders + 1;
else
return MAX_UPLOADERS;
}
public boolean overUpBWLimit()
{
return Snark.overUpBWLimit();
}
public boolean overUpBWLimit(long total)
{
return Snark.overUpBWLimit(total * 1000 / CHECK_PERIOD);
}
}

View File

@ -0,0 +1,39 @@
package org.klomp.snark;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Hmm, any guesses as to what this is? Used by the multitorrent functionality
* in the PeerAcceptor to pick the right PeerCoordinator to accept the con for.
* Each PeerCoordinator is added to the set from within the Snark (and removed
* from it there too)
*/
public class PeerCoordinatorSet {
private static final PeerCoordinatorSet _instance = new PeerCoordinatorSet();
public static final PeerCoordinatorSet instance() { return _instance; }
private Set _coordinators;
private PeerCoordinatorSet() {
_coordinators = new HashSet();
}
public Iterator iterator() {
synchronized (_coordinators) {
return new ArrayList(_coordinators).iterator();
}
}
public void add(PeerCoordinator coordinator) {
synchronized (_coordinators) {
_coordinators.add(coordinator);
}
}
public void remove(PeerCoordinator coordinator) {
synchronized (_coordinators) {
_coordinators.remove(coordinator);
}
}
}

View File

@ -0,0 +1,210 @@
/* 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.UnknownHostException;
import java.util.Map;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import org.klomp.snark.bencode.BDecoder;
import org.klomp.snark.bencode.BEValue;
import org.klomp.snark.bencode.InvalidBEncodingException;
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
* and address is the base64 dest (was the base64 hash of the dest) which
* should match what the bytemonsoon tracker reports on its web pages.
*/
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.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();
}
}

View File

@ -0,0 +1,172 @@
/* 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.
* @param off byte offset into the piece.
* @param len length of the chunk 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, int off, int len);
/**
* 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);
/**
* Called when the peer has disconnected and the peer task may have a partially
* downloaded piece that the PeerCoordinator can save
*
* @param state the PeerState for the peer
*/
void savePeerPartial(PeerState state);
/**
* Called when a peer has connected and there may be a partially
* downloaded piece that the coordinatorator can give the peer task
*
* @param havePieces the have-pieces bitmask for the peer
*
* @return request (contains the partial data and valid length)
*/
Request getPeerPartial(BitField havePieces);
/** Mark a peer's requested pieces unrequested when it is disconnected
* This prevents premature end game
*
* @param peer the peer that is disconnecting
*/
void markUnrequested(Peer peer);
}

View File

@ -0,0 +1,129 @@
/* 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.Iterator;
import java.util.TimerTask;
/**
* 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;
}
}

View File

@ -0,0 +1,608 @@
/* 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 net.i2p.util.Log;
class PeerState
{
private Log _log = new Log(PeerState.class);
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 = 2; // this is for outbound requests
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
public final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv alive");
/* XXX - ignored */
}
void chokeMessage(boolean choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
choked = choke;
if (choked)
resend = true;
listener.gotChoke(peer, choke);
if (!choked && interesting)
request();
}
void interestedMessage(boolean interest)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv " + (interest ? "" : "un")
+ "interested");
interested = interest;
listener.gotInterest(peer, interest);
}
void haveMessage(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv have(" + piece + ")");
// Sanity check
if (piece < 0 || piece >= metainfo.getPieces())
{
// XXX disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv bitfield");
if (bitfield != null)
{
// XXX - Be liberal in what you except?
if (_log.shouldLog(Log.WARN))
_log.warn("Got unexpected bitfield message from " + peer);
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv request("
+ piece + ", " + begin + ", " + length + ") ");
if (choking)
{
if (_log.shouldLog(Log.INFO))
_log.info("Request received, but choking " + peer);
return;
}
// Sanity check
if (piece < 0
|| piece >= metainfo.getPieces()
|| begin < 0
|| begin > metainfo.getPieceLength(piece)
|| length <= 0
|| length > MAX_PARTSIZE)
{
// XXX - Protocol error -> disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
return;
}
// Limit total pipelined requests to MAX_PIPELINE bytes
// to conserve memory and prevent DOS
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Discarding request over pipeline limit from " + peer);
return;
}
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
if (pieceBytes == null)
{
// XXX - Protocol error-> diconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got request for unknown piece: " + piece);
return;
}
// More sanity checks
if (length != pieceBytes.length)
{
// XXX - Protocol error-> disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got out of range 'request: " + piece
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Sending (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
out.sendPiece(piece, begin, length, pieceBytes);
}
/**
* 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);
}
// This is used to flag that we have to back up from the firstOutstandingRequest
// when calculating how far we've gotten
Request pendingRequest = null;
/**
* 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);
pendingRequest = null;
// Last chunk needed for this piece?
if (getFirstOutstandingRequest(req.piece) == -1)
{
if (listener.gotPiece(peer, req.piece, req.bs))
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got " + req.piece + ": " + peer);
}
else
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.piece + " from " + peer);
// 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 (_log.shouldLog(Log.DEBUG))
_log.debug("getChunk("
+ piece + "," + begin + "," + length + ") "
+ peer);
int r = getFirstOutstandingRequest(piece);
// Unrequested piece number?
if (r == -1)
{
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested 'piece: " + piece + ", "
+ begin + ", " + length + "' received from "
+ peer);
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 (_log.shouldLog(Log.INFO))
_log.info("Unrequested or unneeded 'piece: "
+ piece + ", "
+ begin + ", "
+ length + "' received from "
+ peer);
downloaded = 0; // XXX - punishment?
return null;
}
// Report missing requests.
if (r != 0)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Some requests dropped, got " + req
+ ", wanted for peer: " + peer);
for (int i = 0; i < r; i++)
{
Request dropReq = (Request)outstandingRequests.remove(0);
outstandingRequests.add(dropReq);
if (!choked)
out.sendRequest(dropReq);
if (_log.shouldLog(Log.WARN))
_log.warn("dropped " + dropReq + " with peer " + peer);
}
}
outstandingRequests.remove(0);
}
// Request more if necessary to keep the pipeline filled.
addRequest();
pendingRequest = req;
return req;
}
// get longest partial piece
Request getPartialRequest()
{
Request req = null;
for (int i = 0; i < outstandingRequests.size(); i++) {
Request r1 = (Request)outstandingRequests.get(i);
int j = getFirstOutstandingRequest(r1.piece);
if (j == -1)
continue;
Request r2 = (Request)outstandingRequests.get(j);
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
req = r2;
}
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
if (pendingRequest.off != 0)
req = pendingRequest;
else
req = null;
}
return req;
}
// return array of pieces terminated by -1
// remove most duplicates
// but still could be some duplicates, not guaranteed
int[] getRequestedPieces()
{
int size = outstandingRequests.size();
int[] arr = new int[size+2];
int pc = -1;
int pos = 0;
if (pendingRequest != null) {
pc = pendingRequest.piece;
arr[pos++] = pc;
}
Request req = null;
for (int i = 0; i < size; i++) {
Request r1 = (Request)outstandingRequests.get(i);
if (pc != r1.piece) {
pc = r1.piece;
arr[pos++] = pc;
}
}
arr[pos] = -1;
return(arr);
}
void cancelMessage(int piece, int begin, int length)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got cancel message ("
+ piece + ", " + begin + ", " + length + ")");
out.cancelRequest(piece, begin, length);
}
void unknownMessage(int type, byte[] bs)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Warning: Ignoring unknown message type: " + type
+ " length: " + bs.length);
}
void havePiece(int piece)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
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)
{
synchronized (this) {
out.sendRequests(outstandingRequests);
}
resend = false;
}
// Add/Send some more requests if necessary.
addRequest();
}
/**
* Adds a new request to the outstanding requests list.
*/
synchronized private void addRequest()
{
boolean more_pieces = true;
while (more_pieces)
{
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;
pieceLength = metainfo.getPieceLength(lastRequest.piece);
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
// Last part of a piece?
if (isLastChunk)
more_pieces = requestNextPiece();
else
{
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " requests " + outstandingRequests);
}
// 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)
{
// Check for adopting an orphaned partial piece
Request r = listener.getPeerPartial(bitfield);
if (r != null) {
// Check that r not already in outstandingRequests
int[] arr = getRequestedPieces();
boolean found = false;
for (int i = 0; arr[i] >= 0; i++) {
if (arr[i] == r.piece) {
found = true;
break;
}
}
if (!found) {
outstandingRequests.add(r);
if (!choked)
out.sendRequest(r);
lastRequest = r;
return true;
}
}
int nextPiece = listener.wantPiece(peer, bitfield);
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " want piece " + nextPiece);
if (nextPiece != -1
&& (lastRequest == null || lastRequest.piece != nextPiece))
{
int piece_length = metainfo.getPieceLength(nextPiece);
//Catch a common place for OOMs esp. on 1MB pieces
byte[] bs;
try {
bs = new byte[piece_length];
} catch (OutOfMemoryError oom) {
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
return false;
}
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 (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setInteresting(" + interest + ")");
if (interest != interesting)
{
interesting = interest;
out.sendInterest(interest);
if (interesting && !choked)
request();
}
}
synchronized void setChoking(boolean choke)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " setChoking(" + choke + ")");
if (choking != choke)
{
choking = choke;
out.sendChoke(choke);
}
}
void keepAlive()
{
out.sendAlive();
}
synchronized void retransmitRequests()
{
if (interesting && !choked)
out.retransmitRequests(outstandingRequests);
}
}

View File

@ -0,0 +1,42 @@
package org.klomp.snark;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
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; }
public String toString() {
return String.valueOf(id);
}
}

View File

@ -0,0 +1,74 @@
/* 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;
long sendTime;
/**
* 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 + ")";
}
}

View 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();
}

View File

@ -0,0 +1,808 @@
/* 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.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import org.klomp.snark.bencode.BDecoder;
/**
* 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
String activity = "Not started";
private static class OOMListener implements I2PThread.OOMEventListener {
public void outOfMemory(OutOfMemoryError err) {
try {
err.printStackTrace();
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
} catch (Throwable t) {
System.out.println("OOM in the OOM");
}
System.exit(0);
}
}
public static void main(String[] args)
{
System.out.println(copyright);
System.out.println();
if ( (args.length > 0) && ("--config".equals(args[0])) ) {
I2PThread.addOOMEventListener(new OOMListener());
SnarkManager sm = SnarkManager.instance();
if (args.length > 1)
sm.loadConfig(args[1]);
System.out.println("Running in multitorrent mode");
while (true) {
try {
synchronized (sm) {
sm.wait();
}
} catch (InterruptedException ie) {}
}
}
// 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(snark.coordinator.peers)
{
System.out.println(snark.coordinator.peers.size()
+ " peers -"
+ " (i)nterested,"
+ " (I)nteresting,"
+ " (c)hoking,"
+ " (C)hoked:");
Iterator it = snark.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: " + snark.meta.getName());
System.out.println("Torrent: " + snark.torrent);
System.out.println("Tracker: " + snark.meta.getAnnounce());
List files = snark.meta.getFiles();
System.out.println("Files: "
+ ((files == null) ? 1 : files.size()));
System.out.println("Pieces: " + snark.meta.getPieces());
System.out.println("Piece size: "
+ snark.meta.getPieceLength(0) / 1024
+ " KB");
System.out.println("Total size: "
+ snark.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();
}
}
public String torrent;
public MetaInfo meta;
public Storage storage;
public PeerCoordinator coordinator;
public ConnectionAcceptor acceptor;
public TrackerClient trackerclient;
public String rootDataDir = ".";
public CompleteListener completeListener;
public boolean stopped;
byte[] id;
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
this(torrent, ip, user_port, slistener, clistener, true, ".");
}
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
{
if (slistener == null)
slistener = this;
if (clistener == null)
clistener = this;
this.torrent = torrent;
this.rootDataDir = rootDir;
stopped = true;
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;
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;
/*
* Don't start a tunnel if the torrent isn't going to be started.
* If we are starting,
* startTorrent() will force a connect.
*
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 {
Destination d = serversocket.getManager().getSession().getMyDestination();
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().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, 3);
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(rootDataDir);
// have to figure out when to reopen
// if (!start)
// storage.close();
}
catch (IOException ioe)
{
try { storage.close(); } catch (IOException ioee) {
ioee.printStackTrace();
}
fatal("Could not create storage", ioe);
}
}
/*
* see comment above
*
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.add(coordinator);
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
acceptor.startAccepting(set, serversocket);
trackerclient = new TrackerClient(meta, coordinator);
*/
if (start)
startTorrent();
}
/**
* Start up contacting peers and querying the tracker
*/
public void startTorrent() {
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) fatal("Unable to connect to I2P");
if (coordinator == null) {
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
if (serversocket == null)
fatal("Unable to listen for I2P connections");
else {
Destination d = serversocket.getManager().getSession().getMyDestination();
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
}
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, this, this);
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.add(coordinator);
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
acceptor.startAccepting(set, serversocket);
trackerclient = new TrackerClient(meta, coordinator);
}
stopped = false;
boolean coordinatorChanged = false;
if (coordinator.halted()) {
// ok, we have already started and stopped, but the coordinator seems a bit annoying to
// restart safely, so lets build a new one to replace the old
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.remove(coordinator);
PeerCoordinator newCoord = new PeerCoordinator(coordinator.getID(), coordinator.getMetaInfo(),
coordinator.getStorage(), coordinator.getListener(), this);
set.add(newCoord);
coordinator = newCoord;
coordinatorChanged = true;
}
if (!trackerclient.started() && !coordinatorChanged) {
trackerclient.start();
} else if (trackerclient.halted() || coordinatorChanged) {
try
{
storage.reopen(rootDataDir);
}
catch (IOException ioe)
{
try { storage.close(); } catch (IOException ioee) {
ioee.printStackTrace();
}
fatal("Could not reopen storage", ioe);
}
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
if (!trackerclient.halted())
trackerclient.halt();
trackerclient = newClient;
trackerclient.start();
}
}
/**
* Stop contacting the tracker and talking with peers
*/
public void stopTorrent() {
stopped = true;
TrackerClient tc = trackerclient;
if (tc != null)
tc.halt();
PeerCoordinator pc = coordinator;
if (pc != null)
pc.halt();
Storage st = storage;
if (st != null) {
if (storage.changed)
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
try {
storage.close();
} catch (IOException ioe) {
System.out.println("Error closing " + torrent);
ioe.printStackTrace();
}
}
if (pc != null)
PeerCoordinatorSet.instance().remove(pc);
}
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;
boolean configured = I2PSnarkUtil.instance().configured();
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];
if (!configured)
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));
}
}
}
}
if (!configured)
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 void fatal(String s)
{
fatal(s, null);
}
/**
* Aborts program abnormally.
*/
public 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();
stopTorrent();
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)
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 storageCompleted(Storage storage)
{
Snark.debug("Completely received " + torrent, Snark.INFO);
//storage.close();
//System.out.println("Completely received: " + torrent);
if (completeListener != null)
completeListener.torrentComplete(this);
}
public void setWantedPieces(Storage storage)
{
coordinator.setWantedPieces();
}
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);
}
public interface CompleteListener {
public void torrentComplete(Snark snark);
}
/** Maintain a configurable total uploader cap
*/
final static int MIN_TOTAL_UPLOADERS = 4;
final static int MAX_TOTAL_UPLOADERS = 10;
public static boolean overUploadLimit(int uploaders) {
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
if (coordinators == null || uploaders <= 0)
return false;
int totalUploaders = 0;
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
PeerCoordinator c = (PeerCoordinator)iter.next();
if (!c.halted())
totalUploaders += c.uploaders;
}
int limit = I2PSnarkUtil.instance().getMaxUploaders();
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
return totalUploaders > limit;
}
public static boolean overUpBWLimit() {
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
if (coordinators == null)
return false;
long total = 0;
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
PeerCoordinator c = (PeerCoordinator)iter.next();
if (!c.halted())
total += c.getCurrentUploadRate();
}
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
Snark.debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
return total > limit;
}
public static boolean overUpBWLimit(long total) {
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
return total > limit;
}
}

View File

@ -0,0 +1,742 @@
package org.klomp.snark;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Manage multiple snarks
*/
public class SnarkManager implements Snark.CompleteListener {
private static SnarkManager _instance = new SnarkManager();
public static SnarkManager instance() { return _instance; }
/** map of (canonical) filename to Snark instance (unsynchronized) */
private Map _snarks;
private Object _addSnarkLock;
private String _configFile;
private Properties _config;
private I2PAppContext _context;
private Log _log;
private List _messages;
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
public static final String PROP_DIR = "i2psnark.dir";
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
public static final String DEFAULT_AUTO_START = "false";
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final String DEFAULT_USE_OPENTRACKERS = "true";
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
public static final int MIN_UP_BW = 2;
public static final int DEFAULT_MAX_UP_BW = 10;
private SnarkManager() {
_snarks = new HashMap();
_addSnarkLock = new Object();
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SnarkManager.class);
_messages = new ArrayList(16);
loadConfig("i2psnark.config");
int minutes = getStartupDelayMinutes();
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
if (_context instanceof RouterContext)
((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
}
private static final int MAX_MESSAGES = 5;
public void addMessage(String message) {
synchronized (_messages) {
_messages.add(message);
while (_messages.size() > MAX_MESSAGES)
_messages.remove(0);
}
if (_log.shouldLog(Log.INFO))
_log.info("MSG: " + message);
}
/** newest last */
public List getMessages() {
synchronized (_messages) {
return new ArrayList(_messages);
}
}
public boolean shouldAutoStart() {
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
}
public boolean shouldUseOpenTrackers() {
return Boolean.valueOf(_config.getProperty(PROP_USE_OPENTRACKERS, DEFAULT_USE_OPENTRACKERS)).booleanValue();
}
private int getStartupDelayMinutes() { return 3; }
public File getDataDir() {
String dir = _config.getProperty(PROP_DIR);
if ( (dir == null) || (dir.trim().length() <= 0) )
dir = "i2psnark";
return new File(dir);
}
public void loadConfig(String filename) {
_configFile = filename;
if (_config == null)
_config = new Properties();
File cfg = new File(filename);
if (cfg.exists()) {
try {
DataHelper.loadProps(_config, cfg);
} catch (IOException ioe) {
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
}
}
// now add sane defaults
if (!_config.containsKey(PROP_I2CP_HOST))
_config.setProperty(PROP_I2CP_HOST, "localhost");
if (!_config.containsKey(PROP_I2CP_PORT))
_config.setProperty(PROP_I2CP_PORT, "7654");
if (!_config.containsKey(PROP_I2CP_OPTS))
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=1 inbound.lengthVariance=1 outbound.length=1 outbound.lengthVariance=1");
if (!_config.containsKey(PROP_EEP_HOST))
_config.setProperty(PROP_EEP_HOST, "localhost");
if (!_config.containsKey(PROP_EEP_PORT))
_config.setProperty(PROP_EEP_PORT, "4444");
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
if (!_config.containsKey(PROP_UPBW_MAX)) {
try {
if (_context instanceof RouterContext)
_config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 2));
else
_config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
} catch (NoClassDefFoundError ncdfe) {
_config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
}
}
if (!_config.containsKey(PROP_DIR))
_config.setProperty(PROP_DIR, "i2psnark");
if (!_config.containsKey(PROP_AUTO_START))
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
updateConfig();
}
private void updateConfig() {
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
String opts = _config.getProperty(PROP_I2CP_OPTS);
Map i2cpOpts = new HashMap();
if (opts != null) {
StringTokenizer tok = new StringTokenizer(opts, " ");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
i2cpOpts.put(pair.substring(0, split), pair.substring(split+1));
}
}
if (i2cpHost != null) {
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
_log.debug("Configuring with I2CP options " + i2cpOpts);
}
//I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
String eepHost = _config.getProperty(PROP_EEP_HOST);
int eepPort = getInt(PROP_EEP_PORT, 4444);
if (eepHost != null)
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
I2PSnarkUtil.instance().setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
getDataDir().mkdirs();
}
private int getInt(String prop, int defaultVal) {
String p = _config.getProperty(prop);
try {
if ( (p != null) && (p.trim().length() > 0) )
return Integer.parseInt(p.trim());
} catch (NumberFormatException nfe) {
// ignore
}
return defaultVal;
}
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
boolean changed = false;
if (eepHost != null) {
int port = I2PSnarkUtil.instance().getEepProxyPort();
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
String host = I2PSnarkUtil.instance().getEepProxyHost();
if ( (eepHost.trim().length() > 0) && (port > 0) &&
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
I2PSnarkUtil.instance().setProxy(eepHost, port);
changed = true;
_config.setProperty(PROP_EEP_HOST, eepHost);
_config.setProperty(PROP_EEP_PORT, eepPort+"");
addMessage("EepProxy location changed to " + eepHost + ":" + port);
}
}
if (upLimit != null) {
int limit = I2PSnarkUtil.instance().getMaxUploaders();
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
if ( limit != I2PSnarkUtil.instance().getMaxUploaders()) {
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
I2PSnarkUtil.instance().setMaxUploaders(limit);
changed = true;
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
addMessage("Total uploaders limit changed to " + limit);
} else {
addMessage("Minimum total uploaders limit is " + Snark.MIN_TOTAL_UPLOADERS);
}
}
}
if (upBW != null) {
int limit = I2PSnarkUtil.instance().getMaxUpBW();
try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
if ( limit != I2PSnarkUtil.instance().getMaxUpBW()) {
if ( limit >= MIN_UP_BW ) {
I2PSnarkUtil.instance().setMaxUpBW(limit);
changed = true;
_config.setProperty(PROP_UPBW_MAX, "" + limit);
addMessage("Up BW limit changed to " + limit + "KBps");
} else {
addMessage("Minimum Up BW limit is " + MIN_UP_BW + "KBps");
}
}
}
if (i2cpHost != null) {
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
int port = oldI2CPPort;
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
String host = oldI2CPHost;
Map opts = new HashMap();
if (i2cpOpts == null) i2cpOpts = "";
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
opts.put(pair.substring(0, split), pair.substring(split+1));
}
Map oldOpts = new HashMap();
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
if (oldI2CPOpts == null) oldI2CPOpts = "";
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
}
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
((!host.equals(i2cpHost) ||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
(!oldOpts.equals(opts)))) ) {
boolean snarksActive = false;
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
Snark snark = getTorrent((String)iter.next());
if ( (snark != null) && (!snark.stopped) ) {
snarksActive = true;
break;
}
}
if (snarksActive) {
addMessage("Cannot change the I2CP settings while torrents are active");
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
+ "] oldOpts [" + oldOpts + "]");
} else {
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
addMessage("Disconnecting old I2CP destination");
}
Properties p = new Properties();
p.putAll(opts);
addMessage("I2CP settings changed to " + i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")");
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, port, p);
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
addMessage("Unable to connect with the new settings, reverting to the old I2CP settings");
I2PSnarkUtil.instance().setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
ok = I2PSnarkUtil.instance().connect();
if (!ok)
addMessage("Unable to reconnect with the old settings!");
} else {
addMessage("Reconnected on the new I2CP destination");
_config.setProperty(PROP_I2CP_HOST, i2cpHost.trim());
_config.setProperty(PROP_I2CP_PORT, "" + port);
_config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim());
changed = true;
// no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = getTorrent(name);
if ( (snark != null) && (snark.acceptor != null) ) {
snark.acceptor.restart();
addMessage("I2CP listener restarted for " + snark.meta.getName());
}
}
}
}
changed = true;
}
}
if (shouldAutoStart() != autoStart) {
_config.setProperty(PROP_AUTO_START, autoStart + "");
addMessage("Adjusted autostart to " + autoStart);
changed = true;
}
if (shouldUseOpenTrackers() != useOpenTrackers) {
_config.setProperty(PROP_USE_OPENTRACKERS, useOpenTrackers + "");
addMessage((useOpenTrackers ? "En" : "Dis") + "abled open trackers - torrent restart required to take effect");
changed = true;
}
if (openTrackers != null) {
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(getOpenTrackerString())) {
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
addMessage("Open Tracker list changed - torrent restart required to take effect");
changed = true;
}
}
if (changed) {
saveConfig();
} else {
addMessage("Configuration unchanged");
}
}
public void saveConfig() {
try {
synchronized (_configFile) {
DataHelper.storeProps(_config, new File(_configFile));
}
} catch (IOException ioe) {
addMessage("Unable to save the config to '" + _configFile + "'");
}
}
public Properties getConfig() { return _config; }
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
private static final int MAX_FILES_PER_TORRENT = 128;
/** set of filenames that we are dealing with */
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
/**
* Grab the torrent given the (canonical) filename
*/
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
public void addTorrent(String filename) { addTorrent(filename, false); }
public void addTorrent(String filename, boolean dontAutoStart) {
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
addMessage("Connecting to I2P");
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
addMessage("Error connecting to I2P - check your I2CP settings");
return;
}
}
File sfile = new File(filename);
try {
filename = sfile.getCanonicalPath();
} catch (IOException ioe) {
_log.error("Unable to add the torrent " + filename, ioe);
addMessage("ERR: Could not add the torrent '" + filename + "': " + ioe.getMessage());
return;
}
File dataDir = getDataDir();
Snark torrent = null;
synchronized (_snarks) {
torrent = (Snark)_snarks.get(filename);
}
// don't hold the _snarks lock while verifying the torrent
if (torrent == null) {
synchronized (_addSnarkLock) {
// double-check
synchronized (_snarks) {
if(_snarks.get(filename) != null)
return;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(sfile);
MetaInfo info = new MetaInfo(fis);
fis.close();
fis = null;
String rejectMessage = locked_validateTorrent(info);
if (rejectMessage != null) {
sfile.delete();
addMessage(rejectMessage);
return;
} else {
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
torrent.completeListener = this;
synchronized (_snarks) {
_snarks.put(filename, torrent);
}
}
} catch (IOException ioe) {
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
if (sfile.exists())
sfile.delete();
return;
} finally {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
} else {
return;
}
// ok, snark created, now lets start it up or configure it further
File f = new File(filename);
if (!dontAutoStart && shouldAutoStart()) {
torrent.startTorrent();
addMessage("Torrent added and started: '" + f.getName() + "'");
} else {
addMessage("Torrent added: '" + f.getName() + "'");
}
}
/**
* Get the timestamp for a torrent from the config file
*/
public long getSavedTorrentTime(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
if (time == null)
return 0;
int comma = time.indexOf(',');
if (comma <= 0)
return 0;
time = time.substring(0, comma);
try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
return 0;
}
/**
* Get the saved bitfield for a torrent from the config file.
* Convert "." to a full bitfield.
*/
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
if (bf == null)
return null;
int comma = bf.indexOf(',');
if (comma <= 0)
return null;
bf = bf.substring(comma + 1).trim();
int len = metainfo.getPieces();
if (bf.equals(".")) {
BitField bitfield = new BitField(len);
for (int i = 0; i < len; i++)
bitfield.set(i);
return bitfield;
}
byte[] bitfield = Base64.decode(bf);
if (bitfield == null)
return null;
if (bitfield.length * 8 < len)
return null;
return new BitField(bitfield, len);
}
/**
* Save the completion status of a torrent and the current time in the config file
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
* The config file property key is appended with the Base64 of the infohash,
* with the '=' changed to '$' since a key can't contain '='.
* The time is a standard long converted to string.
* The status is either a bitfield converted to Base64 or "." for a completed
* torrent to save space in the config file and in memory.
*/
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String now = "" + System.currentTimeMillis();
String bfs;
if (bitfield.complete()) {
bfs = ".";
} else {
byte[] bf = bitfield.getFieldBytes();
bfs = Base64.encode(bf);
}
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
saveConfig();
}
/**
* Remove the status of a torrent from the config file.
* This may help the config file from growing too big.
*/
public void removeTorrentStatus(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
saveConfig();
}
private String locked_validateTorrent(MetaInfo info) throws IOException {
String announce = info.getAnnounce();
// basic validation of url
if ((!announce.startsWith("http://")) ||
(announce.indexOf(".i2p/") < 0))
return "Non-i2p tracker in " + info.getName() + ", deleting it";
List files = info.getFiles();
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
} else if (info.getPieces() <= 0) {
return "No pieces in " + info.getName() + "? deleting it";
} else if (info.getPieceLength(0) > 1*1024*1024) {
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB), deleting it";
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
System.out.println("torrent info: " + info.toString());
List lengths = info.getLengths();
if (lengths != null)
for (int i = 0; i < lengths.size(); i++)
System.out.println("File " + i + " is " + lengths.get(i) + " long");
return "Torrents larger than 10GB are not supported yet (because we're paranoid): " + info.getName() + ", deleting it";
} else {
// ok
return null;
}
}
/**
* Stop the torrent, leaving it on the list of torrents unless told to remove it
*/
public Snark stopTorrent(String filename, boolean shouldRemove) {
File sfile = new File(filename);
try {
filename = sfile.getCanonicalPath();
} catch (IOException ioe) {
_log.error("Unable to remove the torrent " + filename, ioe);
addMessage("ERR: Could not remove the torrent '" + filename + "': " + ioe.getMessage());
return null;
}
int remaining = 0;
Snark torrent = null;
synchronized (_snarks) {
if (shouldRemove)
torrent = (Snark)_snarks.remove(filename);
else
torrent = (Snark)_snarks.get(filename);
remaining = _snarks.size();
}
if (torrent != null) {
boolean wasStopped = torrent.stopped;
torrent.stopTorrent();
if (remaining == 0) {
// should we disconnect/reconnect here (taking care to deal with the other thread's
// I2PServerSocket.accept() call properly?)
////I2PSnarkUtil.instance().
}
if (!wasStopped)
addMessage("Torrent stopped: '" + sfile.getName() + "'");
}
return torrent;
}
/**
* Stop the torrent and delete the torrent file itself, but leaving the data
* behind.
*/
public void removeTorrent(String filename) {
Snark torrent = stopTorrent(filename, true);
if (torrent != null) {
File torrentFile = new File(filename);
torrentFile.delete();
if (torrent.storage != null)
removeTorrentStatus(torrent.storage.getMetaInfo());
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
}
}
private class DirMonitor implements Runnable {
public void run() {
try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {}
// the first message was a "We are starting up in 1m"
synchronized (_messages) {
if (_messages.size() == 1)
_messages.remove(0);
}
while (true) {
File dir = getDataDir();
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
try {
monitorTorrents(dir);
} catch (Exception e) {
_log.error("Error in the DirectoryMonitor", e);
}
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
}
}
}
public void torrentComplete(Snark snark) {
File f = new File(snark.torrent);
long len = snark.meta.getTotalLength();
addMessage("Download complete of " + f.getName()
+ (len < 5*1024*1024 ? " (size: " + (len/1024) + "KB)" : " (size: " + (len/(1024*1024l)) + "MB)"));
}
private void monitorTorrents(File dir) {
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
List foundNames = new ArrayList(0);
if (fileNames != null) {
for (int i = 0; i < fileNames.length; i++) {
try {
foundNames.add(new File(dir, fileNames[i]).getCanonicalPath());
} catch (IOException ioe) {
_log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe);
}
}
}
Set existingNames = listTorrentFiles();
// lets find new ones first...
for (int i = 0; i < foundNames.size(); i++) {
if (existingNames.contains(foundNames.get(i))) {
// already known. noop
} else {
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
addMessage("Unable to connect to I2P");
addTorrent((String)foundNames.get(i), !shouldAutoStart());
}
}
// now lets see which ones have been removed...
for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
if (foundNames.contains(name)) {
// known and still there. noop
} else {
// known, but removed. drop it
stopTorrent(name, true);
}
}
}
private static final String DEFAULT_TRACKERS[] = {
"Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/"
// , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/"
// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/"
, "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/"
// , "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/"
// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/"
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
, "welterde", "http://BGKmlDOoH3RzFbPRfRpZV2FjpVj8~3moFftw5-dZfDf2070TOe8Tf2~DAVeaM6ZRLdmFEt~9wyFL8YMLMoLoiwGEH6IGW6rc45tstN68KsBDWZqkTohV1q9XFgK9JnCwE~Oi89xLBHsLMTHOabowWM6dkC8nI6QqJC2JODqLPIRfOVrDdkjLwtCrsckzLybNdFmgfoqF05UITDyczPsFVaHtpF1sRggOVmdvCM66otyonlzNcJbn59PA-R808vUrCPMGU~O9Wys0i-NoqtIbtWfOKnjCRFMNw5ex4n9m5Sxm9e20UkpKG6qzEuvKZWi8vTLe1NW~CBrj~vG7I3Ok4wybUFflBFOaBabxYJLlx4xTE1zJIVxlsekmAjckB4v-cQwulFeikR4LxPQ6mCQknW2HZ4JQIq6hL9AMabxjOlYnzh7kjOfRGkck8YgeozcyTvcDUcUsOuSTk06L4kdrv8h2Cozjbloi5zl6KTbj5ZTciKCxi73Pn9grICn-HQqEAAAA.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
, "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
, "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
};
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
public static final String PROP_TRACKERS = "i2psnark.trackers";
private static Map trackerMap = null;
/** sorted map of name to announceURL=baseURL */
public Map getTrackers() {
if (trackerMap != null) // only do this once, can't be updated while running
return trackerMap;
Map rv = new TreeMap();
String trackers = _config.getProperty(PROP_TRACKERS);
if ( (trackers == null) || (trackers.trim().length() <= 0) )
trackers = _context.getProperty(PROP_TRACKERS);
if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2)
rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
} else {
StringTokenizer tok = new StringTokenizer(trackers, ",");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split <= 0)
continue;
String name = pair.substring(0, split).trim();
String url = pair.substring(split+1).trim();
if ( (name.length() > 0) && (url.length() > 0) )
rv.put(name, url);
}
}
trackerMap = rv;
return trackerMap;
}
public String getOpenTrackerString() {
return _config.getProperty(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
}
/** comma delimited list open trackers to use as backups */
/** sorted map of name to announceURL=baseURL */
public List getOpenTrackers() {
if (!shouldUseOpenTrackers())
return null;
List rv = new ArrayList(1);
String trackers = getOpenTrackerString();
StringTokenizer tok = new StringTokenizer(trackers, ", ");
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
if (rv.size() <= 0)
return null;
return rv;
}
private static class TorrentFilenameFilter implements FilenameFilter {
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
public static TorrentFilenameFilter instance() { return _filter; }
public boolean accept(File dir, String name) {
return (name != null) && (name.endsWith(".torrent"));
}
}
public class SnarkManagerShutdown extends I2PThread {
public void run() {
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
Snark snark = getTorrent((String)iter.next());
if ( (snark != null) && (!snark.stopped) )
snark.stopTorrent();
}
}
}
}

View File

@ -0,0 +1,92 @@
/* 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;
import net.i2p.util.I2PThread;
/**
* Makes sure everything ends correctly when shutting down.
*/
public class SnarkShutdown extends I2PThread
{
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)
{
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
throw new RuntimeException("b0rking");
}
}
// XXX - Should actually wait till done...
try
{
Snark.debug("Waiting 5 seconds...", Snark.INFO);
Thread.sleep(5*1000);
}
catch (InterruptedException ie) { /* ignored */ }
listener.shutdown();
}
}

View File

@ -0,0 +1,43 @@
/* 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;
/**
* 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);
}
}

View File

@ -0,0 +1,707 @@
/* 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.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.crypto.SHA1;
/**
* 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 BitField bitfield; // BitField to represent the pieces
private int needed; // Number of pieces needed
// XXX - Not always set correctly
int piece_size;
int pieces;
boolean changed;
/** The default piece size. */
private static int MIN_PIECE_SIZE = 256*1024;
private static int MAX_PIECE_SIZE = 1024*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 < MAX_PIECE_SIZE)
{
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) // FIXME: ...and if base file not a directory or should this be the only check?
// this makes a bad metainfo if the directory has only one file in it
{
files = null;
lengthsList = null;
}
// Note that the piece_hashes are not correctly setup yet.
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
lengthsList, piece_size, piece_hashes, total);
}
// Creates piece hases for a new storage.
public void create() throws IOException
{
// if (true) {
fast_digestCreate();
// } else {
// orig_digestCreate();
// }
}
/*
private void orig_digestCreate() 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);
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 fast_digestCreate() throws IOException {
// Calculate piece_hashes
SHA1 digest = new SHA1();
byte[] piece_hashes = metainfo.getPieceHashes();
byte[] piece = new byte[piece_size];
for (int i = 0; i < pieces; i++)
{
int length = getUncheckedPiece(i, piece);
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();
if (base.isDirectory() && names[i].startsWith(base.getPath()))
names[i] = names[i].substring(base.getPath().length() + 1);
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(String rootDir) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
// look for saved bitfield and timestamp in the config file
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
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();
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (base.exists() && ((useSavedBitField && savedBitField.complete()) || !base.canWrite()))
rafs[0] = new RandomAccessFile(base, "r");
else
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];
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (f.exists() && ((useSavedBitField && savedBitField.complete()) || !f.canWrite()))
rafs[i] = new RandomAccessFile(f, "r");
else
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);
}
if (useSavedBitField) {
bitfield = savedBitField;
needed = metainfo.getPieces() - bitfield.count();
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
} else {
checkCreateFiles();
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
/**
* Reopen the file descriptors for a restart
* Do existence check but no length check or data reverification
*/
public void reopen(String rootDir) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
List files = metainfo.getFiles();
if (files == null)
{
// Reopen base as file.
Snark.debug("Reopening file: " + base, Snark.NOTICE);
if (!base.exists())
throw new IOException("Could not reopen file " + base);
if (complete() || !base.canWrite()) // hope we can get away with this, if we are only seeding...
rafs[0] = new RandomAccessFile(base, "r");
else
rafs[0] = new RandomAccessFile(base, "rw");
}
else
{
// Reopen base as dir.
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
if (!base.isDirectory())
throw new IOException("Could not reopen directory " + base);
int size = files.size();
for (int i = 0; i < size; i++)
{
File f = getFileFromNames(base, (List)files.get(i));
if (!f.exists())
throw new IOException("Could not reopen file " + f);
if (complete() || !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
rafs[i] = new RandomAccessFile(f, "rw");
}
}
}
/**
* Removes 'suspicious' characters from the give file name.
*/
private static 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;
}
public static File getFileFromNames(File base, List names)
{
Iterator it = names.iterator();
while (it.hasNext())
{
String name = filterName((String)it.next());
base = new File(base, name);
}
return base;
}
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 {
Snark.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
rafs[i].setLength(lengths[i]);
}
}
// 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);
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);
if (needed <= 0)
listener.storageCompleted(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
{
if (rafs == null) return;
for (int i = 0; i < rafs.length; i++)
{
try {
synchronized(rafs[i])
{
rafs[i].close();
}
} catch (IOException ioe) {
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
// gobble gobble
}
}
changed = false;
}
/**
* Returns a byte array containing a portion of the requested piece or null if
* the storage doesn't contain the piece yet.
*/
public byte[] getPiece(int piece, int off, int len) throws IOException
{
if (!bitfield.get(piece))
return null;
//Catch a common place for OOMs esp. on 1MB pieces
byte[] bs;
try {
bs = new byte[len];
} catch (OutOfMemoryError oom) {
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
return null;
}
getUncheckedPiece(piece, bs, off, len);
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[] ba) throws IOException
{
// First check if the piece is correct.
// Copy the array first to be paranoid.
byte[] bs = (byte[]) ba.clone();
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;
}
}
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) 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;
}
}
changed = true;
if (complete) {
// listener.storageCompleted(this);
// do we also need to close all of the files and reopen
// them readonly?
// Do a complete check to be sure.
// Temporarily resets the 'needed' variable and 'bitfield', then call
// checkCreateFiles() which will set 'needed' and 'bitfield'
// and also call listener.storageCompleted() if the double-check
// was successful.
// Todo: set a listener variable so the web shows "checking" and don't
// have the user panic when completed amount goes to zero temporarily?
needed = metainfo.getPieces();
bitfield = new BitField(needed);
checkCreateFiles();
if (needed > 0) {
listener.setWantedPieces(this);
Snark.debug("WARNING: Not really done, missing " + needed
+ " pieces", Snark.WARNING);
} else {
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
return true;
}
private int getUncheckedPiece(int piece, byte[] bs)
throws IOException
{
return getUncheckedPiece(piece, bs, 0, metainfo.getPieceLength(piece));
}
private int getUncheckedPiece(int piece, byte[] bs, int off, int length)
throws IOException
{
// XXX - copy/paste code from putPiece().
// Early typecast, avoid possibly overflowing a temp integer
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
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, read, len);
}
read += len;
if (need - len > 0)
{
i++;
raflen = lengths[i];
start = 0;
}
}
return length;
}
}

View File

@ -0,0 +1,65 @@
/* 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);
/**
* Called the one time when the data is completely received and checked.
*
*/
void storageCompleted(Storage storage);
/** Reset the peer's wanted pieces table
* Call after the storage double-check fails
*
* @param peer the peer
*/
void setWantedPieces(Storage storage);
}

View File

@ -0,0 +1,415 @@
/* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Informs metainfo tracker of events and gets new peers for peer
* coordinator.
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class TrackerClient extends I2PThread
{
private static final Log _log = new Log(TrackerClient.class);
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 static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
private final static int SLEEP = 5; // 5 minutes.
private final static int DELAY_MIN = 2000; // 2 secs.
private final static int DELAY_MUL = 1500; // 1.5 secs.
private final static int MAX_REGISTER_FAILS = 10; // * INITIAL_SLEEP = 15m to register
private final static int INITIAL_SLEEP = 90*1000;
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
private final MetaInfo meta;
private final PeerCoordinator coordinator;
private final int port;
private boolean stop;
private boolean started;
private List trackers;
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;
started = false;
}
public void start() {
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
super.start();
started = true;
}
public boolean halted() { return stop; }
public boolean started() { return started; }
/**
* Interrupts this Thread to stop it.
*/
public void halt()
{
stop = true;
this.interrupt();
}
private boolean verifyConnected() {
while (!stop && !I2PSnarkUtil.instance().connected()) {
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
}
return !stop && I2PSnarkUtil.instance().connected();
}
public void run()
{
String infoHash = urlencode(meta.getInfoHash());
String peerID = urlencode(coordinator.getID());
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash);
// Construct the list of trackers for this torrent,
// starting with the primary one listed in the metainfo,
// followed by the secondary open trackers
// It's painful, but try to make sure if an open tracker is also
// the primary tracker, that we don't add it twice.
trackers = new ArrayList(2);
trackers.add(new Tracker(meta.getAnnounce(), true));
List tlist = SnarkManager.instance().getOpenTrackers();
if (tlist != null) {
for (int i = 0; i < tlist.size(); i++) {
String url = (String)tlist.get(i);
if (!url.startsWith("http://")) {
_log.error("Bad announce URL: [" + url + "]");
continue;
}
int slash = url.indexOf('/', 7);
if (slash <= 7) {
_log.error("Bad announce URL: [" + url + "]");
continue;
}
if (meta.getAnnounce().startsWith(url.substring(0, slash)))
continue;
String dest = I2PSnarkUtil.instance().lookup(url.substring(7, slash));
if (dest == null) {
_log.error("Announce host unknown: [" + url + "]");
continue;
}
if (meta.getAnnounce().startsWith("http://" + dest))
continue;
if (meta.getAnnounce().startsWith("http://i2p/" + dest))
continue;
trackers.add(new Tracker(url, false));
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
}
}
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft();
boolean completed = (left == 0);
int sleptTime = 0;
try
{
if (!verifyConnected()) return;
boolean started = false;
boolean firstTime = true;
int consecutiveFails = 0;
Random r = new Random();
while(!stop)
{
try
{
// Sleep some minutes...
// Sleep the minimum interval for all the trackers, but 60s minimum
// except for the first time...
int delay;
int random = r.nextInt(120*1000);
if (firstTime) {
delay = r.nextInt(30*1000);
firstTime = false;
} else if (completed && started)
delay = 3*SLEEP*60*1000 + random;
else if (coordinator.trackerProblems != null && ++consecutiveFails < MAX_CONSEC_FAILS)
delay = INITIAL_SLEEP;
else
// sleep a while, when we wake up we will contact only the trackers whose intervals have passed
delay = SLEEP*60*1000 + random;
if (delay > 0)
Thread.sleep(delay);
}
catch(InterruptedException interrupt)
{
// ignore
}
if (stop)
break;
if (!verifyConnected()) return;
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;
// *** loop once for each tracker
// Only do a request when necessary.
sleptTime = 0;
int maxSeenPeers = 0;
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
Tracker tr = (Tracker)iter.next();
if ((!stop) && (!tr.stop) &&
(completed || coordinator.needPeers()) &&
(event == COMPLETED_EVENT || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
{
try
{
if (!tr.started)
event = STARTED_EVENT;
TrackerInfo info = doRequest(tr, infoHash, peerID,
uploaded, downloaded, left,
event);
coordinator.trackerProblems = null;
tr.trackerProblems = null;
tr.registerFails = 0;
tr.consecutiveFails = 0;
if (tr.isPrimary)
consecutiveFails = 0;
started = true;
tr.started = true;
Set peers = info.getPeers();
tr.seenPeers = peers.size();
if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
coordinator.trackerSeenPeers = tr.seenPeers;
if ( (left > 0) && (!completed) ) {
// we only want to talk to new people if we need things
// from them (duh)
List ordered = new ArrayList(peers);
Collections.shuffle(ordered);
Iterator it = ordered.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
// only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur)) {
int delay = DELAY_MUL;
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
delay += DELAY_MIN;
sleptTime += delay;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
}
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
Snark.debug
("WARNING: Could not contact tracker at '"
+ tr.announce + "': " + ioe, Snark.WARNING);
tr.trackerProblems = ioe.getMessage();
// don't show secondary tracker problems to the user
if (tr.isPrimary)
coordinator.trackerProblems = tr.trackerProblems;
if (tr.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
// Give a guy some time to register it if using opentrackers too
if (trackers.size() == 1) {
stop = true;
coordinator.snark.stopTorrent();
} else { // hopefully each on the opentrackers list is really open
if (tr.registerFails++ > MAX_REGISTER_FAILS)
tr.stop = true;
}
}
if (++tr.consecutiveFails == MAX_CONSEC_FAILS) {
tr.seenPeers = 0;
if (tr.interval < LONG_SLEEP)
tr.interval = LONG_SLEEP; // slow down
}
}
}
if ((!tr.stop) && maxSeenPeers < tr.seenPeers)
maxSeenPeers = tr.seenPeers;
} // *** end of trackers loop here
// we could try and total the unique peers but that's too hard for now
coordinator.trackerSeenPeers = maxSeenPeers;
if (!started)
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
} // *** end of while loop
} // try
catch (Throwable t)
{
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
try
{
// try to contact everybody we can
// We don't need I2CP connection for eepget
// if (!verifyConnected()) return;
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
Tracker tr = (Tracker)iter.next();
if (tr.started && (!tr.stop) && tr.trackerProblems == null)
doRequest(tr, infoHash, peerID, uploaded,
downloaded, left, STOPPED_EVENT);
}
}
catch(IOException ioe) { /* ignored */ }
}
}
private TrackerInfo doRequest(Tracker tr, String infoHash,
String peerID, long uploaded,
long downloaded, long left, String event)
throws IOException
{
String s = tr.announce
+ "?info_hash=" + infoHash
+ "&peer_id=" + peerID
+ "&port=" + port
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString() + ".i2p"
+ "&uploaded=" + uploaded
+ "&downloaded=" + downloaded
+ "&left=" + left
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
tr.lastRequestTime = System.currentTimeMillis();
File fetched = I2PSnarkUtil.instance().get(s);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = null;
try {
in = new FileInputStream(fetched);
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
Snark.debug("TrackerClient response: " + info, Snark.INFO);
String failure = info.getFailureReason();
if (failure != null)
throw new IOException(failure);
tr.interval = info.getInterval() * 1000;
return info;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
fetched.delete();
}
}
/**
* Very lazy byte[] to URL encoder. Just encodes everything, even
* "normal" chars.
*/
public 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();
}
private class Tracker
{
String announce;
boolean isPrimary;
long interval;
long lastRequestTime;
String trackerProblems;
boolean stop;
boolean started;
int registerFails;
int consecutiveFails;
int seenPeers;
public Tracker(String a, boolean p)
{
announce = a;
isPrimary = p;
interval = INITIAL_SLEEP;
lastRequestTime = 0;
trackerProblems = null;
stop = false;
started = false;
registerFails = 0;
consecutiveFails = 0;
seenPeers = 0;
}
}
}

View File

@ -0,0 +1,136 @@
/* 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.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.klomp.snark.bencode.BDecoder;
import org.klomp.snark.bencode.BEValue;
import org.klomp.snark.bencode.InvalidBEncodingException;
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;
try {
peerID = new PeerID(((BEValue)it.next()).getMap());
} catch (InvalidBEncodingException ibe) {
// don't let one bad entry spoil the whole list
Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
continue;
}
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 + "]";
}
}

View File

@ -0,0 +1,351 @@
/* 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.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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;
}
}

View File

@ -0,0 +1,192 @@
/* 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());
}
}
/** return the untyped value */
public Object getValue() { return value; }
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 + "]";
}
}

View 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
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 if (o instanceof BEValue)
bencode(((BEValue)o).getValue(), 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');
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,916 @@
package org.klomp.snark.web;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import org.klomp.snark.I2PSnarkUtil;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Peer;
import org.klomp.snark.Snark;
import org.klomp.snark.SnarkManager;
import org.klomp.snark.Storage;
import org.klomp.snark.TrackerClient;
/**
*
*/
public class I2PSnarkServlet extends HttpServlet {
private I2PAppContext _context;
private Log _log;
private SnarkManager _manager;
private static long _nonce;
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(I2PSnarkServlet.class);
_nonce = _context.random().nextLong();
_manager = SnarkManager.instance();
String configFile = _context.getProperty(PROP_CONFIG_FILE);
if ( (configFile == null) || (configFile.trim().length() <= 0) )
configFile = "i2psnark.config";
_manager.loadConfig(configFile);
}
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
long stats[] = {0,0,0,0};
String nonce = req.getParameter("nonce");
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
processRequest(req);
String peerParam = req.getParameter("p");
String peerString;
if (peerParam == null) {
peerString = "";
} else {
peerString = "?p=" + peerParam;
}
PrintWriter out = resp.getWriter();
out.write(HEADER_BEGIN);
// we want it to go to the base URI so we don't refresh with some funky action= value
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\n");
out.write(HEADER);
out.write("<table border=\"0\" width=\"100%\">\n");
out.write("<tr><td width=\"20%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
out.write("I2PSnark<br />\n");
out.write("<table border=\"0\" width=\"100%\">\n");
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
int count = 0;
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String baseURL = (String)trackers.get(name);
int e = baseURL.indexOf('=');
if (e < 0)
continue;
baseURL = baseURL.substring(e + 1);
if (count++ % 2 == 0)
out.write("<tr>");
out.write("<td><a href=\"" + baseURL + "\" class=\"snarkRefresh\">" + name + "</a>\n");
}
if (count % 2 == 1)
out.write("<td>&nbsp;\n");
out.write("</table>\n");
out.write("</td><td width=\"80%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
List msgs = _manager.getMessages();
for (int i = msgs.size()-1; i >= 0; i--) {
String msg = (String)msgs.get(i);
out.write(msg + "\n");
}
out.write("</pre></td></tr></table>\n");
List snarks = getSortedSnarks(req);
String uri = req.getRequestURI();
out.write(TABLE_HEADER);
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
if (peerParam != null)
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
else
out.write("(<a href=\"" + req.getRequestURI() + "?p=1" + "\">Show Peers</a>)<br />\n");
}
out.write(TABLE_HEADER2);
out.write("<th align=\"left\" valign=\"top\">");
if (I2PSnarkUtil.instance().connected())
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
else if (snarks.size() > 0)
out.write("<a href=\"" + uri + "?action=StartAll&nonce=" + _nonce +
"\" title=\"Start all torrents and the i2p tunnel\">Start All</a>");
else
out.write("&nbsp;");
out.write("</th></tr></thead>\n");
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
boolean showDebug = "2".equals(peerParam);
boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
displaySnark(out, snark, uri, i, stats, showPeers, showDebug);
}
if (snarks.size() <= 0) {
out.write(TABLE_EMPTY);
} else if (snarks.size() > 1) {
out.write(TABLE_TOTAL);
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
" <th>&nbsp;</th></tr>\n" +
"</tfoot>\n");
}
out.write(TABLE_FOOTER);
writeAddForm(out, req);
if (true) // seeding needs to register the torrent first, so we can't start it automatically (boo, hiss)
writeSeedForm(out, req);
writeConfigForm(out, req);
out.write(FOOTER);
}
/**
* Do what they ask, adding messages to _manager.addMessage as necessary
*/
private void processRequest(HttpServletRequest req) {
String action = req.getParameter("action");
if (action == null) {
// noop
} else if ("Add torrent".equals(action)) {
String newFile = req.getParameter("newFile");
String newURL = req.getParameter("newURL");
File f = null;
if ( (newFile != null) && (newFile.trim().length() > 0) )
f = new File(newFile.trim());
if ( (f != null) && (!f.exists()) ) {
_manager.addMessage("Torrent file " + newFile +" does not exist");
}
if ( (f != null) && (f.exists()) ) {
File local = new File(_manager.getDataDir(), f.getName());
String canonical = null;
try {
canonical = local.getCanonicalPath();
if (local.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + newFile);
else
_manager.addMessage("Torrent already in the queue: " + newFile);
} else {
boolean ok = FileUtil.copy(f.getAbsolutePath(), local.getAbsolutePath(), true);
if (ok) {
_manager.addMessage("Copying torrent to " + local.getAbsolutePath());
_manager.addTorrent(canonical);
} else {
_manager.addMessage("Unable to copy the torrent to " + local.getAbsolutePath() + " from " + f.getAbsolutePath());
}
}
} catch (IOException ioe) {
_log.warn("hrm: " + local, ioe);
}
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
_manager.addMessage("Fetching " + newURL);
I2PThread fetch = new I2PThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
fetch.start();
} else {
// no file or URL specified
}
} else if ("Stop".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, false);
break;
}
}
}
}
} else if ("Start".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
snark.startTorrent();
_manager.addMessage("Starting up torrent " + name);
break;
}
}
}
}
} else if ("Remove".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
// should we delete the torrent file?
// yeah, need to, otherwise it'll get autoadded again (at the moment
File f = new File(name);
f.delete();
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
break;
}
}
}
}
} else if ("Delete".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
File f = new File(name);
f.delete();
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
List files = snark.meta.getFiles();
String dataFile = snark.meta.getName();
f = new File(_manager.getDataDir(), dataFile);
if (files == null) { // single file torrent
if (f.delete())
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
break;
}
for (int i = 0; i < files.size(); i++) { // pass 1 delete files
// multifile torrents have the getFiles() return lists of lists of filenames, but
// each of those lists just contain a single file afaict...
File df = Storage.getFileFromNames(f, (List) files.get(i));
if (df.delete())
_manager.addMessage("Data file deleted: " + df.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + df.getAbsolutePath());
}
for (int i = files.size() - 1; i >= 0; i--) { // pass 2 delete dirs - not foolproof,
// we could sort and do a strict bottom-up
File df = Storage.getFileFromNames(f, (List) files.get(i));
df = df.getParentFile();
if (df == null || !df.exists())
continue;
if(df.delete())
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
}
break;
}
}
}
}
} else if ("Save configuration".equals(action)) {
String dataDir = req.getParameter("dataDir");
boolean autoStart = req.getParameter("autoStart") != null;
String seedPct = req.getParameter("seedPct");
String eepHost = req.getParameter("eepHost");
String eepPort = req.getParameter("eepPort");
String i2cpHost = req.getParameter("i2cpHost");
String i2cpPort = req.getParameter("i2cpPort");
String i2cpOpts = req.getParameter("i2cpOpts");
String upLimit = req.getParameter("upLimit");
String upBW = req.getParameter("upBW");
boolean useOpenTrackers = req.getParameter("useOpenTrackers") != null;
String openTrackers = req.getParameter("openTrackers");
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit, upBW, useOpenTrackers, openTrackers);
} else if ("Create torrent".equals(action)) {
String baseData = req.getParameter("baseFile");
if (baseData != null) {
File baseFile = new File(_manager.getDataDir(), baseData);
String announceURL = req.getParameter("announceURL");
String announceURLOther = req.getParameter("announceURLOther");
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
announceURL = announceURLOther;
if (announceURL == null || announceURL.length() <= 0)
_manager.addMessage("Error creating torrent - you must select a tracker");
else if (baseFile.exists()) {
try {
Storage s = new Storage(baseFile, announceURL, null);
s.create();
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
MetaInfo info = s.getMetaInfo();
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
if (torrentFile.exists())
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
_manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck
// DirMonitor could grab this first, maybe hold _snarks lock?
FileOutputStream out = new FileOutputStream(torrentFile);
out.write(info.getTorrentData());
out.close();
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
// now fire it up, but don't automatically seed it
_manager.addTorrent(torrentFile.getCanonicalPath(), true);
_manager.addMessage("Many I2P trackers require you to register new torrents before seeding - please do so before starting " + baseFile.getName());
} catch (IOException ioe) {
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
}
} else {
_manager.addMessage("Cannot create a torrent for the nonexistent data: " + baseFile.getAbsolutePath());
}
}
} else if ("StopAll".equals(action)) {
_manager.addMessage("Stopping all torrents and closing the I2P tunnel");
List snarks = getSortedSnarks(req);
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
if (!snark.stopped)
_manager.stopTorrent(snark.torrent, false);
}
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
_manager.addMessage("I2P tunnel closed");
}
} else if ("StartAll".equals(action)) {
_manager.addMessage("Opening the I2P tunnel and starting all torrents");
List snarks = getSortedSnarks(req);
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
if (snark.stopped)
snark.startTorrent();
}
}
}
private List getSortedSnarks(HttpServletRequest req) {
Set files = _manager.listTorrentFiles();
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
ArrayList rv = new ArrayList(fileNames.size());
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if (snark != null)
rv.add(snark);
}
return rv;
}
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
private static final int MAX_DISPLAYED_ERROR_LENGTH = 40;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers, boolean showDebug) throws IOException {
String filename = snark.torrent;
File f = new File(filename);
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
int i = filename.lastIndexOf(".torrent");
if (i > 0)
filename = filename.substring(0, i);
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
long total = snark.meta.getTotalLength();
// Early typecast, avoid possibly overflowing a temp integer
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
if (remaining > total)
remaining = total;
long downBps = 0;
long upBps = 0;
if (snark.coordinator != null) {
downBps = snark.coordinator.getDownloadRate();
upBps = snark.coordinator.getUploadRate();
}
long remainingSeconds;
if (downBps > 0)
remainingSeconds = remaining / downBps;
else
remainingSeconds = -1;
boolean isRunning = !snark.stopped;
long uploaded = 0;
if (snark.coordinator != null) {
uploaded = snark.coordinator.getUploaded();
stats[0] += snark.coordinator.getDownloaded();
}
stats[1] += uploaded;
if (isRunning) {
stats[2] += downBps;
stats[3] += upBps;
}
boolean isValid = snark.meta != null;
boolean singleFile = snark.meta.getFiles() == null;
String err = null;
int curPeers = 0;
int knownPeers = 0;
if (snark.coordinator != null) {
err = snark.coordinator.trackerProblems;
curPeers = snark.coordinator.getPeerCount();
knownPeers = snark.coordinator.trackerSeenPeers;
}
String statusString = "Unknown";
if (err != null) {
if (isRunning && curPeers > 0 && !showPeers)
statusString = "<a title=\"" + err + "\">TrackerErr</a> (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning)
statusString = "<a title=\"" + err + "\">TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
else {
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
statusString = "TrackerErr<br />(" + err + ")";
}
} else if (remaining <= 0) {
if (isRunning && curPeers > 0 && !showPeers)
statusString = "Seeding (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning)
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Complete";
} else {
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
statusString = "OK (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning && curPeers > 0 && downBps > 0)
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning && curPeers > 0 && !showPeers)
statusString = "Stalled (" +
curPeers + "/" + knownPeers +
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
else if (isRunning && curPeers > 0)
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
else if (isRunning)
statusString = "No Peers (0/" + knownPeers + ")";
else
statusString = "Stopped";
}
String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
out.write("<tr class=\"" + rowClass + "\">");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
out.write(statusString + "</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
if (remaining == 0)
out.write("<a href=\"file:///" + _manager.getDataDir().getAbsolutePath() + File.separatorChar + snark.meta.getName()
+ "\" title=\"Download the completed file\">");
out.write(filename);
if (remaining == 0)
out.write("</a>");
// temporarily hardcoded for postman and anonymity, requires bytemonsoon patch for lookup by info_hash
String announce = snark.meta.getAnnounce();
if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr")) {
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String baseURL = (String)trackers.get(name);
if (!baseURL.startsWith(announce))
continue;
int e = baseURL.indexOf('=');
if (e < 0)
continue;
baseURL = baseURL.substring(e + 1);
out.write("&nbsp;&nbsp;&nbsp;(<a href=\"" + baseURL + "details.php?dllist=1&filelist=1&info_hash=");
out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
out.write("\" title=\"" + name + " Tracker\">Details</a>)");
break;
}
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
if(isRunning && remainingSeconds > 0)
out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
if (remaining > 0)
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
else
out.write(formatSize(total)); // 3GB
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentUploaded " + rowClass
+ "\">" + formatSize(uploaded) + "</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
if(isRunning && remaining > 0)
out.write(formatSize(downBps) + "ps");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
if(isRunning)
out.write(formatSize(upBps) + "ps");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
if (showPeers)
parameters = parameters + "&p=1";
if (isRunning) {
out.write("<a href=\"" + uri + "?action=Stop" + parameters
+ "\" title=\"Stop the torrent\">Stop</a>");
} else {
if (isValid)
out.write("<a href=\"" + uri + "?action=Start" + parameters
+ "\" title=\"Start the torrent\">Start</a> ");
out.write("<a href=\"" + uri + "?action=Remove" + parameters
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
out.write("<a href=\"" + uri + "?action=Delete" + parameters
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
}
out.write("</td>\n</tr>\n");
if(showPeers && isRunning && curPeers > 0) {
List peers = snark.coordinator.peerList();
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer peer = (Peer)it.next();
if (!peer.isConnected())
continue;
out.write("<tr class=\"" + rowClass + "\">");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
String ch = peer.toString().substring(0, 4);
String client;
if ("AwMD".equals(ch))
client = "I2PSnark";
else if ("BFJT".equals(ch))
client = "I2PRufus";
else if ("TTMt".equals(ch))
client = "I2P-BT";
else if ("LUFa".equals(ch))
client = "Azureus";
else
client = "Unknown";
out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;<tt>" + peer.toString().substring(5, 9) + "</tt>");
if (showDebug)
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
if (pct == 100.0)
out.write("<font size=-1>Seed</font>");
else {
String ps = String.valueOf(pct);
if (ps.length() > 5)
ps = ps.substring(0, 5);
out.write("<font size=-1>" + ps + "%</font>");
}
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
if (remaining > 0) {
if (peer.isInteresting() && !peer.isChoked()) {
out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getDownloadRate()) + "ps</font></font>");
} else {
out.write("<font color=#a00000><font size=-1><a title=\"");
if (!peer.isInteresting())
out.write("Uninteresting\">");
else
out.write("Choked\">");
out.write(formatSize(peer.getDownloadRate()) + "ps</a></font></font>");
}
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
if (pct != 100.0) {
if (peer.isInterested() && !peer.isChoking()) {
out.write("<font color=#008000>");
out.write("<font size=-1>" + formatSize(peer.getUploadRate()) + "ps</font></font>");
} else {
out.write("<font color=#a00000><font size=-1><a title=\"");
if (!peer.isInterested())
out.write("Uninterested\">");
else
out.write("Choking\">");
out.write(formatSize(peer.getUploadRate()) + "ps</a></font></font>");
}
}
out.write("</td>\n\t");
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
out.write("</td></tr>\n\t");
if (showDebug)
out.write("<tr><td colspan=\"8\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">" + peer.getSocket() + "</td></tr>");
}
}
}
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String newURL = req.getParameter("newURL");
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
String newFile = req.getParameter("newFile");
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
out.write("<span class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("<span class=\"snarkConfigTitle\">Add Torrent:</span><br />\n");
out.write("From URL&nbsp;: <input type=\"text\" name=\"newURL\" size=\"80\" value=\"" + newURL + "\" /> \n");
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
out.write("<span class=\"snarkAddInfo\">Alternately, you can copy .torrent files to " + _manager.getDataDir().getAbsolutePath() + "<br />\n");
out.write("Removing that .torrent file will cause the torrent to stop.<br /></span>\n");
out.write("</form>\n</span>\n");
}
private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String baseFile = req.getParameter("baseFile");
if (baseFile == null)
baseFile = "";
out.write("<span class=\"snarkNewTorrent\"><hr />\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("<span class=\"snarkConfigTitle\">Create Torrent:</span><br />\n");
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
out.write("Data to seed: " + _manager.getDataDir().getAbsolutePath() + File.separatorChar
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
+ "\" title=\"File to seed (must be within the specified path)\" /><br />\n");
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String announceURL = (String)trackers.get(name);
int e = announceURL.indexOf('=');
if (e > 0)
announceURL = announceURL.substring(0, e);
out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
}
out.write("</select>\n");
out.write("or <input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
"title=\"Custom tracker URL\" /> ");
out.write("<input type=\"submit\" value=\"Create torrent\" name=\"action\" />\n");
out.write("</form>\n</span>\n");
}
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String dataDir = _manager.getDataDir().getAbsolutePath();
boolean autoStart = _manager.shouldAutoStart();
boolean useOpenTrackers = _manager.shouldUseOpenTrackers();
String openTrackers = _manager.getOpenTrackerString();
//int seedPct = 0;
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<span class=\"snarkConfig\"><hr />\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("<span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /> <i>(Edit i2psnark.config and restart to change)</i><br />\n");
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
+ (autoStart ? "checked " : "")
+ "title=\"If true, automatically start torrents that are added\" />");
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
//out.write("<br />\n");
/*
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
if (seedPct <= 0)
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
else
out.write("<option value=\"0\">Unlimited</option>\n\t");
if (seedPct == 100)
out.write("<option value=\"100\" selected=\"true\">100%</option>\n\t");
else
out.write("<option value=\"100\">100%</option>\n\t");
if (seedPct == 150)
out.write("<option value=\"150\" selected=\"true\">150%</option>\n\t");
else
out.write("<option value=\"150\">150%</option>\n\t");
out.write("</select><br />\n");
*/
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
out.write("Up bandwidth limit: <input type=\"text\" name=\"upBW\" value=\""
+ I2PSnarkUtil.instance().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
out.write("Use open trackers also: <input type=\"checkbox\" name=\"useOpenTrackers\" value=\"true\" "
+ (useOpenTrackers ? "checked " : "")
+ "title=\"If true, uses open trackers in addition\" /> ");
out.write("Announce URLs: <input type=\"text\" name=\"openTrackers\" value=\""
+ openTrackers + "\" size=\"50\" /><br />\n");
//out.write("<hr />\n");
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
StringBuffer opts = new StringBuffer(64);
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = (String)options.get(key);
opts.append(key).append('=').append(val).append(' ');
}
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
+ opts.toString() + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
out.write("</span>\n");
out.write("</form>\n");
}
// rounding makes us look faster :)
private String formatSize(long bytes) {
if (bytes < 5*1024)
return bytes + "B";
else if (bytes < 5*1024*1024)
return ((bytes + 512)/1024) + "KB";
else if (bytes < 5*1024*1024*1024l)
return ((bytes + 512*1024)/(1024*1024)) + "MB";
else
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
}
private static final String HEADER_BEGIN = "<html>\n" +
"<head>\n" +
"<title>I2PSnark - anonymous bittorrent</title>\n";
private static final String HEADER = "<style>\n" +
"body {\n" +
" background-color: #C7CFB4;\n" +
"}\n" +
".snarkTitle {\n" +
" text-align: left;\n" +
" float: left;\n" +
" margin: 0px 0px 5px 5px;\n" +
" display: inline;\n" +
" font-size: 16pt;\n" +
" font-weight: bold;\n" +
"}\n" +
".snarkRefresh {\n" +
" font-size: 10pt;\n" +
"}\n" +
".snarkMessages {\n" +
" border: none;\n" +
" background-color: #CECFC6;\n" +
" font-family: monospace;\n" +
" font-size: 10pt;\n" +
" font-weight: 100;\n" +
" width: 100%;\n" +
" text-align: left;\n" +
" margin: 0px 0px 0px 0px;\n" +
" border: 0px;\n" +
" padding: 5px;\n" +
" border-width: 0px;\n" +
" border-spacing: 0px;\n" +
"}\n" +
"table {\n" +
" margin: 0px 0px 0px 0px;\n" +
" border: 0px;\n" +
" padding: 0px;\n" +
" border-width: 0px;\n" +
" border-spacing: 0px;\n" +
"}\n" +
"th {\n" +
" background-color: #C7D5D5;\n" +
" padding: 0px 7px 0px 3px;\n" +
"}\n" +
"td {\n" +
" padding: 0px 7px 0px 3px;\n" +
"}\n" +
".snarkTorrentEven {\n" +
" background-color: #E7E7E7;\n" +
"}\n" +
".snarkTorrentOdd {\n" +
" background-color: #DDDDCC;\n" +
"}\n" +
".snarkNewTorrent {\n" +
" font-size: 10pt;\n" +
"}\n" +
".snarkAddInfo {\n" +
" font-size: 10pt;\n" +
"}\n" +
".snarkConfigTitle {\n" +
" font-size: 12pt;\n" +
" font-weight: bold;\n" +
"}\n" +
".snarkConfig {\n" +
" font-size: 10pt;\n" +
"}\n" +
"</style>\n" +
"</head>\n" +
"<body>\n";
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
"<thead>\n" +
"<tr><th align=\"left\" valign=\"top\">Status \n";
private static final String TABLE_HEADER2 = "</th>\n" +
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
" <th align=\"right\" valign=\"top\">ETA</th>\n" +
" <th align=\"right\" valign=\"top\">Downloaded</th>\n" +
" <th align=\"right\" valign=\"top\">Uploaded</th>\n" +
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
private static final String TABLE_TOTAL = "<tfoot>\n" +
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
" <th>&nbsp;</th>\n" +
" <th>&nbsp;</th>\n";
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
"<td class=\"snarkTorrentEven\" align=\"left\"" +
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
private static final String TABLE_FOOTER = "</table>\n";
private static final String FOOTER = "</body></html>";
}
class FetchAndAdd implements Runnable {
private SnarkManager _manager;
private String _url;
public FetchAndAdd(SnarkManager mgr, String url) {
_manager = mgr;
_url = url;
}
public void run() {
_url = _url.trim();
// 3 retries
File file = I2PSnarkUtil.instance().get(_url, false, 3);
try {
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
_manager.addMessage("Torrent fetched from " + _url);
FileInputStream in = null;
try {
in = new FileInputStream(file);
MetaInfo info = new MetaInfo(in);
String name = info.getName();
name = name.replace('/', '_');
name = name.replace('\\', '_');
name = name.replace('&', '+');
name = name.replace('\'', '_');
name = name.replace('"', '_');
name = name.replace('`', '_');
name = name + ".torrent";
File torrentFile = new File(_manager.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + name);
else
_manager.addMessage("Torrent already in the queue: " + name);
} else {
FileUtil.copy(file.getAbsolutePath(), canonical, true);
_manager.addTorrent(canonical);
}
} catch (IOException ioe) {
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
} finally {
try { in.close(); } catch (IOException ioe) {}
}
} else {
_manager.addMessage("Torrent was not retrieved from " + _url);
}
} finally {
if (file != null) file.delete();
}
}
}

View File

@ -0,0 +1,48 @@
package org.klomp.snark.web;
import java.io.File;
import net.i2p.util.FileUtil;
import org.mortbay.jetty.Server;
public class RunStandalone {
private Server _server;
static {
System.setProperty("org.mortbay.http.Version.paranoid", "true");
System.setProperty("org.mortbay.xml.XmlParser.NotValidating", "true");
}
private RunStandalone(String args[]) {}
public static void main(String args[]) {
RunStandalone runner = new RunStandalone(args);
runner.start();
}
public void start() {
File workDir = new File("work");
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
if (!workDirRemoved)
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
boolean workDirCreated = workDir.mkdirs();
if (!workDirCreated)
System.err.println("ERROR: Unable to create Jetty temporary work directory");
try {
_server = new Server("jetty-i2psnark.xml");
_server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
try {
_server.stop();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
<!-- =============================================================== -->
<!-- Configure the Jetty Server -->
<!-- =============================================================== -->
<Configure class="org.mortbay.jetty.Server">
<!-- =============================================================== -->
<!-- Configure the Request Listeners -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add and configure a HTTP listener to port 8080 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SocketListener">
<Arg>
<New class="org.mortbay.util.InetAddrPort">
<Set name="host">127.0.0.1</Set>
<Set name="port">8002</Set>
</New>
</Arg>
<Set name="MinThreads">3</Set>
<Set name="MaxThreads">10</Set>
<Set name="MaxIdleTimeMs">30000</Set>
<Set name="LowResourcePersistTimeMs">1000</Set>
<Set name="ConfidentialPort">8443</Set>
<Set name="IntegralPort">8443</Set>
<Set name="PoolName">main</Set>
</New>
</Arg>
</Call>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a HTTPS SSL listener on port 8443 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- UNCOMMENT TO ACTIVATE
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.SunJsseListener">
<Set name="Port">8443</Set>
<Set name="PoolName">main</Set>
<Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set>
<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
<Set name="NonPersistentUserAgent">MSIE 5</Set>
</New>
</Arg>
</Call>
-->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a AJP13 listener on port 8009 -->
<!-- This protocol can be used with mod_jk in apache, IIS etc. -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!--
<Call name="addListener">
<Arg>
<New class="org.mortbay.http.ajp.AJP13Listener">
<Set name="PoolName">ajp</Set>
<Set name="Port">8009</Set>
<Set name="MinThreads">3</Set>
<Set name="MaxThreads">20</Set>
<Set name="MaxIdleTimeMs">0</Set>
<Set name="confidentialPort">443</Set>
</New>
</Arg>
</Call>
-->
<!-- =============================================================== -->
<!-- Configure the Contexts -->
<!-- =============================================================== -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- Add a all web application within the webapps directory. -->
<!-- + No virtual host specified -->
<!-- + Look in the webapps directory relative to jetty.home or . -->
<!-- + Use the default webdefault.xml in jetty's install -->
<!-- + Upack the war file -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<Set name="rootWebApp">i2psnark</Set>
<Call name="addWebApplication">
<Arg>/</Arg>
<Arg>webapps/i2psnark.war</Arg>
</Call>
<!-- =============================================================== -->
<!-- Configure the Request Log -->
<!-- =============================================================== -->
<Set name="RequestLog">
<New class="org.mortbay.http.NCSARequestLog">
<Arg>./logs/yyyy_mm_dd.i2psnark-request.log</Arg>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
<Set name="extended">false</Set>
<Set name="buffered">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>
</Set>
<!-- =============================================================== -->
<!-- Configure the Other Server Options -->
<!-- =============================================================== -->
<Set name="requestsPerGC">2000</Set>
<Set name="statsOn">false</Set>
</Configure>

View File

@ -0,0 +1,6 @@
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
to run it with the web UI, run "java -jar launch-i2psnark.jar". I2PSnark is
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
(http://www.i2p.net/) within a webserver (such as the bundled Jetty from
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
with the folks at http://forum.i2p.net/

11
apps/i2psnark/readme.txt Normal file
View 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

View 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>

25
apps/i2psnark/web.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<servlet>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<servlet-class>org.klomp.snark.web.I2PSnarkServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- precompiled servlets -->
<servlet-mapping>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>

View File

@ -8,11 +8,15 @@ package net.i2p.i2ptunnel;
*
*/
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.zip.GZIPInputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.util.ByteCache;
@ -66,7 +70,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (_headerWritten) {
out.write(buf, off, len);
_dataWritten += len;
out.flush();
//out.flush();
return;
}
@ -82,7 +86,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
// write out the remaining
out.write(buf, off+i+1, len-i-1);
_dataWritten += len-i-1;
out.flush();
//out.flush();
}
return;
}
@ -138,31 +142,35 @@ class HTTPResponseOutputStream extends FilterOutputStream {
if (lastEnd == -1) {
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
responseLine = filterResponseLine(responseLine);
responseLine = (responseLine.trim() + "\n");
responseLine = (responseLine.trim() + "\r\n");
out.write(responseLine.getBytes());
} else {
for (int j = lastEnd+1; j < i; j++) {
if (_headerBuffer.getData()[j] == ':') {
int keyLen = j-(lastEnd+1);
int valLen = i-(j+2);
if ( (keyLen <= 0) || (valLen <= 0) )
int valLen = i-(j+1);
if ( (keyLen <= 0) || (valLen < 0) )
throw new IOException("Invalid header @ " + j);
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
String val = null;
if (valLen == 0)
val = "";
else
val = new String(_headerBuffer.getData(), j+2, valLen).trim();
if (_log.shouldLog(Log.INFO))
_log.info("Response header [" + key + "] = [" + val + "]");
if ("Connection".equalsIgnoreCase(key)) {
out.write("Connection: close\n".getBytes());
out.write("Connection: close\r\n".getBytes());
connectionSent = true;
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
out.write("Proxy-Connection: close\n".getBytes());
out.write("Proxy-Connection: close\r\n".getBytes());
proxyConnectionSent = true;
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
_gzip = true;
} else {
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
}
break;
}
@ -173,9 +181,9 @@ class HTTPResponseOutputStream extends FilterOutputStream {
}
if (!connectionSent)
out.write("Connection: close\n".getBytes());
out.write("Connection: close\r\n".getBytes());
if (!proxyConnectionSent)
out.write("Proxy-Connection: close\n".getBytes());
out.write("Proxy-Connection: close\r\n".getBytes());
finishHeaders();
@ -196,7 +204,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
protected boolean shouldCompress() { return _gzip; }
protected void finishHeaders() throws IOException {
out.write("\n".getBytes()); // end of the headers
out.write("\r\n".getBytes()); // end of the headers
}
public void close() throws IOException {
out.close();
}
protected void beginProcessing() throws IOException {
@ -263,10 +275,34 @@ class HTTPResponseOutputStream extends FilterOutputStream {
public InternalGZIPInputStream(InputStream in) throws IOException {
super(in);
}
public long getTotalRead() { return super.inf.getTotalIn(); }
public long getTotalExpanded() { return super.inf.getTotalOut(); }
public long getRemaining() { return super.inf.getRemaining(); }
public boolean getFinished() { return super.inf.finished(); }
public long getTotalRead() {
try {
return super.inf.getTotalIn();
} catch (Exception e) {
return 0;
}
}
public long getTotalExpanded() {
try {
return super.inf.getTotalOut();
} catch (Exception e) {
return 0;
}
}
public long getRemaining() {
try {
return super.inf.getRemaining();
} catch (Exception e) {
return 0;
}
}
public boolean getFinished() {
try {
return super.inf.finished();
} catch (Exception e) {
return true;
}
}
public String toString() {
return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished();
}
@ -321,6 +357,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
"Content-length: 32\n" +
"\n" +
"hi ho, this is the body";
String blankval = "HTTP/1.0 200 OK\n" +
"A:\n" +
"\n";
/* */
test("Simple", simple, true);
test("Filtered", filtered, true);
@ -328,6 +368,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
test("Minimal", minimal, true);
test("Windows", winmin, true);
test("Large", large, true);
test("Blank whitespace", blankval, true);
test("Invalid (short headers)", invalid1, true);
test("Invalid (no headers)", invalid2, true);
test("Invalid (windows with short headers)", invalid3, true);

View File

@ -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)) {
@ -291,6 +293,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("genkeys <privkeyfile> [<pubkeyfile>]");
l.log("gentextkeys");
l.log("client <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("ircclient <port> <pubkey>[,<pubkey,...]|file:<pubkeyfile> [<sharedClient>]");
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
l.log("lookup <name>");
l.log("quit");
@ -596,6 +599,60 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
}
/**
* Run an IRC client on the given port number
*
* Sets the event "ircclientTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error).
* Also sets "ircclientStatus" = "ok" or "error" after the client tunnel has started.
* parameter sharedClient is a String, either "true" or "false"
*
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient]}
* @param l logger to receive events and output
*/
public void runIrcClient(String args[], Logging l) {
if (args.length >= 2 && args.length <= 3) {
int port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
l.log("invalid port");
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
notifyEvent("ircclientTaskId", new Integer(-1));
return;
}
boolean isShared = true;
if (args.length > 2) {
if ("true".equalsIgnoreCase(args[2].trim())) {
isShared = true;
} 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
isShared = true;
}
}
I2PTunnelTask task;
ownDest = !isShared;
try {
task = new I2PTunnelIRCClient(port, args[1],l, ownDest, (EventDispatcher) this, this);
addtask(task);
notifyEvent("ircclientTaskId", new Integer(task.getId()));
} catch (IllegalArgumentException iae) {
_log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ port + "]", iae);
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
notifyEvent("ircclientTaskId", new Integer(-1));
}
} else {
l.log("ircclient <port> [<sharedClient>]");
l.log(" creates a client that filter IRC protocol.");
l.log(" <sharedClient> (optional) indicates if this client shares tunnels with other clients (true of false)");
notifyEvent("ircclientTaskId", new Integer(-1));
}
}
/**
* Run an SOCKS tunnel on the given port number
*

View File

@ -16,6 +16,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.client.streaming.I2PSocket;
@ -25,12 +26,13 @@ import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Destination;
import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.SimpleTimer;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
private static final Log _log = new Log(I2PTunnelClientBase.class);
protected I2PAppContext _context;
protected Logging l;
static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
@ -60,7 +62,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private Object conLock = new Object();
/** List of Socket for those accept()ed but not yet started up */
private List _waitingSockets;
private List _waitingSockets = new ArrayList();
/** How many connections will we allow to be in the process of being built at once? */
private int _numConnectionBuilders;
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
@ -101,6 +103,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
this.l = l;
this.handlerName = handlerName + _clientId;
_context = tunnel.getContext();
_context.statManager().createRateStat("i2ptunnel.client.closeBacklog", "How many pending sockets remain when we close one due to backlog?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.closeNoBacklog", "How many pending sockets remain when it was removed prior to backlog timeout?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.manageTime", "How long it takes to accept a socket and fire it into an i2ptunnel runner (or queue it for the pool)?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
_context.statManager().createRateStat("i2ptunnel.client.buildRunTime", "How long it takes to run a queued socket into an i2ptunnel runner?", "I2PTunnel", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
// no need to load the netDb with leaseSets for destinations that will never
// be looked up
tunnel.getClientOptions().setProperty("i2cp.dontPublishLeaseSet", "true");
@ -362,7 +370,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
while (true) {
Socket s = ss.accept();
long before = System.currentTimeMillis();
manageConnection(s);
long total = System.currentTimeMillis() - before;
_context.statManager().addRateData("i2ptunnel.client.manageTime", total, total);
}
} catch (IOException ex) {
_log.error("Error listening for connections on " + localPort, ex);
@ -422,14 +433,20 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private Socket _s;
public CloseEvent(Socket s) { _s = s; }
public void timeReached() {
int remaining = 0;
boolean stillWaiting = false;
synchronized (_waitingSockets) {
stillWaiting = _waitingSockets.remove(_s);
remaining = _waitingSockets.size();
}
if (stillWaiting) {
try { _s.close(); } catch (IOException ioe) {}
if (_log.shouldLog(Log.INFO))
if (_log.shouldLog(Log.INFO)) {
_context.statManager().addRateData("i2ptunnel.client.closeBacklog", remaining, 0);
_log.info("Closed a waiting socket because of backlog");
}
} else {
_context.statManager().addRateData("i2ptunnel.client.closeNoBacklog", remaining, 0);
}
}
}
@ -496,8 +513,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
} catch (InterruptedException ie) {}
if (s != null)
if (s != null) {
long before = System.currentTimeMillis();
clientConnectionRun(s);
long total = System.currentTimeMillis() - before;
_context.statManager().addRateData("i2ptunnel.client.buildRunTime", total, 0);
}
s = null;
}
}

View File

@ -32,7 +32,7 @@ public class I2PTunnelGUI extends Frame implements ActionListener, Logging {
log.setEditable(false);
log("enter 'help' for help.");
pack();
show();
setVisible(true);
}
public void log(String s) {

View File

@ -11,10 +11,10 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@ -112,6 +112,35 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"or naming one of them differently.<P/>")
.getBytes();
private final static byte[] ERR_BAD_PROTOCOL =
("HTTP/1.1 403 Bad Protocol\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: NON-HTTP PROTOCOL</H1>"+
"The request uses a bad protocol. "+
"The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed.<BR>")
.getBytes();
private final static byte[] ERR_LOCALHOST =
("HTTP/1.1 403 Access Denied\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
.getBytes();
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
private final static byte[] ERR_MAXPOST =
("HTTP/1.1 503 Bad POST\r\n"+
"Content-Type: text/html; charset=iso-8859-1\r\n"+
"Cache-control: no-cache\r\n"+
"\r\n"+
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
.getBytes();
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
@ -224,6 +253,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Method is null for [" + line + "]");
line = line.trim();
int pos = line.indexOf(" ");
if (pos == -1) break;
method = line.substring(0, pos);
@ -383,9 +413,31 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
usingWWWProxy = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
} else if (host.toLowerCase().startsWith("localhost:")) {
if (out != null) {
out.write(ERR_LOCALHOST);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
} else {
request = request.substring(pos + 1);
pos = request.indexOf("/");
if (pos < 0) {
l.log("Invalid request url [" + request + "]");
if (out != null) {
out.write(ERR_REQUEST_DENIED);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
destination = request.substring(0, pos);
line = method + " " + request.substring(pos);
}
@ -444,28 +496,49 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
boolean gzip = DEFAULT_GZIP;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
if (gzip)
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
if (gzip) {
// according to rfc2616 s14.3, this *should* force identity, even if
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
// seems to work.
newRequest.append("Accept-Encoding: \r\n");
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
}
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
newRequest.append(line).append("\r\n"); // HTTP spec
newRequest.append(line.trim()).append("\r\n"); // HTTP spec
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
int postbytes = 0;
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
newRequest.append((char) i);
if (++postbytes > MAX_POSTBYTES) {
if (out != null) {
out.write(ERR_MAXPOST);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
out.flush();
}
s.close();
return;
}
}
}
if (method == null || destination == null) {
l.log("No HTTP method found in the request.");
if (out != null) {
out.write(ERR_REQUEST_DENIED);
if ("http://".equalsIgnoreCase(protocol))
out.write(ERR_REQUEST_DENIED);
else
out.write(ERR_BAD_PROTOCOL);
out.write("<p /><i>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
out.write("</i></body></html>\n".getBytes());
@ -485,27 +558,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
String str;
byte[] header;
boolean showAddrHelper = false;
if (usingWWWProxy)
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
else if(ahelper != 0)
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
else
str = FileUtil.readTextFile("docs/dnf-header.ht", 100, true);
else {
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
showAddrHelper = true;
}
if (str != null)
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper);
s.close();
return;
}
String remoteID;
Properties opts = new Properties();
opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
//opts.setProperty("i2p.streaming.inactivityTimeout", ""+120*1000);
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
// dont want to hard link to here
opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
//opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
I2PSocket i2ps = createI2PSocket(dest, getDefaultOptions(opts));
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
@ -525,6 +601,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
IOException ex = new IOException("OOM (in POST?)");
_log.info("getPrefix(requestId) + Error trying to connect", ex);
l.log(ex.getMessage());
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
closeSocket(s);
}
}
@ -563,19 +645,52 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
}
private static String jumpServers[] = {
"http://i2host.i2p/cgi-bin/i2hostjump?",
// "http://orion.i2p/jump/",
"http://stats.i2p/cgi-bin/jump.cgi?a=",
// "http://trevorreznik.i2p/cgi-bin/jump.php?hostname=",
"http://i2jump.i2p/"
};
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
boolean usingWWWProxy, String wwwProxy) throws IOException {
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
if (out != null) {
out.write(errMessage);
if (targetRequest != null) {
int protopos = targetRequest.indexOf(" ");
String uri = targetRequest.substring(0, protopos);
String uri = null;
if (protopos >= 0)
uri = targetRequest.substring(0, protopos);
else
uri = targetRequest;
out.write("<a href=\"http://".getBytes());
out.write(uri.getBytes());
out.write("\">http://".getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
if (showAddrHelper) {
out.write("<br><br>Click a link below to look for an address helper by using a \"jump\" service:<br>".getBytes());
for (int i = 0; i < jumpServers.length; i++) {
// Skip jump servers we don't know
String jumphost = jumpServers[i].substring(7); // "http://"
jumphost = jumphost.substring(0, jumphost.indexOf('/'));
try {
Destination dest = I2PTunnel.destFromName(jumphost);
if (dest == null) continue;
} catch (DataFormatException dfe) {
continue;
}
out.write("<br><a href=\"".getBytes());
out.write(jumpServers[i].getBytes());
out.write(uri.getBytes());
out.write("\">".getBytes());
out.write(jumpServers[i].getBytes());
out.write(uri.getBytes());
out.write("</a>".getBytes());
}
}
}
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
out.write(new Date().toString().getBytes());
@ -601,7 +716,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
header = str.getBytes();
else
header = ERR_DESTINATION_UNKNOWN;
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
} catch (IOException ioe) {
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
}

View File

@ -3,17 +3,14 @@
*/
package net.i2p.i2ptunnel;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
/**

View File

@ -3,7 +3,10 @@
*/
package net.i2p.i2ptunnel;
import java.io.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
@ -11,9 +14,6 @@ import java.util.Iterator;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataHelper;
import net.i2p.util.EventDispatcher;
@ -37,18 +37,21 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
super(host, port, privData, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
_spoofHost = spoofHost;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}
/**
@ -74,7 +77,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
// we keep the enc sent by the browser before clobbering it, since it may have
// been x-i2p-gzip
String enc = headers.getProperty("Accept-encoding");
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
String altEnc = headers.getProperty("X-Accept-encoding");
// according to rfc2616 s14.3, this *should* force identity, even if
// "identity;q=1, *;q=0" didn't.
headers.setProperty("Accept-encoding", "");
String modifiedHeader = formatHeaders(headers, command);
//String modifiedHeader = getModifiedHeader(socket);
@ -96,9 +103,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
allowGZIP = false;
}
if (_log.shouldLog(Log.INFO))
_log.info("HTTP server encoding header: " + enc);
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
_log.info("HTTP server encoding header: " + enc + "/" + altEnc);
boolean useGZIP = ( (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) );
if ( (!useGZIP) && (altEnc != null) && (altEnc.indexOf("x-i2p-gzip") >= 0) )
useGZIP = true;
if (allowGZIP && useGZIP) {
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), Thread.currentThread().getName()+".hc");
req.start();
} else {
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
@ -145,7 +156,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
_log.info("request headers: " + _headers);
serverout.write(_headers.getBytes());
browserin = _browser.getInputStream();
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), Thread.currentThread().getName() + "hcs");
sender.start();
browserout = _browser.getOutputStream();
@ -192,13 +203,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
}
if (_log.shouldLog(Log.INFO))
_log.info(_name + ": Done sending: " + total);
_out.flush();
//_out.flush();
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error sending", ioe);
} finally {
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
if (_out != null) try { _out.close(); } catch (IOException ioe) {}
if (_in != null) try { _in.close(); } catch (IOException ioe) {}
}
}
}
@ -212,37 +223,63 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
protected void finishHeaders() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Including x-i2p-gzip as the content encoding in the response");
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
out.write("Content-encoding: x-i2p-gzip\r\n".getBytes());
super.finishHeaders();
}
protected void beginProcessing() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("Beginning compression processing");
out.flush();
//out.flush();
_gzipOut = new InternalGZIPOutputStream(out);
out = _gzipOut;
}
public long getTotalRead() { return _gzipOut.getTotalRead(); }
public long getTotalCompressed() { return _gzipOut.getTotalCompressed(); }
public long getTotalRead() {
InternalGZIPOutputStream gzipOut = _gzipOut;
if (gzipOut != null)
return gzipOut.getTotalRead();
else
return 0;
}
public long getTotalCompressed() {
InternalGZIPOutputStream gzipOut = _gzipOut;
if (gzipOut != null)
return gzipOut.getTotalCompressed();
else
return 0;
}
}
private class InternalGZIPOutputStream extends GZIPOutputStream {
public InternalGZIPOutputStream(OutputStream target) throws IOException {
super(target);
}
public long getTotalRead() { return super.def.getTotalIn(); }
public long getTotalCompressed() { return super.def.getTotalOut(); }
public long getTotalRead() {
try {
return def.getTotalIn();
} catch (Exception e) {
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalIn() implementation
return 0;
}
}
public long getTotalCompressed() {
try {
return def.getTotalOut();
} catch (Exception e) {
// j2se 1.4.2_08 on linux is sometimes throwing an NPE in the getTotalOut() implementation
return 0;
}
}
}
private String formatHeaders(Properties headers, StringBuffer command) {
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
buf.append(command.toString()).append('\n');
buf.append(command.toString().trim()).append("\r\n");
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String val = headers.getProperty(name);
buf.append(name).append(": ").append(val).append('\n');
buf.append(name.trim()).append(": ").append(val.trim()).append("\r\n");
}
buf.append('\n');
buf.append("\r\n");
return buf.toString();
}
@ -256,20 +293,40 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the http command [" + command.toString() + "]");
int trimmed = 0;
if (command.length() > 0) {
for (int i = 0; i < command.length(); i++) {
if (command.charAt(i) == 0) {
command = command.deleteCharAt(i);
i--;
trimmed++;
}
}
}
if (trimmed > 0)
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed, 0);
while (true) {
buf.setLength(0);
ok = DataHelper.readLine(in, buf);
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
if ( (buf.length() == 0) ||
((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) {
// end of headers reached
return headers;
} else {
int split = buf.indexOf(": ");
int split = buf.indexOf(":");
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
String name = buf.substring(0, split);
String value = buf.substring(split+2); // ": "
String name = buf.substring(0, split).trim();
String value = null;
if (buf.length() > split + 1)
value = buf.substring(split+1).trim(); // ":"
else
value = "";
if ("Accept-encoding".equalsIgnoreCase(name))
name = "Accept-encoding";
else if ("X-Accept-encoding".equalsIgnoreCase(name))
name = "X-Accept-encoding";
headers.setProperty(name, value);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the header [" + name + "] = [" + value + "]");

View File

@ -0,0 +1,482 @@
package net.i2p.i2ptunnel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException;
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 {
private static final Log _log = new Log(I2PTunnelIRCClient.class);
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
/** list of Destination objects that we point at */
protected List dests;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
protected long readTimeout = DEFAULT_READ_TIMEOUT;
/**
* @throws IllegalArgumentException if the I2PTunnel does not contain
* valid config to contact the router
*/
public I2PTunnelIRCClient(
int localPort,
String destinations,
Logging l,
boolean ownDest,
EventDispatcher notifyThis,
I2PTunnel tunnel) throws IllegalArgumentException {
super(localPort,
ownDest,
l,
notifyThis,
"IRCHandler " + (++__clientId), tunnel);
StringTokenizer tok = new StringTokenizer(destinations, ",");
dests = new ArrayList(1);
while (tok.hasMoreTokens()) {
String destination = tok.nextToken();
try {
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null)
l.log("Could not resolve " + destination);
else
dests.add(dest);
} catch (DataFormatException dfe) {
l.log("Bad format parsing \"" + destination + "\"");
}
}
if (dests.size() <= 0) {
l.log("No target destinations found");
notifyEvent("openClientResult", "error");
return;
}
setName(getLocalPort() + " -> IRCClient");
startRunning();
notifyEvent("openIRCClientResult", "ok");
}
protected void clientConnectionRun(Socket s) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("got a connection.");
Destination dest = pickDestination();
I2PSocket i2ps = null;
try {
i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
StringBuffer expectedPong = new StringBuffer();
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
in.start();
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
out.start();
} catch (Exception ex) {
if (_log.shouldLog(Log.ERROR))
_log.error("Error connecting", ex);
l.log(ex.getMessage());
closeSocket(s);
if (i2ps != null) {
synchronized (sockLock) {
mySockets.remove(sockLock);
}
}
}
}
private final Destination pickDestination() {
int size = dests.size();
if (size <= 0) {
if (_log.shouldLog(Log.ERROR))
_log.error("No client targets?!");
return null;
}
if (size == 1) // skip the rand in the most common case
return (Destination)dests.get(0);
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
return (Destination)dests.get(index);
}
/*************************************************************************
*
*/
private class IrcInboundFilter implements Runnable {
private Socket local;
private I2PSocket remote;
private StringBuffer expectedPong;
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
local=_local;
remote=_remote;
expectedPong=pong;
}
public void run() {
BufferedReader in;
OutputStream output;
try {
in = new BufferedReader(new InputStreamReader(remote.getInputStream(), "ISO-8859-1"));
output=local.getOutputStream();
} catch (IOException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("IrcInboundFilter: no streams",e);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("IrcInboundFilter: Running.");
try {
while(true)
{
try {
String inmsg = in.readLine();
if(inmsg==null)
break;
if(inmsg.endsWith("\r"))
inmsg=inmsg.substring(0,inmsg.length()-1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("in: [" + inmsg + "]");
String outmsg = inboundFilter(inmsg, expectedPong);
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+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
} 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.");
}
}
/*************************************************************************
*
*/
private class IrcOutboundFilter implements Runnable {
private Socket local;
private I2PSocket remote;
private StringBuffer expectedPong;
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
local=_local;
remote=_remote;
expectedPong=pong;
}
public void run() {
BufferedReader in;
OutputStream output;
try {
in = new BufferedReader(new InputStreamReader(local.getInputStream(), "ISO-8859-1"));
output=remote.getOutputStream();
} catch (IOException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("IrcOutboundFilter: no streams",e);
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("IrcOutboundFilter: Running.");
try {
while(true)
{
try {
String inmsg = in.readLine();
if(inmsg==null)
break;
if(inmsg.endsWith("\r"))
inmsg=inmsg.substring(0,inmsg.length()-1);
if (_log.shouldLog(Log.DEBUG))
_log.debug("out: [" + inmsg + "]");
String outmsg = outboundFilter(inmsg, expectedPong);
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+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
} 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.");
}
}
/*************************************************************************
*
*/
public String inboundFilter(String s, StringBuffer expectedPong) {
String field[]=s.split(" ",4);
String command;
int idx=0;
final String[] allowedCommands =
{
// "NOTICE", // can contain CTCP
//"PING",
//"PONG",
"MODE",
"JOIN",
"NICK",
"QUIT",
"PART",
"WALLOPS",
"ERROR",
"KICK",
"H", // "hide operator status" (after kicking an op)
"TOPIC"
};
if(field[0].charAt(0)==':')
idx++;
try { command = field[idx++]; }
catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command?
{
_log.warn("Dropping defective message: index out of bounds while extracting command.");
return null;
}
idx++; //skip victim
// Allow numerical responses
try {
new Integer(command);
return s;
} catch(NumberFormatException nfe){}
if ("PING".equalsIgnoreCase(command))
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
if ("PONG".equalsIgnoreCase(command)) {
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
// is blank.
//
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
// If we aren't going to rewrite it, pass it through
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
expectedPong.setLength(0);
return pong;
}
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++) {
if(allowedCommands[i].equalsIgnoreCase(command))
return s;
}
// Allow PRIVMSG, but block CTCP.
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
{
String msg;
msg = field[idx++];
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
{
// CTCP
msg=msg.substring(2);
if(msg.startsWith("ACTION ")) {
// /me says hello
return s;
}
return null; // Block all other ctcp
}
return s;
}
// Block the rest
return null;
}
public String outboundFilter(String s, StringBuffer expectedPong) {
String field[]=s.split(" ",3);
String command;
final String[] allowedCommands =
{
// "NOTICE", // can contain CTCP
"MODE",
"JOIN",
"NICK",
"WHO",
"WHOIS",
"LIST",
"NAMES",
"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",
// "PONG", // replaced with a filtered PING/PONG since some clients send the server IP (thanks aardvax!)
// "PING",
"KICK",
"HELPME",
"RULES",
"TOPIC"
};
if(field[0].length()==0)
return null; // W T F?
if(field[0].charAt(0)==':')
return null; // wtf
command = field[0].toUpperCase();
if ("PING".equalsIgnoreCase(command)) {
// Most clients just send a PING and are happy with any old PONG. Others,
// like BitchX, actually expect certain behavior. It sends two different pings:
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
// sent to the server, but when we get a PONG back, use what we expected, rather than
// what they sent.
//
// Yuck.
String rv = null;
expectedPong.setLength(0);
if (field.length == 1) { // PING
rv = "PING";
// If we aren't rewriting the PING don't rewrite the PONG
// expectedPong.append("PONG 127.0.0.1");
} else if (field.length == 2) { // PING nonce
rv = "PING " + field[1];
// If we aren't rewriting the PING don't rewrite the PONG
// expectedPong.append("PONG ").append(field[1]);
} else if (field.length == 3) { // PING nonce serverLocation
rv = "PING " + field[1];
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
rv = null;
}
if (_log.shouldLog(Log.WARN))
_log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
return rv;
}
if ("PONG".equalsIgnoreCase(command))
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
// Allow all allowedCommands
for(int i=0;i<allowedCommands.length;i++)
{
if(allowedCommands[i].equalsIgnoreCase(command))
return s;
}
// mIRC sends "NOTICE user :DCC Send file (IP)"
// in addition to the CTCP version
if("NOTICE".equalsIgnoreCase(command))
{
String msg = field[2];
if(msg.startsWith(":DCC "))
return null;
// fall through
}
// Allow PRIVMSG, but block CTCP (except ACTION).
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
{
String msg;
msg = field[2];
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
{
// CTCP
msg=msg.substring(2);
if(msg.startsWith("ACTION ")) {
// /me says hello
return s;
}
return null; // Block all other ctcp
}
return s;
}
if("USER".equalsIgnoreCase(command)) {
int idx = field[2].lastIndexOf(":");
if(idx<0)
return "USER user hostname localhost :realname";
String realname = field[2].substring(idx+1);
String ret = "USER "+field[1]+" hostname localhost :"+realname;
return ret;
} else if ("QUIT".equalsIgnoreCase(command)) {
return "QUIT :leaving";
}
// Block the rest
return null;
}
}

View File

@ -250,6 +250,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
+ from + " and " + to);
}
// boo, hiss! shouldn't need this - the streaming lib should be configurable, but
// somehow the inactivity timer is sometimes failing to get triggered properly
//i2ps.setReadTimeout(2*60*1000);
ByteArray ba = _cache.acquire();
byte[] buffer = ba.getData(); // new byte[NETWORK_BUFFER_SIZE];
try {

Some files were not shown because too many files have changed in this diff Show More