Compare commits
85 Commits
muwire-0.4
...
muwire-0.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
549e8c2d98 | ||
![]() |
b54d24db0d | ||
![]() |
fa12e84345 | ||
![]() |
6430ff2691 | ||
![]() |
591313c81c | ||
![]() |
ce7b6a0c65 | ||
![]() |
5c4d4c4580 | ||
![]() |
4cb864ff9f | ||
![]() |
417675ad07 | ||
![]() |
9513e5ba3c | ||
![]() |
85610cf169 | ||
![]() |
e8322384b8 | ||
![]() |
179279ed30 | ||
![]() |
ae79f0fded | ||
![]() |
ed878b3762 | ||
![]() |
623cca0ef2 | ||
![]() |
eaa883c3ba | ||
![]() |
7ae8076865 | ||
![]() |
b1aa92661c | ||
![]() |
9ed94c8376 | ||
![]() |
fa6aea1abe | ||
![]() |
0de84e704b | ||
![]() |
a767dda044 | ||
![]() |
56e9235d7b | ||
![]() |
2fba9a74ce | ||
![]() |
2bb6826906 | ||
![]() |
9f339629a9 | ||
![]() |
58d4207f94 | ||
![]() |
32577a28dc | ||
![]() |
f7b43304d4 | ||
![]() |
dcbe09886d | ||
![]() |
5a54b2dcda | ||
![]() |
581293b24f | ||
![]() |
cd072b9f76 | ||
![]() |
6b74fc5956 | ||
![]() |
3de2f872bb | ||
![]() |
fcde917d08 | ||
![]() |
4ded065010 | ||
![]() |
18a1c7091a | ||
![]() |
46aee19f80 | ||
![]() |
92dd7064c6 | ||
![]() |
b2e4dda677 | ||
![]() |
e77a2c8961 | ||
![]() |
ee2fd2ef68 | ||
![]() |
3f95d2bf1d | ||
![]() |
1390983732 | ||
![]() |
ce660cefe9 | ||
![]() |
72b81eb886 | ||
![]() |
57d593a68a | ||
![]() |
39a81a3376 | ||
![]() |
fd0bf17c24 | ||
![]() |
ac12bff69b | ||
![]() |
feef773bac | ||
![]() |
239d8f12a7 | ||
![]() |
8bbc61a7cb | ||
![]() |
7f31c4477f | ||
![]() |
6bad67c1bf | ||
![]() |
c76e6dc99f | ||
![]() |
acf9db0db3 | ||
![]() |
69b4f0b547 | ||
![]() |
80e165b505 | ||
![]() |
bcce55b873 | ||
![]() |
d5c92560db | ||
![]() |
f827c1c9bf | ||
![]() |
88c5f1a02d | ||
![]() |
d8e44f5f39 | ||
![]() |
72ff47ffe5 | ||
![]() |
066ee2c96d | ||
![]() |
0a8016dea7 | ||
![]() |
db36367b11 | ||
![]() |
b6c9ccb7f6 | ||
![]() |
a9dc636bce | ||
![]() |
3cc0574d11 | ||
![]() |
20fab9b16d | ||
![]() |
4015818323 | ||
![]() |
f569d45c8c | ||
![]() |
3773647869 | ||
![]() |
29cdbf018c | ||
![]() |
94bb7022eb | ||
![]() |
39808302df | ||
![]() |
2d22f9c39e | ||
![]() |
ee8f80bab6 | ||
![]() |
3e6242e583 | ||
![]() |
41181616ee | ||
![]() |
eb2530ca32 |
22
README.md
22
README.md
@@ -4,14 +4,14 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.4.11 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
You need JRE 8 or newer. After installing that and setting up the appropriate paths, just type
|
You need JRE 8 or newer. After installing that and setting up the appropriate paths, just type
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew clean assemble
|
./gradlew clean assemble
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to run the unit tests, type
|
If you want to run the unit tests, type
|
||||||
@@ -21,11 +21,23 @@ If you want to run the unit tests, type
|
|||||||
|
|
||||||
Some of the UI tests will fail because they haven't been written yet :-/
|
Some of the UI tests will fail because they haven't been written yet :-/
|
||||||
|
|
||||||
|
If you want to build binary bundles for Windows and Mac that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
|
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
|
||||||
|
|
||||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
|
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
||||||
|
|
||||||
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
|
[Default I2CP port]\: `7654`
|
||||||
|
|
||||||
|
### GPG Fingerprint
|
||||||
|
|
||||||
|
```
|
||||||
|
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find the full key at https://keybase.io/zlatinb
|
||||||
|
|
||||||
|
|
||||||
|
[Default I2CP port]: https://geti2p.net/en/docs/ports
|
||||||
|
5
TODO.md
5
TODO.md
@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
|||||||
|
|
||||||
This helps with scalability
|
This helps with scalability
|
||||||
|
|
||||||
##### Content Control Panel
|
|
||||||
|
|
||||||
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
|
||||||
|
|
||||||
##### Web UI, REST Interface, etc.
|
##### Web UI, REST Interface, etc.
|
||||||
|
|
||||||
Basically any non-gui non-cli user interface
|
Basically any non-gui non-cli user interface
|
||||||
@@ -29,3 +25,4 @@ To enable parsing of metadata from known file types and the user editing it or a
|
|||||||
* Wrapper of some kind for in-place upgrades
|
* Wrapper of some kind for in-place upgrades
|
||||||
* Download file sequentially
|
* Download file sequentially
|
||||||
* Multiple-selection download, Ctrl-A
|
* Multiple-selection download, Ctrl-A
|
||||||
|
* Automatic adjustment of number of I2P tunnels
|
||||||
|
@@ -2,7 +2,7 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:i2p:0.9.40'
|
compile 'net.i2p:i2p:0.9.42'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,66 +16,66 @@ import com.muwire.core.upload.UploadEvent
|
|||||||
import com.muwire.core.upload.UploadFinishedEvent
|
import com.muwire.core.upload.UploadFinishedEvent
|
||||||
|
|
||||||
class Cli {
|
class Cli {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
home = new File(home)
|
home = new File(home)
|
||||||
if (!home.exists())
|
if (!home.exists())
|
||||||
home.mkdirs()
|
home.mkdirs()
|
||||||
|
|
||||||
def propsFile = new File(home,"MuWire.properties")
|
def propsFile = new File(home,"MuWire.properties")
|
||||||
if (!propsFile.exists()) {
|
if (!propsFile.exists()) {
|
||||||
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def props = new Properties()
|
def props = new Properties()
|
||||||
propsFile.withInputStream { props.load(it) }
|
propsFile.withInputStream { props.load(it) }
|
||||||
props = new MuWireSettings(props)
|
props = new MuWireSettings(props)
|
||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.4.12")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def filesList
|
def filesList
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
println "Enter a file containing list of files to share"
|
println "Enter a file containing list of files to share"
|
||||||
def reader = new BufferedReader(new InputStreamReader(System.in))
|
def reader = new BufferedReader(new InputStreamReader(System.in))
|
||||||
filesList = reader.readLine()
|
filesList = reader.readLine()
|
||||||
} else
|
} else
|
||||||
filesList = args[0]
|
filesList = args[0]
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
println "loading shared files from $filesList"
|
println "loading shared files from $filesList"
|
||||||
|
|
||||||
// listener for shared files
|
// listener for shared files
|
||||||
def sharedListener = new SharedListener()
|
def sharedListener = new SharedListener()
|
||||||
core.eventBus.register(FileHashedEvent.class, sharedListener)
|
core.eventBus.register(FileHashedEvent.class, sharedListener)
|
||||||
core.eventBus.register(FileLoadedEvent.class, sharedListener)
|
core.eventBus.register(FileLoadedEvent.class, sharedListener)
|
||||||
|
|
||||||
// for connections
|
// for connections
|
||||||
def connectionsListener = new ConnectionListener()
|
def connectionsListener = new ConnectionListener()
|
||||||
core.eventBus.register(ConnectionEvent.class, connectionsListener)
|
core.eventBus.register(ConnectionEvent.class, connectionsListener)
|
||||||
core.eventBus.register(DisconnectionEvent.class, connectionsListener)
|
core.eventBus.register(DisconnectionEvent.class, connectionsListener)
|
||||||
|
|
||||||
// for uploads
|
// for uploads
|
||||||
def uploadsListener = new UploadsListener()
|
def uploadsListener = new UploadsListener()
|
||||||
core.eventBus.register(UploadEvent.class, uploadsListener)
|
core.eventBus.register(UploadEvent.class, uploadsListener)
|
||||||
core.eventBus.register(UploadFinishedEvent.class, uploadsListener)
|
core.eventBus.register(UploadFinishedEvent.class, uploadsListener)
|
||||||
|
|
||||||
Timer timer = new Timer("status-printer", true)
|
Timer timer = new Timer("status-printer", true)
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
println String.valueOf(new Date()) + " Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
|
println String.valueOf(new Date()) + " Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
|
||||||
} as TimerTask, 60000, 60000)
|
} as TimerTask, 60000, 60000)
|
||||||
|
|
||||||
def latch = new CountDownLatch(1)
|
def latch = new CountDownLatch(1)
|
||||||
def fileLoader = new Object() {
|
def fileLoader = new Object() {
|
||||||
public void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
public void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
@@ -85,14 +85,14 @@ class Cli {
|
|||||||
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
core.eventBus.publish(new UILoadedEvent())
|
core.eventBus.publish(new UILoadedEvent())
|
||||||
println "waiting for files to load"
|
println "waiting for files to load"
|
||||||
latch.await()
|
latch.await()
|
||||||
// now we begin
|
// now we begin
|
||||||
println "MuWire is ready"
|
println "MuWire is ready"
|
||||||
|
|
||||||
filesList = new File(filesList)
|
filesList = new File(filesList)
|
||||||
filesList.withReader {
|
filesList.withReader {
|
||||||
def toShare = it.readLine()
|
def toShare = it.readLine()
|
||||||
core.eventBus.publish(new FileSharedEvent(file : new File(toShare)))
|
core.eventBus.publish(new FileSharedEvent(file : new File(toShare)))
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ class Cli {
|
|||||||
})
|
})
|
||||||
Thread.sleep(Integer.MAX_VALUE)
|
Thread.sleep(Integer.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ConnectionListener {
|
static class ConnectionListener {
|
||||||
volatile int connections
|
volatile int connections
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
@@ -114,7 +114,7 @@ class Cli {
|
|||||||
connections--
|
connections--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class UploadsListener {
|
static class UploadsListener {
|
||||||
volatile int uploads
|
volatile int uploads
|
||||||
public void onUploadEvent(UploadEvent e) {
|
public void onUploadEvent(UploadEvent e) {
|
||||||
@@ -126,7 +126,7 @@ class Cli {
|
|||||||
println String.valueOf(new Date()) + " Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
println String.valueOf(new Date()) + " Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SharedListener {
|
static class SharedListener {
|
||||||
volatile int shared
|
volatile int shared
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
|
@@ -17,31 +17,31 @@ import com.muwire.core.search.UIResultEvent
|
|||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class CliDownloader {
|
class CliDownloader {
|
||||||
|
|
||||||
private static final List<Downloader> downloaders = Collections.synchronizedList(new ArrayList<>())
|
private static final List<Downloader> downloaders = Collections.synchronizedList(new ArrayList<>())
|
||||||
private static final Map<UUID,ResultsHolder> resultsListeners = new ConcurrentHashMap<>()
|
private static final Map<UUID,ResultsHolder> resultsListeners = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
public static void main(String []args) {
|
public static void main(String []args) {
|
||||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
home = new File(home)
|
home = new File(home)
|
||||||
if (!home.exists())
|
if (!home.exists())
|
||||||
home.mkdirs()
|
home.mkdirs()
|
||||||
|
|
||||||
def propsFile = new File(home,"MuWire.properties")
|
def propsFile = new File(home,"MuWire.properties")
|
||||||
if (!propsFile.exists()) {
|
if (!propsFile.exists()) {
|
||||||
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def props = new Properties()
|
def props = new Properties()
|
||||||
propsFile.withInputStream { props.load(it) }
|
propsFile.withInputStream { props.load(it) }
|
||||||
props = new MuWireSettings(props)
|
props = new MuWireSettings(props)
|
||||||
|
|
||||||
def filesList
|
def filesList
|
||||||
int connections
|
int connections
|
||||||
int resultWait
|
int resultWait
|
||||||
if (args.length != 3) {
|
if (args.length != 3) {
|
||||||
println "Enter a file containing list of hashes of files to download, " +
|
println "Enter a file containing list of hashes of files to download, " +
|
||||||
"how many connections you want before searching" +
|
"how many connections you want before searching" +
|
||||||
"and how long to wait for results to arrive"
|
"and how long to wait for results to arrive"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
@@ -53,24 +53,24 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.4.12")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def latch = new CountDownLatch(connections)
|
def latch = new CountDownLatch(connections)
|
||||||
def connectionListener = new ConnectionWaiter(latch : latch)
|
def connectionListener = new ConnectionWaiter(latch : latch)
|
||||||
core.eventBus.register(ConnectionEvent.class, connectionListener)
|
core.eventBus.register(ConnectionEvent.class, connectionListener)
|
||||||
|
|
||||||
core.startServices()
|
core.startServices()
|
||||||
println "starting to wait until there are $connections connections"
|
println "starting to wait until there are $connections connections"
|
||||||
latch.await()
|
latch.await()
|
||||||
|
|
||||||
println "connected, searching for files"
|
println "connected, searching for files"
|
||||||
|
|
||||||
def file = new File(filesList)
|
def file = new File(filesList)
|
||||||
file.eachLine {
|
file.eachLine {
|
||||||
String[] split = it.split(",")
|
String[] split = it.split(",")
|
||||||
@@ -79,22 +79,22 @@ class CliDownloader {
|
|||||||
def hash = Base64.decode(split[0])
|
def hash = Base64.decode(split[0])
|
||||||
def searchEvent = new SearchEvent(searchHash : hash, uuid : uuid)
|
def searchEvent = new SearchEvent(searchHash : hash, uuid : uuid)
|
||||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop:true,
|
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop:true,
|
||||||
replyTo: core.me.destination, receivedOn : core.me.destination, originator: core.me))
|
replyTo: core.me.destination, receivedOn : core.me.destination, originator: core.me))
|
||||||
}
|
}
|
||||||
|
|
||||||
println "waiting for results to arrive"
|
println "waiting for results to arrive"
|
||||||
Thread.sleep(resultWait * 1000)
|
Thread.sleep(resultWait * 1000)
|
||||||
|
|
||||||
core.eventBus.register(DownloadStartedEvent.class, new DownloadListener())
|
core.eventBus.register(DownloadStartedEvent.class, new DownloadListener())
|
||||||
resultsListeners.each { uuid, resultsListener ->
|
resultsListeners.each { uuid, resultsListener ->
|
||||||
println "starting download of $resultsListener.fileName from ${resultsListener.getResults().size()} hosts"
|
println "starting download of $resultsListener.fileName from ${resultsListener.getResults().size()} hosts"
|
||||||
File target = new File(resultsListener.fileName)
|
File target = new File(resultsListener.fileName)
|
||||||
|
|
||||||
core.eventBus.publish(new UIDownloadEvent(target : target, result : resultsListener.getResults()))
|
core.eventBus.publish(new UIDownloadEvent(target : target, result : resultsListener.getResults()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
|
|
||||||
Timer timer = new Timer("stats-printer")
|
Timer timer = new Timer("stats-printer")
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
println "==== STATUS UPDATE ==="
|
println "==== STATUS UPDATE ==="
|
||||||
@@ -109,7 +109,7 @@ class CliDownloader {
|
|||||||
}
|
}
|
||||||
println "==== END ==="
|
println "==== END ==="
|
||||||
} as TimerTask, 60000, 60000)
|
} as TimerTask, 60000, 60000)
|
||||||
|
|
||||||
println "waiting for downloads to finish"
|
println "waiting for downloads to finish"
|
||||||
while(true) {
|
while(true) {
|
||||||
boolean allFinished = true
|
boolean allFinished = true
|
||||||
@@ -120,10 +120,10 @@ class CliDownloader {
|
|||||||
break
|
break
|
||||||
Thread.sleep(1000)
|
Thread.sleep(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
println "all downloads finished"
|
println "all downloads finished"
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ResultsHolder {
|
static class ResultsHolder {
|
||||||
final List<UIResultEvent> results = Collections.synchronizedList(new ArrayList<>())
|
final List<UIResultEvent> results = Collections.synchronizedList(new ArrayList<>())
|
||||||
String fileName
|
String fileName
|
||||||
@@ -134,7 +134,7 @@ class CliDownloader {
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ResultsListener {
|
static class ResultsListener {
|
||||||
UUID uuid
|
UUID uuid
|
||||||
String fileName
|
String fileName
|
||||||
@@ -148,7 +148,7 @@ class CliDownloader {
|
|||||||
listener.add(e)
|
listener.add(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ConnectionWaiter {
|
static class ConnectionWaiter {
|
||||||
CountDownLatch latch
|
CountDownLatch latch
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
@@ -156,7 +156,7 @@ class CliDownloader {
|
|||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class DownloadListener {
|
static class DownloadListener {
|
||||||
public void onDownloadStartedEvent(DownloadStartedEvent e) {
|
public void onDownloadStartedEvent(DownloadStartedEvent e) {
|
||||||
|
@@ -11,10 +11,10 @@ class FileList {
|
|||||||
println "pass files.json as argument"
|
println "pass files.json as argument"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
File filesJson = new File(args[0])
|
File filesJson = new File(args[0])
|
||||||
filesJson.eachLine {
|
filesJson.eachLine {
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
String name = DataUtil.readi18nString(Base64.decode(json.file))
|
String name = DataUtil.readi18nString(Base64.decode(json.file))
|
||||||
println "$name,$json.length,$json.pieceSize,$json.infoHash"
|
println "$name,$json.length,$json.pieceSize,$json.infoHash"
|
||||||
|
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:router:0.9.40'
|
compile 'net.i2p:router:0.9.42'
|
||||||
compile 'net.i2p.client:mstreaming:0.9.40'
|
compile 'net.i2p.client:mstreaming:0.9.42'
|
||||||
compile 'net.i2p.client:streaming:0.9.40'
|
compile 'net.i2p.client:streaming:0.9.42'
|
||||||
|
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
@@ -4,10 +4,10 @@ import net.i2p.crypto.SigType
|
|||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
public static final byte PERSONA_VERSION = (byte)1
|
public static final byte PERSONA_VERSION = (byte)1
|
||||||
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519
|
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519
|
||||||
|
|
||||||
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||||
public static final int MAX_HEADERS = 16
|
public static final int MAX_HEADERS = 16
|
||||||
|
|
||||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
|
|||||||
import com.muwire.core.download.UIDownloadResumedEvent
|
import com.muwire.core.download.UIDownloadResumedEvent
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
|
import com.muwire.core.files.FileHashingEvent
|
||||||
import com.muwire.core.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileManager
|
import com.muwire.core.files.FileManager
|
||||||
@@ -47,6 +48,8 @@ import com.muwire.core.trust.TrustSubscriptionEvent
|
|||||||
import com.muwire.core.update.UpdateClient
|
import com.muwire.core.update.UpdateClient
|
||||||
import com.muwire.core.upload.UploadManager
|
import com.muwire.core.upload.UploadManager
|
||||||
import com.muwire.core.util.MuWireLogManager
|
import com.muwire.core.util.MuWireLogManager
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
|
import com.muwire.core.content.ContentManager
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.I2PAppContext
|
import net.i2p.I2PAppContext
|
||||||
@@ -68,13 +71,13 @@ import net.i2p.router.RouterContext
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class Core {
|
public class Core {
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final Persona me
|
final Persona me
|
||||||
final File home
|
final File home
|
||||||
final Properties i2pOptions
|
final Properties i2pOptions
|
||||||
final MuWireSettings muOptions
|
final MuWireSettings muOptions
|
||||||
|
|
||||||
private final TrustService trustService
|
private final TrustService trustService
|
||||||
private final TrustSubscriber trustSubscriber
|
private final TrustSubscriber trustSubscriber
|
||||||
private final PersisterService persisterService
|
private final PersisterService persisterService
|
||||||
@@ -89,20 +92,21 @@ public class Core {
|
|||||||
private final DirectoryWatcher directoryWatcher
|
private final DirectoryWatcher directoryWatcher
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
|
final ContentManager contentManager
|
||||||
|
|
||||||
private final Router router
|
private final Router router
|
||||||
|
|
||||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||||
|
|
||||||
public Core(MuWireSettings props, File home, String myVersion) {
|
public Core(MuWireSettings props, File home, String myVersion) {
|
||||||
this.home = home
|
this.home = home
|
||||||
this.muOptions = props
|
this.muOptions = props
|
||||||
|
|
||||||
i2pOptions = new Properties()
|
i2pOptions = new Properties()
|
||||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
def i2pOptionsFile = new File(home,"i2p.properties")
|
||||||
if (i2pOptionsFile.exists()) {
|
if (i2pOptionsFile.exists()) {
|
||||||
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
||||||
|
|
||||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
i2pOptions["inbound.nickname"] = "MuWire"
|
||||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||||
@@ -122,7 +126,7 @@ public class Core {
|
|||||||
i2pOptions["i2np.udp.port"] = String.valueOf(port)
|
i2pOptions["i2np.udp.port"] = String.valueOf(port)
|
||||||
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
|
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.embeddedRouter) {
|
if (!props.embeddedRouter) {
|
||||||
log.info "Initializing I2P context"
|
log.info "Initializing I2P context"
|
||||||
I2PAppContext.getGlobalContext().logManager()
|
I2PAppContext.getGlobalContext().logManager()
|
||||||
@@ -140,34 +144,34 @@ public class Core {
|
|||||||
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||||
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||||
router = new Router(routerProps)
|
router = new Router(routerProps)
|
||||||
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
|
router.getContext().setLogManager(new MuWireLogManager())
|
||||||
router.runRouter()
|
router.runRouter()
|
||||||
while(!router.isRunning())
|
while(!router.isRunning())
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("initializing I2P socket manager")
|
log.info("initializing I2P socket manager")
|
||||||
def i2pClient = new I2PClientFactory().createClient()
|
def i2pClient = new I2PClientFactory().createClient()
|
||||||
File keyDat = new File(home, "key.dat")
|
File keyDat = new File(home, "key.dat")
|
||||||
if (!keyDat.exists()) {
|
if (!keyDat.exists()) {
|
||||||
log.info("Creating new key.dat")
|
log.info("Creating new key.dat")
|
||||||
keyDat.withOutputStream {
|
keyDat.withOutputStream {
|
||||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// options like tunnel length and quantity
|
// options like tunnel length and quantity
|
||||||
I2PSession i2pSession
|
I2PSession i2pSession
|
||||||
I2PSocketManager socketManager
|
I2PSocketManager socketManager
|
||||||
keyDat.withInputStream {
|
keyDat.withInputStream {
|
||||||
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
||||||
}
|
}
|
||||||
socketManager.getDefaultOptions().setReadTimeout(60000)
|
socketManager.getDefaultOptions().setReadTimeout(60000)
|
||||||
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
||||||
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
||||||
i2pSession = socketManager.getSession()
|
i2pSession = socketManager.getSession()
|
||||||
|
|
||||||
def destination = new Destination()
|
def destination = new Destination()
|
||||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||||
keyDat.withInputStream {
|
keyDat.withInputStream {
|
||||||
@@ -175,8 +179,8 @@ public class Core {
|
|||||||
def privateKey = new PrivateKey()
|
def privateKey = new PrivateKey()
|
||||||
privateKey.readBytes(it)
|
privateKey.readBytes(it)
|
||||||
spk.readBytes(it)
|
spk.readBytes(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
def daos = new DataOutputStream(baos)
|
def daos = new DataOutputStream(baos)
|
||||||
daos.write(Constants.PERSONA_VERSION)
|
daos.write(Constants.PERSONA_VERSION)
|
||||||
@@ -193,66 +197,66 @@ public class Core {
|
|||||||
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
||||||
log.info("Loaded myself as "+me.getHumanReadableName())
|
log.info("Loaded myself as "+me.getHumanReadableName())
|
||||||
|
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
|
|
||||||
log.info("initializing trust service")
|
log.info("initializing trust service")
|
||||||
File goodTrust = new File(home, "trusted")
|
File goodTrust = new File(home, "trusted")
|
||||||
File badTrust = new File(home, "distrusted")
|
File badTrust = new File(home, "distrusted")
|
||||||
trustService = new TrustService(goodTrust, badTrust, 5000)
|
trustService = new TrustService(goodTrust, badTrust, 5000)
|
||||||
eventBus.register(TrustEvent.class, trustService)
|
eventBus.register(TrustEvent.class, trustService)
|
||||||
|
|
||||||
|
|
||||||
log.info "initializing file manager"
|
log.info "initializing file manager"
|
||||||
fileManager = new FileManager(eventBus, props)
|
fileManager = new FileManager(eventBus, props)
|
||||||
eventBus.register(FileHashedEvent.class, fileManager)
|
eventBus.register(FileHashedEvent.class, fileManager)
|
||||||
eventBus.register(FileLoadedEvent.class, fileManager)
|
eventBus.register(FileLoadedEvent.class, fileManager)
|
||||||
eventBus.register(FileDownloadedEvent.class, fileManager)
|
eventBus.register(FileDownloadedEvent.class, fileManager)
|
||||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||||
|
|
||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
eventBus.register(UILoadedEvent.class, persisterService)
|
||||||
|
|
||||||
log.info("initializing host cache")
|
log.info("initializing host cache")
|
||||||
File hostStorage = new File(home, "hosts.json")
|
File hostStorage = new File(home, "hosts.json")
|
||||||
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
||||||
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
||||||
eventBus.register(ConnectionEvent.class, hostCache)
|
eventBus.register(ConnectionEvent.class, hostCache)
|
||||||
|
|
||||||
log.info("initializing connection manager")
|
log.info("initializing connection manager")
|
||||||
connectionManager = props.isLeaf() ?
|
connectionManager = props.isLeaf() ?
|
||||||
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
||||||
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
||||||
eventBus.register(TrustEvent.class, connectionManager)
|
eventBus.register(TrustEvent.class, connectionManager)
|
||||||
eventBus.register(ConnectionEvent.class, connectionManager)
|
eventBus.register(ConnectionEvent.class, connectionManager)
|
||||||
eventBus.register(DisconnectionEvent.class, connectionManager)
|
eventBus.register(DisconnectionEvent.class, connectionManager)
|
||||||
eventBus.register(QueryEvent.class, connectionManager)
|
eventBus.register(QueryEvent.class, connectionManager)
|
||||||
|
|
||||||
log.info("initializing cache client")
|
log.info("initializing cache client")
|
||||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||||
|
|
||||||
log.info("initializing update client")
|
log.info("initializing update client")
|
||||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
||||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||||
|
|
||||||
log.info("initializing connector")
|
log.info("initializing connector")
|
||||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||||
|
|
||||||
log.info "initializing results sender"
|
log.info "initializing results sender"
|
||||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
||||||
|
|
||||||
log.info "initializing search manager"
|
log.info "initializing search manager"
|
||||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||||
eventBus.register(QueryEvent.class, searchManager)
|
eventBus.register(QueryEvent.class, searchManager)
|
||||||
eventBus.register(ResultsEvent.class, searchManager)
|
eventBus.register(ResultsEvent.class, searchManager)
|
||||||
|
|
||||||
log.info("initializing download manager")
|
log.info("initializing download manager")
|
||||||
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
||||||
eventBus.register(UIDownloadEvent.class, downloadManager)
|
eventBus.register(UIDownloadEvent.class, downloadManager)
|
||||||
@@ -262,34 +266,39 @@ public class Core {
|
|||||||
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
|
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
|
||||||
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
|
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
|
||||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||||
|
|
||||||
log.info("initializing upload manager")
|
log.info("initializing upload manager")
|
||||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
||||||
|
|
||||||
log.info("initializing connection establisher")
|
log.info("initializing connection establisher")
|
||||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||||
|
|
||||||
log.info("initializing acceptor")
|
log.info("initializing acceptor")
|
||||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
log.info("initializing directory watcher")
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||||
|
|
||||||
log.info("initializing hasher service")
|
log.info("initializing hasher service")
|
||||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||||
eventBus.register(FileSharedEvent.class, hasherService)
|
eventBus.register(FileSharedEvent.class, hasherService)
|
||||||
|
|
||||||
log.info("initializing trust subscriber")
|
log.info("initializing trust subscriber")
|
||||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||||
}
|
|
||||||
|
log.info("initializing content manager")
|
||||||
|
contentManager = new ContentManager()
|
||||||
|
eventBus.register(ContentControlEvent.class, contentManager)
|
||||||
|
eventBus.register(QueryEvent.class, contentManager)
|
||||||
|
}
|
||||||
|
|
||||||
public void startServices() {
|
public void startServices() {
|
||||||
hasherService.start()
|
hasherService.start()
|
||||||
trustService.start()
|
trustService.start()
|
||||||
@@ -302,7 +311,7 @@ public class Core {
|
|||||||
hostCache.waitForLoad()
|
hostCache.waitForLoad()
|
||||||
updateClient.start()
|
updateClient.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (!shutdown.compareAndSet(false, true)) {
|
if (!shutdown.compareAndSet(false, true)) {
|
||||||
log.info("already shutting down")
|
log.info("already shutting down")
|
||||||
@@ -328,19 +337,6 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class RouterContextMetaClass extends DelegatingMetaClass {
|
|
||||||
private final Object logManager = new MuWireLogManager()
|
|
||||||
RouterContextMetaClass() {
|
|
||||||
super(RouterContext.class)
|
|
||||||
}
|
|
||||||
|
|
||||||
Object invokeMethod(Object object, String name, Object[] args) {
|
|
||||||
if (name == "logManager")
|
|
||||||
return logManager
|
|
||||||
super.invokeMethod(object, name, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static main(args) {
|
static main(args) {
|
||||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
home = new File(home)
|
home = new File(home)
|
||||||
@@ -348,7 +344,7 @@ public class Core {
|
|||||||
log.info("creating home dir")
|
log.info("creating home dir")
|
||||||
home.mkdir()
|
home.mkdir()
|
||||||
}
|
}
|
||||||
|
|
||||||
def props = new Properties()
|
def props = new Properties()
|
||||||
def propsFile = new File(home, "MuWire.properties")
|
def propsFile = new File(home, "MuWire.properties")
|
||||||
if (propsFile.exists()) {
|
if (propsFile.exists()) {
|
||||||
@@ -364,10 +360,10 @@ public class Core {
|
|||||||
props.write(it)
|
props.write(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.4.7")
|
Core core = new Core(props, home, "0.4.12")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
log.info("initialized everything, sleeping")
|
log.info("initialized everything, sleeping")
|
||||||
|
@@ -4,17 +4,17 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
|
|
||||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||||
final long seqNo
|
final long seqNo
|
||||||
final long timestamp
|
final long timestamp
|
||||||
|
|
||||||
Event() {
|
Event() {
|
||||||
seqNo = SEQ_NO.getAndIncrement()
|
seqNo = SEQ_NO.getAndIncrement()
|
||||||
timestamp = System.currentTimeMillis()
|
timestamp = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"seqNo $seqNo timestamp $timestamp"
|
"seqNo $seqNo timestamp $timestamp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,42 +10,47 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
@Log
|
@Log
|
||||||
class EventBus {
|
class EventBus {
|
||||||
|
|
||||||
private Map handlers = new HashMap()
|
|
||||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
|
||||||
def rv = new Thread(r)
|
|
||||||
rv.setDaemon(true)
|
|
||||||
rv.setName("event-bus")
|
|
||||||
rv
|
|
||||||
}
|
|
||||||
|
|
||||||
void publish(Event e) {
|
private Map handlers = new HashMap()
|
||||||
executor.execute({publishInternal(e)} as Runnable)
|
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||||
}
|
def rv = new Thread(r)
|
||||||
|
rv.setDaemon(true)
|
||||||
private void publishInternal(Event e) {
|
rv.setName("event-bus")
|
||||||
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
rv
|
||||||
def currentHandlers
|
}
|
||||||
final def clazz = e.getClass()
|
|
||||||
synchronized(this) {
|
void publish(Event e) {
|
||||||
currentHandlers = handlers.getOrDefault(clazz, [])
|
executor.execute({publishInternal(e)} as Runnable)
|
||||||
}
|
}
|
||||||
currentHandlers.each {
|
|
||||||
|
private void publishInternal(Event e) {
|
||||||
|
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
||||||
|
def currentHandlers
|
||||||
|
final def clazz = e.getClass()
|
||||||
|
synchronized(this) {
|
||||||
|
currentHandlers = handlers.getOrDefault(clazz, [])
|
||||||
|
}
|
||||||
|
currentHandlers.each {
|
||||||
try {
|
try {
|
||||||
it."on${clazz.getSimpleName()}"(e)
|
it."on${clazz.getSimpleName()}"(e)
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.SEVERE, "exception dispatching event",bad)
|
log.log(Level.SEVERE, "exception dispatching event",bad)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||||
log.info "Registering $handler for type $eventType"
|
log.info "Registering $handler for type $eventType"
|
||||||
def currentHandlers = handlers.get(eventType)
|
def currentHandlers = handlers.get(eventType)
|
||||||
if (currentHandlers == null) {
|
if (currentHandlers == null) {
|
||||||
currentHandlers = new CopyOnWriteArrayList()
|
currentHandlers = new CopyOnWriteArrayList()
|
||||||
handlers.put(eventType, currentHandlers)
|
handlers.put(eventType, currentHandlers)
|
||||||
}
|
}
|
||||||
currentHandlers.add handler
|
currentHandlers.add handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void unregister(Class<? extends Event> eventType, def handler) {
|
||||||
|
log.info("Unregistering $handler for type $eventType")
|
||||||
|
handlers[eventType]?.remove(handler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,5 +13,5 @@ class InvalidSignatureException extends Exception {
|
|||||||
public InvalidSignatureException(Throwable cause) {
|
public InvalidSignatureException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import com.muwire.core.util.DataUtil
|
|||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class MuWireSettings {
|
class MuWireSettings {
|
||||||
|
|
||||||
final boolean isLeaf
|
final boolean isLeaf
|
||||||
boolean allowUntrusted
|
boolean allowUntrusted
|
||||||
boolean allowTrustLists
|
boolean allowTrustLists
|
||||||
@@ -28,19 +28,21 @@ class MuWireSettings {
|
|||||||
int meshExpiration
|
int meshExpiration
|
||||||
boolean embeddedRouter
|
boolean embeddedRouter
|
||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
|
Set<String> watchedKeywords
|
||||||
MuWireSettings() {
|
Set<String> watchedRegexes
|
||||||
|
|
||||||
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
}
|
}
|
||||||
|
|
||||||
MuWireSettings(Properties props) {
|
MuWireSettings(Properties props) {
|
||||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||||
nickname = props.getProperty("nickname","MuWireUser")
|
nickname = props.getProperty("nickname","MuWireUser")
|
||||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||||
System.getProperty("user.home")))
|
System.getProperty("user.home")))
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
||||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||||
@@ -53,21 +55,21 @@ class MuWireSettings {
|
|||||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||||
|
|
||||||
watchedDirectories = new HashSet<>()
|
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||||
if (props.containsKey("watchedDirectories")) {
|
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
trustSubscriptions = new HashSet<>()
|
trustSubscriptions = new HashSet<>()
|
||||||
if (props.containsKey("trustSubscriptions")) {
|
if (props.containsKey("trustSubscriptions")) {
|
||||||
props.getProperty("trustSubscriptions").split(",").each {
|
props.getProperty("trustSubscriptions").split(",").each {
|
||||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
props.setProperty("leaf", isLeaf.toString())
|
props.setProperty("leaf", isLeaf.toString())
|
||||||
@@ -88,44 +90,59 @@ class MuWireSettings {
|
|||||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||||
props.setProperty("inBw", String.valueOf(inBw))
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
props.setProperty("outBw", String.valueOf(outBw))
|
||||||
|
|
||||||
if (!watchedDirectories.isEmpty()) {
|
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||||
String encoded = watchedDirectories.stream().
|
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty("watchedDirectories", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!trustSubscriptions.isEmpty()) {
|
if (!trustSubscriptions.isEmpty()) {
|
||||||
String encoded = trustSubscriptions.stream().
|
String encoded = trustSubscriptions.stream().
|
||||||
map({it.toBase64()}).
|
map({it.toBase64()}).
|
||||||
collect(Collectors.joining(","))
|
collect(Collectors.joining(","))
|
||||||
props.setProperty("trustSubscriptions", encoded)
|
props.setProperty("trustSubscriptions", encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
props.store(out, "")
|
props.store(out, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isLeaf() {
|
|
||||||
isLeaf
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean allowUntrusted() {
|
|
||||||
allowUntrusted
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAllowUntrusted(boolean allowUntrusted) {
|
|
||||||
this.allowUntrusted = allowUntrusted
|
|
||||||
}
|
|
||||||
|
|
||||||
CrawlerResponse getCrawlerResponse() {
|
|
||||||
crawlerResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
|
||||||
this.crawlerResponse = crawlerResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||||
|
Set<String> rv = new HashSet<>()
|
||||||
|
if (props.containsKey(property)) {
|
||||||
|
String[] encoded = props.getProperty(property).split(",")
|
||||||
|
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
||||||
|
}
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||||
|
if (set.isEmpty())
|
||||||
|
return
|
||||||
|
String encoded = set.stream().
|
||||||
|
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
||||||
|
collect(Collectors.joining(","))
|
||||||
|
props.setProperty(property, encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isLeaf() {
|
||||||
|
isLeaf
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allowUntrusted() {
|
||||||
|
allowUntrusted
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAllowUntrusted(boolean allowUntrusted) {
|
||||||
|
this.allowUntrusted = allowUntrusted
|
||||||
|
}
|
||||||
|
|
||||||
|
CrawlerResponse getCrawlerResponse() {
|
||||||
|
crawlerResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
||||||
|
this.crawlerResponse = crawlerResponse
|
||||||
|
}
|
||||||
|
|
||||||
String getNickname() {
|
String getNickname() {
|
||||||
nickname
|
nickname
|
||||||
}
|
}
|
||||||
|
@@ -7,11 +7,11 @@ import java.nio.charset.StandardCharsets
|
|||||||
*/
|
*/
|
||||||
public class Name {
|
public class Name {
|
||||||
final String name
|
final String name
|
||||||
|
|
||||||
Name(String name) {
|
Name(String name) {
|
||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
Name(InputStream nameStream) throws IOException {
|
Name(InputStream nameStream) throws IOException {
|
||||||
DataInputStream dis = new DataInputStream(nameStream)
|
DataInputStream dis = new DataInputStream(nameStream)
|
||||||
int length = dis.readUnsignedShort()
|
int length = dis.readUnsignedShort()
|
||||||
@@ -19,22 +19,22 @@ public class Name {
|
|||||||
dis.readFully(nameBytes)
|
dis.readFully(nameBytes)
|
||||||
this.name = new String(nameBytes, StandardCharsets.UTF_8)
|
this.name = new String(nameBytes, StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(OutputStream out) throws IOException {
|
public void write(OutputStream out) throws IOException {
|
||||||
DataOutputStream dos = new DataOutputStream(out)
|
DataOutputStream dos = new DataOutputStream(out)
|
||||||
dos.writeShort(name.length())
|
dos.writeShort(name.length())
|
||||||
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName() {
|
public getName() {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
name.hashCode()
|
name.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof Name))
|
if (!(o instanceof Name))
|
||||||
|
@@ -9,7 +9,7 @@ import net.i2p.data.SigningPublicKey
|
|||||||
|
|
||||||
public class Persona {
|
public class Persona {
|
||||||
private static final int SIG_LEN = Constants.SIG_TYPE.getSigLen()
|
private static final int SIG_LEN = Constants.SIG_TYPE.getSigLen()
|
||||||
|
|
||||||
private final byte version
|
private final byte version
|
||||||
private final Name name
|
private final Name name
|
||||||
private final Destination destination
|
private final Destination destination
|
||||||
@@ -17,12 +17,12 @@ public class Persona {
|
|||||||
private volatile String humanReadableName
|
private volatile String humanReadableName
|
||||||
private volatile String base64
|
private volatile String base64
|
||||||
private volatile byte[] payload
|
private volatile byte[] payload
|
||||||
|
|
||||||
public Persona(InputStream personaStream) throws IOException, InvalidSignatureException {
|
public Persona(InputStream personaStream) throws IOException, InvalidSignatureException {
|
||||||
version = (byte) (personaStream.read() & 0xFF)
|
version = (byte) (personaStream.read() & 0xFF)
|
||||||
if (version != Constants.PERSONA_VERSION)
|
if (version != Constants.PERSONA_VERSION)
|
||||||
throw new IOException("Unknown version "+version)
|
throw new IOException("Unknown version "+version)
|
||||||
|
|
||||||
name = new Name(personaStream)
|
name = new Name(personaStream)
|
||||||
destination = Destination.create(personaStream)
|
destination = Destination.create(personaStream)
|
||||||
sig = new byte[SIG_LEN]
|
sig = new byte[SIG_LEN]
|
||||||
@@ -31,7 +31,7 @@ public class Persona {
|
|||||||
if (!verify(version, name, destination, sig))
|
if (!verify(version, name, destination, sig))
|
||||||
throw new InvalidSignatureException(getHumanReadableName() + " didn't verify")
|
throw new InvalidSignatureException(getHumanReadableName() + " didn't verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verify(byte version, Name name, Destination destination, byte [] sig) {
|
private static boolean verify(byte version, Name name, Destination destination, byte [] sig) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
baos.write(version)
|
baos.write(version)
|
||||||
@@ -42,7 +42,7 @@ public class Persona {
|
|||||||
Signature signature = new Signature(Constants.SIG_TYPE, sig)
|
Signature signature = new Signature(Constants.SIG_TYPE, sig)
|
||||||
DSAEngine.getInstance().verifySignature(signature, payload, spk)
|
DSAEngine.getInstance().verifySignature(signature, payload, spk)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(OutputStream out) throws IOException {
|
public void write(OutputStream out) throws IOException {
|
||||||
if (payload == null) {
|
if (payload == null) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
@@ -54,13 +54,13 @@ public class Persona {
|
|||||||
}
|
}
|
||||||
out.write(payload)
|
out.write(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHumanReadableName() {
|
public String getHumanReadableName() {
|
||||||
if (humanReadableName == null)
|
if (humanReadableName == null)
|
||||||
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32)
|
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32)
|
||||||
humanReadableName
|
humanReadableName
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toBase64() {
|
public String toBase64() {
|
||||||
if (base64 == null) {
|
if (base64 == null) {
|
||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
@@ -69,12 +69,12 @@ public class Persona {
|
|||||||
}
|
}
|
||||||
base64
|
base64
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
name.hashCode() ^ destination.hashCode()
|
name.hashCode() ^ destination.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof Persona))
|
if (!(o instanceof Persona))
|
||||||
@@ -82,7 +82,7 @@ public class Persona {
|
|||||||
Persona other = (Persona)o
|
Persona other = (Persona)o
|
||||||
name.equals(other.name) && destination.equals(other.destination)
|
name.equals(other.name) && destination.equals(other.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String []args) {
|
public static void main(String []args) {
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
println "This utility decodes a bas64-encoded persona"
|
println "This utility decodes a bas64-encoded persona"
|
||||||
|
@@ -2,12 +2,12 @@ package com.muwire.core
|
|||||||
|
|
||||||
abstract class Service {
|
abstract class Service {
|
||||||
|
|
||||||
volatile boolean loaded
|
volatile boolean loaded
|
||||||
|
|
||||||
abstract void load()
|
abstract void load()
|
||||||
|
|
||||||
void waitForLoad() {
|
void waitForLoad() {
|
||||||
while (!loaded)
|
while (!loaded)
|
||||||
Thread.sleep(10)
|
Thread.sleep(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,109 +21,109 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
abstract class Connection implements Closeable {
|
abstract class Connection implements Closeable {
|
||||||
|
|
||||||
private static final int SEARCHES = 10
|
private static final int SEARCHES = 10
|
||||||
private static final long INTERVAL = 1000
|
private static final long INTERVAL = 1000
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final Endpoint endpoint
|
final Endpoint endpoint
|
||||||
final boolean incoming
|
final boolean incoming
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
|
|
||||||
private final AtomicBoolean running = new AtomicBoolean()
|
private final AtomicBoolean running = new AtomicBoolean()
|
||||||
private final BlockingQueue messages = new LinkedBlockingQueue()
|
private final BlockingQueue messages = new LinkedBlockingQueue()
|
||||||
private final Thread reader, writer
|
private final Thread reader, writer
|
||||||
private final LinkedList<Long> searchTimestamps = new LinkedList<>()
|
private final LinkedList<Long> searchTimestamps = new LinkedList<>()
|
||||||
|
|
||||||
protected final String name
|
protected final String name
|
||||||
|
|
||||||
long lastPingSentTime, lastPongReceivedTime
|
long lastPingSentTime, lastPongReceivedTime
|
||||||
|
|
||||||
Connection(EventBus eventBus, Endpoint endpoint, boolean incoming,
|
Connection(EventBus eventBus, Endpoint endpoint, boolean incoming,
|
||||||
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.incoming = incoming
|
this.incoming = incoming
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
|
|
||||||
this.name = endpoint.destination.toBase32().substring(0,8)
|
this.name = endpoint.destination.toBase32().substring(0,8)
|
||||||
|
|
||||||
this.reader = new Thread({readLoop()} as Runnable)
|
this.reader = new Thread({readLoop()} as Runnable)
|
||||||
this.reader.setName("reader-$name")
|
this.reader.setName("reader-$name")
|
||||||
this.reader.setDaemon(true)
|
this.reader.setDaemon(true)
|
||||||
|
|
||||||
this.writer = new Thread({writeLoop()} as Runnable)
|
this.writer = new Thread({writeLoop()} as Runnable)
|
||||||
this.writer.setName("writer-$name")
|
this.writer.setName("writer-$name")
|
||||||
this.writer.setDaemon(true)
|
this.writer.setDaemon(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* starts the connection threads
|
* starts the connection threads
|
||||||
*/
|
*/
|
||||||
void start() {
|
void start() {
|
||||||
if (!running.compareAndSet(false, true)) {
|
if (!running.compareAndSet(false, true)) {
|
||||||
log.log(Level.WARNING,"$name already running", new Exception())
|
log.log(Level.WARNING,"$name already running", new Exception())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reader.start()
|
reader.start()
|
||||||
writer.start()
|
writer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!running.compareAndSet(true, false)) {
|
if (!running.compareAndSet(true, false)) {
|
||||||
log.log(Level.WARNING, "$name already closed", new Exception() )
|
log.log(Level.WARNING, "$name already closed", new Exception() )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.info("closing $name")
|
log.info("closing $name")
|
||||||
reader.interrupt()
|
reader.interrupt()
|
||||||
writer.interrupt()
|
writer.interrupt()
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void readLoop() {
|
protected void readLoop() {
|
||||||
try {
|
try {
|
||||||
while(running.get()) {
|
while(running.get()) {
|
||||||
read()
|
read()
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"unhandled exception in reader",e)
|
log.log(Level.WARNING,"unhandled exception in reader",e)
|
||||||
} finally {
|
} finally {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void read()
|
protected abstract void read()
|
||||||
|
|
||||||
protected void writeLoop() {
|
protected void writeLoop() {
|
||||||
try {
|
try {
|
||||||
while(running.get()) {
|
while(running.get()) {
|
||||||
def message = messages.take()
|
def message = messages.take()
|
||||||
write(message)
|
write(message)
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "unhandled exception in writer",e)
|
log.log(Level.WARNING, "unhandled exception in writer",e)
|
||||||
} finally {
|
} finally {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void write(def message);
|
protected abstract void write(def message);
|
||||||
|
|
||||||
void sendPing() {
|
void sendPing() {
|
||||||
def ping = [:]
|
def ping = [:]
|
||||||
ping.type = "Ping"
|
ping.type = "Ping"
|
||||||
ping.version = 1
|
ping.version = 1
|
||||||
messages.put(ping)
|
messages.put(ping)
|
||||||
lastPingSentTime = System.currentTimeMillis()
|
lastPingSentTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendQuery(QueryEvent e) {
|
void sendQuery(QueryEvent e) {
|
||||||
def query = [:]
|
def query = [:]
|
||||||
query.type = "Search"
|
query.type = "Search"
|
||||||
@@ -139,27 +139,27 @@ abstract class Connection implements Closeable {
|
|||||||
query.originator = e.originator.toBase64()
|
query.originator = e.originator.toBase64()
|
||||||
messages.put(query)
|
messages.put(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePing() {
|
protected void handlePing() {
|
||||||
log.fine("$name received ping")
|
log.fine("$name received ping")
|
||||||
def pong = [:]
|
def pong = [:]
|
||||||
pong.type = "Pong"
|
pong.type = "Pong"
|
||||||
pong.version = 1
|
pong.version = 1
|
||||||
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
||||||
messages.put(pong)
|
messages.put(pong)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePong(def pong) {
|
protected void handlePong(def pong) {
|
||||||
log.fine("$name received pong")
|
log.fine("$name received pong")
|
||||||
lastPongReceivedTime = System.currentTimeMillis()
|
lastPongReceivedTime = System.currentTimeMillis()
|
||||||
if (pong.pongs == null)
|
if (pong.pongs == null)
|
||||||
throw new Exception("Pong doesn't have pongs")
|
throw new Exception("Pong doesn't have pongs")
|
||||||
pong.pongs.each {
|
pong.pongs.each {
|
||||||
def dest = new Destination(it)
|
def dest = new Destination(it)
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean throttleSearch() {
|
private boolean throttleSearch() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
if (searchTimestamps.size() < SEARCHES) {
|
if (searchTimestamps.size() < SEARCHES) {
|
||||||
@@ -173,19 +173,19 @@ abstract class Connection implements Closeable {
|
|||||||
searchTimestamps.removeFirst()
|
searchTimestamps.removeFirst()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleSearch(def search) {
|
protected void handleSearch(def search) {
|
||||||
if (throttleSearch()) {
|
if (throttleSearch()) {
|
||||||
log.info("dropping excessive search")
|
log.info("dropping excessive search")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
UUID uuid = UUID.fromString(search.uuid)
|
UUID uuid = UUID.fromString(search.uuid)
|
||||||
byte [] infohash = null
|
byte [] infohash = null
|
||||||
if (search.infohash != null) {
|
if (search.infohash != null) {
|
||||||
search.keywords = null
|
search.keywords = null
|
||||||
infohash = Base64.decode(search.infohash)
|
infohash = Base64.decode(search.infohash)
|
||||||
}
|
}
|
||||||
|
|
||||||
Destination replyTo = new Destination(search.replyTo)
|
Destination replyTo = new Destination(search.replyTo)
|
||||||
TrustLevel trustLevel = trustService.getLevel(replyTo)
|
TrustLevel trustLevel = trustService.getLevel(replyTo)
|
||||||
if (trustLevel == TrustLevel.DISTRUSTED) {
|
if (trustLevel == TrustLevel.DISTRUSTED) {
|
||||||
@@ -196,7 +196,7 @@ abstract class Connection implements Closeable {
|
|||||||
log.info("dropping search from neutral peer")
|
log.info("dropping search from neutral peer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Persona originator = null
|
Persona originator = null
|
||||||
if (search.originator != null) {
|
if (search.originator != null) {
|
||||||
originator = new Persona(new ByteArrayInputStream(Base64.decode(search.originator)))
|
originator = new Persona(new ByteArrayInputStream(Base64.decode(search.originator)))
|
||||||
@@ -205,11 +205,11 @@ abstract class Connection implements Closeable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean oob = false
|
boolean oob = false
|
||||||
if (search.oobInfohash != null)
|
if (search.oobInfohash != null)
|
||||||
oob = search.oobInfohash
|
oob = search.oobInfohash
|
||||||
|
|
||||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||||
searchHash : infohash,
|
searchHash : infohash,
|
||||||
uuid : uuid,
|
uuid : uuid,
|
||||||
@@ -220,6 +220,6 @@ abstract class Connection implements Closeable {
|
|||||||
receivedOn : endpoint.destination,
|
receivedOn : endpoint.destination,
|
||||||
firstHop : search.firstHop )
|
firstHop : search.firstHop )
|
||||||
eventBus.publish(event)
|
eventBus.publish(event)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,97 +29,97 @@ import groovy.util.logging.Log
|
|||||||
@Log
|
@Log
|
||||||
class ConnectionAcceptor {
|
class ConnectionAcceptor {
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final UltrapeerConnectionManager manager
|
final UltrapeerConnectionManager manager
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final I2PAcceptor acceptor
|
final I2PAcceptor acceptor
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final SearchManager searchManager
|
final SearchManager searchManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
final ConnectionEstablisher establisher
|
final ConnectionEstablisher establisher
|
||||||
|
|
||||||
final ExecutorService acceptorThread
|
final ExecutorService acceptorThread
|
||||||
final ExecutorService handshakerThreads
|
final ExecutorService handshakerThreads
|
||||||
|
|
||||||
private volatile shutdown
|
private volatile shutdown
|
||||||
|
|
||||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||||
ConnectionEstablisher establisher) {
|
ConnectionEstablisher establisher) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.acceptor = acceptor
|
this.acceptor = acceptor
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.searchManager = searchManager
|
this.searchManager = searchManager
|
||||||
this.uploadManager = uploadManager
|
this.uploadManager = uploadManager
|
||||||
this.establisher = establisher
|
this.establisher = establisher
|
||||||
|
|
||||||
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("acceptor")
|
rv.setName("acceptor")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakerThreads = Executors.newCachedThreadPool { r ->
|
handshakerThreads = Executors.newCachedThreadPool { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
acceptorThread.execute({acceptLoop()} as Runnable)
|
acceptorThread.execute({acceptLoop()} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
shutdown = true
|
shutdown = true
|
||||||
acceptorThread.shutdownNow()
|
acceptorThread.shutdownNow()
|
||||||
handshakerThreads.shutdownNow()
|
handshakerThreads.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptLoop() {
|
private void acceptLoop() {
|
||||||
try {
|
try {
|
||||||
while(true) {
|
while(true) {
|
||||||
def incoming = acceptor.accept()
|
def incoming = acceptor.accept()
|
||||||
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
||||||
switch(trustService.getLevel(incoming.destination)) {
|
switch(trustService.getLevel(incoming.destination)) {
|
||||||
case TrustLevel.TRUSTED : break
|
case TrustLevel.TRUSTED : break
|
||||||
case TrustLevel.NEUTRAL :
|
case TrustLevel.NEUTRAL :
|
||||||
if (settings.allowUntrusted())
|
if (settings.allowUntrusted())
|
||||||
break
|
break
|
||||||
case TrustLevel.DISTRUSTED :
|
case TrustLevel.DISTRUSTED :
|
||||||
log.info("Disallowing distrusted connection")
|
log.info("Disallowing distrusted connection")
|
||||||
incoming.close()
|
incoming.close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "exception in accept loop",e)
|
log.log(Level.WARNING, "exception in accept loop",e)
|
||||||
if (!shutdown)
|
if (!shutdown)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIncoming(Endpoint e) {
|
private void processIncoming(Endpoint e) {
|
||||||
InputStream is = e.inputStream
|
InputStream is = e.inputStream
|
||||||
try {
|
try {
|
||||||
int read = is.read()
|
int read = is.read()
|
||||||
switch(read) {
|
switch(read) {
|
||||||
case (byte)'M':
|
case (byte)'M':
|
||||||
if (settings.isLeaf())
|
if (settings.isLeaf())
|
||||||
throw new IOException("Incoming connection as leaf")
|
throw new IOException("Incoming connection as leaf")
|
||||||
processMuWire(e)
|
processMuWire(e)
|
||||||
break
|
break
|
||||||
case (byte)'G':
|
case (byte)'G':
|
||||||
processGET(e)
|
processGET(e)
|
||||||
break
|
break
|
||||||
case (byte)'H':
|
case (byte)'H':
|
||||||
processHashList(e)
|
processHashList(e)
|
||||||
break
|
break
|
||||||
@@ -129,76 +129,76 @@ class ConnectionAcceptor {
|
|||||||
case (byte)'T':
|
case (byte)'T':
|
||||||
processTRUST(e)
|
processTRUST(e)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid read $read")
|
throw new Exception("Invalid read $read")
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.log(Level.WARNING, "incoming connection failed",ex)
|
log.log(Level.WARNING, "incoming connection failed",ex)
|
||||||
e.close()
|
e.close()
|
||||||
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processMuWire(Endpoint e) {
|
private void processMuWire(Endpoint e) {
|
||||||
byte[] uWire = "uWire ".bytes
|
byte[] uWire = "uWire ".bytes
|
||||||
for (int i = 0; i < uWire.length; i++) {
|
for (int i = 0; i < uWire.length; i++) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != uWire[i]) {
|
if (read != uWire[i]) {
|
||||||
throw new IOException("unexpected value $read at position $i")
|
throw new IOException("unexpected value $read at position $i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] type = new byte[4]
|
byte[] type = new byte[4]
|
||||||
DataInputStream dis = new DataInputStream(e.inputStream)
|
DataInputStream dis = new DataInputStream(e.inputStream)
|
||||||
dis.readFully(type)
|
dis.readFully(type)
|
||||||
|
|
||||||
if (type == "leaf".bytes)
|
if (type == "leaf".bytes)
|
||||||
handleIncoming(e, true)
|
handleIncoming(e, true)
|
||||||
else if (type == "peer".bytes)
|
else if (type == "peer".bytes)
|
||||||
handleIncoming(e, false)
|
handleIncoming(e, false)
|
||||||
else
|
else
|
||||||
throw new IOException("unknown connection type $type")
|
throw new IOException("unknown connection type $type")
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIncoming(Endpoint e, boolean leaf) {
|
private void handleIncoming(Endpoint e, boolean leaf) {
|
||||||
boolean accept = !manager.isConnected(e.destination) &&
|
boolean accept = !manager.isConnected(e.destination) &&
|
||||||
!establisher.isInProgress(e.destination) &&
|
!establisher.isInProgress(e.destination) &&
|
||||||
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
||||||
if (accept) {
|
if (accept) {
|
||||||
log.info("accepting connection, leaf:$leaf")
|
log.info("accepting connection, leaf:$leaf")
|
||||||
e.outputStream.write("OK".bytes)
|
e.outputStream.write("OK".bytes)
|
||||||
e.outputStream.flush()
|
e.outputStream.flush()
|
||||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.SUCCESSFUL))
|
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
} else {
|
} else {
|
||||||
log.info("rejecting connection, leaf:$leaf")
|
log.info("rejecting connection, leaf:$leaf")
|
||||||
e.outputStream.write("REJECT".bytes)
|
e.outputStream.write("REJECT".bytes)
|
||||||
def hosts = hostCache.getGoodHosts(10)
|
def hosts = hostCache.getGoodHosts(10)
|
||||||
if (!hosts.isEmpty()) {
|
if (!hosts.isEmpty()) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
def os = new DataOutputStream(e.outputStream)
|
def os = new DataOutputStream(e.outputStream)
|
||||||
os.writeShort(json.bytes.length)
|
os.writeShort(json.bytes.length)
|
||||||
os.write(json.bytes)
|
os.write(json.bytes)
|
||||||
}
|
}
|
||||||
e.outputStream.flush()
|
e.outputStream.flush()
|
||||||
e.close()
|
e.close()
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void processGET(Endpoint e) {
|
private void processGET(Endpoint e) {
|
||||||
byte[] et = new byte[3]
|
byte[] et = new byte[3]
|
||||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
dis.readFully(et)
|
dis.readFully(et)
|
||||||
if (et != "ET ".getBytes(StandardCharsets.US_ASCII))
|
if (et != "ET ".getBytes(StandardCharsets.US_ASCII))
|
||||||
throw new IOException("Invalid GET connection")
|
throw new IOException("Invalid GET connection")
|
||||||
uploadManager.processGET(e)
|
uploadManager.processGET(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processHashList(Endpoint e) {
|
private void processHashList(Endpoint e) {
|
||||||
byte[] ashList = new byte[8]
|
byte[] ashList = new byte[8]
|
||||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
@@ -207,7 +207,7 @@ class ConnectionAcceptor {
|
|||||||
throw new IOException("Invalid HASHLIST connection")
|
throw new IOException("Invalid HASHLIST connection")
|
||||||
uploadManager.processHashList(e)
|
uploadManager.processHashList(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processPOST(final Endpoint e) throws IOException {
|
private void processPOST(final Endpoint e) throws IOException {
|
||||||
byte [] ost = new byte[4]
|
byte [] ost = new byte[4]
|
||||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
@@ -246,7 +246,7 @@ class ConnectionAcceptor {
|
|||||||
e.close()
|
e.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processTRUST(Endpoint e) {
|
private void processTRUST(Endpoint e) {
|
||||||
byte[] RUST = new byte[6]
|
byte[] RUST = new byte[6]
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
@@ -255,7 +255,7 @@ class ConnectionAcceptor {
|
|||||||
throw new IOException("Invalid TRUST connection")
|
throw new IOException("Invalid TRUST connection")
|
||||||
String header
|
String header
|
||||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||||
|
|
||||||
OutputStream os = e.getOutputStream()
|
OutputStream os = e.getOutputStream()
|
||||||
if (!settings.allowTrustLists) {
|
if (!settings.allowTrustLists) {
|
||||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -263,7 +263,7 @@ class ConnectionAcceptor {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
List<Persona> good = new ArrayList<>(trustService.good.values())
|
List<Persona> good = new ArrayList<>(trustService.good.values())
|
||||||
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
||||||
@@ -273,7 +273,7 @@ class ConnectionAcceptor {
|
|||||||
good.each {
|
good.each {
|
||||||
it.write(dos)
|
it.write(dos)
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
||||||
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
||||||
bad = bad.subList(0, size)
|
bad = bad.subList(0, size)
|
||||||
@@ -281,9 +281,9 @@ class ConnectionAcceptor {
|
|||||||
bad.each {
|
bad.each {
|
||||||
it.write(dos)
|
it.write(dos)
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.flush()
|
dos.flush()
|
||||||
e.close()
|
e.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,164 +21,164 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class ConnectionEstablisher {
|
class ConnectionEstablisher {
|
||||||
|
|
||||||
private static final int CONCURRENT = 4
|
|
||||||
|
|
||||||
final EventBus eventBus
|
private static final int CONCURRENT = 4
|
||||||
final I2PConnector i2pConnector
|
|
||||||
final MuWireSettings settings
|
final EventBus eventBus
|
||||||
final ConnectionManager connectionManager
|
final I2PConnector i2pConnector
|
||||||
final HostCache hostCache
|
final MuWireSettings settings
|
||||||
|
final ConnectionManager connectionManager
|
||||||
final Timer timer
|
final HostCache hostCache
|
||||||
final ExecutorService executor
|
|
||||||
|
final Timer timer
|
||||||
final Set inProgress = new ConcurrentHashSet()
|
final ExecutorService executor
|
||||||
|
|
||||||
|
final Set inProgress = new ConcurrentHashSet()
|
||||||
|
|
||||||
ConnectionEstablisher(){}
|
ConnectionEstablisher(){}
|
||||||
|
|
||||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
|
||||||
ConnectionManager connectionManager, HostCache hostCache) {
|
|
||||||
this.eventBus = eventBus
|
|
||||||
this.i2pConnector = i2pConnector
|
|
||||||
this.settings = settings
|
|
||||||
this.connectionManager = connectionManager
|
|
||||||
this.hostCache = hostCache
|
|
||||||
timer = new Timer("connection-timer",true)
|
|
||||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
|
||||||
def rv = new Thread(r)
|
|
||||||
rv.setDaemon(true)
|
|
||||||
rv.setName("connector-${System.currentTimeMillis()}")
|
|
||||||
rv
|
|
||||||
} as ThreadFactory)
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
timer.cancel()
|
|
||||||
executor.shutdownNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
|
||||||
if (!connectionManager.needsConnections())
|
|
||||||
return
|
|
||||||
if (inProgress.size() >= CONCURRENT)
|
|
||||||
return
|
|
||||||
|
|
||||||
def toTry = null
|
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||||
for (int i = 0; i < 5; i++) {
|
ConnectionManager connectionManager, HostCache hostCache) {
|
||||||
toTry = hostCache.getHosts(1)
|
this.eventBus = eventBus
|
||||||
if (toTry.isEmpty())
|
this.i2pConnector = i2pConnector
|
||||||
return
|
this.settings = settings
|
||||||
toTry = toTry[0]
|
this.connectionManager = connectionManager
|
||||||
if (!connectionManager.isConnected(toTry) &&
|
this.hostCache = hostCache
|
||||||
!inProgress.contains(toTry)) {
|
timer = new Timer("connection-timer",true)
|
||||||
break
|
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||||
}
|
def rv = new Thread(r)
|
||||||
}
|
rv.setDaemon(true)
|
||||||
if (toTry == null)
|
rv.setName("connector-${System.currentTimeMillis()}")
|
||||||
return
|
rv
|
||||||
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
} as ThreadFactory)
|
||||||
executor.execute({connect(toTry)} as Runnable)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void connect(Destination toTry) {
|
|
||||||
log.info("starting connect to ${toTry.toBase32()}")
|
|
||||||
try {
|
|
||||||
def endpoint = i2pConnector.connect(toTry)
|
|
||||||
log.info("successful transport connect to ${toTry.toBase32()}")
|
|
||||||
|
|
||||||
// outgoing handshake
|
|
||||||
endpoint.outputStream.write("MuWire ".bytes)
|
|
||||||
def type = settings.isLeaf() ? "leaf" : "peer"
|
|
||||||
endpoint.outputStream.write(type.bytes)
|
|
||||||
endpoint.outputStream.flush()
|
|
||||||
|
|
||||||
InputStream is = endpoint.inputStream
|
|
||||||
int read = is.read()
|
|
||||||
if (read == -1) {
|
|
||||||
fail endpoint
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch(read) {
|
|
||||||
case (byte)'O': readK(endpoint); break
|
|
||||||
case (byte)'R': readEJECT(endpoint); break
|
|
||||||
default :
|
|
||||||
log.warning("unknown response $read")
|
|
||||||
fail endpoint
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
|
||||||
def endpoint = new Endpoint(toTry, null, null, null)
|
|
||||||
fail(endpoint)
|
|
||||||
} finally {
|
|
||||||
inProgress.remove(toTry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
|
||||||
endpoint.close()
|
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readK(Endpoint e) {
|
|
||||||
int read = e.inputStream.read()
|
|
||||||
if (read != 'K') {
|
|
||||||
log.warning("unknown response after O: $read")
|
|
||||||
fail e
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("connection to ${e.destination.toBase32()} established")
|
|
||||||
|
|
||||||
// wrap into deflater / inflater streams and publish
|
|
||||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readEJECT(Endpoint e) {
|
|
||||||
byte[] eject = "EJECT".bytes
|
|
||||||
for (int i = 0; i < eject.length; i++) {
|
|
||||||
int read = e.inputStream.read()
|
|
||||||
if (read != eject[i]) {
|
|
||||||
log.warning("Unknown response after R at position $i")
|
|
||||||
fail e
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("connection to ${e.destination.toBase32()} rejected")
|
|
||||||
|
|
||||||
|
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
|
||||||
try {
|
|
||||||
DataInputStream dais = new DataInputStream(e.inputStream)
|
|
||||||
int payloadSize = dais.readUnsignedShort()
|
|
||||||
byte[] payload = new byte[payloadSize]
|
|
||||||
dais.readFully(payload)
|
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
void start() {
|
||||||
json = json.parse(payload)
|
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
if (json.tryHosts == null) {
|
void stop() {
|
||||||
log.warning("post-rejection json didn't contain hosts to try")
|
timer.cancel()
|
||||||
return
|
executor.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void connectIfNeeded() {
|
||||||
|
if (!connectionManager.needsConnections())
|
||||||
|
return
|
||||||
|
if (inProgress.size() >= CONCURRENT)
|
||||||
|
return
|
||||||
|
|
||||||
|
def toTry = null
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
toTry = hostCache.getHosts(1)
|
||||||
|
if (toTry.isEmpty())
|
||||||
|
return
|
||||||
|
toTry = toTry[0]
|
||||||
|
if (!connectionManager.isConnected(toTry) &&
|
||||||
|
!inProgress.contains(toTry)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toTry == null)
|
||||||
|
return
|
||||||
|
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
||||||
|
executor.execute({connect(toTry)} as Runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(Destination toTry) {
|
||||||
|
log.info("starting connect to ${toTry.toBase32()}")
|
||||||
|
try {
|
||||||
|
def endpoint = i2pConnector.connect(toTry)
|
||||||
|
log.info("successful transport connect to ${toTry.toBase32()}")
|
||||||
|
|
||||||
|
// outgoing handshake
|
||||||
|
endpoint.outputStream.write("MuWire ".bytes)
|
||||||
|
def type = settings.isLeaf() ? "leaf" : "peer"
|
||||||
|
endpoint.outputStream.write(type.bytes)
|
||||||
|
endpoint.outputStream.flush()
|
||||||
|
|
||||||
|
InputStream is = endpoint.inputStream
|
||||||
|
int read = is.read()
|
||||||
|
if (read == -1) {
|
||||||
|
fail endpoint
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch(read) {
|
||||||
|
case (byte)'O': readK(endpoint); break
|
||||||
|
case (byte)'R': readEJECT(endpoint); break
|
||||||
|
default :
|
||||||
|
log.warning("unknown response $read")
|
||||||
|
fail endpoint
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
||||||
|
def endpoint = new Endpoint(toTry, null, null, null)
|
||||||
|
fail(endpoint)
|
||||||
|
} finally {
|
||||||
|
inProgress.remove(toTry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fail(Endpoint endpoint) {
|
||||||
|
endpoint.close()
|
||||||
|
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readK(Endpoint e) {
|
||||||
|
int read = e.inputStream.read()
|
||||||
|
if (read != 'K') {
|
||||||
|
log.warning("unknown response after O: $read")
|
||||||
|
fail e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("connection to ${e.destination.toBase32()} established")
|
||||||
|
|
||||||
|
// wrap into deflater / inflater streams and publish
|
||||||
|
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||||
|
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readEJECT(Endpoint e) {
|
||||||
|
byte[] eject = "EJECT".bytes
|
||||||
|
for (int i = 0; i < eject.length; i++) {
|
||||||
|
int read = e.inputStream.read()
|
||||||
|
if (read != eject[i]) {
|
||||||
|
log.warning("Unknown response after R at position $i")
|
||||||
|
fail e
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("connection to ${e.destination.toBase32()} rejected")
|
||||||
|
|
||||||
|
|
||||||
|
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
||||||
|
try {
|
||||||
|
DataInputStream dais = new DataInputStream(e.inputStream)
|
||||||
|
int payloadSize = dais.readUnsignedShort()
|
||||||
|
byte[] payload = new byte[payloadSize]
|
||||||
|
dais.readFully(payload)
|
||||||
|
|
||||||
|
def json = new JsonSlurper()
|
||||||
|
json = json.parse(payload)
|
||||||
|
|
||||||
|
if (json.tryHosts == null) {
|
||||||
|
log.warning("post-rejection json didn't contain hosts to try")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.tryHosts.asList().each {
|
||||||
|
Destination suggested = new Destination(it)
|
||||||
|
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||||
|
} finally {
|
||||||
|
// the end
|
||||||
|
e.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
json.tryHosts.asList().each {
|
|
||||||
Destination suggested = new Destination(it)
|
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
|
||||||
}
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
|
||||||
} finally {
|
|
||||||
// the end
|
|
||||||
e.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInProgress(Destination d) {
|
public boolean isInProgress(Destination d) {
|
||||||
inProgress.contains(d)
|
inProgress.contains(d)
|
||||||
}
|
}
|
||||||
|
@@ -6,14 +6,14 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class ConnectionEvent extends Event {
|
class ConnectionEvent extends Event {
|
||||||
|
|
||||||
Endpoint endpoint
|
Endpoint endpoint
|
||||||
boolean incoming
|
boolean incoming
|
||||||
Boolean leaf // can be null if uknown
|
Boolean leaf // can be null if uknown
|
||||||
ConnectionAttemptStatus status
|
ConnectionAttemptStatus status
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,64 +11,64 @@ import com.muwire.core.trust.TrustLevel
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
abstract class ConnectionManager {
|
abstract class ConnectionManager {
|
||||||
|
|
||||||
private static final int PING_TIME = 20000
|
|
||||||
|
|
||||||
final EventBus eventBus
|
private static final int PING_TIME = 20000
|
||||||
|
|
||||||
private final Timer timer
|
final EventBus eventBus
|
||||||
|
|
||||||
protected final HostCache hostCache
|
private final Timer timer
|
||||||
|
|
||||||
|
protected final HostCache hostCache
|
||||||
protected final Persona me
|
protected final Persona me
|
||||||
protected final MuWireSettings settings
|
protected final MuWireSettings settings
|
||||||
|
|
||||||
ConnectionManager() {}
|
ConnectionManager() {}
|
||||||
|
|
||||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.timer = new Timer("connections-pinger",true)
|
this.timer = new Timer("connections-pinger",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
getConnections().each { it.close() }
|
getConnections().each { it.close() }
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
if (e.level == TrustLevel.DISTRUSTED)
|
if (e.level == TrustLevel.DISTRUSTED)
|
||||||
drop(e.persona.destination)
|
drop(e.persona.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void drop(Destination d)
|
abstract void drop(Destination d)
|
||||||
|
|
||||||
abstract Collection<Connection> getConnections()
|
abstract Collection<Connection> getConnections()
|
||||||
|
|
||||||
protected abstract int getDesiredConnections()
|
protected abstract int getDesiredConnections()
|
||||||
|
|
||||||
boolean needsConnections() {
|
boolean needsConnections() {
|
||||||
return getConnections().size() < getDesiredConnections()
|
return getConnections().size() < getDesiredConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract boolean isConnected(Destination d)
|
abstract boolean isConnected(Destination d)
|
||||||
|
|
||||||
abstract void onConnectionEvent(ConnectionEvent e)
|
abstract void onConnectionEvent(ConnectionEvent e)
|
||||||
|
|
||||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||||
|
|
||||||
abstract void shutdown()
|
abstract void shutdown()
|
||||||
|
|
||||||
protected void sendPings() {
|
protected void sendPings() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
getConnections().each {
|
getConnections().each {
|
||||||
if (now - it.lastPingSentTime > PING_TIME)
|
if (now - it.lastPingSentTime > PING_TIME)
|
||||||
it.sendPing()
|
it.sendPing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,11 +5,11 @@ import com.muwire.core.Event
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class DisconnectionEvent extends Event {
|
class DisconnectionEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
|
||||||
|
|
||||||
@Override
|
Destination destination
|
||||||
public String toString() {
|
|
||||||
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
@Override
|
||||||
}
|
public String toString() {
|
||||||
|
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,39 +8,39 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Endpoint implements Closeable {
|
class Endpoint implements Closeable {
|
||||||
final Destination destination
|
final Destination destination
|
||||||
final InputStream inputStream
|
final InputStream inputStream
|
||||||
final OutputStream outputStream
|
final OutputStream outputStream
|
||||||
final def toClose
|
final def toClose
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean()
|
private final AtomicBoolean closed = new AtomicBoolean()
|
||||||
|
|
||||||
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.inputStream = inputStream
|
this.inputStream = inputStream
|
||||||
this.outputStream = outputStream
|
this.outputStream = outputStream
|
||||||
this.toClose = toClose
|
this.toClose = toClose
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!closed.compareAndSet(false, true)) {
|
if (!closed.compareAndSet(false, true)) {
|
||||||
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {inputStream.close()} catch (Exception ignore) {}
|
try {inputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
try {outputStream.close()} catch (Exception ignore) {}
|
try {outputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (toClose != null) {
|
if (toClose != null) {
|
||||||
try {toClose.reset()} catch (Exception ignore) {}
|
try {toClose.reset()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"destination: ${destination.toBase32()}"
|
"destination: ${destination.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,18 +5,18 @@ import net.i2p.client.streaming.I2PSocketManager
|
|||||||
|
|
||||||
class I2PAcceptor {
|
class I2PAcceptor {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
final I2PServerSocket serverSocket
|
final I2PServerSocket serverSocket
|
||||||
|
|
||||||
I2PAcceptor() {}
|
I2PAcceptor() {}
|
||||||
|
|
||||||
I2PAcceptor(I2PSocketManager socketManager) {
|
I2PAcceptor(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
this.serverSocket = socketManager.getServerSocket()
|
this.serverSocket = socketManager.getServerSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint accept() {
|
Endpoint accept() {
|
||||||
def socket = serverSocket.accept()
|
def socket = serverSocket.accept()
|
||||||
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,18 +4,18 @@ import net.i2p.client.streaming.I2PSocketManager
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class I2PConnector {
|
class I2PConnector {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
|
|
||||||
I2PConnector() {}
|
I2PConnector() {}
|
||||||
|
|
||||||
I2PConnector(I2PSocketManager socketManager) {
|
I2PConnector(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint connect(Destination dest) {
|
Endpoint connect(Destination dest) {
|
||||||
def socket = socketManager.connect(dest)
|
def socket = socketManager.connect(dest)
|
||||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,27 +11,27 @@ import com.muwire.core.trust.TrustService
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection where the other side is a leaf.
|
* Connection where the other side is a leaf.
|
||||||
* Such connections can only be incoming.
|
* Such connections can only be incoming.
|
||||||
* @author zab
|
* @author zab
|
||||||
*/
|
*/
|
||||||
class LeafConnection extends Connection {
|
class LeafConnection extends Connection {
|
||||||
|
|
||||||
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
||||||
TrustService trustService, MuWireSettings settings) {
|
TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
}
|
||||||
protected void write(Object message) {
|
|
||||||
// TODO Auto-generated method stub
|
@Override
|
||||||
|
protected void write(Object message) {
|
||||||
}
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,68 +13,68 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class LeafConnectionManager extends ConnectionManager {
|
class LeafConnectionManager extends ConnectionManager {
|
||||||
|
|
||||||
final int maxConnections
|
final int maxConnections
|
||||||
|
|
||||||
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
||||||
|
|
||||||
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
||||||
HostCache hostCache, MuWireSettings settings) {
|
HostCache hostCache, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxConnections = maxConnections
|
this.maxConnections = maxConnections
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
if (me.destination == e.receivedOn) {
|
if (me.destination == e.receivedOn) {
|
||||||
connections.values().each { it.sendQuery(e) }
|
connections.values().each { it.sendQuery(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
connections.values()
|
connections.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxConnections;
|
return maxConnections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
connections.containsKey(d)
|
connections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
|
if (e.incoming || e.leaf) {
|
||||||
|
log.severe("Got inconsistent event as a leaf! $e")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
|
return
|
||||||
|
|
||||||
|
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||||
|
connections.put(e.endpoint.destination, c)
|
||||||
|
c.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
|
def removed = connections.remove(e.destination)
|
||||||
|
if (removed == null)
|
||||||
|
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
|
||||||
if (e.incoming || e.leaf) {
|
|
||||||
log.severe("Got inconsistent event as a leaf! $e")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
|
||||||
return
|
|
||||||
|
|
||||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
|
||||||
connections.put(e.endpoint.destination, c)
|
|
||||||
c.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
|
||||||
def removed = connections.remove(e.destination)
|
|
||||||
if (removed == null)
|
|
||||||
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,63 +20,63 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
@Log
|
@Log
|
||||||
class PeerConnection extends Connection {
|
class PeerConnection extends Connection {
|
||||||
|
|
||||||
private final DataInputStream dis
|
|
||||||
private final DataOutputStream dos
|
|
||||||
|
|
||||||
private final byte[] readHeader = new byte[3]
|
|
||||||
private final byte[] writeHeader = new byte[3]
|
|
||||||
|
|
||||||
private final JsonSlurper slurper = new JsonSlurper()
|
|
||||||
|
|
||||||
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
private final DataInputStream dis
|
||||||
boolean incoming, HostCache hostCache, TrustService trustService,
|
private final DataOutputStream dos
|
||||||
|
|
||||||
|
private final byte[] readHeader = new byte[3]
|
||||||
|
private final byte[] writeHeader = new byte[3]
|
||||||
|
|
||||||
|
private final JsonSlurper slurper = new JsonSlurper()
|
||||||
|
|
||||||
|
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||||
|
boolean incoming, HostCache hostCache, TrustService trustService,
|
||||||
MuWireSettings settings) {
|
MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
||||||
this.dis = new DataInputStream(endpoint.inputStream)
|
this.dis = new DataInputStream(endpoint.inputStream)
|
||||||
this.dos = new DataOutputStream(endpoint.outputStream)
|
this.dos = new DataOutputStream(endpoint.outputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
dis.readFully(readHeader)
|
dis.readFully(readHeader)
|
||||||
int length = DataUtil.readLength(readHeader)
|
int length = DataUtil.readLength(readHeader)
|
||||||
log.fine("$name read length $length")
|
log.fine("$name read length $length")
|
||||||
|
|
||||||
byte[] payload = new byte[length]
|
byte[] payload = new byte[length]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
|
|
||||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||||
// TODO process binary
|
// TODO process binary
|
||||||
} else {
|
} else {
|
||||||
def json = slurper.parse(payload)
|
def json = slurper.parse(payload)
|
||||||
if (json.type == null)
|
if (json.type == null)
|
||||||
throw new Exception("missing json type")
|
throw new Exception("missing json type")
|
||||||
switch(json.type) {
|
switch(json.type) {
|
||||||
case "Ping" : handlePing(); break;
|
case "Ping" : handlePing(); break;
|
||||||
case "Pong" : handlePong(json); break;
|
case "Pong" : handlePong(json); break;
|
||||||
case "Search": handleSearch(json); break
|
case "Search": handleSearch(json); break
|
||||||
default :
|
default :
|
||||||
throw new Exception("unknown json type ${json.type}")
|
throw new Exception("unknown json type ${json.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
byte[] payload
|
byte[] payload
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
payload = JsonOutput.toJson(message).bytes
|
payload = JsonOutput.toJson(message).bytes
|
||||||
DataUtil.packHeader(payload.length, writeHeader)
|
DataUtil.packHeader(payload.length, writeHeader)
|
||||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||||
writeHeader[0] &= (byte)0x7F
|
writeHeader[0] &= (byte)0x7F
|
||||||
} else {
|
} else {
|
||||||
// TODO: write binary
|
// TODO: write binary
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.write(writeHeader)
|
dos.write(writeHeader)
|
||||||
dos.write(payload)
|
dos.write(payload)
|
||||||
dos.flush()
|
dos.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,30 +17,30 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
class UltrapeerConnection extends Connection {
|
class UltrapeerConnection extends Connection {
|
||||||
|
|
||||||
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
||||||
super(eventBus, endpoint, false, hostCache, trustService)
|
super(eventBus, endpoint, false, hostCache, trustService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
}
|
||||||
protected void write(Object message) {
|
|
||||||
if (message instanceof Map) {
|
|
||||||
writeJsonMessage(message)
|
|
||||||
} else {
|
|
||||||
writeBinaryMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeJsonMessage(def message) {
|
@Override
|
||||||
|
protected void write(Object message) {
|
||||||
}
|
if (message instanceof Map) {
|
||||||
|
writeJsonMessage(message)
|
||||||
private void writeBinaryMessage(def message) {
|
} else {
|
||||||
|
writeBinaryMessage(message)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeJsonMessage(def message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeBinaryMessage(def message) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,28 +15,28 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class UltrapeerConnectionManager extends ConnectionManager {
|
class UltrapeerConnectionManager extends ConnectionManager {
|
||||||
|
|
||||||
final int maxPeers, maxLeafs
|
|
||||||
final TrustService trustService
|
|
||||||
|
|
||||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
|
||||||
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
|
||||||
|
|
||||||
UltrapeerConnectionManager() {}
|
|
||||||
|
|
||||||
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
final int maxPeers, maxLeafs
|
||||||
|
final TrustService trustService
|
||||||
|
|
||||||
|
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||||
|
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
||||||
|
|
||||||
|
UltrapeerConnectionManager() {}
|
||||||
|
|
||||||
|
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
||||||
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxPeers = maxPeers
|
this.maxPeers = maxPeers
|
||||||
this.maxLeafs = maxLeafs
|
this.maxLeafs = maxLeafs
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
peerConnections.get(d)?.close()
|
peerConnections.get(d)?.close()
|
||||||
leafConnections.get(d)?.close()
|
leafConnections.get(d)?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
forwardQueryToLeafs(e)
|
forwardQueryToLeafs(e)
|
||||||
if (!e.firstHop)
|
if (!e.firstHop)
|
||||||
@@ -50,58 +50,58 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
||||||
rv.addAll(peerConnections.values())
|
rv.addAll(peerConnections.values())
|
||||||
rv.addAll(leafConnections.values())
|
rv.addAll(leafConnections.values())
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLeafSlots() {
|
boolean hasLeafSlots() {
|
||||||
leafConnections.size() < maxLeafs
|
leafConnections.size() < maxLeafs
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPeerSlots() {
|
boolean hasPeerSlots() {
|
||||||
peerConnections.size() < maxPeers
|
peerConnections.size() < maxPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxPeers / 2;
|
return maxPeers / 2;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
|
if (!e.incoming && e.leaf) {
|
||||||
|
log.severe("Inconsistent event $e")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
|
return
|
||||||
|
|
||||||
|
Connection c = e.leaf ?
|
||||||
|
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
||||||
|
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
||||||
|
def map = e.leaf ? leafConnections : peerConnections
|
||||||
|
map.put(e.endpoint.destination, c)
|
||||||
|
c.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
|
def removed = peerConnections.remove(e.destination)
|
||||||
|
if (removed == null)
|
||||||
|
removed = leafConnections.remove(e.destination)
|
||||||
|
if (removed == null)
|
||||||
|
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
|
||||||
if (!e.incoming && e.leaf) {
|
|
||||||
log.severe("Inconsistent event $e")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
|
||||||
return
|
|
||||||
|
|
||||||
Connection c = e.leaf ?
|
|
||||||
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
|
||||||
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
|
||||||
def map = e.leaf ? leafConnections : peerConnections
|
|
||||||
map.put(e.endpoint.destination, c)
|
|
||||||
c.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
|
||||||
def removed = peerConnections.remove(e.destination)
|
|
||||||
if (removed == null)
|
|
||||||
removed = leafConnections.remove(e.destination)
|
|
||||||
if (removed == null)
|
|
||||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
peerConnections.values().stream().parallel().forEach({v -> v.close()})
|
peerConnections.values().stream().parallel().forEach({v -> v.close()})
|
||||||
@@ -109,8 +109,8 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
peerConnections.clear()
|
peerConnections.clear()
|
||||||
leafConnections.clear()
|
leafConnections.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
void forwardQueryToLeafs(QueryEvent e) {
|
void forwardQueryToLeafs(QueryEvent e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class ContentControlEvent extends Event {
|
||||||
|
String term
|
||||||
|
boolean regex
|
||||||
|
boolean add
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
import net.i2p.util.ConcurrentHashSet
|
||||||
|
|
||||||
|
class ContentManager {
|
||||||
|
|
||||||
|
Set<Matcher> matchers = new ConcurrentHashSet()
|
||||||
|
|
||||||
|
void onContentControlEvent(ContentControlEvent e) {
|
||||||
|
Matcher m
|
||||||
|
if (e.regex)
|
||||||
|
m = new RegexMatcher(e.term)
|
||||||
|
else
|
||||||
|
m = new KeywordMatcher(e.term)
|
||||||
|
if (e.add)
|
||||||
|
matchers.add(m)
|
||||||
|
else
|
||||||
|
matchers.remove(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onQueryEvent(QueryEvent e) {
|
||||||
|
if (e.searchEvent.searchTerms == null)
|
||||||
|
return
|
||||||
|
matchers.each { it.process(e) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
class KeywordMatcher extends Matcher {
|
||||||
|
private final String keyword
|
||||||
|
KeywordMatcher(String keyword) {
|
||||||
|
this.keyword = keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> searchTerms) {
|
||||||
|
boolean found = false
|
||||||
|
searchTerms.each {
|
||||||
|
if (keyword == it)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
keyword.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof KeywordMatcher))
|
||||||
|
return false
|
||||||
|
KeywordMatcher other = (KeywordMatcher) o
|
||||||
|
keyword.equals(other.keyword)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class Match {
|
||||||
|
Persona persona
|
||||||
|
String [] keywords
|
||||||
|
long timestamp
|
||||||
|
}
|
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
abstract class Matcher {
|
||||||
|
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
|
||||||
|
final Set<UUID> uuids = new HashSet<>()
|
||||||
|
|
||||||
|
protected abstract boolean match(List<String> searchTerms);
|
||||||
|
|
||||||
|
public abstract String getTerm();
|
||||||
|
|
||||||
|
public void process(QueryEvent qe) {
|
||||||
|
def terms = qe.searchEvent.searchTerms
|
||||||
|
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
class RegexMatcher extends Matcher {
|
||||||
|
private final Pattern pattern
|
||||||
|
RegexMatcher(String pattern) {
|
||||||
|
this.pattern = Pattern.compile(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> keywords) {
|
||||||
|
String combined = keywords.join(" ")
|
||||||
|
return pattern.matcher(combined).find()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
pattern.pattern()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
pattern.pattern().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof RegexMatcher))
|
||||||
|
return false
|
||||||
|
RegexMatcher other = (RegexMatcher) o
|
||||||
|
pattern.pattern() == other.pattern.pattern()
|
||||||
|
}
|
||||||
|
}
|
@@ -21,5 +21,5 @@ class BadHashException extends Exception {
|
|||||||
public BadHashException(Throwable cause) {
|
public BadHashException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ import java.util.concurrent.Executor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
public class DownloadManager {
|
public class DownloadManager {
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final TrustService trustService
|
private final TrustService trustService
|
||||||
private final MeshManager meshManager
|
private final MeshManager meshManager
|
||||||
@@ -36,9 +36,9 @@ public class DownloadManager {
|
|||||||
private final Executor executor
|
private final Executor executor
|
||||||
private final File incompletes, home
|
private final File incompletes, home
|
||||||
private final Persona me
|
private final Persona me
|
||||||
|
|
||||||
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
public DownloadManager(EventBus eventBus, TrustService trustService, MeshManager meshManager, MuWireSettings muSettings,
|
public DownloadManager(EventBus eventBus, TrustService trustService, MeshManager meshManager, MuWireSettings muSettings,
|
||||||
I2PConnector connector, File home, Persona me) {
|
I2PConnector connector, File home, Persona me) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
@@ -49,9 +49,9 @@ public class DownloadManager {
|
|||||||
this.incompletes = new File(home,"incompletes")
|
this.incompletes = new File(home,"incompletes")
|
||||||
this.home = home
|
this.home = home
|
||||||
this.me = me
|
this.me = me
|
||||||
|
|
||||||
incompletes.mkdir()
|
incompletes.mkdir()
|
||||||
|
|
||||||
this.executor = Executors.newCachedThreadPool({ r ->
|
this.executor = Executors.newCachedThreadPool({ r ->
|
||||||
Thread rv = new Thread(r)
|
Thread rv = new Thread(r)
|
||||||
rv.setName("download-worker")
|
rv.setName("download-worker")
|
||||||
@@ -59,23 +59,23 @@ public class DownloadManager {
|
|||||||
rv
|
rv
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void onUIDownloadEvent(UIDownloadEvent e) {
|
public void onUIDownloadEvent(UIDownloadEvent e) {
|
||||||
|
|
||||||
def size = e.result[0].size
|
def size = e.result[0].size
|
||||||
def infohash = e.result[0].infohash
|
def infohash = e.result[0].infohash
|
||||||
def pieceSize = e.result[0].pieceSize
|
def pieceSize = e.result[0].pieceSize
|
||||||
|
|
||||||
Set<Destination> destinations = new HashSet<>()
|
Set<Destination> destinations = new HashSet<>()
|
||||||
e.result.each {
|
e.result.each {
|
||||||
destinations.add(it.sender.destination)
|
destinations.add(it.sender.destination)
|
||||||
}
|
}
|
||||||
destinations.addAll(e.sources)
|
destinations.addAll(e.sources)
|
||||||
destinations.remove(me.destination)
|
destinations.remove(me.destination)
|
||||||
|
|
||||||
Pieces pieces = getPieces(infohash, size, pieceSize)
|
Pieces pieces = getPieces(infohash, size, pieceSize)
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
||||||
infohash, pieceSize, connector, destinations,
|
infohash, pieceSize, connector, destinations,
|
||||||
incompletes, pieces)
|
incompletes, pieces)
|
||||||
@@ -84,24 +84,24 @@ public class DownloadManager {
|
|||||||
executor.execute({downloader.download()} as Runnable)
|
executor.execute({downloader.download()} as Runnable)
|
||||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
|
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
|
||||||
downloaders.remove(e.downloader.infoHash)
|
downloaders.remove(e.downloader.infoHash)
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
|
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
|
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
void resume(Downloader downloader) {
|
void resume(Downloader downloader) {
|
||||||
executor.execute({downloader.download() as Runnable})
|
executor.execute({downloader.download() as Runnable})
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
File downloadsFile = new File(home, "downloads.json")
|
File downloadsFile = new File(home, "downloads.json")
|
||||||
if (!downloadsFile.exists())
|
if (!downloadsFile.exists())
|
||||||
@@ -111,7 +111,7 @@ public class DownloadManager {
|
|||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
File file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
File file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
def destinations = new HashSet<>()
|
def destinations = new HashSet<>()
|
||||||
json.destinations.each { destination ->
|
json.destinations.each { destination ->
|
||||||
destinations.add new Destination(destination)
|
destinations.add new Destination(destination)
|
||||||
}
|
}
|
||||||
InfoHash infoHash
|
InfoHash infoHash
|
||||||
@@ -122,9 +122,9 @@ public class DownloadManager {
|
|||||||
byte [] root = Base64.decode(json.hashRoot)
|
byte [] root = Base64.decode(json.hashRoot)
|
||||||
infoHash = new InfoHash(root)
|
infoHash = new InfoHash(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
|
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||||
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
||||||
if (json.paused != null)
|
if (json.paused != null)
|
||||||
@@ -136,7 +136,7 @@ public class DownloadManager {
|
|||||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2) {
|
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2) {
|
||||||
int pieceSize = 0x1 << pieceSizePow2
|
int pieceSize = 0x1 << pieceSizePow2
|
||||||
int nPieces = (int)(length / pieceSize)
|
int nPieces = (int)(length / pieceSize)
|
||||||
@@ -145,7 +145,7 @@ public class DownloadManager {
|
|||||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
|
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
|
||||||
mesh.pieces
|
mesh.pieces
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
||||||
Downloader downloader = downloaders.get(e.infoHash)
|
Downloader downloader = downloaders.get(e.infoHash)
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
@@ -156,19 +156,19 @@ public class DownloadManager {
|
|||||||
case TrustLevel.NEUTRAL: ok = muSettings.allowUntrusted; break
|
case TrustLevel.NEUTRAL: ok = muSettings.allowUntrusted; break
|
||||||
case TrustLevel.DISTRUSTED: ok = false; break
|
case TrustLevel.DISTRUSTED: ok = false; break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ok)
|
if (ok)
|
||||||
downloader.addSource(e.source.destination)
|
downloader.addSource(e.source.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
downloaders.remove(e.downloader.infoHash)
|
downloaders.remove(e.downloader.infoHash)
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistDownloaders() {
|
private void persistDownloaders() {
|
||||||
File downloadsFile = new File(home,"downloads.json")
|
File downloadsFile = new File(home,"downloads.json")
|
||||||
downloadsFile.withPrintWriter { writer ->
|
downloadsFile.withPrintWriter { writer ->
|
||||||
downloaders.values().each { downloader ->
|
downloaders.values().each { downloader ->
|
||||||
if (!downloader.cancelled) {
|
if (!downloader.cancelled) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
@@ -180,20 +180,20 @@ public class DownloadManager {
|
|||||||
destinations << it.toBase64()
|
destinations << it.toBase64()
|
||||||
}
|
}
|
||||||
json.destinations = destinations
|
json.destinations = destinations
|
||||||
|
|
||||||
InfoHash infoHash = downloader.getInfoHash()
|
InfoHash infoHash = downloader.getInfoHash()
|
||||||
if (infoHash.hashList != null)
|
if (infoHash.hashList != null)
|
||||||
json.hashList = Base64.encode(infoHash.hashList)
|
json.hashList = Base64.encode(infoHash.hashList)
|
||||||
else
|
else
|
||||||
json.hashRoot = Base64.encode(infoHash.getRoot())
|
json.hashRoot = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
json.paused = downloader.paused
|
json.paused = downloader.paused
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
downloaders.values().each { it.stop() }
|
downloaders.values().each { it.stop() }
|
||||||
Downloader.executorService.shutdownNow()
|
Downloader.executorService.shutdownNow()
|
||||||
|
@@ -14,6 +14,7 @@ import static com.muwire.core.util.DataUtil.readTillRN
|
|||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.MappedByteBuffer
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@@ -24,7 +25,7 @@ import java.util.logging.Level
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class DownloadSession {
|
class DownloadSession {
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final String meB64
|
private final String meB64
|
||||||
private final Pieces pieces
|
private final Pieces pieces
|
||||||
@@ -37,11 +38,11 @@ class DownloadSession {
|
|||||||
private final MessageDigest digest
|
private final MessageDigest digest
|
||||||
|
|
||||||
private long lastSpeedRead = System.currentTimeMillis()
|
private long lastSpeedRead = System.currentTimeMillis()
|
||||||
private long dataSinceLastRead
|
private long dataSinceLastRead
|
||||||
|
|
||||||
private ByteBuffer mapped
|
private MappedByteBuffer mapped
|
||||||
|
|
||||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||||
int pieceSize, long fileLength, Set<Integer> available) {
|
int pieceSize, long fileLength, Set<Integer> available) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.meB64 = meB64
|
this.meB64 = meB64
|
||||||
@@ -59,7 +60,7 @@ class DownloadSession {
|
|||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the request will proceed. The only time it may not
|
* @return if the request will proceed. The only time it may not
|
||||||
* is if all the pieces have been claimed by other sessions.
|
* is if all the pieces have been claimed by other sessions.
|
||||||
@@ -68,44 +69,46 @@ class DownloadSession {
|
|||||||
public boolean request() throws IOException {
|
public boolean request() throws IOException {
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
|
|
||||||
int piece
|
int[] pieceAndPosition
|
||||||
if (available.isEmpty())
|
if (available.isEmpty())
|
||||||
piece = pieces.claim()
|
pieceAndPosition = pieces.claim()
|
||||||
else
|
else
|
||||||
piece = pieces.claim(new HashSet<>(available))
|
pieceAndPosition = pieces.claim(new HashSet<>(available))
|
||||||
if (piece == -1)
|
if (pieceAndPosition == null)
|
||||||
return false
|
return false
|
||||||
|
int piece = pieceAndPosition[0]
|
||||||
|
int position = pieceAndPosition[1]
|
||||||
|
boolean steal = pieceAndPosition[2] == 1
|
||||||
boolean unclaim = true
|
boolean unclaim = true
|
||||||
|
|
||||||
log.info("will download piece $piece")
|
log.info("will download piece $piece from position $position steal $steal")
|
||||||
|
|
||||||
long start = piece * pieceSize
|
long pieceStart = piece * ((long)pieceSize)
|
||||||
long end = Math.min(fileLength, start + pieceSize) - 1
|
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
||||||
long length = end - start + 1
|
long start = pieceStart + position
|
||||||
|
|
||||||
String root = Base64.encode(infoHash.getRoot())
|
String root = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("X-Persona: $meB64\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("X-Persona: $meB64\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
String xHave = DataUtil.encodeXHave(pieces.getDownloaded(), pieces.nPieces)
|
String xHave = DataUtil.encodeXHave(pieces.getDownloaded(), pieces.nPieces)
|
||||||
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.flush()
|
os.flush()
|
||||||
String codeString = readTillRN(is)
|
String codeString = readTillRN(is)
|
||||||
int space = codeString.indexOf(' ')
|
int space = codeString.indexOf(' ')
|
||||||
if (space > 0)
|
if (space > 0)
|
||||||
codeString = codeString.substring(0, space)
|
codeString = codeString.substring(0, space)
|
||||||
|
|
||||||
int code = Integer.parseInt(codeString.trim())
|
int code = Integer.parseInt(codeString.trim())
|
||||||
|
|
||||||
if (code == 404) {
|
if (code == 404) {
|
||||||
log.warning("file not found")
|
log.warning("file not found")
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(code == 200 || code == 416)) {
|
if (!(code == 200 || code == 416)) {
|
||||||
log.warning("unknown code $code")
|
log.warning("unknown code $code")
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
@@ -120,10 +123,10 @@ class DownloadSession {
|
|||||||
if (colon == -1 || colon == header.length() - 1)
|
if (colon == -1 || colon == header.length() - 1)
|
||||||
throw new IOException("invalid header $header")
|
throw new IOException("invalid header $header")
|
||||||
String key = header.substring(0, colon)
|
String key = header.substring(0, colon)
|
||||||
String value = header.substring(colon + 1)
|
String value = header.substring(colon + 1)
|
||||||
headers[key] = value.trim()
|
headers[key] = value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
// prase X-Alt if present
|
// prase X-Alt if present
|
||||||
if (headers.containsKey("X-Alt")) {
|
if (headers.containsKey("X-Alt")) {
|
||||||
headers["X-Alt"].split(",").each {
|
headers["X-Alt"].split(",").each {
|
||||||
@@ -136,7 +139,7 @@ class DownloadSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse X-Have if present
|
// parse X-Have if present
|
||||||
if (headers.containsKey("X-Have")) {
|
if (headers.containsKey("X-Have")) {
|
||||||
DataUtil.decodeXHave(headers["X-Have"]).each {
|
DataUtil.decodeXHave(headers["X-Have"]).each {
|
||||||
available.add(it)
|
available.add(it)
|
||||||
}
|
}
|
||||||
@@ -147,16 +150,16 @@ class DownloadSession {
|
|||||||
throw new IOException("Code $code but no X-Have")
|
throw new IOException("Code $code but no X-Have")
|
||||||
available.clear()
|
available.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code != 200)
|
if (code != 200)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
String range = headers["Content-Range"]
|
String range = headers["Content-Range"]
|
||||||
if (range == null)
|
if (range == null)
|
||||||
throw new IOException("Code 200 but no Content-Range")
|
throw new IOException("Code 200 but no Content-Range")
|
||||||
|
|
||||||
def group = (range =~ /^(\d+)-(\d+)$/)
|
def group = (range =~ /^(\d+)-(\d+)$/)
|
||||||
if (group.size() != 1)
|
if (group.size() != 1)
|
||||||
throw new IOException("invalid Content-Range header $range")
|
throw new IOException("invalid Content-Range header $range")
|
||||||
|
|
||||||
long receivedStart = Long.parseLong(group[0][1])
|
long receivedStart = Long.parseLong(group[0][1])
|
||||||
@@ -167,13 +170,14 @@ class DownloadSession {
|
|||||||
endpoint.close()
|
endpoint.close()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the download
|
// start the download
|
||||||
FileChannel channel
|
FileChannel channel
|
||||||
try {
|
try {
|
||||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
||||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
StandardOpenOption.SPARSE, StandardOpenOption.CREATE))
|
||||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
|
||||||
|
mapped.position(position)
|
||||||
|
|
||||||
byte[] tmp = new byte[0x1 << 13]
|
byte[] tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
@@ -185,35 +189,38 @@ class DownloadSession {
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
mapped.put(tmp, 0, read)
|
mapped.put(tmp, 0, read)
|
||||||
dataSinceLastRead += read
|
dataSinceLastRead += read
|
||||||
|
pieces.markPartial(piece, mapped.position())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapped.clear()
|
mapped.clear()
|
||||||
digest.update(mapped)
|
digest.update(mapped)
|
||||||
DataUtil.tryUnmap(mapped)
|
|
||||||
byte [] hash = digest.digest()
|
byte [] hash = digest.digest()
|
||||||
byte [] expected = new byte[32]
|
byte [] expected = new byte[32]
|
||||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||||
if (hash != expected)
|
if (hash != expected) {
|
||||||
throw new BadHashException()
|
pieces.markPartial(piece, 0)
|
||||||
|
throw new BadHashException("bad hash on piece $piece")
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try { channel?.close() } catch (IOException ignore) {}
|
try { channel?.close() } catch (IOException ignore) {}
|
||||||
|
DataUtil.tryUnmap(mapped)
|
||||||
}
|
}
|
||||||
pieces.markDownloaded(piece)
|
pieces.markDownloaded(piece)
|
||||||
unclaim = false
|
unclaim = false
|
||||||
} finally {
|
} finally {
|
||||||
if (unclaim)
|
if (unclaim && !steal)
|
||||||
pieces.unclaim(piece)
|
pieces.unclaim(piece)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int positionInPiece() {
|
synchronized int positionInPiece() {
|
||||||
if (mapped == null)
|
if (mapped == null)
|
||||||
return 0
|
return 0
|
||||||
mapped.position()
|
mapped.position()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int speed() {
|
synchronized int speed() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
long interval = Math.max(1000, now - lastSpeedRead)
|
long interval = Math.max(1000, now - lastSpeedRead)
|
||||||
|
@@ -29,7 +29,7 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
public class Downloader {
|
public class Downloader {
|
||||||
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
||||||
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
||||||
|
|
||||||
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
|
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
|
||||||
Thread rv = new Thread(r)
|
Thread rv = new Thread(r)
|
||||||
rv.setName("download worker")
|
rv.setName("download worker")
|
||||||
@@ -38,8 +38,8 @@ public class Downloader {
|
|||||||
})
|
})
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final File file
|
private final File file
|
||||||
private final Pieces pieces
|
private final Pieces pieces
|
||||||
private final long length
|
private final long length
|
||||||
@@ -53,8 +53,8 @@ public class Downloader {
|
|||||||
final int pieceSizePow2
|
final int pieceSizePow2
|
||||||
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
||||||
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
|
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
|
||||||
|
|
||||||
|
|
||||||
private volatile boolean cancelled, paused
|
private volatile boolean cancelled, paused
|
||||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||||
private boolean piecesFileClosed
|
private boolean piecesFileClosed
|
||||||
@@ -64,8 +64,8 @@ public class Downloader {
|
|||||||
private int speedAvg = 0
|
private int speedAvg = 0
|
||||||
private long timestamp = Instant.now().toEpochMilli()
|
private long timestamp = Instant.now().toEpochMilli()
|
||||||
|
|
||||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||||
Persona me, File file, long length, InfoHash infoHash,
|
Persona me, File file, long length, InfoHash infoHash,
|
||||||
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
||||||
File incompletes, Pieces pieces) {
|
File incompletes, Pieces pieces) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
@@ -87,15 +87,15 @@ public class Downloader {
|
|||||||
// it's easily adjustable by resizing the size of speedArr
|
// it's easily adjustable by resizing the size of speedArr
|
||||||
this.speedArr = [ 0, 0, 0, 0, 0 ]
|
this.speedArr = [ 0, 0, 0, 0, 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized InfoHash getInfoHash() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
infoHash
|
infoHash
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setInfoHash(InfoHash infoHash) {
|
private synchronized void setInfoHash(InfoHash infoHash) {
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
}
|
}
|
||||||
|
|
||||||
void download() {
|
void download() {
|
||||||
readPieces()
|
readPieces()
|
||||||
destinations.each {
|
destinations.each {
|
||||||
@@ -106,33 +106,37 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void readPieces() {
|
void readPieces() {
|
||||||
if (!piecesFile.exists())
|
if (!piecesFile.exists())
|
||||||
return
|
return
|
||||||
piecesFile.eachLine {
|
piecesFile.eachLine {
|
||||||
int piece = Integer.parseInt(it)
|
String [] split = it.split(",")
|
||||||
pieces.markDownloaded(piece)
|
int piece = Integer.parseInt(split[0])
|
||||||
|
if (split.length == 1)
|
||||||
|
pieces.markDownloaded(piece)
|
||||||
|
else {
|
||||||
|
int position = Integer.parseInt(split[1])
|
||||||
|
pieces.markPartial(piece, position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writePieces() {
|
void writePieces() {
|
||||||
synchronized(piecesFile) {
|
synchronized(piecesFile) {
|
||||||
if (piecesFileClosed)
|
if (piecesFileClosed)
|
||||||
return
|
return
|
||||||
piecesFile.withPrintWriter { writer ->
|
piecesFile.withPrintWriter { writer ->
|
||||||
pieces.getDownloaded().each { piece ->
|
pieces.write(writer)
|
||||||
writer.println(piece)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long donePieces() {
|
public long donePieces() {
|
||||||
pieces.donePieces()
|
pieces.donePieces()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int speed() {
|
public int speed() {
|
||||||
int currSpeed = 0
|
int currSpeed = 0
|
||||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||||
@@ -164,15 +168,15 @@ public class Downloader {
|
|||||||
|
|
||||||
speedAvg
|
speedAvg
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadState getCurrentState() {
|
public DownloadState getCurrentState() {
|
||||||
if (cancelled)
|
if (cancelled)
|
||||||
return DownloadState.CANCELLED
|
return DownloadState.CANCELLED
|
||||||
if (paused)
|
if (paused)
|
||||||
return DownloadState.PAUSED
|
return DownloadState.PAUSED
|
||||||
|
|
||||||
boolean allFinished = true
|
boolean allFinished = true
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
allFinished &= it.currentState == WorkerState.FINISHED
|
allFinished &= it.currentState == WorkerState.FINISHED
|
||||||
}
|
}
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
@@ -180,22 +184,22 @@ public class Downloader {
|
|||||||
return DownloadState.FINISHED
|
return DownloadState.FINISHED
|
||||||
return DownloadState.FAILED
|
return DownloadState.FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
// if at least one is downloading...
|
// if at least one is downloading...
|
||||||
boolean oneDownloading = false
|
boolean oneDownloading = false
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
if (it.currentState == WorkerState.DOWNLOADING) {
|
if (it.currentState == WorkerState.DOWNLOADING) {
|
||||||
oneDownloading = true
|
oneDownloading = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oneDownloading)
|
if (oneDownloading)
|
||||||
return DownloadState.DOWNLOADING
|
return DownloadState.DOWNLOADING
|
||||||
|
|
||||||
// at least one is requesting hashlist
|
// at least one is requesting hashlist
|
||||||
boolean oneHashlist = false
|
boolean oneHashlist = false
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
if (it.currentState == WorkerState.HASHLIST) {
|
if (it.currentState == WorkerState.HASHLIST) {
|
||||||
oneHashlist = true
|
oneHashlist = true
|
||||||
return
|
return
|
||||||
@@ -203,10 +207,10 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
if (oneHashlist)
|
if (oneHashlist)
|
||||||
return DownloadState.HASHLIST
|
return DownloadState.HASHLIST
|
||||||
|
|
||||||
return DownloadState.CONNECTING
|
return DownloadState.CONNECTING
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
stop()
|
stop()
|
||||||
@@ -217,27 +221,27 @@ public class Downloader {
|
|||||||
incompleteFile.delete()
|
incompleteFile.delete()
|
||||||
pieces.clearAll()
|
pieces.clearAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause() {
|
public void pause() {
|
||||||
paused = true
|
paused = true
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
it.cancel()
|
it.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int activeWorkers() {
|
public int activeWorkers() {
|
||||||
int active = 0
|
int active = 0
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
if (it.currentState != WorkerState.FINISHED)
|
if (it.currentState != WorkerState.FINISHED)
|
||||||
active++
|
active++
|
||||||
}
|
}
|
||||||
active
|
active
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resume() {
|
public void resume() {
|
||||||
paused = false
|
paused = false
|
||||||
readPieces()
|
readPieces()
|
||||||
@@ -256,7 +260,7 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSource(Destination d) {
|
void addSource(Destination d) {
|
||||||
if (activeWorkers.containsKey(d))
|
if (activeWorkers.containsKey(d))
|
||||||
return
|
return
|
||||||
@@ -264,7 +268,7 @@ public class Downloader {
|
|||||||
activeWorkers.put(d, newWorker)
|
activeWorkers.put(d, newWorker)
|
||||||
executorService.submit(newWorker)
|
executorService.submit(newWorker)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadWorker implements Runnable {
|
class DownloadWorker implements Runnable {
|
||||||
private final Destination destination
|
private final Destination destination
|
||||||
private volatile WorkerState currentState
|
private volatile WorkerState currentState
|
||||||
@@ -272,11 +276,11 @@ public class Downloader {
|
|||||||
private Endpoint endpoint
|
private Endpoint endpoint
|
||||||
private volatile DownloadSession currentSession
|
private volatile DownloadSession currentSession
|
||||||
private final Set<Integer> available = new HashSet<>()
|
private final Set<Integer> available = new HashSet<>()
|
||||||
|
|
||||||
DownloadWorker(Destination destination) {
|
DownloadWorker(Destination destination) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
downloadThread = Thread.currentThread()
|
downloadThread = Thread.currentThread()
|
||||||
currentState = WorkerState.CONNECTING
|
currentState = WorkerState.CONNECTING
|
||||||
@@ -292,7 +296,7 @@ public class Downloader {
|
|||||||
currentState = WorkerState.DOWNLOADING
|
currentState = WorkerState.DOWNLOADING
|
||||||
boolean requestPerformed
|
boolean requestPerformed
|
||||||
while(!pieces.isComplete()) {
|
while(!pieces.isComplete()) {
|
||||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||||
endpoint, incompleteFile, pieceSize, length, available)
|
endpoint, incompleteFile, pieceSize, length, available)
|
||||||
requestPerformed = currentSession.request()
|
requestPerformed = currentSession.request()
|
||||||
if (!requestPerformed)
|
if (!requestPerformed)
|
||||||
@@ -303,12 +307,17 @@ public class Downloader {
|
|||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
||||||
} finally {
|
} finally {
|
||||||
|
writePieces()
|
||||||
currentState = WorkerState.FINISHED
|
currentState = WorkerState.FINISHED
|
||||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
||||||
synchronized(piecesFile) {
|
synchronized(piecesFile) {
|
||||||
piecesFileClosed = true
|
piecesFileClosed = true
|
||||||
piecesFile.delete()
|
piecesFile.delete()
|
||||||
}
|
}
|
||||||
|
activeWorkers.values().each {
|
||||||
|
if (it.destination != destination)
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
@@ -317,20 +326,20 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
endpoint?.close()
|
endpoint?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int speed() {
|
int speed() {
|
||||||
if (currentSession == null)
|
if (currentSession == null)
|
||||||
return 0
|
return 0
|
||||||
currentSession.speed()
|
currentSession.speed()
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
downloadThread?.interrupt()
|
downloadThread?.interrupt()
|
||||||
}
|
}
|
||||||
|
@@ -20,32 +20,32 @@ class HashListSession {
|
|||||||
private final String meB64
|
private final String meB64
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final Endpoint endpoint
|
private final Endpoint endpoint
|
||||||
|
|
||||||
HashListSession(String meB64, InfoHash infoHash, Endpoint endpoint) {
|
HashListSession(String meB64, InfoHash infoHash, Endpoint endpoint) {
|
||||||
this.meB64 = meB64
|
this.meB64 = meB64
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash request() throws IOException {
|
InfoHash request() throws IOException {
|
||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
|
|
||||||
String root = Base64.encode(infoHash.getRoot())
|
String root = Base64.encode(infoHash.getRoot())
|
||||||
os.write("HASHLIST $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("HASHLIST $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("X-Persona: $meB64\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("X-Persona: $meB64\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.flush()
|
os.flush()
|
||||||
|
|
||||||
String code = readTillRN(is)
|
String code = readTillRN(is)
|
||||||
if (!code.startsWith("200"))
|
if (!code.startsWith("200"))
|
||||||
throw new IOException("unknown code $code")
|
throw new IOException("unknown code $code")
|
||||||
|
|
||||||
// parse all headers
|
// parse all headers
|
||||||
Set<String> headers = new HashSet<>()
|
Set<String> headers = new HashSet<>()
|
||||||
String header
|
String header
|
||||||
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS)
|
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS)
|
||||||
headers.add(header)
|
headers.add(header)
|
||||||
|
|
||||||
long receivedStart = -1
|
long receivedStart = -1
|
||||||
long receivedEnd = -1
|
long receivedEnd = -1
|
||||||
for (String receivedHeader : headers) {
|
for (String receivedHeader : headers) {
|
||||||
@@ -58,10 +58,10 @@ class HashListSession {
|
|||||||
receivedStart = Long.parseLong(group[0][1])
|
receivedStart = Long.parseLong(group[0][1])
|
||||||
receivedEnd = Long.parseLong(group[0][2])
|
receivedEnd = Long.parseLong(group[0][2])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receivedStart != 0)
|
if (receivedStart != 0)
|
||||||
throw new IOException("hashlist started at $receivedStart")
|
throw new IOException("hashlist started at $receivedStart")
|
||||||
|
|
||||||
byte[] hashList = new byte[receivedEnd]
|
byte[] hashList = new byte[receivedEnd]
|
||||||
ByteBuffer hashListBuf = ByteBuffer.wrap(hashList)
|
ByteBuffer hashListBuf = ByteBuffer.wrap(hashList)
|
||||||
byte[] tmp = new byte[0x1 << 13]
|
byte[] tmp = new byte[0x1 << 13]
|
||||||
@@ -73,7 +73,7 @@ class HashListSession {
|
|||||||
throw new IOException()
|
throw new IOException()
|
||||||
hashListBuf.put(tmp, 0, read)
|
hashListBuf.put(tmp, 0, read)
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash received = InfoHash.fromHashList(hashList)
|
InfoHash received = InfoHash.fromHashList(hashList)
|
||||||
if (received.getRoot() != infoHash.getRoot())
|
if (received.getRoot() != infoHash.getRoot())
|
||||||
throw new IOException("fetched list doesn't match root")
|
throw new IOException("fetched list doesn't match root")
|
||||||
|
@@ -5,51 +5,66 @@ class Pieces {
|
|||||||
private final int nPieces
|
private final int nPieces
|
||||||
private final float ratio
|
private final float ratio
|
||||||
private final Random random = new Random()
|
private final Random random = new Random()
|
||||||
|
private final Map<Integer,Integer> partials = new HashMap<>()
|
||||||
|
|
||||||
Pieces(int nPieces) {
|
Pieces(int nPieces) {
|
||||||
this(nPieces, 1.0f)
|
this(nPieces, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
Pieces(int nPieces, float ratio) {
|
Pieces(int nPieces, float ratio) {
|
||||||
this.nPieces = nPieces
|
this.nPieces = nPieces
|
||||||
this.ratio = ratio
|
this.ratio = ratio
|
||||||
done = new BitSet(nPieces)
|
done = new BitSet(nPieces)
|
||||||
claimed = new BitSet(nPieces)
|
claimed = new BitSet(nPieces)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int claim() {
|
synchronized int[] claim() {
|
||||||
int claimedCardinality = claimed.cardinality()
|
int claimedCardinality = claimed.cardinality()
|
||||||
if (claimedCardinality == nPieces)
|
if (claimedCardinality == nPieces) {
|
||||||
return -1
|
// steal
|
||||||
|
int downloadedCardinality = done.cardinality()
|
||||||
|
if (downloadedCardinality == nPieces)
|
||||||
|
return null
|
||||||
|
int rv = done.nextClearBit(0)
|
||||||
|
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||||
|
}
|
||||||
|
|
||||||
// if fuller than ratio just do sequential
|
// if fuller than ratio just do sequential
|
||||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||||
int rv = claimed.nextClearBit(0)
|
int rv = claimed.nextClearBit(0)
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
return rv
|
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
int start = random.nextInt(nPieces)
|
int start = random.nextInt(nPieces)
|
||||||
if (claimed.get(start))
|
if (claimed.get(start))
|
||||||
continue
|
continue
|
||||||
claimed.set(start)
|
claimed.set(start)
|
||||||
return start
|
return [start, partials.getOrDefault(start,0), 0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int claim(Set<Integer> available) {
|
synchronized int[] claim(Set<Integer> available) {
|
||||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
||||||
available.remove(i)
|
available.remove(i)
|
||||||
if (available.isEmpty())
|
if (available.isEmpty())
|
||||||
return -1
|
return null
|
||||||
List<Integer> toList = available.toList()
|
Set<Integer> availableCopy = new HashSet<>(available)
|
||||||
|
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||||
|
availableCopy.remove(i)
|
||||||
|
if (availableCopy.isEmpty()) {
|
||||||
|
// steal
|
||||||
|
int rv = available.first()
|
||||||
|
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||||
|
}
|
||||||
|
List<Integer> toList = availableCopy.toList()
|
||||||
Collections.shuffle(toList)
|
Collections.shuffle(toList)
|
||||||
int rv = toList[0]
|
int rv = toList[0]
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
rv
|
[rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized def getDownloaded() {
|
synchronized def getDownloaded() {
|
||||||
def rv = []
|
def rv = []
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||||
@@ -57,30 +72,45 @@ class Pieces {
|
|||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void markDownloaded(int piece) {
|
synchronized void markDownloaded(int piece) {
|
||||||
done.set(piece)
|
done.set(piece)
|
||||||
claimed.set(piece)
|
claimed.set(piece)
|
||||||
|
partials.remove(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void markPartial(int piece, int position) {
|
||||||
|
partials.put(piece, position)
|
||||||
|
}
|
||||||
|
|
||||||
synchronized void unclaim(int piece) {
|
synchronized void unclaim(int piece) {
|
||||||
claimed.clear(piece)
|
claimed.clear(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isComplete() {
|
synchronized boolean isComplete() {
|
||||||
done.cardinality() == nPieces
|
done.cardinality() == nPieces
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int donePieces() {
|
synchronized int donePieces() {
|
||||||
done.cardinality()
|
done.cardinality()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isDownloaded(int piece) {
|
synchronized boolean isDownloaded(int piece) {
|
||||||
done.get(piece)
|
done.get(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void clearAll() {
|
synchronized void clearAll() {
|
||||||
done.clear()
|
done.clear()
|
||||||
claimed.clear()
|
claimed.clear()
|
||||||
|
partials.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void write(PrintWriter writer) {
|
||||||
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||||
|
writer.println(i)
|
||||||
|
}
|
||||||
|
partials.each { piece, position ->
|
||||||
|
writer.println("$piece,$position")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import com.muwire.core.search.UIResultEvent
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class UIDownloadEvent extends Event {
|
class UIDownloadEvent extends Event {
|
||||||
|
|
||||||
UIResultEvent[] result
|
UIResultEvent[] result
|
||||||
Set<Destination> sources
|
Set<Destination> sources
|
||||||
File target
|
File target
|
||||||
|
@@ -20,9 +20,9 @@ import net.i2p.util.SystemVersion
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class DirectoryWatcher {
|
class DirectoryWatcher {
|
||||||
|
|
||||||
private static final long WAIT_TIME = 1000
|
private static final long WAIT_TIME = 1000
|
||||||
|
|
||||||
private static final WatchEvent.Kind[] kinds
|
private static final WatchEvent.Kind[] kinds
|
||||||
static {
|
static {
|
||||||
if (SystemVersion.isMac())
|
if (SystemVersion.isMac())
|
||||||
@@ -30,7 +30,7 @@ class DirectoryWatcher {
|
|||||||
else
|
else
|
||||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
||||||
}
|
}
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final Thread watcherThread, publisherThread
|
private final Thread watcherThread, publisherThread
|
||||||
@@ -38,7 +38,7 @@ class DirectoryWatcher {
|
|||||||
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
|
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
|
||||||
private WatchService watchService
|
private WatchService watchService
|
||||||
private volatile boolean shutdown
|
private volatile boolean shutdown
|
||||||
|
|
||||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
@@ -47,29 +47,29 @@ class DirectoryWatcher {
|
|||||||
this.publisherThread = new Thread({publish()} as Runnable, "watched-files-publisher")
|
this.publisherThread = new Thread({publish()} as Runnable, "watched-files-publisher")
|
||||||
publisherThread.setDaemon(true)
|
publisherThread.setDaemon(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
watchService = FileSystems.getDefault().newWatchService()
|
watchService = FileSystems.getDefault().newWatchService()
|
||||||
watcherThread.start()
|
watcherThread.start()
|
||||||
publisherThread.start()
|
publisherThread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
shutdown = true
|
shutdown = true
|
||||||
watcherThread?.interrupt()
|
watcherThread?.interrupt()
|
||||||
publisherThread?.interrupt()
|
publisherThread?.interrupt()
|
||||||
watchService?.close()
|
watchService?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent e) {
|
void onFileSharedEvent(FileSharedEvent e) {
|
||||||
if (!e.file.isDirectory())
|
if (!e.file.isDirectory())
|
||||||
return
|
return
|
||||||
Path path = e.file.getCanonicalFile().toPath()
|
Path path = e.file.getCanonicalFile().toPath()
|
||||||
WatchKey wk = path.register(watchService, kinds)
|
WatchKey wk = path.register(watchService, kinds)
|
||||||
watchedDirectories.put(e.file, wk)
|
watchedDirectories.put(e.file, wk)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||||
wk?.cancel()
|
wk?.cancel()
|
||||||
@@ -93,7 +93,7 @@ class DirectoryWatcher {
|
|||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void processCreated(Path parent, Path path) {
|
private void processCreated(Path parent, Path path) {
|
||||||
File f= join(parent, path)
|
File f= join(parent, path)
|
||||||
@@ -103,13 +103,13 @@ class DirectoryWatcher {
|
|||||||
else
|
else
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
waitingFiles.put(f, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processModified(Path parent, Path path) {
|
private void processModified(Path parent, Path path) {
|
||||||
File f = join(parent, path)
|
File f = join(parent, path)
|
||||||
log.fine("modified entry $f")
|
log.fine("modified entry $f")
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
waitingFiles.put(f, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDeleted(Path parent, Path path) {
|
private void processDeleted(Path parent, Path path) {
|
||||||
File f = join(parent, path)
|
File f = join(parent, path)
|
||||||
log.fine("deleted entry $f")
|
log.fine("deleted entry $f")
|
||||||
@@ -117,12 +117,12 @@ class DirectoryWatcher {
|
|||||||
if (sf != null)
|
if (sf != null)
|
||||||
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File join(Path parent, Path path) {
|
private static File join(Path parent, Path path) {
|
||||||
File parentFile = parent.toFile().getCanonicalFile()
|
File parentFile = parent.toFile().getCanonicalFile()
|
||||||
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publish() {
|
private void publish() {
|
||||||
try {
|
try {
|
||||||
while(!shutdown) {
|
while(!shutdown) {
|
||||||
|
@@ -8,5 +8,5 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class FileDownloadedEvent extends Event {
|
class FileDownloadedEvent extends Event {
|
||||||
Downloader downloader
|
Downloader downloader
|
||||||
DownloadedFile downloadedFile
|
DownloadedFile downloadedFile
|
||||||
}
|
}
|
||||||
|
@@ -5,12 +5,12 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileHashedEvent extends Event {
|
class FileHashedEvent extends Event {
|
||||||
|
|
||||||
SharedFile sharedFile
|
SharedFile sharedFile
|
||||||
String error
|
String error
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
|
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,75 +13,75 @@ import java.security.NoSuchAlgorithmException
|
|||||||
|
|
||||||
class FileHasher {
|
class FileHasher {
|
||||||
|
|
||||||
/** max size of shared file is 128 GB */
|
/** max size of shared file is 128 GB */
|
||||||
public static final long MAX_SIZE = 0x1L << 37
|
public static final long MAX_SIZE = 0x1L << 37
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param size of the file to be shared
|
* @param size of the file to be shared
|
||||||
* @return the size of each piece in power of 2
|
* @return the size of each piece in power of 2
|
||||||
* piece size is minimum 128 KBytees and maximum 16 MBytes in power of 2 steps (2^17 - 2^24)
|
* piece size is minimum 128 KBytees and maximum 16 MBytes in power of 2 steps (2^17 - 2^24)
|
||||||
* there can be up to 8192 pieces maximum per file
|
* there can be up to 8192 pieces maximum per file
|
||||||
*/
|
*/
|
||||||
static int getPieceSize(long size) {
|
static int getPieceSize(long size) {
|
||||||
if (size <= 0x1 << 30)
|
if (size <= 0x1 << 30)
|
||||||
return 17
|
return 17
|
||||||
|
|
||||||
for (int i = 31; i <= 37; i++) {
|
for (int i = 31; i <= 37; i++) {
|
||||||
if (size <= 0x1L << i) {
|
if (size <= 0x1L << i) {
|
||||||
return i-13
|
return i-13
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("File too large $size")
|
throw new IllegalArgumentException("File too large $size")
|
||||||
}
|
}
|
||||||
|
|
||||||
final MessageDigest digest
|
final MessageDigest digest
|
||||||
|
|
||||||
FileHasher() {
|
FileHasher() {
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA-256")
|
digest = MessageDigest.getInstance("SHA-256")
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
digest = null
|
digest = null
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash hashFile(File file) {
|
InfoHash hashFile(File file) {
|
||||||
final long length = file.length()
|
final long length = file.length()
|
||||||
final int size = 0x1 << getPieceSize(length)
|
final int size = 0x1 << getPieceSize(length)
|
||||||
int numPieces = (int) (length / size)
|
int numPieces = (int) (length / size)
|
||||||
if (numPieces * size < length)
|
if (numPieces * size < length)
|
||||||
numPieces++
|
numPieces++
|
||||||
|
|
||||||
def output = new ByteArrayOutputStream()
|
def output = new ByteArrayOutputStream()
|
||||||
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
||||||
try {
|
try {
|
||||||
MappedByteBuffer buf
|
MappedByteBuffer buf
|
||||||
for (int i = 0; i < numPieces - 1; i++) {
|
for (int i = 0; i < numPieces - 1; i++) {
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||||
digest.update buf
|
digest.update buf
|
||||||
DataUtil.tryUnmap(buf)
|
DataUtil.tryUnmap(buf)
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
}
|
}
|
||||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
||||||
digest.update buf
|
digest.update buf
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
} finally {
|
} finally {
|
||||||
raf.close()
|
raf.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
byte [] hashList = output.toByteArray()
|
byte [] hashList = output.toByteArray()
|
||||||
InfoHash.fromHashList(hashList)
|
InfoHash.fromHashList(hashList)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
println "This utility computes an infohash of a file"
|
println "This utility computes an infohash of a file"
|
||||||
println "Pass absolute path to a file as an argument"
|
println "Pass absolute path to a file as an argument"
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def file = new File(args[0])
|
def file = new File(args[0])
|
||||||
file = file.getAbsoluteFile()
|
file = file.getAbsoluteFile()
|
||||||
def hasher = new FileHasher()
|
def hasher = new FileHasher()
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
class FileHashingEvent extends Event {
|
||||||
|
|
||||||
|
File hashingFile
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
super.toString() + " hashingFile " + hashingFile.getAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -5,5 +5,5 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileLoadedEvent extends Event {
|
class FileLoadedEvent extends Event {
|
||||||
|
|
||||||
SharedFile loadedFile
|
SharedFile loadedFile
|
||||||
}
|
}
|
||||||
|
@@ -15,129 +15,129 @@ import groovy.util.logging.Log
|
|||||||
class FileManager {
|
class FileManager {
|
||||||
|
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
if (e.sharedFile != null)
|
if (e.sharedFile != null)
|
||||||
addToIndex(e.sharedFile)
|
addToIndex(e.sharedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
addToIndex(e.loadedFile)
|
addToIndex(e.loadedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (settings.shareDownloadedFiles) {
|
if (settings.shareDownloadedFiles) {
|
||||||
addToIndex(e.downloadedFile)
|
addToIndex(e.downloadedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToIndex(SharedFile sf) {
|
private void addToIndex(SharedFile sf) {
|
||||||
log.info("Adding shared file " + sf.getFile())
|
log.info("Adding shared file " + sf.getFile())
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
log.info("adding new root")
|
log.info("adding new root")
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
rootToFiles.put(infoHash, existing);
|
rootToFiles.put(infoHash, existing);
|
||||||
}
|
}
|
||||||
existing.add(sf)
|
existing.add(sf)
|
||||||
fileToSharedFile.put(sf.file, sf)
|
fileToSharedFile.put(sf.file, sf)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles == null) {
|
if (existingFiles == null) {
|
||||||
existingFiles = new HashSet<>()
|
existingFiles = new HashSet<>()
|
||||||
nameToFiles.put(name, existingFiles)
|
nameToFiles.put(name, existingFiles)
|
||||||
}
|
}
|
||||||
existingFiles.add(sf.getFile())
|
existingFiles.add(sf.getFile())
|
||||||
|
|
||||||
index.add(name)
|
index.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
SharedFile sf = e.unsharedFile
|
SharedFile sf = e.unsharedFile
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(sf)
|
existing.remove(sf)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
rootToFiles.remove(infoHash)
|
rootToFiles.remove(infoHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileToSharedFile.remove(sf.file)
|
fileToSharedFile.remove(sf.file)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles != null) {
|
if (existingFiles != null) {
|
||||||
existingFiles.remove(sf.file)
|
existingFiles.remove(sf.file)
|
||||||
if (existingFiles.isEmpty()) {
|
if (existingFiles.isEmpty()) {
|
||||||
nameToFiles.remove(name)
|
nameToFiles.remove(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index.remove(name)
|
index.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
synchronized(fileToSharedFile) {
|
synchronized(fileToSharedFile) {
|
||||||
return new HashMap<>(fileToSharedFile)
|
return new HashMap<>(fileToSharedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<SharedFile> getSharedFiles(byte []root) {
|
Set<SharedFile> getSharedFiles(byte []root) {
|
||||||
return rootToFiles.get(new InfoHash(root))
|
return rootToFiles.get(new InfoHash(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSearchEvent(SearchEvent e) {
|
void onSearchEvent(SearchEvent e) {
|
||||||
// hash takes precedence
|
// hash takes precedence
|
||||||
ResultsEvent re = null
|
ResultsEvent re = null
|
||||||
if (e.searchHash != null) {
|
if (e.searchHash != null) {
|
||||||
Set<SharedFile> found
|
Set<SharedFile> found
|
||||||
found = rootToFiles.get new InfoHash(e.searchHash)
|
found = rootToFiles.get new InfoHash(e.searchHash)
|
||||||
found = filter(found, e.oobInfohash)
|
found = filter(found, e.oobInfohash)
|
||||||
if (found != null && !found.isEmpty())
|
if (found != null && !found.isEmpty())
|
||||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
} else {
|
} else {
|
||||||
def names = index.search e.searchTerms
|
def names = index.search e.searchTerms
|
||||||
Set<File> files = new HashSet<>()
|
Set<File> files = new HashSet<>()
|
||||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||||
files = filter(sharedFiles, e.oobInfohash)
|
files = filter(sharedFiles, e.oobInfohash)
|
||||||
if (!sharedFiles.isEmpty())
|
if (!sharedFiles.isEmpty())
|
||||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (re != null)
|
if (re != null)
|
||||||
eventBus.publish(re)
|
eventBus.publish(re)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
||||||
if (!oob)
|
if (!oob)
|
||||||
return files
|
return files
|
||||||
Set<SharedFile> rv = new HashSet<>()
|
Set<SharedFile> rv = new HashSet<>()
|
||||||
files.each {
|
files.each {
|
||||||
if (it.getPieceSize() != 0)
|
if (it.getPieceSize() != 0)
|
||||||
rv.add(it)
|
rv.add(it)
|
||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
e.directory.listFiles().each {
|
e.directory.listFiles().each {
|
||||||
if (it.isDirectory())
|
if (it.isDirectory())
|
||||||
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
||||||
else {
|
else {
|
||||||
|
@@ -4,8 +4,8 @@ import com.muwire.core.Event
|
|||||||
|
|
||||||
class FileSharedEvent extends Event {
|
class FileSharedEvent extends Event {
|
||||||
|
|
||||||
File file
|
File file
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + " file: "+file.getAbsolutePath()
|
return super.toString() + " file: "+file.getAbsolutePath()
|
||||||
|
@@ -4,5 +4,5 @@ import com.muwire.core.Event
|
|||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class FileUnsharedEvent extends Event {
|
class FileUnsharedEvent extends Event {
|
||||||
SharedFile unsharedFile
|
SharedFile unsharedFile
|
||||||
}
|
}
|
||||||
|
@@ -8,40 +8,41 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class HasherService {
|
class HasherService {
|
||||||
|
|
||||||
final FileHasher hasher
|
final FileHasher hasher
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
Executor executor
|
Executor executor
|
||||||
|
|
||||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||||
this.hasher = hasher
|
this.hasher = hasher
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
executor = Executors.newSingleThreadExecutor()
|
executor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||||
return
|
return
|
||||||
executor.execute( { -> process(evt.file) } as Runnable)
|
executor.execute( { -> process(evt.file) } as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||||
} else {
|
} else {
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||||
} else {
|
} else {
|
||||||
def hash = hasher.hashFile f
|
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
def hash = hasher.hashFile f
|
||||||
}
|
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,135 +23,135 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class PersisterService extends Service {
|
class PersisterService extends Service {
|
||||||
|
|
||||||
final File location
|
final File location
|
||||||
final EventBus listener
|
final EventBus listener
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
|
|
||||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||||
this.location = location
|
this.location = location
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
timer = new Timer("file persister", true)
|
timer = new Timer("file persister", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (location.exists() && location.isFile()) {
|
if (location.exists() && location.isFile()) {
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
try {
|
try {
|
||||||
location.eachLine {
|
location.eachLine {
|
||||||
if (it.trim().length() > 0) {
|
if (it.trim().length() > 0) {
|
||||||
def parsed = slurper.parseText it
|
def parsed = slurper.parseText it
|
||||||
def event = fromJson parsed
|
def event = fromJson parsed
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
log.fine("loaded file $event.loadedFile.file")
|
log.fine("loaded file $event.loadedFile.file")
|
||||||
listener.publish event
|
listener.publish event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
} catch (IllegalArgumentException|NumberFormatException e) {
|
||||||
log.log(Level.WARNING, "couldn't load files",e)
|
log.log(Level.WARNING, "couldn't load files",e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
}
|
}
|
||||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileLoadedEvent fromJson(def json) {
|
private static FileLoadedEvent fromJson(def json) {
|
||||||
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!(json.hashList instanceof List))
|
if (!(json.hashList instanceof List))
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
|
|
||||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
file = file.getCanonicalFile()
|
file = file.getCanonicalFile()
|
||||||
if (!file.exists() || file.isDirectory())
|
if (!file.exists() || file.isDirectory())
|
||||||
return null
|
return null
|
||||||
long length = Long.valueOf(json.length)
|
long length = Long.valueOf(json.length)
|
||||||
if (length != file.length())
|
if (length != file.length())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
List hashList = (List) json.hashList
|
List hashList = (List) json.hashList
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
hashList.each {
|
hashList.each {
|
||||||
byte [] hash = Base64.decode it.toString()
|
byte [] hash = Base64.decode it.toString()
|
||||||
if (hash == null)
|
if (hash == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
baos.write hash
|
baos.write hash
|
||||||
}
|
}
|
||||||
byte[] hashListBytes = baos.toByteArray()
|
byte[] hashListBytes = baos.toByteArray()
|
||||||
|
|
||||||
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
||||||
byte [] root = Base64.decode(json.infoHash.toString())
|
byte [] root = Base64.decode(json.infoHash.toString())
|
||||||
if (root == null)
|
if (root == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!Arrays.equals(root, ih.getRoot()))
|
if (!Arrays.equals(root, ih.getRoot()))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
int pieceSize = 0
|
int pieceSize = 0
|
||||||
if (json.pieceSize != null)
|
if (json.pieceSize != null)
|
||||||
pieceSize = json.pieceSize
|
pieceSize = json.pieceSize
|
||||||
|
|
||||||
if (json.sources != null) {
|
if (json.sources != null) {
|
||||||
List sources = (List)json.sources
|
List sources = (List)json.sources
|
||||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
||||||
return new FileLoadedEvent(loadedFile : df)
|
return new FileLoadedEvent(loadedFile : df)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
return new FileLoadedEvent(loadedFile: sf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistFiles() {
|
private void persistFiles() {
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
def sharedFiles = fileManager.getSharedFiles()
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
tmp.withPrintWriter { writer ->
|
tmp.withPrintWriter { writer ->
|
||||||
sharedFiles.each { k, v ->
|
sharedFiles.each { k, v ->
|
||||||
def json = toJson(k,v)
|
def json = toJson(k,v)
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
tmp.delete()
|
tmp.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
private def toJson(File f, SharedFile sf) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
json.file = Base64.encode DataUtil.encodei18nString(f.toString())
|
||||||
json.length = f.length()
|
json.length = sf.getCachedLength()
|
||||||
InfoHash ih = sf.getInfoHash()
|
InfoHash ih = sf.getInfoHash()
|
||||||
json.infoHash = Base64.encode ih.getRoot()
|
json.infoHash = Base64.encode ih.getRoot()
|
||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
byte [] tmp = new byte [32]
|
byte [] tmp = new byte [32]
|
||||||
json.hashList = []
|
json.hashList = []
|
||||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||||
json.hashList.add Base64.encode(tmp)
|
json.hashList.add Base64.encode(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
if (sf instanceof DownloadedFile) {
|
||||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
json
|
json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,177 +17,177 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class CacheClient {
|
class CacheClient {
|
||||||
|
|
||||||
private static final int CRAWLER_RETURN = 10
|
|
||||||
|
|
||||||
final EventBus eventBus
|
|
||||||
final HostCache cache
|
|
||||||
final ConnectionManager manager
|
|
||||||
final I2PSession session
|
|
||||||
final long interval
|
|
||||||
final MuWireSettings settings
|
|
||||||
final Timer timer
|
|
||||||
|
|
||||||
public CacheClient(EventBus eventBus, HostCache cache,
|
private static final int CRAWLER_RETURN = 10
|
||||||
ConnectionManager manager, I2PSession session,
|
|
||||||
MuWireSettings settings, long interval) {
|
|
||||||
this.eventBus = eventBus
|
|
||||||
this.cache = cache
|
|
||||||
this.manager = manager
|
|
||||||
this.session = session
|
|
||||||
this.settings = settings
|
|
||||||
this.interval = interval
|
|
||||||
this.timer = new Timer("hostcache-client",true)
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
|
||||||
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
timer.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void queryIfNeeded() {
|
|
||||||
if (!manager.getConnections().isEmpty())
|
|
||||||
return
|
|
||||||
if (!cache.getHosts(1).isEmpty())
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info "Will query hostcaches"
|
|
||||||
|
|
||||||
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
|
||||||
ping = JsonOutput.toJson(ping)
|
|
||||||
def maker = new I2PDatagramMaker(session)
|
|
||||||
ping = maker.makeI2PDatagram(ping.bytes)
|
|
||||||
def options = new SendMessageOptions()
|
|
||||||
options.setSendLeaseSet(true)
|
|
||||||
CacheServers.getCacheServers().each {
|
|
||||||
log.info "Querying hostcache ${it.toBase32()}"
|
|
||||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Listener implements I2PSessionMuxedListener {
|
|
||||||
|
|
||||||
private final JsonSlurper slurper = new JsonSlurper()
|
|
||||||
|
|
||||||
@Override
|
final EventBus eventBus
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
final HostCache cache
|
||||||
}
|
final ConnectionManager manager
|
||||||
|
final I2PSession session
|
||||||
|
final long interval
|
||||||
|
final MuWireSettings settings
|
||||||
|
final Timer timer
|
||||||
|
|
||||||
@Override
|
public CacheClient(EventBus eventBus, HostCache cache,
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
ConnectionManager manager, I2PSession session,
|
||||||
|
MuWireSettings settings, long interval) {
|
||||||
if (proto != I2PSession.PROTO_DATAGRAM) {
|
this.eventBus = eventBus
|
||||||
log.warning "Received unexpected protocol $proto"
|
this.cache = cache
|
||||||
return
|
this.manager = manager
|
||||||
}
|
this.session = session
|
||||||
|
this.settings = settings
|
||||||
def payload = session.receiveMessage(msgId)
|
this.interval = interval
|
||||||
def dissector = new I2PDatagramDissector()
|
this.timer = new Timer("hostcache-client",true)
|
||||||
try {
|
}
|
||||||
dissector.loadI2PDatagram(payload)
|
|
||||||
def sender = dissector.getSender()
|
|
||||||
log.info("Received something from ${sender.toBase32()}")
|
|
||||||
|
|
||||||
payload = dissector.getPayload()
|
|
||||||
payload = slurper.parse(payload)
|
|
||||||
|
|
||||||
if (payload.type == null) {
|
|
||||||
log.warning("type missing")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(payload.type) {
|
|
||||||
case "Pong" : handlePong(sender, payload); break
|
|
||||||
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
|
||||||
default : log.warning("unknown type ${payload.type}")
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warning("Invalid datagram $e")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
void start() {
|
||||||
public void reportAbuse(I2PSession session, int severity) {
|
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
||||||
}
|
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
void stop() {
|
||||||
public void disconnected(I2PSession session) {
|
timer.cancel()
|
||||||
log.severe "I2P session disconnected"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void queryIfNeeded() {
|
||||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
if (!manager.getConnections().isEmpty())
|
||||||
log.severe "I2P error occured $message $error"
|
return
|
||||||
}
|
if (!cache.getHosts(1).isEmpty())
|
||||||
|
return
|
||||||
}
|
|
||||||
|
log.info "Will query hostcaches"
|
||||||
private void handlePong(Destination from, def pong) {
|
|
||||||
if (!CacheServers.isRegistered(from)) {
|
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
||||||
log.warning("received pong from non-registered destination")
|
ping = JsonOutput.toJson(ping)
|
||||||
return
|
def maker = new I2PDatagramMaker(session)
|
||||||
}
|
ping = maker.makeI2PDatagram(ping.bytes)
|
||||||
|
def options = new SendMessageOptions()
|
||||||
if (pong.pongs == null) {
|
options.setSendLeaseSet(true)
|
||||||
log.warning("malformed pong - no pongs")
|
CacheServers.getCacheServers().each {
|
||||||
return
|
log.info "Querying hostcache ${it.toBase32()}"
|
||||||
}
|
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||||
|
}
|
||||||
pong.pongs.asList().each {
|
}
|
||||||
Destination dest = new Destination(it)
|
|
||||||
if (!session.getMyDestination().equals(dest))
|
class Listener implements I2PSessionMuxedListener {
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
|
||||||
}
|
private final JsonSlurper slurper = new JsonSlurper()
|
||||||
|
|
||||||
}
|
@Override
|
||||||
|
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||||
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
}
|
||||||
if (settings.isLeaf()) {
|
|
||||||
log.warning("Received crawler ping but I'm a leaf")
|
@Override
|
||||||
return
|
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
||||||
}
|
|
||||||
|
if (proto != I2PSession.PROTO_DATAGRAM) {
|
||||||
switch(settings.getCrawlerResponse()) {
|
log.warning "Received unexpected protocol $proto"
|
||||||
case CrawlerResponse.NONE:
|
return
|
||||||
log.info("Responding to crawlers is disabled by user")
|
}
|
||||||
break
|
|
||||||
case CrawlerResponse.ALL:
|
def payload = session.receiveMessage(msgId)
|
||||||
respondToCrawler(session, from, ping)
|
def dissector = new I2PDatagramDissector()
|
||||||
break;
|
try {
|
||||||
case CrawlerResponse.REGISTERED:
|
dissector.loadI2PDatagram(payload)
|
||||||
if (CacheServers.isRegistered(from))
|
def sender = dissector.getSender()
|
||||||
respondToCrawler(session, from, ping)
|
log.info("Received something from ${sender.toBase32()}")
|
||||||
else
|
|
||||||
log.warning("Ignoring crawler ping from non-registered crawler")
|
payload = dissector.getPayload()
|
||||||
break
|
payload = slurper.parse(payload)
|
||||||
}
|
|
||||||
}
|
if (payload.type == null) {
|
||||||
|
log.warning("type missing")
|
||||||
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
return
|
||||||
log.info "responding to crawler ping"
|
}
|
||||||
|
|
||||||
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
switch(payload.type) {
|
||||||
Collections.shuffle(neighbors)
|
case "Pong" : handlePong(sender, payload); break
|
||||||
if (neighbors.size() > CRAWLER_RETURN)
|
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
||||||
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
default : log.warning("unknown type ${payload.type}")
|
||||||
|
}
|
||||||
def upManager = (UltrapeerConnectionManager) manager;
|
} catch (Exception e) {
|
||||||
def pong = [:]
|
log.warning("Invalid datagram $e")
|
||||||
pong.peers = neighbors
|
}
|
||||||
pong.uuid = ping.uuid
|
}
|
||||||
pong.type = "CrawlerPong"
|
|
||||||
pong.version = 1
|
@Override
|
||||||
pong.leafSlots = upManager.hasLeafSlots()
|
public void reportAbuse(I2PSession session, int severity) {
|
||||||
pong.peerSlots = upManager.hasPeerSlots()
|
}
|
||||||
pong = JsonOutput.toJson(pong)
|
|
||||||
|
@Override
|
||||||
def maker = new I2PDatagramMaker(session)
|
public void disconnected(I2PSession session) {
|
||||||
pong = maker.makeI2PDatagram(pong.bytes)
|
log.severe "I2P session disconnected"
|
||||||
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||||
|
log.severe "I2P error occured $message $error"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePong(Destination from, def pong) {
|
||||||
|
if (!CacheServers.isRegistered(from)) {
|
||||||
|
log.warning("received pong from non-registered destination")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pong.pongs == null) {
|
||||||
|
log.warning("malformed pong - no pongs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pong.pongs.asList().each {
|
||||||
|
Destination dest = new Destination(it)
|
||||||
|
if (!session.getMyDestination().equals(dest))
|
||||||
|
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
||||||
|
if (settings.isLeaf()) {
|
||||||
|
log.warning("Received crawler ping but I'm a leaf")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(settings.getCrawlerResponse()) {
|
||||||
|
case CrawlerResponse.NONE:
|
||||||
|
log.info("Responding to crawlers is disabled by user")
|
||||||
|
break
|
||||||
|
case CrawlerResponse.ALL:
|
||||||
|
respondToCrawler(session, from, ping)
|
||||||
|
break;
|
||||||
|
case CrawlerResponse.REGISTERED:
|
||||||
|
if (CacheServers.isRegistered(from))
|
||||||
|
respondToCrawler(session, from, ping)
|
||||||
|
else
|
||||||
|
log.warning("Ignoring crawler ping from non-registered crawler")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
||||||
|
log.info "responding to crawler ping"
|
||||||
|
|
||||||
|
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
||||||
|
Collections.shuffle(neighbors)
|
||||||
|
if (neighbors.size() > CRAWLER_RETURN)
|
||||||
|
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
||||||
|
|
||||||
|
def upManager = (UltrapeerConnectionManager) manager;
|
||||||
|
def pong = [:]
|
||||||
|
pong.peers = neighbors
|
||||||
|
pong.uuid = ping.uuid
|
||||||
|
pong.type = "CrawlerPong"
|
||||||
|
pong.version = 1
|
||||||
|
pong.leafSlots = upManager.hasLeafSlots()
|
||||||
|
pong.peerSlots = upManager.hasPeerSlots()
|
||||||
|
pong = JsonOutput.toJson(pong)
|
||||||
|
|
||||||
|
def maker = new I2PDatagramMaker(session)
|
||||||
|
pong = maker.makeI2PDatagram(pong.bytes)
|
||||||
|
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,21 +4,25 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class CacheServers {
|
class CacheServers {
|
||||||
|
|
||||||
private static final int TO_GIVE = 3
|
private static final int TO_GIVE = 3
|
||||||
private static Set<Destination> CACHES = [
|
private static Set<Destination> CACHES = [
|
||||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
// zlatinb
|
||||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||||
]
|
// sNL
|
||||||
|
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA=="),
|
||||||
|
// dark_trion
|
||||||
|
new Destination("Gec9L29FVcQvYDgpcYuEYdltJn06PPoOWAcAM8Af-gDm~ehlrJcwlLXXs0hidq~yP2A0X7QcDi6i6shAfuEofTchxGJl8LRNqj9lio7WnB7cIixXWL~uCkD7Np5LMX0~akNX34oOb9RcBYVT2U5rFGJmJ7OtBv~IBkGeLhsMrqaCjahd0jdBO~QJ-t82ZKZhh044d24~JEfF9zSJxdBoCdAcXzryGNy7sYtFVDFsPKJudAxSW-UsSQiGw2~k-TxyF0r-iAt1IdzfNu8Lu0WPqLdhDYJWcPldx2PR5uJorI~zo~z3I5RX3NwzarlbD4nEP5s65ahPSfVCEkzmaJUBgP8DvBqlFaX89K4nGRYc7jkEjJ8cX4L6YPXUpTPWcfKkW259WdQY3YFh6x7rzijrGZewpczOLCrt-bZRYgDrUibmZxKZmNhy~lQu4gYVVjkz1i4tL~DWlhIc4y0x2vItwkYLArPPi~ejTnt-~Lhb7oPMXRcWa3UrwGKpFvGZY4NXBQAEAAcAAA==")
|
||||||
|
]
|
||||||
|
|
||||||
static List<Destination> getCacheServers() {
|
static List<Destination> getCacheServers() {
|
||||||
List<Destination> allCaches = new ArrayList<>(CACHES)
|
List<Destination> allCaches = new ArrayList<>(CACHES)
|
||||||
Collections.shuffle(allCaches)
|
Collections.shuffle(allCaches)
|
||||||
if (allCaches.size() <= TO_GIVE)
|
if (allCaches.size() <= TO_GIVE)
|
||||||
return allCaches
|
return allCaches
|
||||||
allCaches[0..TO_GIVE-1]
|
allCaches[0..TO_GIVE-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isRegistered(Destination d) {
|
static boolean isRegistered(Destination d) {
|
||||||
return CACHES.contains(d)
|
return CACHES.contains(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,42 +4,42 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Host {
|
class Host {
|
||||||
|
|
||||||
private static final int MAX_FAILURES = 3
|
private static final int MAX_FAILURES = 3
|
||||||
|
|
||||||
final Destination destination
|
|
||||||
private final int clearInterval
|
|
||||||
int failures,successes
|
|
||||||
long lastAttempt
|
|
||||||
|
|
||||||
public Host(Destination destination, int clearInterval) {
|
|
||||||
this.destination = destination
|
|
||||||
this.clearInterval = clearInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void onConnect() {
|
final Destination destination
|
||||||
failures = 0
|
private final int clearInterval
|
||||||
successes++
|
int failures,successes
|
||||||
|
long lastAttempt
|
||||||
|
|
||||||
|
public Host(Destination destination, int clearInterval) {
|
||||||
|
this.destination = destination
|
||||||
|
this.clearInterval = clearInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void onConnect() {
|
||||||
|
failures = 0
|
||||||
|
successes++
|
||||||
lastAttempt = System.currentTimeMillis()
|
lastAttempt = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onFailure() {
|
synchronized void onFailure() {
|
||||||
failures++
|
failures++
|
||||||
successes = 0
|
successes = 0
|
||||||
lastAttempt = System.currentTimeMillis()
|
lastAttempt = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isFailed() {
|
synchronized boolean isFailed() {
|
||||||
failures >= MAX_FAILURES
|
failures >= MAX_FAILURES
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean hasSucceeded() {
|
synchronized boolean hasSucceeded() {
|
||||||
successes > 0
|
successes > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void clearFailures() {
|
synchronized void clearFailures() {
|
||||||
failures = 0
|
failures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void canTryAgain() {
|
synchronized void canTryAgain() {
|
||||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||||
}
|
}
|
||||||
|
@@ -15,141 +15,141 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class HostCache extends Service {
|
class HostCache extends Service {
|
||||||
|
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final File storage
|
final File storage
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Destination myself
|
final Destination myself
|
||||||
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
HostCache(){}
|
|
||||||
|
|
||||||
public HostCache(TrustService trustService, File storage, int interval,
|
|
||||||
MuWireSettings settings, Destination myself) {
|
|
||||||
this.trustService = trustService
|
|
||||||
this.storage = storage
|
|
||||||
this.interval = interval
|
|
||||||
this.settings = settings
|
|
||||||
this.myself = myself
|
|
||||||
this.timer = new Timer("host-persister",true)
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
HostCache(){}
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
|
||||||
}
|
public HostCache(TrustService trustService, File storage, int interval,
|
||||||
|
MuWireSettings settings, Destination myself) {
|
||||||
void stop() {
|
this.trustService = trustService
|
||||||
timer.cancel()
|
this.storage = storage
|
||||||
}
|
this.interval = interval
|
||||||
|
this.settings = settings
|
||||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
this.myself = myself
|
||||||
if (myself == e.destination)
|
this.timer = new Timer("host-persister",true)
|
||||||
return
|
}
|
||||||
if (hosts.containsKey(e.destination)) {
|
|
||||||
|
void start() {
|
||||||
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
timer.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||||
|
if (myself == e.destination)
|
||||||
|
return
|
||||||
|
if (hosts.containsKey(e.destination)) {
|
||||||
if (!e.fromHostcache)
|
if (!e.fromHostcache)
|
||||||
return
|
return
|
||||||
hosts.get(e.destination).clearFailures()
|
hosts.get(e.destination).clearFailures()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
hosts.put(e.destination, host)
|
hosts.put(e.destination, host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
|
||||||
if (e.leaf)
|
|
||||||
return
|
|
||||||
Destination dest = e.endpoint.destination
|
|
||||||
Host host = hosts.get(dest)
|
|
||||||
if (host == null) {
|
|
||||||
host = new Host(dest, settings.hostClearInterval)
|
|
||||||
hosts.put(dest, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(e.status) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
if (e.leaf)
|
||||||
case ConnectionAttemptStatus.REJECTED:
|
return
|
||||||
host.onConnect()
|
Destination dest = e.endpoint.destination
|
||||||
break
|
Host host = hosts.get(dest)
|
||||||
case ConnectionAttemptStatus.FAILED:
|
if (host == null) {
|
||||||
host.onFailure()
|
host = new Host(dest, settings.hostClearInterval)
|
||||||
break
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
switch(e.status) {
|
||||||
List<Destination> getHosts(int n) {
|
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
case ConnectionAttemptStatus.REJECTED:
|
||||||
rv.retainAll {allowHost(hosts[it])}
|
host.onConnect()
|
||||||
if (rv.size() <= n)
|
break
|
||||||
return rv
|
case ConnectionAttemptStatus.FAILED:
|
||||||
Collections.shuffle(rv)
|
host.onFailure()
|
||||||
rv[0..n-1]
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
List<Destination> getGoodHosts(int n) {
|
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
List<Destination> getHosts(int n) {
|
||||||
rv.retainAll {
|
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||||
Host host = hosts[it]
|
rv.retainAll {allowHost(hosts[it])}
|
||||||
allowHost(host) && host.hasSucceeded()
|
if (rv.size() <= n)
|
||||||
}
|
return rv
|
||||||
if (rv.size() <= n)
|
Collections.shuffle(rv)
|
||||||
return rv
|
rv[0..n-1]
|
||||||
Collections.shuffle(rv)
|
}
|
||||||
rv[0..n-1]
|
|
||||||
}
|
List<Destination> getGoodHosts(int n) {
|
||||||
|
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||||
void load() {
|
rv.retainAll {
|
||||||
if (storage.exists()) {
|
Host host = hosts[it]
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
allowHost(host) && host.hasSucceeded()
|
||||||
storage.eachLine {
|
}
|
||||||
def entry = slurper.parseText(it)
|
if (rv.size() <= n)
|
||||||
Destination dest = new Destination(entry.destination)
|
return rv
|
||||||
Host host = new Host(dest, settings.hostClearInterval)
|
Collections.shuffle(rv)
|
||||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
rv[0..n-1]
|
||||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
}
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
if (storage.exists()) {
|
||||||
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
|
storage.eachLine {
|
||||||
|
def entry = slurper.parseText(it)
|
||||||
|
Destination dest = new Destination(entry.destination)
|
||||||
|
Host host = new Host(dest, settings.hostClearInterval)
|
||||||
|
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||||
|
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||||
if (entry.lastAttempt != null)
|
if (entry.lastAttempt != null)
|
||||||
host.lastAttempt = entry.lastAttempt
|
host.lastAttempt = entry.lastAttempt
|
||||||
if (allowHost(host))
|
if (allowHost(host))
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({save()} as TimerTask, interval, interval)
|
timer.schedule({save()} as TimerTask, interval, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowHost(Host host) {
|
private boolean allowHost(Host host) {
|
||||||
if (host.isFailed() && !host.canTryAgain())
|
if (host.isFailed() && !host.canTryAgain())
|
||||||
return false
|
return false
|
||||||
if (host.destination == myself)
|
if (host.destination == myself)
|
||||||
return false
|
return false
|
||||||
TrustLevel trust = trustService.getLevel(host.destination)
|
TrustLevel trust = trustService.getLevel(host.destination)
|
||||||
switch(trust) {
|
switch(trust) {
|
||||||
case TrustLevel.DISTRUSTED :
|
case TrustLevel.DISTRUSTED :
|
||||||
return false
|
return false
|
||||||
case TrustLevel.TRUSTED :
|
case TrustLevel.TRUSTED :
|
||||||
return true
|
return true
|
||||||
case TrustLevel.NEUTRAL :
|
case TrustLevel.NEUTRAL :
|
||||||
return settings.allowUntrusted()
|
return settings.allowUntrusted()
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
storage.delete()
|
storage.delete()
|
||||||
storage.withPrintWriter { writer ->
|
storage.withPrintWriter { writer ->
|
||||||
hosts.each { dest, host ->
|
hosts.each { dest, host ->
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
map.destination = dest.toBase64()
|
map.destination = dest.toBase64()
|
||||||
map.failures = host.failures
|
map.failures = host.failures
|
||||||
map.successes = host.successes
|
map.successes = host.successes
|
||||||
map.lastAttempt = host.lastAttempt
|
map.lastAttempt = host.lastAttempt
|
||||||
def json = JsonOutput.toJson(map)
|
def json = JsonOutput.toJson(map)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class HostDiscoveredEvent extends Event {
|
class HostDiscoveredEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
Destination destination
|
||||||
boolean fromHostcache
|
boolean fromHostcache
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,12 @@ class Mesh {
|
|||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final Set<Persona> sources = new ConcurrentHashSet<>()
|
private final Set<Persona> sources = new ConcurrentHashSet<>()
|
||||||
private final Pieces pieces
|
private final Pieces pieces
|
||||||
|
|
||||||
Mesh(InfoHash infoHash, Pieces pieces) {
|
Mesh(InfoHash infoHash, Pieces pieces) {
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
this.pieces = pieces
|
this.pieces = pieces
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Persona> getRandom(int n, Persona exclude) {
|
Set<Persona> getRandom(int n, Persona exclude) {
|
||||||
List<Persona> tmp = new ArrayList<>(sources)
|
List<Persona> tmp = new ArrayList<>(sources)
|
||||||
tmp.remove(exclude)
|
tmp.remove(exclude)
|
||||||
|
@@ -16,23 +16,23 @@ import groovy.json.JsonSlurper
|
|||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class MeshManager {
|
class MeshManager {
|
||||||
|
|
||||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final File home
|
private final File home
|
||||||
private final MuWireSettings settings
|
private final MuWireSettings settings
|
||||||
|
|
||||||
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.home = home
|
this.home = home
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh get(InfoHash infoHash) {
|
Mesh get(InfoHash infoHash) {
|
||||||
meshes.get(infoHash)
|
meshes.get(infoHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
|
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
|
||||||
synchronized(meshes) {
|
synchronized(meshes) {
|
||||||
if (meshes.containsKey(infoHash))
|
if (meshes.containsKey(infoHash))
|
||||||
@@ -47,7 +47,7 @@ class MeshManager {
|
|||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
||||||
Mesh mesh = meshes.get(e.infoHash)
|
Mesh mesh = meshes.get(e.infoHash)
|
||||||
if (mesh == null)
|
if (mesh == null)
|
||||||
@@ -55,7 +55,7 @@ class MeshManager {
|
|||||||
mesh.sources.add(e.source)
|
mesh.sources.add(e.source)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
File meshFile = new File(home, "mesh.json")
|
File meshFile = new File(home, "mesh.json")
|
||||||
synchronized(meshes) {
|
synchronized(meshes) {
|
||||||
@@ -72,29 +72,29 @@ class MeshManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
File meshFile = new File(home, "mesh.json")
|
File meshFile = new File(home, "mesh.json")
|
||||||
if (!meshFile.exists())
|
if (!meshFile.exists())
|
||||||
return
|
return
|
||||||
long now = System.currentTimeMillis()
|
long now = System.currentTimeMillis()
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
meshFile.eachLine {
|
meshFile.eachLine {
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
||||||
return
|
return
|
||||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
||||||
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
||||||
|
|
||||||
Mesh mesh = new Mesh(infoHash, pieces)
|
Mesh mesh = new Mesh(infoHash, pieces)
|
||||||
json.sources.each { source ->
|
json.sources.each { source ->
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
|
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
|
||||||
mesh.sources.add(persona)
|
mesh.sources.add(persona)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.xHave != null)
|
if (json.xHave != null)
|
||||||
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
||||||
|
|
||||||
if (!mesh.sources.isEmpty())
|
if (!mesh.sources.isEmpty())
|
||||||
meshes.put(infoHash, mesh)
|
meshes.put(infoHash, mesh)
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Base32
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class DeleteEvent extends Event {
|
class DeleteEvent extends Event {
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,5 +21,5 @@ class InvalidSearchResultException extends Exception {
|
|||||||
super(cause);
|
super(cause);
|
||||||
// TODO Auto-generated constructor stub
|
// TODO Auto-generated constructor stub
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,33 +6,33 @@ import com.muwire.core.connection.UltrapeerConnectionManager
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class LeafSearcher {
|
class LeafSearcher {
|
||||||
|
|
||||||
final UltrapeerConnectionManager connectionManager
|
final UltrapeerConnectionManager connectionManager
|
||||||
final SearchIndex searchIndex = new SearchIndex()
|
final SearchIndex searchIndex = new SearchIndex()
|
||||||
|
|
||||||
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
||||||
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
||||||
|
|
||||||
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
||||||
|
|
||||||
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||||
this.connectionManager = connectionManager
|
this.connectionManager = connectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpsertEvent(UpsertEvent e) {
|
void onUpsertEvent(UpsertEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDeleteEvent(DeleteEvent e) {
|
void onDeleteEvent(DeleteEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,12 @@ import com.muwire.core.Persona
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class QueryEvent extends Event {
|
class QueryEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
boolean firstHop
|
boolean firstHop
|
||||||
Destination replyTo
|
Destination replyTo
|
||||||
Persona originator
|
Persona originator
|
||||||
Destination receivedOn
|
Destination receivedOn
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||||
|
@@ -6,6 +6,6 @@ import com.muwire.core.SharedFile
|
|||||||
class ResultsEvent extends Event {
|
class ResultsEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
SharedFile[] results
|
SharedFile[] results
|
||||||
UUID uuid
|
UUID uuid
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ class ResultsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static parseV1(Persona p, UUID uuid, def json) {
|
private static parseV1(Persona p, UUID uuid, def json) {
|
||||||
if (json.name == null)
|
if (json.name == null)
|
||||||
throw new InvalidSearchResultException("name missing")
|
throw new InvalidSearchResultException("name missing")
|
||||||
@@ -55,7 +55,7 @@ class ResultsParser {
|
|||||||
InfoHash parsedIH = InfoHash.fromHashList(hashList)
|
InfoHash parsedIH = InfoHash.fromHashList(hashList)
|
||||||
if (parsedIH.getRoot() != infoHash)
|
if (parsedIH.getRoot() != infoHash)
|
||||||
throw new InvalidSearchControlsException("infohash root doesn't match")
|
throw new InvalidSearchControlsException("infohash root doesn't match")
|
||||||
|
|
||||||
return new UIResultEvent( sender : p,
|
return new UIResultEvent( sender : p,
|
||||||
name : name,
|
name : name,
|
||||||
size : size,
|
size : size,
|
||||||
@@ -67,7 +67,7 @@ class ResultsParser {
|
|||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UIResultEvent parseV2(Persona p, UUID uuid, def json) {
|
private static UIResultEvent parseV2(Persona p, UUID uuid, def json) {
|
||||||
if (json.name == null)
|
if (json.name == null)
|
||||||
throw new InvalidSearchResultException("name missing")
|
throw new InvalidSearchResultException("name missing")
|
||||||
@@ -86,11 +86,11 @@ class ResultsParser {
|
|||||||
if (infoHash.length != InfoHash.SIZE)
|
if (infoHash.length != InfoHash.SIZE)
|
||||||
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
||||||
int pieceSize = json.pieceSize
|
int pieceSize = json.pieceSize
|
||||||
|
|
||||||
Set<Destination> sources = Collections.emptySet()
|
Set<Destination> sources = Collections.emptySet()
|
||||||
if (json.sources != null)
|
if (json.sources != null)
|
||||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
||||||
|
|
||||||
return new UIResultEvent( sender : p,
|
return new UIResultEvent( sender : p,
|
||||||
name : name,
|
name : name,
|
||||||
size : size,
|
size : size,
|
||||||
|
@@ -25,9 +25,9 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class ResultsSender {
|
class ResultsSender {
|
||||||
|
|
||||||
private static final AtomicInteger THREAD_NO = new AtomicInteger()
|
private static final AtomicInteger THREAD_NO = new AtomicInteger()
|
||||||
|
|
||||||
private final Executor executor = Executors.newCachedThreadPool(
|
private final Executor executor = Executors.newCachedThreadPool(
|
||||||
new ThreadFactory() {
|
new ThreadFactory() {
|
||||||
@Override
|
@Override
|
||||||
@@ -38,27 +38,27 @@ class ResultsSender {
|
|||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
|
|
||||||
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me) {
|
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me) {
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
||||||
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
||||||
if (target.equals(me.destination)) {
|
if (target.equals(me.destination)) {
|
||||||
results.each {
|
results.each {
|
||||||
long length = it.getFile().length()
|
long length = it.getFile().length()
|
||||||
int pieceSize = it.getPieceSize()
|
int pieceSize = it.getPieceSize()
|
||||||
if (pieceSize == 0)
|
if (pieceSize == 0)
|
||||||
pieceSize = FileHasher.getPieceSize(length)
|
pieceSize = FileHasher.getPieceSize(length)
|
||||||
Set<Destination> suggested = Collections.emptySet()
|
Set<Destination> suggested = Collections.emptySet()
|
||||||
if (it instanceof DownloadedFile)
|
if (it instanceof DownloadedFile)
|
||||||
suggested = it.sources
|
suggested = it.sources
|
||||||
def uiResultEvent = new UIResultEvent( sender : me,
|
def uiResultEvent = new UIResultEvent( sender : me,
|
||||||
name : it.getFile().getName(),
|
name : it.getFile().getName(),
|
||||||
@@ -71,17 +71,17 @@ class ResultsSender {
|
|||||||
eventBus.publish(uiResultEvent)
|
eventBus.publish(uiResultEvent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
||||||
target: target, oobInfohash : oobInfohash))
|
target: target, oobInfohash : oobInfohash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ResultSendJob implements Runnable {
|
private class ResultSendJob implements Runnable {
|
||||||
UUID uuid
|
UUID uuid
|
||||||
SharedFile [] results
|
SharedFile [] results
|
||||||
Destination target
|
Destination target
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
@@ -5,11 +5,11 @@ import com.muwire.core.InfoHash
|
|||||||
|
|
||||||
class SearchEvent extends Event {
|
class SearchEvent extends Event {
|
||||||
|
|
||||||
List<String> searchTerms
|
List<String> searchTerms
|
||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
def infoHash = null
|
def infoHash = null
|
||||||
if (searchHash != null)
|
if (searchHash != null)
|
||||||
|
@@ -4,56 +4,56 @@ import com.muwire.core.Constants
|
|||||||
|
|
||||||
class SearchIndex {
|
class SearchIndex {
|
||||||
|
|
||||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||||
|
|
||||||
void add(String string) {
|
void add(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get(it)
|
Set<String> existing = keywords.get(it)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
keywords.put(it, existing)
|
keywords.put(it, existing)
|
||||||
}
|
}
|
||||||
existing.add(string)
|
existing.add(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(String string) {
|
void remove(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get it
|
Set<String> existing = keywords.get it
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(string)
|
existing.remove(string)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
keywords.remove(it)
|
keywords.remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] split(String source) {
|
private static String[] split(String source) {
|
||||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||||
String [] split = source.split(" ")
|
String [] split = source.split(" ")
|
||||||
def rv = []
|
def rv = []
|
||||||
split.each { if (it.length() > 0) rv << it }
|
split.each { if (it.length() > 0) rv << it }
|
||||||
rv.toArray(new String[0])
|
rv.toArray(new String[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] search(List<String> terms) {
|
String[] search(List<String> terms) {
|
||||||
Set<String> rv = null;
|
Set<String> rv = null;
|
||||||
|
|
||||||
terms.each {
|
terms.each {
|
||||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||||
if (rv == null) {
|
if (rv == null) {
|
||||||
rv = new HashSet<>(forWord)
|
rv = new HashSet<>(forWord)
|
||||||
} else {
|
} else {
|
||||||
rv.retainAll(forWord)
|
rv.retainAll(forWord)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv != null)
|
if (rv != null)
|
||||||
return rv.asList()
|
return rv.asList()
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,17 +8,17 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class SearchManager {
|
public class SearchManager {
|
||||||
|
|
||||||
private static final int EXPIRE_TIME = 60 * 1000 * 1000
|
private static final int EXPIRE_TIME = 60 * 1000 * 1000
|
||||||
private static final int CHECK_INTERVAL = 60 * 1000
|
private static final int CHECK_INTERVAL = 60 * 1000
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final ResultsSender resultsSender
|
private final ResultsSender resultsSender
|
||||||
private final Map<UUID, QueryEvent> responderAddress = Collections.synchronizedMap(new HashMap<>())
|
private final Map<UUID, QueryEvent> responderAddress = Collections.synchronizedMap(new HashMap<>())
|
||||||
|
|
||||||
SearchManager(){}
|
SearchManager(){}
|
||||||
|
|
||||||
SearchManager(EventBus eventBus, Persona me, ResultsSender resultsSender) {
|
SearchManager(EventBus eventBus, Persona me, ResultsSender resultsSender) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
@@ -26,7 +26,7 @@ public class SearchManager {
|
|||||||
Timer timer = new Timer("query-expirer", true)
|
Timer timer = new Timer("query-expirer", true)
|
||||||
timer.schedule({cleanup()} as TimerTask, CHECK_INTERVAL, CHECK_INTERVAL)
|
timer.schedule({cleanup()} as TimerTask, CHECK_INTERVAL, CHECK_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent event) {
|
void onQueryEvent(QueryEvent event) {
|
||||||
if (responderAddress.containsKey(event.searchEvent.uuid)) {
|
if (responderAddress.containsKey(event.searchEvent.uuid)) {
|
||||||
log.info("Dropping duplicate search uuid $event.searchEvent.uuid")
|
log.info("Dropping duplicate search uuid $event.searchEvent.uuid")
|
||||||
@@ -35,7 +35,7 @@ public class SearchManager {
|
|||||||
responderAddress.put(event.searchEvent.uuid, event)
|
responderAddress.put(event.searchEvent.uuid, event)
|
||||||
eventBus.publish(event.searchEvent)
|
eventBus.publish(event.searchEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onResultsEvent(ResultsEvent event) {
|
void onResultsEvent(ResultsEvent event) {
|
||||||
Destination target = responderAddress.get(event.uuid)?.replyTo
|
Destination target = responderAddress.get(event.uuid)?.replyTo
|
||||||
if (target == null)
|
if (target == null)
|
||||||
@@ -46,11 +46,11 @@ public class SearchManager {
|
|||||||
}
|
}
|
||||||
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash)
|
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash)
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLocalSearch(UUID uuid) {
|
boolean hasLocalSearch(UUID uuid) {
|
||||||
me.destination.equals(responderAddress.get(uuid)?.replyTo)
|
me.destination.equals(responderAddress.get(uuid)?.replyTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
synchronized(responderAddress) {
|
synchronized(responderAddress) {
|
||||||
|
@@ -14,7 +14,7 @@ class UIResultEvent extends Event {
|
|||||||
long size
|
long size
|
||||||
InfoHash infohash
|
InfoHash infohash
|
||||||
int pieceSize
|
int pieceSize
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
super.toString() + "name:$name size:$size sender:${sender.getHumanReadableName()} pieceSize $pieceSize"
|
super.toString() + "name:$name size:$size sender:${sender.getHumanReadableName()} pieceSize $pieceSize"
|
||||||
|
@@ -18,5 +18,5 @@ class UnexpectedResultsException extends Exception {
|
|||||||
public UnexpectedResultsException(String message) {
|
public UnexpectedResultsException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,12 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class UpsertEvent extends Event {
|
class UpsertEvent extends Event {
|
||||||
|
|
||||||
Set<String> names
|
Set<String> names
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,19 +8,19 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
class RemoteTrustList {
|
class RemoteTrustList {
|
||||||
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||||
|
|
||||||
private final Persona persona
|
private final Persona persona
|
||||||
private final Set<Persona> good, bad
|
private final Set<Persona> good, bad
|
||||||
volatile long timestamp
|
volatile long timestamp
|
||||||
volatile boolean forceUpdate
|
volatile boolean forceUpdate
|
||||||
Status status = Status.NEW
|
Status status = Status.NEW
|
||||||
|
|
||||||
RemoteTrustList(Persona persona) {
|
RemoteTrustList(Persona persona) {
|
||||||
this.persona = persona
|
this.persona = persona
|
||||||
good = new ConcurrentHashSet<>()
|
good = new ConcurrentHashSet<>()
|
||||||
bad = new ConcurrentHashSet<>()
|
bad = new ConcurrentHashSet<>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof RemoteTrustList))
|
if (!(o instanceof RemoteTrustList))
|
||||||
|
@@ -5,6 +5,6 @@ import com.muwire.core.Persona
|
|||||||
|
|
||||||
class TrustEvent extends Event {
|
class TrustEvent extends Event {
|
||||||
|
|
||||||
Persona persona
|
Persona persona
|
||||||
TrustLevel level
|
TrustLevel level
|
||||||
}
|
}
|
||||||
|
@@ -11,87 +11,87 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
class TrustService extends Service {
|
class TrustService extends Service {
|
||||||
|
|
||||||
final File persistGood, persistBad
|
final File persistGood, persistBad
|
||||||
final long persistInterval
|
final long persistInterval
|
||||||
|
|
||||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
|
||||||
TrustService() {}
|
TrustService() {}
|
||||||
|
|
||||||
TrustService(File persistGood, File persistBad, long persistInterval) {
|
TrustService(File persistGood, File persistBad, long persistInterval) {
|
||||||
this.persistBad = persistBad
|
this.persistBad = persistBad
|
||||||
this.persistGood = persistGood
|
this.persistGood = persistGood
|
||||||
this.persistInterval = persistInterval
|
this.persistInterval = persistInterval
|
||||||
this.timer = new Timer("trust-persister",true)
|
this.timer = new Timer("trust-persister",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (persistGood.exists()) {
|
if (persistGood.exists()) {
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
good.put(persona.destination, persona)
|
good.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (persistBad.exists()) {
|
if (persistBad.exists()) {
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
bad.put(persona.destination, persona)
|
bad.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persist() {
|
private void persist() {
|
||||||
persistGood.delete()
|
persistGood.delete()
|
||||||
persistGood.withPrintWriter { writer ->
|
persistGood.withPrintWriter { writer ->
|
||||||
good.each {k,v ->
|
good.each {k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
persistBad.delete()
|
persistBad.delete()
|
||||||
persistBad.withPrintWriter { writer ->
|
persistBad.withPrintWriter { writer ->
|
||||||
bad.each { k,v ->
|
bad.each { k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TrustLevel getLevel(Destination dest) {
|
TrustLevel getLevel(Destination dest) {
|
||||||
if (good.containsKey(dest))
|
if (good.containsKey(dest))
|
||||||
return TrustLevel.TRUSTED
|
return TrustLevel.TRUSTED
|
||||||
else if (bad.containsKey(dest))
|
else if (bad.containsKey(dest))
|
||||||
return TrustLevel.DISTRUSTED
|
return TrustLevel.DISTRUSTED
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
switch(e.level) {
|
switch(e.level) {
|
||||||
case TrustLevel.TRUSTED:
|
case TrustLevel.TRUSTED:
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
good.put(e.persona.destination, e.persona)
|
good.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.DISTRUSTED:
|
case TrustLevel.DISTRUSTED:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.put(e.persona.destination, e.persona)
|
bad.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.NEUTRAL:
|
case TrustLevel.NEUTRAL:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,32 +22,32 @@ class TrustSubscriber {
|
|||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final I2PConnector i2pConnector
|
private final I2PConnector i2pConnector
|
||||||
private final MuWireSettings settings
|
private final MuWireSettings settings
|
||||||
|
|
||||||
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
private final Object waitLock = new Object()
|
private final Object waitLock = new Object()
|
||||||
private volatile boolean shutdown
|
private volatile boolean shutdown
|
||||||
private volatile Thread thread
|
private volatile Thread thread
|
||||||
private final ExecutorService updateThreads = Executors.newCachedThreadPool()
|
private final ExecutorService updateThreads = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) {
|
TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.i2pConnector = i2pConnector
|
this.i2pConnector = i2pConnector
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
thread = new Thread({checkLoop()} as Runnable, "trust-subscriber")
|
thread = new Thread({checkLoop()} as Runnable, "trust-subscriber")
|
||||||
thread.setDaemon(true)
|
thread.setDaemon(true)
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
shutdown = true
|
shutdown = true
|
||||||
thread?.interrupt()
|
thread?.interrupt()
|
||||||
updateThreads.shutdownNow()
|
updateThreads.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
||||||
if (!e.subscribe) {
|
if (!e.subscribe) {
|
||||||
remoteTrustLists.remove(e.persona.destination)
|
remoteTrustLists.remove(e.persona.destination)
|
||||||
@@ -59,7 +59,7 @@ class TrustSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkLoop() {
|
private void checkLoop() {
|
||||||
try {
|
try {
|
||||||
while(!shutdown) {
|
while(!shutdown) {
|
||||||
@@ -82,15 +82,15 @@ class TrustSubscriber {
|
|||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateJob implements Runnable {
|
private class UpdateJob implements Runnable {
|
||||||
|
|
||||||
private final RemoteTrustList trustList
|
private final RemoteTrustList trustList
|
||||||
|
|
||||||
UpdateJob(RemoteTrustList trustList) {
|
UpdateJob(RemoteTrustList trustList) {
|
||||||
this.trustList = trustList
|
this.trustList = trustList
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
trustList.status = RemoteTrustList.Status.UPDATING
|
trustList.status = RemoteTrustList.Status.UPDATING
|
||||||
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||||
@@ -111,44 +111,44 @@ class TrustSubscriber {
|
|||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
os.write("TRUST\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("TRUST\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.flush()
|
os.flush()
|
||||||
|
|
||||||
String codeString = DataUtil.readTillRN(is)
|
String codeString = DataUtil.readTillRN(is)
|
||||||
int space = codeString.indexOf(' ')
|
int space = codeString.indexOf(' ')
|
||||||
if (space > 0)
|
if (space > 0)
|
||||||
codeString = codeString.substring(0,space)
|
codeString = codeString.substring(0,space)
|
||||||
int code = Integer.parseInt(codeString.trim())
|
int code = Integer.parseInt(codeString.trim())
|
||||||
|
|
||||||
if (code != 200) {
|
if (code != 200) {
|
||||||
log.info("couldn't fetch trust list, code $code")
|
log.info("couldn't fetch trust list, code $code")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// swallow any headers
|
// swallow any headers
|
||||||
String header
|
String header
|
||||||
while (( header = DataUtil.readTillRN(is)) != "");
|
while (( header = DataUtil.readTillRN(is)) != "");
|
||||||
|
|
||||||
DataInputStream dis = new DataInputStream(is)
|
DataInputStream dis = new DataInputStream(is)
|
||||||
|
|
||||||
Set<Persona> good = new HashSet<>()
|
Set<Persona> good = new HashSet<>()
|
||||||
int nGood = dis.readUnsignedShort()
|
int nGood = dis.readUnsignedShort()
|
||||||
for (int i = 0; i < nGood; i++) {
|
for (int i = 0; i < nGood; i++) {
|
||||||
Persona p = new Persona(dis)
|
Persona p = new Persona(dis)
|
||||||
good.add(p)
|
good.add(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Persona> bad = new HashSet<>()
|
Set<Persona> bad = new HashSet<>()
|
||||||
int nBad = dis.readUnsignedShort()
|
int nBad = dis.readUnsignedShort()
|
||||||
for (int i = 0; i < nBad; i++) {
|
for (int i = 0; i < nBad; i++) {
|
||||||
Persona p = new Persona(dis)
|
Persona p = new Persona(dis)
|
||||||
bad.add(p)
|
bad.add(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
trustList.timestamp = now
|
trustList.timestamp = now
|
||||||
trustList.good.clear()
|
trustList.good.clear()
|
||||||
trustList.good.addAll(good)
|
trustList.good.addAll(good)
|
||||||
trustList.bad.clear()
|
trustList.bad.clear()
|
||||||
trustList.bad.addAll(bad)
|
trustList.bad.addAll(bad)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
|
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
|
||||||
@@ -156,6 +156,6 @@ class TrustSubscriber {
|
|||||||
} finally {
|
} finally {
|
||||||
endpoint?.close()
|
endpoint?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,15 +32,15 @@ class UpdateClient {
|
|||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final Persona me
|
final Persona me
|
||||||
|
|
||||||
private final Timer timer
|
private final Timer timer
|
||||||
|
|
||||||
private long lastUpdateCheckTime
|
private long lastUpdateCheckTime
|
||||||
|
|
||||||
private volatile InfoHash updateInfoHash
|
private volatile InfoHash updateInfoHash
|
||||||
private volatile String version, signer
|
private volatile String version, signer
|
||||||
private volatile boolean updateDownloading
|
private volatile boolean updateDownloading
|
||||||
|
|
||||||
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.session = session
|
this.session = session
|
||||||
@@ -50,16 +50,16 @@ class UpdateClient {
|
|||||||
this.me = me
|
this.me = me
|
||||||
timer = new Timer("update-client",true)
|
timer = new Timer("update-client",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 2)
|
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 2)
|
||||||
timer.schedule({checkUpdate()} as TimerTask, 60000, 60 * 60 * 1000)
|
timer.schedule({checkUpdate()} as TimerTask, 60000, 60 * 60 * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUIResultBatchEvent(UIResultBatchEvent results) {
|
void onUIResultBatchEvent(UIResultBatchEvent results) {
|
||||||
if (results.results[0].infohash != updateInfoHash)
|
if (results.results[0].infohash != updateInfoHash)
|
||||||
return
|
return
|
||||||
@@ -70,14 +70,14 @@ class UpdateClient {
|
|||||||
def downloadEvent = new UIDownloadEvent(result: results.results[0], sources : results.results[0].sources, target : file)
|
def downloadEvent = new UIDownloadEvent(result: results.results[0], sources : results.results[0].sources, target : file)
|
||||||
eventBus.publish(downloadEvent)
|
eventBus.publish(downloadEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (e.downloadedFile.infoHash != updateInfoHash)
|
if (e.downloadedFile.infoHash != updateInfoHash)
|
||||||
return
|
return
|
||||||
updateDownloading = false
|
updateDownloading = false
|
||||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkUpdate() {
|
private void checkUpdate() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
if (lastUpdateCheckTime > 0) {
|
if (lastUpdateCheckTime > 0) {
|
||||||
@@ -85,20 +85,20 @@ class UpdateClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastUpdateCheckTime = now
|
lastUpdateCheckTime = now
|
||||||
|
|
||||||
log.info("checking for update")
|
log.info("checking for update")
|
||||||
|
|
||||||
def ping = [version : 1, myVersion : myVersion]
|
def ping = [version : 1, myVersion : myVersion]
|
||||||
ping = JsonOutput.toJson(ping)
|
ping = JsonOutput.toJson(ping)
|
||||||
def maker = new I2PDatagramMaker(session)
|
def maker = new I2PDatagramMaker(session)
|
||||||
ping = maker.makeI2PDatagram(ping.bytes)
|
ping = maker.makeI2PDatagram(ping.bytes)
|
||||||
def options = new SendMessageOptions()
|
def options = new SendMessageOptions()
|
||||||
options.setSendLeaseSet(true)
|
options.setSendLeaseSet(true)
|
||||||
session.sendMessage(UpdateServers.UPDATE_SERVER, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 2, 0, options)
|
session.sendMessage(UpdateServers.UPDATE_SERVER, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 2, 0, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Listener implements I2PSessionMuxedListener {
|
class Listener implements I2PSessionMuxedListener {
|
||||||
|
|
||||||
final JsonSlurper slurper = new JsonSlurper()
|
final JsonSlurper slurper = new JsonSlurper()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,7 +111,7 @@ class UpdateClient {
|
|||||||
log.warning "Received unexpected protocol $proto"
|
log.warning "Received unexpected protocol $proto"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
def payload = session.receiveMessage(msgId)
|
def payload = session.receiveMessage(msgId)
|
||||||
def dissector = new I2PDatagramDissector()
|
def dissector = new I2PDatagramDissector()
|
||||||
try {
|
try {
|
||||||
@@ -121,33 +121,33 @@ class UpdateClient {
|
|||||||
log.warning("received something not from update server " + sender.toBase32())
|
log.warning("received something not from update server " + sender.toBase32())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Received something from update server")
|
log.info("Received something from update server")
|
||||||
|
|
||||||
payload = dissector.getPayload()
|
payload = dissector.getPayload()
|
||||||
payload = slurper.parse(payload)
|
payload = slurper.parse(payload)
|
||||||
|
|
||||||
if (payload.version == null) {
|
if (payload.version == null) {
|
||||||
log.warning("version missing")
|
log.warning("version missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.signer == null) {
|
if (payload.signer == null) {
|
||||||
log.warning("signer missing")
|
log.warning("signer missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VersionComparator.comp(myVersion, payload.version) >= 0) {
|
if (VersionComparator.comp(myVersion, payload.version) >= 0) {
|
||||||
log.info("no new version available")
|
log.info("no new version available")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
String infoHash
|
String infoHash
|
||||||
if (settings.updateType == "jar") {
|
if (settings.updateType == "jar") {
|
||||||
infoHash = payload.infoHash
|
infoHash = payload.infoHash
|
||||||
} else
|
} else
|
||||||
infoHash = payload[settings.updateType]
|
infoHash = payload[settings.updateType]
|
||||||
|
|
||||||
|
|
||||||
if (!settings.autoDownloadUpdate) {
|
if (!settings.autoDownloadUpdate) {
|
||||||
log.info("new version $payload.version available, publishing event")
|
log.info("new version $payload.version available, publishing event")
|
||||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
||||||
@@ -167,7 +167,7 @@ class UpdateClient {
|
|||||||
eventBus.publish(queryEvent)
|
eventBus.publish(queryEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"Invalid datagram",e)
|
log.log(Level.WARNING,"Invalid datagram",e)
|
||||||
}
|
}
|
||||||
@@ -186,6 +186,6 @@ class UpdateClient {
|
|||||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||||
log.log(Level.SEVERE, message, error)
|
log.log(Level.SEVERE, message, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,5 +3,5 @@ package com.muwire.core.update
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class UpdateServers {
|
class UpdateServers {
|
||||||
static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA")
|
static final Destination UPDATE_SERVER = new Destination("VJYAiCPZHNLraWvLkeRLxRiT4PHAqNqRO1nH240r7u1noBw8Pa~-lJOhKR7CccPkEN8ejSi4H6XjqKYLC8BKLVLeOgnAbedUVx81MV7DETPDdPEGV4RVu6YDFri7-tJOeqauGHxtlXT44YWuR69xKrTG3u4~iTWgxKnlBDht9Q3aVpSPFD2KqEizfVxolqXI0zmAZ2xMi8jfl0oe4GbgHrD9hR2FYj6yKfdqcUgHVobY4kDdJt-u31QqwWdsQMEj8Y3tR2XcNaITEVPiAjoKgBrYwB4jddWPNaT4XdHz76d9p9Iqes7dhOKq3OKpk6kg-bfIKiEOiA1mY49fn5h8pNShTqV7QBhh4CE4EDT3Szl~WsLdrlHUKJufSi7erEMh3coF7HORpF1wah2Xw7q470t~b8dKGKi7N7xQsqhGruDm66PH9oE9Kt9WBVBq2zORdPRtRM61I7EnrwDlbOkL0y~XpvQ3JKUQKdBQ3QsOJt8CHlhHHXMMbvqhntR61RSDBQAEAAcAAA==")
|
||||||
}
|
}
|
||||||
|
@@ -15,12 +15,12 @@ import com.muwire.core.util.DataUtil
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class ContentUploader extends Uploader {
|
class ContentUploader extends Uploader {
|
||||||
|
|
||||||
private final File file
|
private final File file
|
||||||
private final ContentRequest request
|
private final ContentRequest request
|
||||||
private final Mesh mesh
|
private final Mesh mesh
|
||||||
private final int pieceSize
|
private final int pieceSize
|
||||||
|
|
||||||
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.file = file
|
this.file = file
|
||||||
@@ -28,7 +28,7 @@ class ContentUploader extends Uploader {
|
|||||||
this.mesh = mesh
|
this.mesh = mesh
|
||||||
this.pieceSize = pieceSize
|
this.pieceSize = pieceSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void respond() {
|
void respond() {
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
@@ -55,7 +55,7 @@ class ContentUploader extends Uploader {
|
|||||||
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
writeMesh(request.downloader)
|
writeMesh(request.downloader)
|
||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
FileChannel channel = null
|
FileChannel channel = null
|
||||||
try {
|
try {
|
||||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
||||||
@@ -78,12 +78,12 @@ class ContentUploader extends Uploader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeMesh(Persona toExclude) {
|
private void writeMesh(Persona toExclude) {
|
||||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
||||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
Set<Persona> sources = mesh.getRandom(3, toExclude)
|
Set<Persona> sources = mesh.getRandom(9, toExclude)
|
||||||
if (!sources.isEmpty()) {
|
if (!sources.isEmpty()) {
|
||||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
@@ -11,19 +11,19 @@ import net.i2p.data.Base64
|
|||||||
class HashListUploader extends Uploader {
|
class HashListUploader extends Uploader {
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final HashListRequest request
|
private final HashListRequest request
|
||||||
|
|
||||||
HashListUploader(Endpoint endpoint, InfoHash infoHash, HashListRequest request) {
|
HashListUploader(Endpoint endpoint, InfoHash infoHash, HashListRequest request) {
|
||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
||||||
this.request = request
|
this.request = request
|
||||||
}
|
}
|
||||||
|
|
||||||
void respond() {
|
void respond() {
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("Content-Range: 0-${mapped.remaining()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Content-Range: 0-${mapped.remaining()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
byte[]tmp = new byte[0x1 << 13]
|
byte[]tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
int start = mapped.position()
|
int start = mapped.position()
|
||||||
@@ -60,7 +60,7 @@ class HashListUploader extends Uploader {
|
|||||||
public int getTotalPieces() {
|
public int getTotalPieces() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalSize() {
|
public long getTotalSize() {
|
||||||
return -1;
|
return -1;
|
||||||
|
@@ -2,7 +2,7 @@ package com.muwire.core.upload
|
|||||||
|
|
||||||
class Range {
|
class Range {
|
||||||
final long start, end
|
final long start, end
|
||||||
|
|
||||||
Range(long start, long end) {
|
Range(long start, long end) {
|
||||||
this.start = start
|
this.start = start
|
||||||
this.end = end
|
this.end = end
|
||||||
|
@@ -12,21 +12,21 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Request {
|
class Request {
|
||||||
|
|
||||||
private static final byte R = "\r".getBytes(StandardCharsets.US_ASCII)[0]
|
private static final byte R = "\r".getBytes(StandardCharsets.US_ASCII)[0]
|
||||||
private static final byte N = "\n".getBytes(StandardCharsets.US_ASCII)[0]
|
private static final byte N = "\n".getBytes(StandardCharsets.US_ASCII)[0]
|
||||||
|
|
||||||
InfoHash infoHash
|
InfoHash infoHash
|
||||||
Persona downloader
|
Persona downloader
|
||||||
Map<String, String> headers
|
Map<String, String> headers
|
||||||
|
|
||||||
static Request parseContentRequest(InfoHash infoHash, InputStream is) throws IOException {
|
static Request parseContentRequest(InfoHash infoHash, InputStream is) throws IOException {
|
||||||
|
|
||||||
Map<String, String> headers = parseHeaders(is)
|
Map<String, String> headers = parseHeaders(is)
|
||||||
|
|
||||||
if (!headers.containsKey("Range"))
|
if (!headers.containsKey("Range"))
|
||||||
throw new IOException("Range header not found")
|
throw new IOException("Range header not found")
|
||||||
|
|
||||||
String range = headers.get("Range").trim()
|
String range = headers.get("Range").trim()
|
||||||
String[] split = range.split("-")
|
String[] split = range.split("-")
|
||||||
if (split.length != 2)
|
if (split.length != 2)
|
||||||
@@ -39,26 +39,26 @@ class Request {
|
|||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
throw new IOException(nfe)
|
throw new IOException(nfe)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < 0 || end < start)
|
if (start < 0 || end < start)
|
||||||
throw new IOException("Invalid range $start - $end")
|
throw new IOException("Invalid range $start - $end")
|
||||||
|
|
||||||
Persona downloader = null
|
Persona downloader = null
|
||||||
if (headers.containsKey("X-Persona")) {
|
if (headers.containsKey("X-Persona")) {
|
||||||
def encoded = headers["X-Persona"].trim()
|
def encoded = headers["X-Persona"].trim()
|
||||||
def decoded = Base64.decode(encoded)
|
def decoded = Base64.decode(encoded)
|
||||||
downloader = new Persona(new ByteArrayInputStream(decoded))
|
downloader = new Persona(new ByteArrayInputStream(decoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
int have = 0
|
int have = 0
|
||||||
if (headers.containsKey("X-Have")) {
|
if (headers.containsKey("X-Have")) {
|
||||||
def encoded = headers["X-Have"].trim()
|
def encoded = headers["X-Have"].trim()
|
||||||
have = DataUtil.decodeXHave(encoded).size()
|
have = DataUtil.decodeXHave(encoded).size()
|
||||||
}
|
}
|
||||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||||
headers : headers, downloader : downloader, have : have)
|
headers : headers, downloader : downloader, have : have)
|
||||||
}
|
}
|
||||||
|
|
||||||
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {
|
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {
|
||||||
Map<String,String> headers = parseHeaders(is)
|
Map<String,String> headers = parseHeaders(is)
|
||||||
Persona downloader = null
|
Persona downloader = null
|
||||||
@@ -69,7 +69,7 @@ class Request {
|
|||||||
}
|
}
|
||||||
new HashListRequest(infoHash : infoHash, headers : headers, downloader : downloader)
|
new HashListRequest(infoHash : infoHash, headers : headers, downloader : downloader)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, String> parseHeaders(InputStream is) {
|
private static Map<String, String> parseHeaders(InputStream is) {
|
||||||
Map<String,String> headers = new HashMap<>()
|
Map<String,String> headers = new HashMap<>()
|
||||||
byte [] tmp = new byte[Constants.MAX_HEADER_SIZE]
|
byte [] tmp = new byte[Constants.MAX_HEADER_SIZE]
|
||||||
@@ -81,7 +81,7 @@ class Request {
|
|||||||
byte read = is.read()
|
byte read = is.read()
|
||||||
if (read == -1)
|
if (read == -1)
|
||||||
throw new IOException("Stream closed")
|
throw new IOException("Stream closed")
|
||||||
|
|
||||||
if (!r && read == N)
|
if (!r && read == N)
|
||||||
throw new IOException("Received N before R")
|
throw new IOException("Received N before R")
|
||||||
if (read == R) {
|
if (read == R) {
|
||||||
@@ -90,7 +90,7 @@ class Request {
|
|||||||
r = true
|
r = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r && !n) {
|
if (r && !n) {
|
||||||
if (read != N)
|
if (read != N)
|
||||||
throw new IOException("R not followed by N")
|
throw new IOException("R not followed by N")
|
||||||
@@ -101,13 +101,13 @@ class Request {
|
|||||||
throw new IOException("Header too long")
|
throw new IOException("Header too long")
|
||||||
tmp[idx++] = read
|
tmp[idx++] = read
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx == 0)
|
if (idx == 0)
|
||||||
break
|
break
|
||||||
|
|
||||||
String header = new String(tmp, 0, idx, StandardCharsets.US_ASCII)
|
String header = new String(tmp, 0, idx, StandardCharsets.US_ASCII)
|
||||||
log.fine("Read header $header")
|
log.fine("Read header $header")
|
||||||
|
|
||||||
int keyIdx = header.indexOf(":")
|
int keyIdx = header.indexOf(":")
|
||||||
if (keyIdx < 1)
|
if (keyIdx < 1)
|
||||||
throw new IOException("Header key not found")
|
throw new IOException("Header key not found")
|
||||||
@@ -119,5 +119,5 @@ class Request {
|
|||||||
}
|
}
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,17 +22,17 @@ public class UploadManager {
|
|||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final MeshManager meshManager
|
private final MeshManager meshManager
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
|
|
||||||
public UploadManager() {}
|
public UploadManager() {}
|
||||||
|
|
||||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
public UploadManager(EventBus eventBus, FileManager fileManager,
|
||||||
MeshManager meshManager, DownloadManager downloadManager) {
|
MeshManager meshManager, DownloadManager downloadManager) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.meshManager = meshManager
|
this.meshManager = meshManager
|
||||||
this.downloadManager = downloadManager
|
this.downloadManager = downloadManager
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processGET(Endpoint e) throws IOException {
|
public void processGET(Endpoint e) throws IOException {
|
||||||
byte [] infoHashStringBytes = new byte[44]
|
byte [] infoHashStringBytes = new byte[44]
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
@@ -79,10 +79,10 @@ public class UploadManager {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.have > 0)
|
if (request.have > 0)
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||||
|
|
||||||
Mesh mesh
|
Mesh mesh
|
||||||
File file
|
File file
|
||||||
int pieceSize
|
int pieceSize
|
||||||
@@ -96,7 +96,7 @@ public class UploadManager {
|
|||||||
file = sharedFile.file
|
file = sharedFile.file
|
||||||
pieceSize = sharedFile.pieceSize
|
pieceSize = sharedFile.pieceSize
|
||||||
}
|
}
|
||||||
|
|
||||||
Uploader uploader = new ContentUploader(file, request, e, mesh, pieceSize)
|
Uploader uploader = new ContentUploader(file, request, e, mesh, pieceSize)
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
@@ -106,14 +106,14 @@ public class UploadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processHashList(Endpoint e) {
|
public void processHashList(Endpoint e) {
|
||||||
byte [] infoHashStringBytes = new byte[44]
|
byte [] infoHashStringBytes = new byte[44]
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
dis.readFully(infoHashStringBytes)
|
dis.readFully(infoHashStringBytes)
|
||||||
String infoHashString = new String(infoHashStringBytes, StandardCharsets.US_ASCII)
|
String infoHashString = new String(infoHashStringBytes, StandardCharsets.US_ASCII)
|
||||||
log.info("Responding to hashlist request for root $infoHashString")
|
log.info("Responding to hashlist request for root $infoHashString")
|
||||||
|
|
||||||
byte [] infoHashRoot = Base64.decode(infoHashString)
|
byte [] infoHashRoot = Base64.decode(infoHashString)
|
||||||
InfoHash infoHash = new InfoHash(infoHashRoot)
|
InfoHash infoHash = new InfoHash(infoHashRoot)
|
||||||
Downloader downloader = downloadManager.downloaders.get(infoHash)
|
Downloader downloader = downloadManager.downloaders.get(infoHash)
|
||||||
@@ -125,7 +125,7 @@ public class UploadManager {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
byte [] rn = new byte[2]
|
byte [] rn = new byte[2]
|
||||||
dis.readFully(rn)
|
dis.readFully(rn)
|
||||||
if (rn != "\r\n".getBytes(StandardCharsets.US_ASCII)) {
|
if (rn != "\r\n".getBytes(StandardCharsets.US_ASCII)) {
|
||||||
@@ -133,14 +133,14 @@ public class UploadManager {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = Request.parseHashListRequest(infoHash, e.getInputStream())
|
Request request = Request.parseHashListRequest(infoHash, e.getInputStream())
|
||||||
if (request.downloader != null && request.downloader.destination != e.destination) {
|
if (request.downloader != null && request.downloader.destination != e.destination) {
|
||||||
log.info("Downloader persona doesn't match their destination")
|
log.info("Downloader persona doesn't match their destination")
|
||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash fullInfoHash
|
InfoHash fullInfoHash
|
||||||
if (downloader == null) {
|
if (downloader == null) {
|
||||||
fullInfoHash = sharedFiles.iterator().next().infoHash
|
fullInfoHash = sharedFiles.iterator().next().infoHash
|
||||||
@@ -156,7 +156,7 @@ public class UploadManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
@@ -164,7 +164,7 @@ public class UploadManager {
|
|||||||
} finally {
|
} finally {
|
||||||
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
||||||
}
|
}
|
||||||
|
|
||||||
// proceed with content
|
// proceed with content
|
||||||
while(true) {
|
while(true) {
|
||||||
byte[] get = new byte[4]
|
byte[] get = new byte[4]
|
||||||
@@ -204,10 +204,10 @@ public class UploadManager {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.have > 0)
|
if (request.have > 0)
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||||
|
|
||||||
Mesh mesh
|
Mesh mesh
|
||||||
File file
|
File file
|
||||||
int pieceSize
|
int pieceSize
|
||||||
|
@@ -11,31 +11,31 @@ import com.muwire.core.connection.Endpoint
|
|||||||
abstract class Uploader {
|
abstract class Uploader {
|
||||||
protected final Endpoint endpoint
|
protected final Endpoint endpoint
|
||||||
protected ByteBuffer mapped
|
protected ByteBuffer mapped
|
||||||
|
|
||||||
Uploader(Endpoint endpoint) {
|
Uploader(Endpoint endpoint) {
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void respond()
|
abstract void respond()
|
||||||
|
|
||||||
public synchronized int getPosition() {
|
public synchronized int getPosition() {
|
||||||
if (mapped == null)
|
if (mapped == null)
|
||||||
return -1
|
return -1
|
||||||
mapped.position()
|
mapped.position()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract String getName();
|
abstract String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return an integer between 0 and 100
|
* @return an integer between 0 and 100
|
||||||
*/
|
*/
|
||||||
abstract int getProgress();
|
abstract int getProgress();
|
||||||
|
|
||||||
abstract String getDownloader();
|
abstract String getDownloader();
|
||||||
|
|
||||||
abstract int getDonePieces();
|
abstract int getDonePieces();
|
||||||
|
|
||||||
abstract int getTotalPieces();
|
abstract int getTotalPieces();
|
||||||
|
|
||||||
abstract long getTotalSize();
|
abstract long getTotalSize();
|
||||||
}
|
}
|
||||||
|
@@ -10,42 +10,42 @@ import com.muwire.core.Constants
|
|||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class DataUtil {
|
class DataUtil {
|
||||||
|
|
||||||
private final static int MAX_SHORT = (0x1 << 16) - 1
|
|
||||||
|
|
||||||
static void writeUnsignedShort(int value, OutputStream os) {
|
private final static int MAX_SHORT = (0x1 << 16) - 1
|
||||||
if (value > MAX_SHORT || value < 0)
|
|
||||||
throw new IllegalArgumentException("$value invalid")
|
static void writeUnsignedShort(int value, OutputStream os) {
|
||||||
|
if (value > MAX_SHORT || value < 0)
|
||||||
byte lsb = (byte) (value & 0xFF)
|
throw new IllegalArgumentException("$value invalid")
|
||||||
byte msb = (byte) (value >> 8)
|
|
||||||
|
byte lsb = (byte) (value & 0xFF)
|
||||||
os.write(msb)
|
byte msb = (byte) (value >> 8)
|
||||||
os.write(lsb)
|
|
||||||
}
|
os.write(msb)
|
||||||
|
os.write(lsb)
|
||||||
private final static int MAX_HEADER = 0x7FFFFF
|
}
|
||||||
|
|
||||||
static void packHeader(int length, byte [] header) {
|
private final static int MAX_HEADER = 0x7FFFFF
|
||||||
if (header.length != 3)
|
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
static void packHeader(int length, byte [] header) {
|
||||||
if (length < 0 || length > MAX_HEADER)
|
if (header.length != 3)
|
||||||
throw new IllegalArgumentException("length $length")
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
|
if (length < 0 || length > MAX_HEADER)
|
||||||
header[2] = (byte) (length & 0xFF)
|
throw new IllegalArgumentException("length $length")
|
||||||
header[1] = (byte) ((length >> 8) & 0xFF)
|
|
||||||
header[0] = (byte) ((length >> 16) & 0x7F)
|
header[2] = (byte) (length & 0xFF)
|
||||||
}
|
header[1] = (byte) ((length >> 8) & 0xFF)
|
||||||
|
header[0] = (byte) ((length >> 16) & 0x7F)
|
||||||
static int readLength(byte [] header) {
|
}
|
||||||
if (header.length != 3)
|
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
static int readLength(byte [] header) {
|
||||||
|
if (header.length != 3)
|
||||||
return (((int)(header[0] & 0x7F)) << 16) |
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
(((int)(header[1] & 0xFF) << 8)) |
|
|
||||||
((int)header[2] & 0xFF)
|
return (((int)(header[0] & 0x7F)) << 16) |
|
||||||
}
|
(((int)(header[1] & 0xFF) << 8)) |
|
||||||
|
((int)header[2] & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
static String readi18nString(byte [] encoded) {
|
static String readi18nString(byte [] encoded) {
|
||||||
if (encoded.length < 2)
|
if (encoded.length < 2)
|
||||||
throw new IllegalArgumentException("encoding too short $encoded.length")
|
throw new IllegalArgumentException("encoding too short $encoded.length")
|
||||||
@@ -54,9 +54,9 @@ class DataUtil {
|
|||||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
||||||
byte [] string = new byte[length]
|
byte [] string = new byte[length]
|
||||||
System.arraycopy(encoded, 2, string, 0, length)
|
System.arraycopy(encoded, 2, string, 0, length)
|
||||||
new String(string, StandardCharsets.UTF_8)
|
new String(string, StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] encodei18nString(String string) {
|
static byte[] encodei18nString(String string) {
|
||||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
||||||
if (utf8.length > Short.MAX_VALUE)
|
if (utf8.length > Short.MAX_VALUE)
|
||||||
@@ -64,11 +64,11 @@ class DataUtil {
|
|||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
def daos = new DataOutputStream(baos)
|
def daos = new DataOutputStream(baos)
|
||||||
daos.writeShort((short) utf8.length)
|
daos.writeShort((short) utf8.length)
|
||||||
daos.write(utf8)
|
daos.write(utf8)
|
||||||
daos.close()
|
daos.close()
|
||||||
baos.toByteArray()
|
baos.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readTillRN(InputStream is) {
|
public static String readTillRN(InputStream is) {
|
||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
||||||
@@ -84,7 +84,7 @@ class DataUtil {
|
|||||||
}
|
}
|
||||||
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
||||||
int bytes = totalPieces / 8
|
int bytes = totalPieces / 8
|
||||||
if (totalPieces % 8 != 0)
|
if (totalPieces % 8 != 0)
|
||||||
@@ -98,7 +98,7 @@ class DataUtil {
|
|||||||
}
|
}
|
||||||
Base64.encode(raw)
|
Base64.encode(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> decodeXHave(String xHave) {
|
public static List<Integer> decodeXHave(String xHave) {
|
||||||
byte [] availablePieces = Base64.decode(xHave)
|
byte [] availablePieces = Base64.decode(xHave)
|
||||||
List<Integer> available = new ArrayList<>()
|
List<Integer> available = new ArrayList<>()
|
||||||
@@ -112,19 +112,19 @@ class DataUtil {
|
|||||||
}
|
}
|
||||||
available
|
available
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Exception findRoot(Exception e) {
|
public static Exception findRoot(Exception e) {
|
||||||
while(e.getCause() != null)
|
while(e.getCause() != null)
|
||||||
e = e.getCause()
|
e = e.getCause()
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void tryUnmap(ByteBuffer cb) {
|
public static void tryUnmap(ByteBuffer cb) {
|
||||||
if (cb==null || !cb.isDirect()) return;
|
if (cb==null || !cb.isDirect()) return;
|
||||||
// we could use this type cast and call functions without reflection code,
|
// we could use this type cast and call functions without reflection code,
|
||||||
// but static import from sun.* package is risky for non-SUN virtual machine.
|
// but static import from sun.* package is risky for non-SUN virtual machine.
|
||||||
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
||||||
|
|
||||||
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
|
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
|
||||||
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
|
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
|
||||||
try {
|
try {
|
||||||
|
@@ -16,10 +16,10 @@ class JULLog extends Log {
|
|||||||
I2P_TO_JUL.put(Log.ERROR, Level.SEVERE)
|
I2P_TO_JUL.put(Log.ERROR, Level.SEVERE)
|
||||||
I2P_TO_JUL.put(Log.CRIT, Level.SEVERE)
|
I2P_TO_JUL.put(Log.CRIT, Level.SEVERE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Logger delegate
|
private final Logger delegate
|
||||||
private final Level level
|
private final Level level
|
||||||
|
|
||||||
public JULLog(Class<?> cls) {
|
public JULLog(Class<?> cls) {
|
||||||
super(cls)
|
super(cls)
|
||||||
delegate = Logger.getLogger(cls.getName())
|
delegate = Logger.getLogger(cls.getName())
|
||||||
@@ -31,7 +31,7 @@ class JULLog extends Log {
|
|||||||
delegate = Logger.getLogger(name)
|
delegate = Logger.getLogger(name)
|
||||||
level = findLevel(delegate)
|
level = findLevel(delegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Level findLevel(Logger log) {
|
private static Level findLevel(Logger log) {
|
||||||
while (log.getLevel() == null)
|
while (log.getLevel() == null)
|
||||||
log = log.getParent()
|
log = log.getParent()
|
||||||
|
@@ -5,14 +5,14 @@ import net.i2p.util.Log
|
|||||||
import net.i2p.util.LogManager
|
import net.i2p.util.LogManager
|
||||||
|
|
||||||
class MuWireLogManager extends LogManager {
|
class MuWireLogManager extends LogManager {
|
||||||
|
|
||||||
private static final Map<Class<?>, Log> classLogs = new HashMap<>()
|
private static final Map<Class<?>, Log> classLogs = new HashMap<>()
|
||||||
private static final Map<String, Log> stringLogs = new HashMap<>()
|
private static final Map<String, Log> stringLogs = new HashMap<>()
|
||||||
|
|
||||||
MuWireLogManager() {
|
MuWireLogManager() {
|
||||||
super(I2PAppContext.getGlobalContext())
|
super(I2PAppContext.getGlobalContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized Log getLog(Class<?> cls, String name) {
|
public synchronized Log getLog(Class<?> cls, String name) {
|
||||||
@@ -24,7 +24,7 @@ class MuWireLogManager extends LogManager {
|
|||||||
}
|
}
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
Log rv = stringLogs.get(name)
|
Log rv = stringLogs.get(name)
|
||||||
if (rv == null) {
|
if (rv == null) {
|
||||||
rv = new JULLog(name)
|
rv = new JULLog(name)
|
||||||
@@ -32,5 +32,5 @@ class MuWireLogManager extends LogManager {
|
|||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,17 +7,17 @@ import java.util.Set;
|
|||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
|
|
||||||
public class DownloadedFile extends SharedFile {
|
public class DownloadedFile extends SharedFile {
|
||||||
|
|
||||||
private final Set<Destination> sources;
|
|
||||||
|
|
||||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
private final Set<Destination> sources;
|
||||||
throws IOException {
|
|
||||||
super(file, infoHash, pieceSize);
|
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
||||||
this.sources = sources;
|
throws IOException {
|
||||||
}
|
super(file, infoHash, pieceSize);
|
||||||
|
this.sources = sources;
|
||||||
public Set<Destination> getSources() {
|
}
|
||||||
return sources;
|
|
||||||
}
|
public Set<Destination> getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,83 +11,83 @@ import net.i2p.data.Base64;
|
|||||||
|
|
||||||
public class InfoHash {
|
public class InfoHash {
|
||||||
|
|
||||||
public static final int SIZE = 0x1 << 5;
|
public static final int SIZE = 0x1 << 5;
|
||||||
|
|
||||||
private final byte[] root;
|
private final byte[] root;
|
||||||
private final byte[] hashList;
|
private final byte[] hashList;
|
||||||
|
|
||||||
private final int hashCode;
|
private final int hashCode;
|
||||||
|
|
||||||
public InfoHash(byte[] root, byte[] hashList) {
|
public InfoHash(byte[] root, byte[] hashList) {
|
||||||
if (root.length != SIZE)
|
if (root.length != SIZE)
|
||||||
throw new IllegalArgumentException("invalid root size "+root.length);
|
throw new IllegalArgumentException("invalid root size "+root.length);
|
||||||
if (hashList != null && hashList.length % SIZE != 0)
|
if (hashList != null && hashList.length % SIZE != 0)
|
||||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.hashList = hashList;
|
this.hashList = hashList;
|
||||||
hashCode = root[0] << 24 |
|
hashCode = root[0] << 24 |
|
||||||
root[1] << 16 |
|
root[1] << 16 |
|
||||||
root[2] << 8 |
|
root[2] << 8 |
|
||||||
root[3];
|
root[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(byte[] root) {
|
public InfoHash(byte[] root) {
|
||||||
this(root, null);
|
this(root, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(String base32) {
|
public InfoHash(String base32) {
|
||||||
this(Base32.decode(base32));
|
this(Base32.decode(base32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InfoHash fromHashList(byte []hashList) {
|
public static InfoHash fromHashList(byte []hashList) {
|
||||||
try {
|
try {
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
byte[] root = sha256.digest(hashList);
|
byte[] root = sha256.digest(hashList);
|
||||||
return new InfoHash(root, hashList);
|
return new InfoHash(root, hashList);
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
impossible.printStackTrace();
|
impossible.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getRoot() {
|
public byte[] getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getHashList() {
|
public byte[] getHashList() {
|
||||||
return hashList;
|
return hashList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return hashCode;
|
return hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(o instanceof InfoHash)) {
|
if (!(o instanceof InfoHash)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InfoHash other = (InfoHash) o;
|
InfoHash other = (InfoHash) o;
|
||||||
return Arrays.equals(root, other.root);
|
return Arrays.equals(root, other.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
||||||
List<String> b64HashList = new ArrayList<>();
|
List<String> b64HashList = new ArrayList<>();
|
||||||
if (hashList != null) {
|
if (hashList != null) {
|
||||||
byte [] tmp = new byte[SIZE];
|
byte [] tmp = new byte[SIZE];
|
||||||
for (int i = 0; i < hashList.length / SIZE; i++) {
|
for (int i = 0; i < hashList.length / SIZE; i++) {
|
||||||
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
||||||
b64HashList.add(Base64.encode(tmp));
|
b64HashList.add(Base64.encode(tmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rv += b64HashList.toString();
|
rv += b64HashList.toString();
|
||||||
rv += "]";
|
rv += "]";
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,60 +5,60 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class SharedFile {
|
public class SharedFile {
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final InfoHash infoHash;
|
private final InfoHash infoHash;
|
||||||
private final int pieceSize;
|
private final int pieceSize;
|
||||||
|
|
||||||
private final String cachedPath;
|
|
||||||
private final long cachedLength;
|
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
|
||||||
this.file = file;
|
|
||||||
this.infoHash = infoHash;
|
|
||||||
this.pieceSize = pieceSize;
|
|
||||||
this.cachedPath = file.getAbsolutePath();
|
|
||||||
this.cachedLength = file.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getFile() {
|
private final String cachedPath;
|
||||||
return file;
|
private final long cachedLength;
|
||||||
}
|
|
||||||
|
|
||||||
public InfoHash getInfoHash() {
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||||
return infoHash;
|
this.file = file;
|
||||||
}
|
this.infoHash = infoHash;
|
||||||
|
this.pieceSize = pieceSize;
|
||||||
public int getPieceSize() {
|
this.cachedPath = file.getAbsolutePath();
|
||||||
return pieceSize;
|
this.cachedLength = file.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNPieces() {
|
public File getFile() {
|
||||||
long length = file.length();
|
return file;
|
||||||
int rawPieceSize = 0x1 << pieceSize;
|
}
|
||||||
int rv = (int) (length / rawPieceSize);
|
|
||||||
if (length % rawPieceSize != 0)
|
public InfoHash getInfoHash() {
|
||||||
rv++;
|
return infoHash;
|
||||||
return rv;
|
}
|
||||||
}
|
|
||||||
|
public int getPieceSize() {
|
||||||
public String getCachedPath() {
|
return pieceSize;
|
||||||
return cachedPath;
|
}
|
||||||
}
|
|
||||||
|
public int getNPieces() {
|
||||||
public long getCachedLength() {
|
long length = file.length();
|
||||||
return cachedLength;
|
int rawPieceSize = 0x1 << pieceSize;
|
||||||
}
|
int rv = (int) (length / rawPieceSize);
|
||||||
|
if (length % rawPieceSize != 0)
|
||||||
@Override
|
rv++;
|
||||||
public int hashCode() {
|
return rv;
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
}
|
||||||
}
|
|
||||||
|
public String getCachedPath() {
|
||||||
@Override
|
return cachedPath;
|
||||||
public boolean equals(Object o) {
|
}
|
||||||
if (!(o instanceof SharedFile))
|
|
||||||
return false;
|
public long getCachedLength() {
|
||||||
SharedFile other = (SharedFile)o;
|
return cachedLength;
|
||||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return file.hashCode() ^ infoHash.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof SharedFile))
|
||||||
|
return false;
|
||||||
|
SharedFile other = (SharedFile)o;
|
||||||
|
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.connection;
|
package com.muwire.core.connection;
|
||||||
|
|
||||||
public enum ConnectionAttemptStatus {
|
public enum ConnectionAttemptStatus {
|
||||||
SUCCESSFUL, REJECTED, FAILED
|
SUCCESSFUL, REJECTED, FAILED
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@ package com.muwire.core.hostcache;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public enum CrawlerResponse {
|
public enum CrawlerResponse {
|
||||||
ALL, REGISTERED, NONE
|
ALL, REGISTERED, NONE
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.trust;
|
package com.muwire.core.trust;
|
||||||
|
|
||||||
public enum TrustLevel {
|
public enum TrustLevel {
|
||||||
TRUSTED, NEUTRAL, DISTRUSTED
|
TRUSTED, NEUTRAL, DISTRUSTED
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,6 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Destinations {
|
class Destinations {
|
||||||
|
|
||||||
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
||||||
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
||||||
}
|
}
|
||||||
|
@@ -4,23 +4,23 @@ import org.junit.Test
|
|||||||
|
|
||||||
class EventBusTest {
|
class EventBusTest {
|
||||||
|
|
||||||
class FakeEvent extends Event {}
|
class FakeEvent extends Event {}
|
||||||
|
|
||||||
class FakeEventHandler {
|
class FakeEventHandler {
|
||||||
def onFakeEvent(FakeEvent e) {
|
def onFakeEvent(FakeEvent e) {
|
||||||
assert e == fakeEvent
|
assert e == fakeEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeEvent fakeEvent = new FakeEvent()
|
FakeEvent fakeEvent = new FakeEvent()
|
||||||
|
|
||||||
EventBus bus = new EventBus()
|
EventBus bus = new EventBus()
|
||||||
def handler = new FakeEventHandler()
|
def handler = new FakeEventHandler()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDynamicEvent() {
|
void testDynamicEvent() {
|
||||||
bus.register(FakeEvent.class, handler)
|
bus.register(FakeEvent.class, handler)
|
||||||
bus.publish(fakeEvent)
|
bus.publish(fakeEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import org.junit.Test
|
|||||||
|
|
||||||
class InfoHashTest {
|
class InfoHashTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
byte [] empty = new byte[0x1 << 6];
|
byte [] empty = new byte[0x1 << 6];
|
||||||
def ih = InfoHash.fromHashList(empty)
|
def ih = InfoHash.fromHashList(empty)
|
||||||
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
||||||
assertEquals(ih, ih2);
|
assertEquals(ih, ih2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import net.i2p.data.Base64
|
|||||||
class Personas {
|
class Personas {
|
||||||
private final String encoded1 = "AQADemFiO~pgSoEo8wQfwncYMvBQWkvPY9I7DYUllHp289UE~zBaLdbl~wbliktAUsW-S70f3UeYgHq34~c7zVuUQjgHZ506iG9hX8B9S3a9gQ3CSG0GuDpeNyiXmZkpHp5m8vT9PZ1zMWzxvzZY~fP9yKFKgO4yrso5I9~DGOPeyJZJ4BFsTJDERv41aZqjFLYUBDmeHGgg9RjYy~93h-nQMVYj9JSO3AgowW-ix49rtiKYIXHMa2PxWHUXkUHWJZtIZntNIDEFeMnPdzLxjAl8so2G6pDcTMZPLLwyb73Ee5ZVfxUynPqyp~fIGVP8Rl4rlaGFli2~ATGBz3XY54aObC~0p7us2JnWaTC~oQT5DVDM7gaOO885o-m8BB8b0duzMBelbdnMZFQJ5jIHVKxkC6Niw4fxTOoXTyOqQmVhtK-9xcwxMuN5DF9IewkR5bhpq5rgnfBP5zvyBaAHMq-d3TCOjTsZ-d3liB98xX5p8G5zmS7gfKArQtM5~CcK~AlX-lGLBQAEAAcAAN5MW1Tq983szfZgY1l8tQFqy8I9tdMf7vc1Ktj~TCIvXYw6AYMbMGy3S67FSPLZVmfHEMQKj2KLAdaRKQkHPAY"
|
private final String encoded1 = "AQADemFiO~pgSoEo8wQfwncYMvBQWkvPY9I7DYUllHp289UE~zBaLdbl~wbliktAUsW-S70f3UeYgHq34~c7zVuUQjgHZ506iG9hX8B9S3a9gQ3CSG0GuDpeNyiXmZkpHp5m8vT9PZ1zMWzxvzZY~fP9yKFKgO4yrso5I9~DGOPeyJZJ4BFsTJDERv41aZqjFLYUBDmeHGgg9RjYy~93h-nQMVYj9JSO3AgowW-ix49rtiKYIXHMa2PxWHUXkUHWJZtIZntNIDEFeMnPdzLxjAl8so2G6pDcTMZPLLwyb73Ee5ZVfxUynPqyp~fIGVP8Rl4rlaGFli2~ATGBz3XY54aObC~0p7us2JnWaTC~oQT5DVDM7gaOO885o-m8BB8b0duzMBelbdnMZFQJ5jIHVKxkC6Niw4fxTOoXTyOqQmVhtK-9xcwxMuN5DF9IewkR5bhpq5rgnfBP5zvyBaAHMq-d3TCOjTsZ-d3liB98xX5p8G5zmS7gfKArQtM5~CcK~AlX-lGLBQAEAAcAAN5MW1Tq983szfZgY1l8tQFqy8I9tdMf7vc1Ktj~TCIvXYw6AYMbMGy3S67FSPLZVmfHEMQKj2KLAdaRKQkHPAY"
|
||||||
private final String encoded2 = "AQAHemxhdGluYiN~3G-hPoBfJ04mhcC52lC6TYSwWxH-WNWno9Y35JS-WrXlnPsodZtwy96ttEaiKTg-hkRqMsaYKpWar1FwayR6qlo0pZCo5pQOLfR7GIM3~wde0JIBEp8BUpgzF1-QXLhuRG1t7tBbenW2tSgp5jQH61RI-c9flyUlOvf6nrhQMZ3aoviZ4aZW23Fx-ajYQBDk7PIxuyn8qYNwWy3kWOhGan05c54NnumS3XCzQWFDDPlADmco1WROeY9qrwwtmLM8lzDCEtJQXJlk~K5yLbyB63hmAeTK7J4iS6f9nnWv7TbB5r-Z3kC6D9TLYrQbu3h4AAxrqso45P8yHQtKUA4QJicS-6NJoBOnlCCU887wx2k9YSxxwNydlIxb1mZsX65Ke4uY0HDFokZHTzUcxvfLB6G~5JkSPDCyZz~2fREgW2-VXu7gokEdEugkuZRrsiQzyfAOOkv53ti5MzTbMOXinBskSb1vZyN2-XcZNaDJvEqUNj~qpfhe-ov2F7FuwQUABAAHAAAfqq-MneIqWBQY92-sy9Z0s~iQsq6lUFa~sYMdY-5o-94fF8a140dm-emF3rO8vuidUIPNaS-37Rl05mAKUCcB"
|
private final String encoded2 = "AQAHemxhdGluYiN~3G-hPoBfJ04mhcC52lC6TYSwWxH-WNWno9Y35JS-WrXlnPsodZtwy96ttEaiKTg-hkRqMsaYKpWar1FwayR6qlo0pZCo5pQOLfR7GIM3~wde0JIBEp8BUpgzF1-QXLhuRG1t7tBbenW2tSgp5jQH61RI-c9flyUlOvf6nrhQMZ3aoviZ4aZW23Fx-ajYQBDk7PIxuyn8qYNwWy3kWOhGan05c54NnumS3XCzQWFDDPlADmco1WROeY9qrwwtmLM8lzDCEtJQXJlk~K5yLbyB63hmAeTK7J4iS6f9nnWv7TbB5r-Z3kC6D9TLYrQbu3h4AAxrqso45P8yHQtKUA4QJicS-6NJoBOnlCCU887wx2k9YSxxwNydlIxb1mZsX65Ke4uY0HDFokZHTzUcxvfLB6G~5JkSPDCyZz~2fREgW2-VXu7gokEdEugkuZRrsiQzyfAOOkv53ti5MzTbMOXinBskSb1vZyN2-XcZNaDJvEqUNj~qpfhe-ov2F7FuwQUABAAHAAAfqq-MneIqWBQY92-sy9Z0s~iQsq6lUFa~sYMdY-5o-94fF8a140dm-emF3rO8vuidUIPNaS-37Rl05mAKUCcB"
|
||||||
|
|
||||||
Persona persona1 = new Persona(new ByteArrayInputStream(Base64.decode(encoded1)))
|
Persona persona1 = new Persona(new ByteArrayInputStream(Base64.decode(encoded1)))
|
||||||
Persona persona2 = new Persona(new ByteArrayInputStream(Base64.decode(encoded2)))
|
Persona persona2 = new Persona(new ByteArrayInputStream(Base64.decode(encoded2)))
|
||||||
}
|
}
|
||||||
|
@@ -22,386 +22,386 @@ import groovy.mock.interceptor.MockFor
|
|||||||
|
|
||||||
class ConnectionAcceptorTest {
|
class ConnectionAcceptorTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
final Destinations destinations = new Destinations()
|
final Destinations destinations = new Destinations()
|
||||||
def settings
|
def settings
|
||||||
|
|
||||||
def connectionManagerMock
|
def connectionManagerMock
|
||||||
UltrapeerConnectionManager connectionManager
|
UltrapeerConnectionManager connectionManager
|
||||||
|
|
||||||
def i2pAcceptorMock
|
def i2pAcceptorMock
|
||||||
I2PAcceptor i2pAcceptor
|
I2PAcceptor i2pAcceptor
|
||||||
|
|
||||||
def hostCacheMock
|
def hostCacheMock
|
||||||
HostCache hostCache
|
HostCache hostCache
|
||||||
|
|
||||||
def trustServiceMock
|
def trustServiceMock
|
||||||
TrustService trustService
|
TrustService trustService
|
||||||
|
|
||||||
def searchManagerMock
|
def searchManagerMock
|
||||||
SearchManager searchManager
|
SearchManager searchManager
|
||||||
|
|
||||||
def uploadManagerMock
|
def uploadManagerMock
|
||||||
UploadManager uploadManager
|
UploadManager uploadManager
|
||||||
|
|
||||||
def connectionEstablisherMock
|
def connectionEstablisherMock
|
||||||
ConnectionEstablisher connectionEstablisher
|
ConnectionEstablisher connectionEstablisher
|
||||||
|
|
||||||
ConnectionAcceptor acceptor
|
ConnectionAcceptor acceptor
|
||||||
List<ConnectionEvent> connectionEvents
|
List<ConnectionEvent> connectionEvents
|
||||||
InputStream inputStream
|
InputStream inputStream
|
||||||
OutputStream outputStream
|
OutputStream outputStream
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||||
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
||||||
hostCacheMock = new MockFor(HostCache.class)
|
hostCacheMock = new MockFor(HostCache.class)
|
||||||
trustServiceMock = new MockFor(TrustService.class)
|
trustServiceMock = new MockFor(TrustService.class)
|
||||||
searchManagerMock = new MockFor(SearchManager.class)
|
searchManagerMock = new MockFor(SearchManager.class)
|
||||||
uploadManagerMock = new MockFor(UploadManager.class)
|
uploadManagerMock = new MockFor(UploadManager.class)
|
||||||
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
acceptor?.stop()
|
acceptor?.stop()
|
||||||
connectionManagerMock.verify connectionManager
|
connectionManagerMock.verify connectionManager
|
||||||
i2pAcceptorMock.verify i2pAcceptor
|
i2pAcceptorMock.verify i2pAcceptor
|
||||||
hostCacheMock.verify hostCache
|
hostCacheMock.verify hostCache
|
||||||
trustServiceMock.verify trustService
|
trustServiceMock.verify trustService
|
||||||
searchManagerMock.verify searchManager
|
searchManagerMock.verify searchManager
|
||||||
uploadManagerMock.verify uploadManager
|
uploadManagerMock.verify uploadManager
|
||||||
connectionEstablisherMock.verify connectionEstablisher
|
connectionEstablisherMock.verify connectionEstablisher
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
connectionEvents = new CopyOnWriteArrayList()
|
connectionEvents = new CopyOnWriteArrayList()
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
connectionEvents.add e
|
connectionEvents.add e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus.register(ConnectionEvent.class, listener)
|
eventBus.register(ConnectionEvent.class, listener)
|
||||||
|
|
||||||
connectionManager = connectionManagerMock.proxyInstance()
|
connectionManager = connectionManagerMock.proxyInstance()
|
||||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||||
hostCache = hostCacheMock.proxyInstance()
|
hostCache = hostCacheMock.proxyInstance()
|
||||||
trustService = trustServiceMock.proxyInstance()
|
trustService = trustServiceMock.proxyInstance()
|
||||||
searchManager = searchManagerMock.proxyInstance()
|
searchManager = searchManagerMock.proxyInstance()
|
||||||
uploadManager = uploadManagerMock.proxyInstance()
|
uploadManager = uploadManagerMock.proxyInstance()
|
||||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||||
|
|
||||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||||
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
acceptor.start()
|
acceptor.start()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulLeaf() {
|
void testSuccessfulLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { true }
|
connectionManagerMock.demand.hasLeafSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulPeer() {
|
void testSuccessfulPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { true }
|
connectionManagerMock.demand.hasPeerSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsLeaf() {
|
void testLeafRejectsLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsPeer() {
|
void testLeafRejectsPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSlots() {
|
void testPeerRejectsPeerSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsLeafSlots() {
|
void testPeerRejectsLeafSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { false }
|
connectionManagerMock.demand.hasLeafSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSuggests() {
|
void testPeerRejectsPeerSuggests() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
short payloadSize = dis.readUnsignedShort()
|
short payloadSize = dis.readUnsignedShort()
|
||||||
byte[] payload = new byte[payloadSize]
|
byte[] payload = new byte[payloadSize]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
def json = new JsonSlurper()
|
||||||
json = json.parse(payload)
|
json = json.parse(payload)
|
||||||
assert json.tryHosts != null
|
assert json.tryHosts != null
|
||||||
assert json.tryHosts.size() == 1
|
assert json.tryHosts.size() == 1
|
||||||
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user